Program result = read → expand → evaluate → print.
The reader for the language converts the surface notation of the source file into S-expressions and returns a a new module expression.
For this reason, every source file starts with a #lang line that “boots” into the reader.
To find the reader, Racket resolves (that is, determines the path to) the module name on the #lang line and tries to load its reader submodule.
Question: where is the reader located for these source files?
1 2 | #lang pollen/markdown Hello world |
1 2 | #lang racket "Hello world" |
This is the most common usage. The #lang line also supports #lang reader notation that lets you set the reader to any module, including a relative path, so this #lang line is equivalent to the previous:
1 2 | #lang reader (submod pollen/markdown reader) Hello world |
1 2 | #lang reader (submod racket/main reader) "Hello world" |
Racket expects the reader submodule to provide a function called read-syntax, which accepts two input arguments: a source name (that holds the location of the source—e.g., for file input, the name would be a path) and an input port (that points at the source file).
1 2 3 4 5 | #lang br (module reader br (provide read-syntax) (define (read-syntax name port) ···)) |
Question: in what sense is reader a submodule? It seems to be at the top level.
read-syntax reads all the data from the input port (that is, until the port returns eof) and converts it into S-expressions:
1 2 3 4 5 6 | #lang br (module reader br (provide read-syntax) (define (read-syntax name port) (define s-exprs (read-expression port)) ···)) |
read-syntax puts these S-expressions into a syntax object representing a module expression. This syntax object should not have any identifier bindings. This module expression must include the module that will serve as the expander.
In quasiquote notation:
1 2 3 4 5 6 7 8 | #lang br (module reader br (provide read-syntax) (define (read-syntax name port) (define s-exprs (read-expression port)) (datum->syntax #f `(module dsl-mod-name dsl/expander ,@s-exprs)))) |
In quasisyntax notation:
1 2 3 4 5 6 7 8 | #lang br (module reader br (provide read-syntax) (define (read-syntax name port) (define s-exprs (read-expression port)) (strip-bindings #`(module dsl-mod-name dsl/expander #,@s-exprs)))) |
In with-syntax notation, using a dot:
1 2 3 4 5 6 7 8 9 | #lang br (module reader br (provide read-syntax) (define (read-syntax name port) (define s-exprs (read-expression port)) (strip-bindings (with-syntax ([EXPRS s-exprs]) #'(module dsl-mod-name dsl/expander . EXPRS))))) |
In with-syntax notation, using an ellipsis:
1 2 3 4 5 6 7 8 9 | #lang br (module reader br (provide read-syntax) (define (read-syntax name port) (define s-exprs (read-expression port)) (strip-bindings (with-syntax ([(EXPR ...) s-exprs]) #'(module dsl-mod-name dsl/expander EXPR ...))))) |
Question: which of these four variants is the best?
Question: why do we want to strip bindings from our module expression?
BTW, where do those bindings come from? When we use syntax or its variants (quasisyntax, the #' prefix) to make a syntax object, it attaches the current lexical context to any parts of the object that don’t have one. If a part already has lexical context, syntax leaves it alone. Lexical context = fancy term for the set of bindings currently available.
1 2 3 4 5 | (define stx #'(+ plus)) (for ([substx (syntax->list stx)]) #R substx #R (syntax->datum substx) #R (identifier-binding substx)) |
In Racket, this policy of preserving lexical context is called hygiene. It guarantees that we can move syntax objects around without changing their meaning. But occasionally, we want to intentionally change or remove the lexical context (as in this case).
Write a language called #lang read-only that:
Reads all the S-expressions from a source file and
Puts them into a new module expression that invokes the br expander.
The result should be that any #lang read-only file evaluates the same way as #lang racket:
Result:
1 2 | "hello world" -5 |
Hint: You only need one file, with a reader submodule:
Hint: Beyond that, start with one of the templates above.
Hint: read-expression is not a real function. But read is.
Hint: You can test for eof with eof-object?. Or you can figure out how to use in-port.
First things first.