HS test generator: bind run() me: and balanced-brace locals — +2 comparisonOperator
Two related Pattern 1 bugs:
1. The locals capture used `\\{([^}]+)\\}` (greedy non-`}` chars), so
`locals: { that: [1, 2, 3] }` truncated at the first `,` inside `[...]`
and bound `that` to `"[1"`. Switched to balanced-brace extraction +
`split_top_level` so nested arrays/objects survive.
2. `{ me: <X> }` was only forwarded to the SX runtime when X was a single
integer (eval-hs-with-me only accepts numbers). For `me: [1, 2, 3]`
or `me: 1` alongside other locals, `me` was silently dropped, so
`I contain that` couldn't see its receiver. Now any non-numeric `me`
value is bound as a local (`(list (quote me) <val>)`); a numeric
`me` alongside other locals/setups is also bound, so the HS expr
always sees its `me`.
comparisonOperator 79/83 → 81/83 (+2: contains/includes works with arrays).
bind unchanged (43/44).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4341,8 +4341,8 @@
|
|||||||
(assert= (eval-hs "'Hello World' contains 'missing' ignoring case") false)
|
(assert= (eval-hs "'Hello World' contains 'missing' ignoring case") false)
|
||||||
)
|
)
|
||||||
(deftest "contains works with arrays"
|
(deftest "contains works with arrays"
|
||||||
(assert= (eval-hs-locals "I contain that" (list (list (quote that) 1))) true)
|
(assert= (eval-hs-locals "I contain that" (list (list (quote that) 1) (list (quote me) (list 1 2 3)))) true)
|
||||||
(assert= (eval-hs-locals "that contains me" (list (list (quote that) "[1"))) true)
|
(assert= (eval-hs-locals "that contains me" (list (list (quote that) (list 1 2 3)) (list (quote me) 1))) true)
|
||||||
)
|
)
|
||||||
(deftest "contains works with css literals"
|
(deftest "contains works with css literals"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
@@ -4501,8 +4501,8 @@
|
|||||||
(assert= (eval-hs-locals "foobar does not include foo" (list (list (quote foo) "foo") (list (quote foobar) "foobar"))) false)
|
(assert= (eval-hs-locals "foobar does not include foo" (list (list (quote foo) "foo") (list (quote foobar) "foobar"))) false)
|
||||||
)
|
)
|
||||||
(deftest "includes works with arrays"
|
(deftest "includes works with arrays"
|
||||||
(assert= (eval-hs-locals "I include that" (list (list (quote that) 1))) true)
|
(assert= (eval-hs-locals "I include that" (list (list (quote that) 1) (list (quote me) (list 1 2 3)))) true)
|
||||||
(assert= (eval-hs-locals "that includes me" (list (list (quote that) "[1"))) true)
|
(assert= (eval-hs-locals "that includes me" (list (list (quote that) (list 1 2 3)) (list (quote me) 1))) true)
|
||||||
)
|
)
|
||||||
(deftest "includes works with css literals"
|
(deftest "includes works with css literals"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
|
|||||||
@@ -1281,16 +1281,48 @@ def generate_eval_only_test(test, idx):
|
|||||||
hs_expr = extract_hs_expr(m.group(2))
|
hs_expr = extract_hs_expr(m.group(2))
|
||||||
opts_str = m.group(3) or ''
|
opts_str = m.group(3) or ''
|
||||||
expected_sx = js_val_to_sx(m.group(4))
|
expected_sx = js_val_to_sx(m.group(4))
|
||||||
# Check for { me: X } or { locals: { x: X, y: Y } } in opts
|
# Check for { me: X } or { locals: { x: X, y: Y } } in opts.
|
||||||
me_match = re.search(r'\bme:\s*(\d+)', opts_str)
|
# Numeric me uses eval-hs-with-me; other me values get bound as a local.
|
||||||
locals_match = re.search(r'locals:\s*\{([^}]+)\}', opts_str)
|
me_num_match = re.search(r'\bme:\s*(\d+)\b', opts_str)
|
||||||
|
me_val_match = re.search(r'\bme:\s*(\[[^\]]*\]|\{[^}]*\}|"[^"]*"|\'[^\']*\')', opts_str)
|
||||||
|
# Locals: balanced-brace extraction so nested arrays/objects don't truncate.
|
||||||
|
locals_idx = opts_str.find('locals:')
|
||||||
extra = []
|
extra = []
|
||||||
if locals_match:
|
if locals_idx >= 0:
|
||||||
for lm in re.finditer(r'(\w+)\s*:\s*([^,}]+)', locals_match.group(1)):
|
open_idx = opts_str.find('{', locals_idx)
|
||||||
extra.append((lm.group(1), js_val_to_sx(lm.group(2).strip())))
|
if open_idx >= 0:
|
||||||
if me_match and not (window_setups or extra):
|
depth, in_str, end_idx = 1, None, -1
|
||||||
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_match.group(1)}) {expected_sx})')
|
for i in range(open_idx + 1, len(opts_str)):
|
||||||
|
ch = opts_str[i]
|
||||||
|
if in_str:
|
||||||
|
if ch == in_str and opts_str[i - 1] != '\\':
|
||||||
|
in_str = None
|
||||||
|
continue
|
||||||
|
if ch in ('"', "'", '`'):
|
||||||
|
in_str = ch
|
||||||
|
continue
|
||||||
|
if ch == '{':
|
||||||
|
depth += 1
|
||||||
|
elif ch == '}':
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0:
|
||||||
|
end_idx = i
|
||||||
|
break
|
||||||
|
if end_idx > open_idx:
|
||||||
|
for kv in split_top_level(opts_str[open_idx + 1:end_idx]):
|
||||||
|
kv = kv.strip()
|
||||||
|
m2 = re.match(r'^(\w+)\s*:\s*(.+)$', kv, re.DOTALL)
|
||||||
|
if m2:
|
||||||
|
extra.append((m2.group(1), js_val_to_sx(m2.group(2).strip())))
|
||||||
|
if me_val_match:
|
||||||
|
extra.append(('me', js_val_to_sx(me_val_match.group(1))))
|
||||||
|
if me_num_match and not (window_setups or extra):
|
||||||
|
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_num_match.group(1)}) {expected_sx})')
|
||||||
else:
|
else:
|
||||||
|
# If there are other locals/setups but `me: <num>` is present too,
|
||||||
|
# bind it as a local so the HS expression can see it.
|
||||||
|
if me_num_match and not me_val_match:
|
||||||
|
extra.append(('me', me_num_match.group(1)))
|
||||||
assertions.append(emit_eval(hs_expr, expected_sx, extra))
|
assertions.append(emit_eval(hs_expr, expected_sx, extra))
|
||||||
|
|
||||||
# Pattern 1b: Inline — run("expr", opts).toEqual([...])
|
# Pattern 1b: Inline — run("expr", opts).toEqual([...])
|
||||||
|
|||||||
Reference in New Issue
Block a user