Implement reader macros (#;, #|...|, #', #name) and #z3 demo
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m13s

Reader macros in parser.sx spec, Python parser.py, and hand-written sx.js:
- #; datum comment: read and discard next expression
- #|...|  raw string: no escape processing
- #' quote shorthand: (quote expr)
- #name extensible dispatch: registered handler transforms next expression

#z3 reader macro demo (reader_z3.py): translates define-primitive
declarations from primitives.sx into SMT-LIB verification conditions.
Same source, two interpretations — bootstrappers compile to executable
code, #z3 extracts proof obligations.

48 parser tests (SX spec + Python), all passing. Rebootstrapped JS+Python.
Demo page at /plans/reader-macro-demo with side-by-side examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 20:21:40 +00:00
parent 56589a81b2
commit 03ba8e58e5
12 changed files with 7625 additions and 26 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -70,6 +70,9 @@
function isMacro(x) { return x && x._macro === true; }
function isRaw(x) { return x && x._raw === true; }
// --- Reader macro registry ---
var _readerMacros = {};
// --- Parser ---
var RE_WS = /\s+/y;
@@ -155,6 +158,9 @@
}
}
// Reader macro dispatch: #
if (ch === "#") { this._advance(1); return "#"; }
// Symbol
RE_SYMBOL.lastIndex = this.pos;
m = RE_SYMBOL.exec(this.text);
@@ -171,6 +177,27 @@
throw parseErr("Unexpected character: " + ch + " | context: «" + ctx.replace(/\n/g, "\\n") + "»", this);
};
Tokenizer.prototype._readRawString = function () {
var buf = [];
while (this.pos < this.text.length) {
var ch = this.text[this.pos];
if (ch === "|") { this._advance(1); return buf.join(""); }
buf.push(ch);
this._advance(1);
}
throw parseErr("Unterminated raw string", this);
};
Tokenizer.prototype._readIdent = function () {
RE_SYMBOL.lastIndex = this.pos;
var m = RE_SYMBOL.exec(this.text);
if (m && m.index === this.pos) {
this._advance(m[0].length);
return m[0];
}
throw parseErr("Expected identifier after #", this);
};
function isDigit(c) { return c >= "0" && c <= "9"; }
function parseErr(msg, tok) {
@@ -199,6 +226,33 @@
}
return [new Symbol("unquote"), parseExpr(tok)];
}
// Reader macro dispatch: #
if (raw === "#") {
tok._advance(1); // consume #
if (tok.pos >= tok.text.length) throw parseErr("Unexpected end of input after #", tok);
var dispatch = tok.text[tok.pos];
if (dispatch === ";") {
tok._advance(1);
parseExpr(tok); // read and discard
return parseExpr(tok); // return next
}
if (dispatch === "|") {
tok._advance(1);
return tok._readRawString();
}
if (dispatch === "'") {
tok._advance(1);
return [new Symbol("quote"), parseExpr(tok)];
}
// Extensible dispatch: #name expr
if (/[a-zA-Z_~]/.test(dispatch)) {
var macroName = tok._readIdent();
var handler = _readerMacros[macroName];
if (!handler) throw parseErr("Unknown reader macro: #" + macroName, tok);
return handler(parseExpr(tok));
}
throw parseErr("Unknown reader macro: #" + dispatch, tok);
}
return tok.next();
}
@@ -1500,6 +1554,9 @@
}
},
/** Register a reader macro: Sx.registerReaderMacro("z3", fn) */
registerReaderMacro: function (name, handler) { _readerMacros[name] = handler; },
// For testing / sx-test.js
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
_eval: sxEval,