Implement reader macros (#;, #|...|, #', #name) and #z3 demo
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m13s
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:
@@ -20,6 +20,17 @@ from typing import Any
|
||||
|
||||
from .types import Keyword, Symbol, NIL
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reader macro registry
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_READER_MACROS: dict[str, Any] = {}
|
||||
|
||||
|
||||
def register_reader_macro(name: str, handler: Any) -> None:
|
||||
"""Register a reader macro handler: #name expr → handler(expr)."""
|
||||
_READER_MACROS[name] = handler
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SxExpr — pre-built sx source marker
|
||||
@@ -203,8 +214,33 @@ class Tokenizer:
|
||||
return NIL
|
||||
return Symbol(name)
|
||||
|
||||
# Reader macro dispatch: #
|
||||
if char == "#":
|
||||
return "#"
|
||||
|
||||
raise ParseError(f"Unexpected character: {char!r}", self.pos, self.line, self.col)
|
||||
|
||||
def _read_raw_string(self) -> str:
|
||||
"""Read raw string literal until closing |."""
|
||||
buf: list[str] = []
|
||||
while self.pos < len(self.text):
|
||||
ch = self.text[self.pos]
|
||||
if ch == "|":
|
||||
self._advance(1)
|
||||
return "".join(buf)
|
||||
buf.append(ch)
|
||||
self._advance(1)
|
||||
raise ParseError("Unterminated raw string", self.pos, self.line, self.col)
|
||||
|
||||
def _read_ident(self) -> str:
|
||||
"""Read an identifier (for reader macro names)."""
|
||||
import re
|
||||
m = self.SYMBOL.match(self.text, self.pos)
|
||||
if m:
|
||||
self._advance(m.end() - self.pos)
|
||||
return m.group()
|
||||
raise ParseError("Expected identifier after #", self.pos, self.line, self.col)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parsing
|
||||
@@ -264,6 +300,33 @@ def _parse_expr(tok: Tokenizer) -> Any:
|
||||
return [Symbol("splice-unquote"), inner]
|
||||
inner = _parse_expr(tok)
|
||||
return [Symbol("unquote"), inner]
|
||||
# Reader macro dispatch: #
|
||||
if raw == "#":
|
||||
tok._advance(1) # consume the #
|
||||
if tok.pos >= len(tok.text):
|
||||
raise ParseError("Unexpected end of input after #",
|
||||
tok.pos, tok.line, tok.col)
|
||||
dispatch = tok.text[tok.pos]
|
||||
if dispatch == ";":
|
||||
tok._advance(1)
|
||||
_parse_expr(tok) # read and discard
|
||||
return _parse_expr(tok) # return next
|
||||
if dispatch == "|":
|
||||
tok._advance(1)
|
||||
return tok._read_raw_string()
|
||||
if dispatch == "'":
|
||||
tok._advance(1)
|
||||
return [Symbol("quote"), _parse_expr(tok)]
|
||||
# Extensible dispatch: #name expr
|
||||
if dispatch.isalpha() or dispatch in "_~":
|
||||
macro_name = tok._read_ident()
|
||||
handler = _READER_MACROS.get(macro_name)
|
||||
if handler is None:
|
||||
raise ParseError(f"Unknown reader macro: #{macro_name}",
|
||||
tok.pos, tok.line, tok.col)
|
||||
return handler(_parse_expr(tok))
|
||||
raise ParseError(f"Unknown reader macro: #{dispatch}",
|
||||
tok.pos, tok.line, tok.col)
|
||||
# Everything else: strings, keywords, symbols, numbers
|
||||
token = tok.next_token()
|
||||
return token
|
||||
|
||||
Reference in New Issue
Block a user