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: object

A 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: Deferred

A 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: object

The 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

Parameters:

atom – a symbol or gensym

Return type:

list of symbol

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 outer Datum wrapper is peeled only when expanding list or ellipsis structure, not when returning an unexpanded binding reference unchanged

  • mark – 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 mark is given, then peel the Datum boxes

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 gains mark as 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 Datum scoped by mark. A Datum is 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 Datum boxes off a finished expansion in place, so the compiler sees the substituted source. A Datum car is unboxed and its position moved onto the cell; the material inside is the user’s code and is not entered. Datum layers along a cdr chain are unboxed the same way

Parameters:

form – expansion output (pairs, atoms, Datum, gensyms)

Return type:

same root object as form after in-place updates where applicable