hygienic-incremental-macro-expansion
I built, debugged, and verified a complete solution. The full self-contained markdown is at ~/hygienic-incremental-macro-expander/SOLUTION.md (1032 lines). I verified it by extracting its embedded code blocks into a clean directory and re-running them: 12/12 checks pass.
The failure class has five independent root causes; four are standard, one is the subtle killer:
loc.scheme
(define-syntax use-helper (syntax-rules () ((_ x) (helper x))))
(let ((helper 42)) (use-helper 5)) ; naive post-pass: helper binds to the local!A scope-set model (Flatt's Binding as Sets of Scopes) with strict ordering:
lambda/let) add a fresh lexical scope to binder + body.process), so lexical scopes are applied to the raw use before the macro fires. This is what makes use-helper stay free.loc; arguments keep theirs.Key excerpt (full file in SOLUTION.md):
function process(node, env, macroEnv, ctx) {
if (node.type === 'symbol') { /* resolve by largest subset */ }
const head = node.items[0];
if (head && head.type === 'symbol') {
if (macroEnv.has(head.name)) {
const macro = macroEnv.get(head.name);
for (const rule of macro.rules) {
const binds = match(rule.pattern, node, macro.literals, {});
if (binds) {
ctx.usedMacros.add(macro.name);
const scope = ctx.freshScope();
// template gets `scope`; substituted args do not
const inst = instantiate(rule.template, binds, scope, node.loc);
return process(inst, env, macroEnv, ctx); // reassign result!
}
}
}
if (head.name === 'let') {
const lex = ctx.freshLex();
// ...add lex to binders, then to raw body BEFORE recursing...
for (let i = 2; i < node.items.length; i++)
node.items[i] = process(node.items[i], env.concat(added), macroEnv, ctx);
}
}
}
cd ~/hygienic-incremental-macro-expander
node test.js
12 checks passed
Covers: hygiene/no-capture of a macro temp, definition-site free identifiers, nested-scope shadowing, mutually recursive macros (my-even?/my-odd?), smallest sound recomputation region, unrelated-edit reuse, cache-capture safety, determinism, source-location preservation, cross-expansion distinct temp bindings, and transitive B → A → C → user invalidation with independent forms reused.
Deliverables: SOLUTION.md (title, root-cause analysis, full code, verification), plus the runnable macro-expander.js and test.js.
# Evidence - Problem class: hygienic-incremental-macro-expansion - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-10-06T04:06:46.189Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Build an incremental macro expander for a programming language with nested scopes, hygienic identifiers, and mutually recursive macro definitions. After one definition changes, recompute the smallest sound expansion region while preserving source locations and ensuring cached expansions cannot capture identifiers introduced by unrelated edits.", "environment": "node20", "language": "js", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "hygienic-incremental-macro-expansion", "provider": "openrouter", "solved_at": "2026-10-06T04:06:46.189Z", "version": "20"}