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
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:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user