Implements namespace eval, current, which, exists, delete, export,
import, forget, path, and ensemble create (auto-map + -map). Procs
defined inside namespace eval are stored as fully-qualified names
(::ns::proc), resolved relative to the calling namespace at lookup
time. Proc bodies execute in their defining namespace so sibling
calls work without qualification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds lib/tcl/conformance.sh: runs .tcl programs through the epoch
protocol, compares against # expected: annotations, writes
scoreboard.json and scoreboard.md. All 3 classic programs pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 3 headline feature: everything falls out of SX's first-class env chain.
- make-tcl-interp extended with :frame-stack and :procs fields
- proc: user-defined commands with param binding, rest args, isolated scope
- uplevel: run script in ancestor frame with correct frame propagation
- upvar: alias local name to remote frame variable (get/set follow alias)
- global/variable: sugar for upvar #0
- info: level, vars, locals, globals, commands, procs, args, body
- tcl-call-proc propagates updated frames back to caller after proc returns
- test.sh timeout bumped to 90s for larger runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pl-hs-query, pl-hs-predicate/1,2,3, pl-hs-install in hs-bridge.sx.
No parser/compiler changes: Hyperscript already compiles
`when allowed(user, action)` to (allowed user action).
Total 590/590.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bind: verify $nope stays nil when binding to a plain div (compile→nil).
when: verify myVar produces when-feat-no-op (parse-error detected).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hs-coerce HTML list case: use outerHTML for element items, not str.
hs-coerce Fragment case: actually build a DocumentFragment — element
items are appended directly; strings are parsed via a temp div.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JIT saturation after multiple compilations in the 13-test suite
causes tests 818-819 to time out at 10s.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests that call eval-expr-cek twice before the assertion take 7–12 s
cold on the WASM kernel. The 10 s wall-clock deadline fires during the
second warmup call, leaving the kernel in a partially-compiled state
that silently broke adjacent tests (e.g. "loop continue works" started
producing empty output rather than the expected string).
Add 60 s entries to _SLOW_DEADLINE for:
- behavior scoping is isolated from other/core element scope (×2)
- repeat suite preheat tests: can nest loops, only executes init once,
repeat forever (w/ and w/o keyword), until keyword works,
while keyword works (×6)
All eight suites now pass 100 %:
hs-upstream-core/scoping 20/20
hs-upstream-repeat 29/29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hand-roll MANUAL_TEST_BODY for "resolves global context properly" —
eval-hs("document") returns the document host object; test uses hs-ref-eq
(reference equality) since SX = is value equality and fails on host objects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _hs-custom-conversions dict and _hs-dynamic-converters list to
runtime.sx. hs-set-conversion!/hs-clear-conversion!/hs-add-dynamic-converter!/
hs-pop-dynamic-converter!/hs-clear-converters! helpers expose the API.
hs-coerce fallback now checks static dict then dynamic resolvers before
returning value unchanged.
Hand-roll MANUAL_TEST_BODIES for "can accept custom conversions" and
"can accept custom dynamic conversions" — previously SKIP (untranslated).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parser: add 'the ...' as a recognized transition target in parse-transition-cmd's
tgt cond, enabling 'transition the next <div/>'s *width from A to B'.
Generator MANUAL_TEST_BODIES for 4 previously-SKIP tests:
- can transition on query ref with possessive (transition suite, 17/17)
- can write to next element with put command (relativePositionalExpression, 23/23)
- parse error at EOF on trailing newline does not crash (core/parser, 13/14)
- halt works outside of event context (halt suite, 7/7)
Also fix hs-kernel-eval.js navigator assignment for Node.js v22 (read-only global).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
host-call-fn: the K.callFn path had no try-catch, so SX exceptions from
behavior handlers (compiled via K.callFn) propagated through SX guard
frames as JS errors. Add try-catch that swallows non-TIMEOUT errors and
re-throws TIMEOUT (matching the fn.apply path).
_SLOW_DEADLINE_SUITES: behavior tests legitimately take 10-20s per test
(behavior script compilation + install + init). Extend their deadline from
the default 10s to 20s so they pass rather than wall-clock timeout.
Net: hs-upstream-behavior 10/10 (+5 previously timing out).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 11 separate eval-hs-locals compilations with a single
hs-compile call + shared run-sieve fn; reduces wall-clock from
60s+ to ~1s per call.
Generator: pre-resolve string variable concatenations before
pattern matching run() calls so multi-line HS sources translate
correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parse-feat true fallback now routes directly to parse-cmd-list when the first
token is a keyword (e.g. "add - to"), so command-keyword scripts always produce
parse errors rather than being treated as subtraction expressions. Non-keyword
tokens (numbers, identifiers, paren-open) still try expression-first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The eventsource compilation for multi-handler SSE exceeds the CEK
200k step limit. The test is correct; the execution is just expensive
(JIT cascade over repeated hs-compile calls). Add to _NO_STEP_LIMIT so
the wall-clock deadline still guards against true hangs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hs-win-call sets window._hs_null_error as a side channel when a global
function lookup fails. _driveAsync checks this flag and bails early to
avoid error cascades, but the flag was never cleared between tests.
A previous test (call/can call functions w/ underscores) triggers
hs-win-call when global_function is not set up, which leaves
_hs_null_error="'global_function' is null". The bootstrap/can wait test
then calls `wait 20ms` whose io-sleep resume is skipped by _driveAsync,
so .bar is never added and the assertion fails.
Fix: clear _hs_null_error in the per-test reset block in the test runner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three parse-cmd / parse-feat refinements:
1. Remove dict-branch from arith guard: span-mode=true produces dict nodes
with :kind "arith", not lists. The guard only needs the list-branch (for
span-mode=false). Without this, hs-src "x + y" threw a parse error.
2. parse-feat top-level expression-first fallback: when no feature keyword is
found, try parse-expr first. If it fully consumes the input (at-end?),
return the expression directly — bypassing parse-cmd and its arith guard.
This matches upstream _hyperscript("1 + 1") which evaluates as an
expression, not a pseudo-command.
3. paren-close exception in arith guard: when the token after the arithmetic
expression is ")", we are inside a parenthesised context (e.g. "(0+1) em"
string-postfix). Allow it through without the pseudo-command error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous callable check (0bef67dd) was too strict, rejecting legitimate
pseudo-commands like 'as' conversions, array literals, and property accesses.
The new approach:
- at-end? returns nil (trailing-then EOF guard, unchanged)
- arithmetic expressions (op symbols +/-/*//%) throw 'Pseudo-commands must
be function calls', matching upstream _hyperscript behaviour
- everything else (literals, calls, as-expr, arrays, refs) passes through
Handles both hs-span-mode=false (raw list with op as first) and true (dict
with :kind "arith"). pseudoCommand 11/11, asExpression 36/42, arrayLiteral
8/8, breakpoint 2/2, evalStatically 8/8, regressions 16/16.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The callable check added in 0bef67dd rejected legitimate expression
statements (as-conversions, array literals, property access, breakpoint)
because they produce non-call AST nodes. The at-end? guard already handles
the trailing-then EOF case; the callable check is redundant and wrong.
Removing it restores the original open fallback: any parse-expr result is
a valid command. arrayLiteral 8/8, breakpoint 2/2, asExpression +35,
evalStatically +5, regressions +3.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>