Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup
Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests
WASM: regenerate sx_browser.bc.js from updated hs sources
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
167 lines
6.8 KiB
Markdown
167 lines
6.8 KiB
Markdown
# F13 — Step Limit + `meta.caller` (+5 → 100%)
|
||
|
||
Five tests currently timeout or produce wrong values due to two root causes:
|
||
step budget exhaustion and a missing `meta` implementation.
|
||
|
||
## Tests
|
||
|
||
| # | Suite | Test | Failure |
|
||
|---|-------|------|---------|
|
||
| 198 | `hs-upstream-core/runtime` | `has proper stack from event handler` | wrong-value: `meta.caller` returns `""` instead of an object with `.meta.feature.type = "onFeature"` |
|
||
| 200 | `hs-upstream-core/runtime` | `hypertrace is reasonable` | TIMEOUT (15s, step limit) |
|
||
| 615 | `hs-upstream-expressions/in` | `query template returns values` | TIMEOUT (37s, step limit) |
|
||
| 1197 | `hs-upstream-repeat` | `repeat forever works` | TIMEOUT (step limit) |
|
||
| 1198 | `hs-upstream-repeat` | `repeat forever works w/o keyword` | TIMEOUT (step limit) |
|
||
|
||
---
|
||
|
||
## Root cause A — Step limit (tests 200, 615, 1197, 1198)
|
||
|
||
The runner sets `HS_STEP_LIMIT=200000`. Every CEK step consumed by any
|
||
expression in a test — including the double compilation warm-up guard blocks
|
||
that appear before the actual DOM test — counts against this shared budget.
|
||
|
||
### `repeat forever` (1197, 1198)
|
||
|
||
The loop body terminates in exactly **5 iterations** (`if retVal == 5 then return`).
|
||
This is bounded, not infinite. The step budget is exhausted before the loop
|
||
runs because two `eval-expr-cek` compilation warm-up calls each consume tens
|
||
of thousands of steps.
|
||
|
||
Fix: each warm-up guard compiles and discards a HS function definition. Those
|
||
calls are defensive (wrapped in `guard` that swallows errors). We do NOT need
|
||
to run the compiled code — the warm-up's purpose is just to ensure the
|
||
compiler doesn't crash, not to consume steps. The step counter should not tick
|
||
during compilation (compilation is a pure transform, not evaluation). If that's
|
||
impractical to gate, raise `HS_STEP_LIMIT` to `2000000` (10×).
|
||
|
||
### `hypertrace is reasonable` (200)
|
||
|
||
Defines `bar()` → calls `baz()` → throws. Simple call chain. The "hypertrace"
|
||
in the test name implies the HS runtime trace recorder is active during the
|
||
test. If trace recording is on globally, every CEK step generates a trace entry
|
||
allocation. Fix: confirm whether trace recording is always-on in the test runner
|
||
and disable it by default (trace should only be on when explicitly requested).
|
||
Alternatively raise step limit.
|
||
|
||
### `query template returns values` (615)
|
||
|
||
Uses `<${"p"}/>` — a CSS query selector built from a template string. Takes 37
|
||
seconds. Likely the template selector evaluation triggers repeated DOM scanning
|
||
or expensive string construction per step. Fix: profile with `hs_test_run
|
||
verbose=true` to identify which step is slow. If it's a regex compilation
|
||
per-call, cache it. If step limit only, raise to 2M.
|
||
|
||
### Unified fix: raise `HS_STEP_LIMIT` to `2000000`
|
||
|
||
The simplest fix that unblocks all four timeout tests. In
|
||
`tests/hs-run-filtered.js`, change the default step limit. Per-test overrides
|
||
can still be set via `HS_STEP_LIMIT` env var for debugging.
|
||
|
||
If the `query template` test is still slow at 2M steps (37s × 10 = 370s, which
|
||
would be unacceptable), that test needs a separate performance fix — cache the
|
||
compiled regex/query from the template string rather than rebuilding it on every
|
||
access.
|
||
|
||
---
|
||
|
||
## Root cause B — `meta.caller` not implemented (test 198)
|
||
|
||
The HS `meta` object is available inside any function call. It exposes:
|
||
|
||
- `meta.caller` — the calling context object
|
||
- `meta.caller.meta.feature.type` — the HS feature type of the caller
|
||
(e.g. `"onFeature"` when called from an `on click` handler)
|
||
|
||
Test script:
|
||
```
|
||
def bar()
|
||
log meta.caller
|
||
return meta.caller
|
||
end
|
||
```
|
||
Triggered via `on click put bar().meta.feature.type into my.innerHTML`.
|
||
Expects `"onFeature"` in innerHTML. Currently gets `""`.
|
||
|
||
### What `meta` needs
|
||
|
||
`meta` is a dict-like object injected into every function's execution context
|
||
at call time. Minimum fields for this test:
|
||
|
||
```
|
||
meta = {
|
||
:caller <the calling context — a dict with its own :meta field>
|
||
:element <the element the script is attached to>
|
||
}
|
||
```
|
||
|
||
`meta.caller.meta.feature.type` must return `"onFeature"` when called from an
|
||
`on` event handler. The feature type string `"onFeature"` is already used
|
||
internally (event handler features are tagged with this type).
|
||
|
||
### Implementation
|
||
|
||
In `lib/hyperscript/runtime.sx`, at the point where a HS `def` function is
|
||
called:
|
||
|
||
1. Build a `meta` dict:
|
||
```
|
||
{:caller calling-context :element current-element}
|
||
```
|
||
where `calling-context` is the current runtime context dict (which includes
|
||
its own `:meta` field with `:feature {:type "onFeature"}` for event handlers).
|
||
|
||
2. Bind `meta` in the function's execution env.
|
||
|
||
3. Ensure event handler contexts carry `{:meta {:feature {:type "onFeature"}}}`.
|
||
|
||
This is an additive change — nothing currently uses `meta`, so no regression
|
||
risk.
|
||
|
||
---
|
||
|
||
## Implementation checklist
|
||
|
||
### Step A — Raise step limit
|
||
1. In `tests/hs-run-filtered.js`, change default `HS_STEP_LIMIT` from `200000`
|
||
to `2000000`.
|
||
2. Run tests 1197–1198: `hs_test_run(start=1197, end=1199)` — expect 2/2.
|
||
3. Run test 615: `hs_test_run(start=615, end=616)` — expect 1/1 or note if
|
||
still too slow.
|
||
4. Run test 200: `hs_test_run(start=200, end=201)` — expect 1/1.
|
||
|
||
### Step B — `meta.caller` (test 198)
|
||
5. `sx_find_all` in `lib/hyperscript/runtime.sx` for where `def` functions are
|
||
called / where event handler contexts are constructed.
|
||
6. Add `meta` dict construction at call time; bind in function env.
|
||
7. Ensure `on` handler context carries `{:meta {:feature {:type "onFeature"}}}`.
|
||
8. Run test 198: `hs_test_run(start=198, end=199)` — expect 1/1.
|
||
|
||
### Step C — Query template performance (if still slow after step A)
|
||
9. Profile `hs_test_run(start=615, end=616, step_limit=2000000, verbose=true)`.
|
||
10. If the CSS template query `<${"p"}/>` rebuilds on every call, add a memoize
|
||
cache keyed on the template result string.
|
||
11. Rerun — expect < 5s.
|
||
|
||
### Step D — Full suite verification
|
||
12. Run all ranges with raised step limit:
|
||
- `hs_test_run(start=0, end=201, step_limit=2000000)`
|
||
- `hs_test_run(start=201, end=616, step_limit=2000000)`
|
||
- `hs_test_run(start=616, end=1200, step_limit=2000000)`
|
||
- `hs_test_run(start=1200, end=1496, step_limit=2000000)`
|
||
13. Confirm all previously-passing tests still pass.
|
||
14. Commit: `HS: raise step limit to 2M + meta.caller for onFeature stack (+5)`
|
||
|
||
---
|
||
|
||
## Risk
|
||
|
||
- **Step limit raise:** May make test suite slower overall (more steps to exhaust
|
||
before timeout). But if tests pass quickly the limit is never reached.
|
||
The 37s query-template test is the only real concern — if it genuinely needs
|
||
2M steps × (time per step), it needs a performance fix too.
|
||
- **`meta.caller`:** Additive binding in function scope. Zero regression risk.
|
||
The only complexity is constructing the right shape for the calling context
|
||
chain — but since only one test exercises this and the shape is simple, the
|
||
risk is low.
|