Fix parser escape ordering and prim_get for non-dict objects
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m37s

Parser: chained .replace() calls processed \n before \\, causing \\n
to become a real newline. Replaced with character-by-character
_unescape_string. Fixes 2 parser spec test failures.

Primitives: prim_get only handled dict and list. Objects with .get()
methods (like PageDef) returned None. Added hasattr fallback.
Fixes 9 defpage spec test failures.

All 259 spec tests now pass (was 244/259).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 22:17:38 +00:00
parent 5a4a0c0e1c
commit 179631130c
2 changed files with 21 additions and 5 deletions

View File

@@ -62,6 +62,24 @@ class SxExpr(str):
# Errors
# ---------------------------------------------------------------------------
_ESCAPE_MAP = {"n": "\n", "t": "\t", '"': '"', "\\": "\\", "/": "/"}
def _unescape_string(s: str) -> str:
"""Process escape sequences in a parsed string, character by character."""
out: list[str] = []
i = 0
while i < len(s):
if s[i] == "\\" and i + 1 < len(s):
nxt = s[i + 1]
out.append(_ESCAPE_MAP.get(nxt, nxt))
i += 2
else:
out.append(s[i])
i += 1
return "".join(out)
class ParseError(Exception):
"""Error during s-expression parsing."""
@@ -141,11 +159,7 @@ class Tokenizer:
raise ParseError("Unterminated string", self.pos, self.line, self.col)
self._advance(m.end() - self.pos)
content = m.group()[1:-1]
content = content.replace("\\n", "\n")
content = content.replace("\\t", "\t")
content = content.replace('\\"', '"')
content = content.replace("\\/", "/")
content = content.replace("\\\\", "\\")
content = _unescape_string(content)
return content
# Keyword

View File

@@ -374,6 +374,8 @@ def prim_get(coll: Any, key: Any, default: Any = None) -> Any:
return default
if isinstance(coll, list):
return coll[key] if 0 <= key < len(coll) else default
if hasattr(coll, "get"):
return coll.get(key, default)
return default
@register_primitive("len")