Expand description
Macro expansion, hygienic from the first commit.
docs/08-roadmap.md Phase 1: “Macro expander with hygiene —
from the start (§2.4); retrofitting hygiene is a rewrite.” docs/02-syntax.md §2.4:
“Identifiers introduced inside a quote get a fresh hygiene scope in Node.meta; capture is
possible but must be explicit (inject(name)).”
§The algorithm
Flatt’s sets of scopes, which is the model Racket settled on after, as §2.4 puts it, “Scheme’s 20-year history here”. Each expansion step:
- mints a fresh [
Scope]; - adds it to the macro’s input — every identifier the call site supplied;
- substitutes the arguments into the template;
- flips the scope over the whole result.
The flip is what makes it work. Identifiers that came from the call site had the scope added in step 2 and lose it again in step 4, so they mean what they meant where they were written. Identifiers the template introduced never had it, so they gain it — and a binding carrying a scope the call site does not have is invisible to the call site’s references. Capture becomes impossible in both directions rather than unlikely.
Resolution itself lives in beck-types: a binding is a candidate for a reference exactly when
binding.scopes ⊆ reference.scopes, and the most specific candidate wins.
§What a macro body may do
Anything a pure Beck function may do. interp is the compile-time interpreter §2.4 calls for
— bindings, if, for, while, lambdas, calls to the module’s own defs and to the pure
part of the prelude — in a capability-restricted environment: there is no name for a file,
a socket, a clock or a process, and the prelude’s effectful primitives are refused by name so
that reaching for one is a diagnostic rather than a spelling mistake.
quote: is the form whose value is syntax, and $e inside one is an ordinary expression
whose value is reflected back into the template — so $x where x is a parameter is the
caller’s code, and $(n * 2) is a literal. That is the whole difference from the template
expander this used to be: a let in a macro body now computes rather than substituting.
And refuse("…") is how a body says it has no rule for what it was given — a code generator
that meets something it cannot write for otherwise emits code that fails to check somewhere
else, with a message about lines the reader never wrote.
§The two phases
An ordinary macro is expanded here, before anything has been checked. A typed macro is left
exactly as written by this pass and expanded by the checker, because its body asks what its
arguments were inferred to be; typed is that half, and it is the same interpreter with one
more name in scope.
Re-exports§
pub use interp::Val;pub use interp::BUILTINS;pub use interp::MAX_STEPS;pub use interp::RESTRICTED;pub use typed::DeclInfo;pub use typed::Fields;pub use typed::TyKind;pub use typed::TyRepr;pub use typed::TypeEnv;pub use typed::TypedExpander;pub use typed::Variants;
Modules§
- interp
- The macro interpreter: Beck, evaluated at compile time.
- typed
- Typed macros: the half of §2.4 that wants the checker’s answers.
- vocabulary
- What HTML is: the element, attribute and event names
ui:will write.
Structs§
Constants§
- MAX_
DEPTH - How deep a macro may expand before the expander decides it is not going to terminate.
- MAX_
EXPANSION - How many nodes a module’s macros may produce, in total.
Functions§
- expand_
module - Expand every macro in a module to a fixpoint.
- expand_
module_ measured - Expand a module, and say how much of the interpreter’s step budget is left.
- expand_
module_ with - The same, with the macros of the modules this one imports in scope.
- expand_
ui - Expand
(ui (kw do (quote (do …))))into anHtml-valued expression.