Macro hygiene
koskari.hygiene
Template expansion and expansion finalize for syntax transformers.
Hygiene is provenance: a symbol a template introduced becomes a gensym carrying the expansion that introduced it, and the compiler declares and resolves it under the spelling the gensym derives. A keyword is a label and carries no marks. Material substituted from the use site arrives in Datum boxes from the matcher and is never marked. Finalize peels the Datum boxes for compilation and leaves the gensyms for the compiler
- class koskari.hygiene.Deferred(fragment, ancestors)
Bases:
objectA constant awaiting completion, standing in for its final value in the enclosing fragment’s CONST row until that fragment completes
- Parameters:
fragment – the fragment to complete
ancestors – the fragments lexically open around it when it closed, outermost first
- ancestors
- chain(completer, ancestors)
The ancestry the fragment completes under: the completer’s own chain, then the completer when the snapshot lacks it, then the snapshot, outermost first throughout
A function fragment was on the lexical stack while its body compiled; the root never is, and encloses the rest
- Parameters:
completer – the fragment whose completion reaches this
ancestors – the fragments enclosing the completer
- complete(compiler, completer, ancestors)
The final value for the CONST row
- Parameters:
compiler – the compiler completing the completer
completer – the fragment whose completion reaches this
ancestors – the fragments enclosing the completer
- fragment
- class koskari.hygiene.HygieneFunctionSpec(fragment, ancestors, name, formals_spec)
Bases:
DeferredA function body whose marked names resolve against declarations that are final only when the enclosing fragment completes: a sibling defined later in the same body, as the functions of a labels form are, must be visible to a closure defined before it
- Parameters:
fragment – the function body fragment
ancestors – the fragments lexically open around the body when it closed, outermost first
name – the function name, or None
formals_spec – the FormalsSpec
- complete(compiler, completer, ancestors)
The final value for the CONST row
- Parameters:
compiler – the compiler completing the completer
completer – the fragment whose completion reaches this
ancestors – the fragments enclosing the completer
- formals_spec
- name
- class koskari.hygiene.Resolver(fragment, ancestors, module_names)
Bases:
objectThe declarations visible to one completing fragment: its own, those of the fragments enclosing it, and the module’s bound names
A marked spelling bound in the module was defined by an earlier turn, as a macro-generating macro’s expansion leaves behind
- Parameters:
fragment – the fragment being completed
ancestors – the fragments enclosing it, outermost first
module_names – the module’s bound names
- choose(atom, scopes=())
The first candidate for atom whose spelling is declared in an inline scope, the fragment, an ancestor, or the module; the base when none is
- Parameters:
atom – a symbol or gensym
scopes – spelling sets of the inline scopes open around the reference, innermost first
- declared
- module_names
- resolve(fragment)
Resolve every marked name the fragment references to the binding it names, in place: its request buckets and the variable operands of its instruction stream
A marked reference tries its key, then the key with the newest mark removed, down to the bare name, against the declarations visible from its position: the inline scopes open around it in the instruction stream, the fragment’s own, and those of the fragments enclosing it, which are final by the time it completes
The inline scopes compiled into the fragment have their write buckets resolved the same way, with the scopes open around each write, so validation reads the names the stream binds
- Parameters:
fragment – the fragment being completed
- resolve_bucket(bucket, scopes=(), positions=None)
Replace each name in bucket with its choice, in place, keeping the first of any names that choose the same binding. A source position recorded under a name moves with it to the chosen name, so a diagnostic raised after the names have settled still finds the line the request came from
- Parameters:
bucket – a list of atoms
scopes – spelling sets of the inline scopes open around the names, innermost first
positions – the fragment’s position record for this bucket, keyed by atom, or None
- koskari.hygiene.binding_atom(compiler, atom)
The atom a binder declares: in a class body, an attribute of the class, a label named by its bare spelling; elsewhere the atom as written, so a template’s binder stays its own
- Parameters:
compiler – the compiler, whose innermost open scope decides
atom – the binder’s symbol or gensym
- koskari.hygiene.candidates(atom)
The symbols a reference may resolve to, longest first: a gensym, then the gensym under its marks with the newest removed, down to its base. A bare symbol has itself alone
- koskari.hygiene.declared_spellings(fragment)
The spellings a fragment declares or pulls from the module
- koskari.hygiene.equal_by_base(left, right)
Whether two forms are equal once every gensym is read as its base symbol, without copying either. Pair spines are walked together, two symbols compare by base identity, and any other pair of values compares by equality
- Parameters:
left – a form
right – a form
- Returns:
True when the forms are equal up to marks
- koskari.hygiene.expand_template_with_hygiene(template, bindings, mark=None, literal_ellipsis=False)
Substitute pattern bindings into a template and mark the symbols the template itself introduces
Ellipsis repetition is depth-aware: a variable captured under N pattern ellipses carries N nested repeat sequences, and each template
...peels exactly one dimension (see_slice_bindings()). A variable referenced under fewer ellipses than its rank still carries an unconsumed sequence at use site and raises, rather than splicing a raw column into the output. An ellipsis after an element that names no pattern variable with a repeat dimension raises too: it would transcribe to nothing. A literal ellipsis is written through the escape(... TEMPLATE), under which every ellipsis transcribes as itself- Parameters:
template – template form (pairs, symbols, or opaque atoms)
bindings – dict from pattern variable symbol to capture value (often
Datum-wrapped); a single outerDatumwrapper is peeled only when expanding list or ellipsis structure, not when returning an unexpanded binding reference unchangedmark – the expansion mark to box template atoms with, or None to substitute without marking
literal_ellipsis – inside an escape: ellipses are elements
- koskari.hygiene.extern_atoms(pattern, names)
The atoms a pattern form writes for the given base spellings, as a mapping from spelling to atom. A pattern lowers each name it reads to its base spelling; this recovers the atom the name was written as, so its load resolves as the template wrote it
- Parameters:
pattern – the pattern source form
names – the base spellings to recover
- Raises:
CompilerError – when one spelling is written as two different atoms in the pattern
- koskari.hygiene.finalize_expansion(form, mark=None)
Finish one expansion for the compiler: mark a procedural arm’s output when
markis given, then peel theDatumboxes- Parameters:
form – the expansion output
mark – the expansion mark for a procedural arm, or None when transcription marked the output already
- Returns:
the finished form
- koskari.hygiene.function_constant(compiler, parent, body, name, formals_spec)
The CONST value for a function body: its FunctionSpec, completed now, when no name in the body depends on declarations outside it; otherwise a HygieneFunctionSpec completing with the parent, which is marked in turn so its own completion waits for the declarations around it
- Parameters:
compiler – the compiler
parent – the fragment the CONST row is emitted into
body – the function body fragment
name – the function name, or None
formals_spec – the FormalsSpec
- koskari.hygiene.mark_atom(atom, mark)
The atom scoped by
mark: a symbol becomes a gensym over it, a gensym gainsmarkas its newest, and anything else, keywords included, is returned unchanged- Parameters:
atom – a source atom
mark – the expansion mark, an int
- koskari.hygiene.mark_expansion(form, mark, seen=None)
A copy of a procedural expansion with every symbol outside a
Datumscoped bymark. ADatumis substituted material and is carried over as it is; a pair inside one is never entered. Pair cells are rebuilt with their positions and delimiter style. A cell that encloses itself is a cycle, which source cannot hold; a sub-form placed at more than one position is copied at each- Parameters:
form – the handler’s output
mark – the expansion mark
seen – ids of the cells enclosing this form on the walk
- Returns:
the marked copy
- Raises:
CompilerError – when the output is cyclic
- koskari.hygiene.peel_datums(form)
Peel
Datumboxes off a finished expansion in place, so the compiler sees the substituted source. ADatumcar is unboxed and its position moved onto the cell; the material inside is the user’s code and is not entered.Datumlayers along a cdr chain are unboxed the same way- Parameters:
form – expansion output (pairs, atoms,
Datum, gensyms)- Return type:
same root object as
formafter in-place updates where applicable