feat(syntax): tokenize $var and @{uuid} kinds + corpus cases

This commit is contained in:
DumiFlex
2026-04-28 04:05:17 +03:00
parent 11ac437fa3
commit 4fb2c99771
3 changed files with 126 additions and 0 deletions
+38
View File
@@ -9,8 +9,15 @@ var + ref, Task 5 adds dp_brace + dp_pipe + dp_multi.
"""
from __future__ import annotations
import re
from engine.syntax.types import Token, TokenKind
# `$name` — identifier starts with letter or underscore, then word chars
_VAR_RE = re.compile(r"\$([A-Za-z_][A-Za-z0-9_]*)")
# `@{8hex}` — exactly 8 lowercase hex chars in braces
_REF_RE = re.compile(r"@\{([0-9a-f]{8})\}")
def tokenize_text(text: str) -> list[Token]:
"""Tokenize `text` into a flat list of Token. Single-pass, O(n)."""
@@ -64,6 +71,37 @@ def tokenize_text(text: str) -> list[Token]:
i += 2
continue
# Variable: $name (only when followed by a valid identifier start)
if ch == "$":
m = _VAR_RE.match(text, i)
if m:
_flush_text(i)
out.append(Token(
kind=TokenKind.VAR,
raw=m.group(0),
start=i,
end=m.end(),
meta={"name": m.group(1)},
))
i = m.end()
continue
# Lone $ followed by non-ident — fall through to text accumulation
# Ref: @{8hex} (only when exactly 8 lowercase hex chars in braces)
if ch == "@":
m = _REF_RE.match(text, i)
if m:
_flush_text(i)
out.append(Token(
kind=TokenKind.REF,
raw=m.group(0),
start=i,
end=m.end(),
meta={"uuid": m.group(1)},
))
i = m.end()
continue
# Default: accumulate literal text
if text_start is None:
text_start = i
+74
View File
@@ -31,6 +31,80 @@
{"kind": "escape", "raw": "@@", "start": 4, "end": 6, "meta": {"literal": "@"}},
{"kind": "text", "raw": "example", "start": 6, "end": 13, "meta": {}}
]
},
{
"name": "single_var",
"input": "$style",
"expected_tokens": [
{"kind": "var", "raw": "$style", "start": 0, "end": 6, "meta": {"name": "style"}}
]
},
{
"name": "var_in_text",
"input": "a $color cat",
"expected_tokens": [
{"kind": "text", "raw": "a ", "start": 0, "end": 2, "meta": {}},
{"kind": "var", "raw": "$color", "start": 2, "end": 8, "meta": {"name": "color"}},
{"kind": "text", "raw": " cat", "start": 8, "end": 12, "meta": {}}
]
},
{
"name": "var_with_underscore_and_digits",
"input": "$hair_color_2",
"expected_tokens": [
{"kind": "var", "raw": "$hair_color_2", "start": 0, "end": 13, "meta": {"name": "hair_color_2"}}
]
},
{
"name": "lone_dollar_is_literal_text",
"input": "cost: $",
"expected_tokens": [
{"kind": "text", "raw": "cost: $", "start": 0, "end": 7, "meta": {}}
]
},
{
"name": "dollar_followed_by_non_ident_is_literal_text",
"input": "$ space",
"expected_tokens": [
{"kind": "text", "raw": "$ space", "start": 0, "end": 7, "meta": {}}
]
},
{
"name": "single_ref",
"input": "@{a4f7b2e1}",
"expected_tokens": [
{"kind": "ref", "raw": "@{a4f7b2e1}", "start": 0, "end": 11, "meta": {"uuid": "a4f7b2e1"}}
]
},
{
"name": "ref_in_text",
"input": "x @{deadbeef} y",
"expected_tokens": [
{"kind": "text", "raw": "x ", "start": 0, "end": 2, "meta": {}},
{"kind": "ref", "raw": "@{deadbeef}", "start": 2, "end": 13, "meta": {"uuid": "deadbeef"}},
{"kind": "text", "raw": " y", "start": 13, "end": 15, "meta": {}}
]
},
{
"name": "at_without_braces_is_literal",
"input": "email@host",
"expected_tokens": [
{"kind": "text", "raw": "email@host", "start": 0, "end": 10, "meta": {}}
]
},
{
"name": "at_brace_non_hex_is_literal",
"input": "@{not_uuid}",
"expected_tokens": [
{"kind": "text", "raw": "@{not_uuid}", "start": 0, "end": 11, "meta": {}}
]
},
{
"name": "at_brace_wrong_length_is_literal",
"input": "@{abc} short",
"expected_tokens": [
{"kind": "text", "raw": "@{abc} short", "start": 0, "end": 12, "meta": {}}
]
}
]
}
+14
View File
@@ -51,3 +51,17 @@ def test_tokenize_lossless_concatenation_invariant():
toks = tokenize_text(s)
joined = "".join(t.raw for t in toks)
assert joined == s, f"lossless invariant broken for {s!r}: got {joined!r}"
def test_tokenize_lossless_for_var_ref():
samples = [
"$x",
"$x $y",
"@{deadbeef} @{cafebabe}",
"mix $x @{12345678} text",
"lone $ and lone @ stay text",
]
for s in samples:
toks = tokenize_text(s)
joined = "".join(t.raw for t in toks)
assert joined == s, f"lossless invariant broken for {s!r}: got {joined!r}"