At the end of the reader phase, the source code has been converted into a module expression that contains S-expressions, but has no bindings, e.g.—
1 2 | (module dsl-mod-name dsl/expander dsl-sexprs ···) |
The expander provides the initial set of bindings for the module expression returned by the reader, thereby determining the meaning of the identifiers within the S-expressions. This, in turn, allows the expressions to be evaluated as Racket code.
Any module can be used as the expander for a language. It’s invoked by the first line of the module code returned by the reader. For instance, this module expression will rely on dsl/expander as its expander:
1 2 | (module dsl-mod-name dsl/expander dsl-sexprs ···) |
Question: why don’t we use the #lang notation in a module expression?
In essence, the expander name is like an implied require at the beginning of the module:
Question: why can’t require actually replace the expander?
To start the expansion, Racket imports the #%module-begin from the expander specified in the module expression. It replaces the module expression with a call to this #%module-begin, passing it all the parsed expressions that are in the body of the module expression. So this:
1 2 | (module dsl-mod-name dsl/expander dsl-sexprs ···) |
Becomes this:
1 2 3 | (module dsl-mod-name dsl/expander (#%module-begin ;; imported from `dsl/expander` dsl-sexprs ···)) |
Often, the #%module-begin for a language will perform some language-specific processing on the parse tree, and then call the #%module-begin in the implementation language. To prevent namespace collisions between the two #%module-begin macros, use rename-out:
1 2 3 4 5 | #lang br/quicklang (define-macro (dsl-module-begin EXPR ...) #'(#%module-begin ;; from `br/quicklang` EXPR ...)) (provide (rename-out [dsl-module-begin #%module-begin])) |
The define-macro form introduces a macro rather than an ordinary function. The first line contains a syntax pattern that binds pattern variables, which must be in UPPERCASE, but their names are otherwise arbitrary. If the input to the macro doesn’t match the pattern, the macro raises an error.
These pattern variables can (only) be used in syntax templates, including the syntax template that generates the syntax object returned by the macro.
Here, THING is a pattern variable used in two syntax templates:
1 2 3 4 5 | #lang br (define-macro (mac THING) (println #'THING) #'(list THING THING)) (mac (list "foo" 42)) |
But unlike a variable, THING cannot be used on its own:
1 2 3 4 5 | #lang br (define-macro (mac THING) (println THING) ; error #'(list THING THING)) (mac (list "foo" 42)) |
The initial syntax pattern can contain literal values (which must match literally, of course) and sublists:
1 2 3 4 | #lang br (define-macro (mac2 (LEFT 42 RIGHT)) #'(list LEFT RIGHT)) (mac2 ("foo" 42 "bar")) |
Question: why does the input (mac2 (list "foo" 42 "bar")) raise an error?
The ellipsis matches a series of input arguments, and then denotes a “step and repeat” operation in the syntax template:
1 2 3 4 | #lang br (define-macro (mac3 NUM ...) #'(begin (* NUM 100) ...)) (mac3 10 42 325) |
Question: what if you want to handle the first element separately?
Write a language called #lang s-exp expand-only that:
Converts every number into 42.
Converts every string into "whee".
Converts every other expression into 'kaboom.
And otherwise preserves the list structure of the program.
Test input (slightly different from before—note the x instead of 3):
Result:
1 2 | "whee" '(kaboom 42 (kaboom 42 (kaboom kaboom))) |
#lang s-exp is a variant of #lang that handles reading the source file, and just feeds a list of S-expressions to our expander (like we did manually in the previous exercise).
Hint: You can start with this template:
1 2 3 4 5 6 | #lang br (define-macro (my-module-begin EXPR ...) #'(#%module-begin ···)) (provide (rename-out [my-module-begin #%module-begin])) |
Hint: Write a helper function called convert-expr that is applied to each expression in EXPR ....
Hint: If you write 'EXPR in the syntax template, it means “substitute the value of EXPR, and then quote it.”
Hint: In DrRacket, your test file will also produce an error like Interactions disabled: expand-only does not support a REPL. Ignore it.
The hand that rocks the cradle.