hs: query targets, prolog hook, loop scripts, new plans, WASM regen

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>
This commit is contained in:
2026-05-06 09:19:56 +00:00
parent c311d4ebc4
commit 985671cd76
31 changed files with 16041 additions and 7056 deletions

View File

@@ -0,0 +1,107 @@
# F8 — evalStatically (+3)
**Suite:** `hs-upstream-core/evalStatically`
**Target:** 3 failing (untranslated) out of 8. 5 already pass.
## 1. Current state
5 passing tests use `(eval-hs expr)` and check the return value for literals: booleans, null, numbers, plain strings, time expressions. These call `_hyperscript.evaluate(src)` and return the result.
3 failing tests are named:
- `throws on math expressions`
- `throws on symbol references`
- `throws on template strings`
All are `SKIP (untranslated)` — no test body has been generated.
## 2. What upstream checks
From `test/core/evalStatically.js`, the `throwErrors` mode:
```js
expect(() => _hyperscript.evaluate("1 + 2")).toThrow();
expect(() => _hyperscript.evaluate("x")).toThrow();
expect(() => _hyperscript.evaluate(`"hello ${name}"`)).toThrow();
```
`_hyperscript.evaluate(src)` in strict static mode throws when the expression is not a pure literal — math operators, symbol references, and template string interpolation all involve runtime evaluation that can't be statically resolved.
The "static" constraint: only literals that can be evaluated without any runtime context or side effects are allowed. `1 + 2` is not static (it's a math op). `x` is not static (symbol lookup). `"hello ${name}"` is not static (interpolation).
## 3. What `eval-hs` currently does
`eval-hs` in our harness calls `(hs-compile-and-run src)` or equivalent. It does NOT currently have a "static mode" — it runs everything with the full runtime.
We need a new harness helper `eval-hs-static-error` that:
1. Calls `(hs-compile src)` with a flag that makes it throw on non-literal expressions
2. Returns the caught error message, or raises if no error was thrown
## 4. Implementation options
### Option A — Static analysis pass (accurate)
Before evaluation, walk the AST and reject any node that isn't a literal:
- Number literal ✓
- String literal (no interpolation) ✓
- Boolean literal ✓
- Null literal ✓
- Time expression (`200ms`, `2s`) ✓
- Everything else → throw `"expression is not static"`
This is a pre-eval AST check, not a runtime change. Lives in `lib/hyperscript/compiler.sx` as `hs-check-static`.
### Option B — Generator translation (simpler)
The 3 tests are untranslated. All three just verify that `_hyperscript.evaluate(expr)` throws. In our SX harness we can test this with a `guard` form:
```lisp
(deftest "throws on math expressions"
(let ((result (guard (e (true true))
(eval-hs "1 + 2")
false)))
(assert result)))
```
But this only works if `eval-hs` actually throws on math expressions. Currently it doesn't — `eval-hs "1 + 2"` returns `3`. So we'd need the static analysis anyway to make the test pass.
### Chosen approach: Option A
Add `hs-static-check` to the compiler: a fast AST walker that throws on any non-literal node. Wire it as an optional mode. The test harness calls `eval-hs-static` which runs with static-check enabled.
Actually, reading the upstream more carefully: `_hyperscript.evaluate` already throws in static mode without additional flags — the "evaluate" API is documented as static-only. Our `eval-hs` in the passing tests works because booleans/numbers/strings/time ARE static. `1 + 2`, `x`, and template strings are NOT static and should throw.
So the fix is: make `hs-compile-and-run` (or whatever backs `eval-hs`) reject non-literal AST nodes. The 5 passing tests will continue to pass (they use literals). The 3 failing tests will get translated using `eval-hs-error` or a guard pattern.
## 5. Non-literal AST node types to reject
| Expression | AST node type | Reject? |
|-----------|--------------|---------|
| `1`, `3.14` | number literal | ✓ allow |
| `"hello"`, `'world'` | string literal (no interpolation) | ✓ allow |
| `true`, `false` | boolean literal | ✓ allow |
| `null` | null literal | ✓ allow |
| `200ms`, `2s` | time literal | ✓ allow |
| `1 + 2` | math operator | ✗ throw |
| `x` | symbol reference | ✗ throw |
| `"hello ${name}"` | template string | ✗ throw |
## 6. Implementation checklist
1. In `lib/hyperscript/compiler.sx`, add `hs-static?` predicate: returns true only for literal AST node types.
2. In the `eval-hs` path (wherever `hs-compile-and-run` is called for the evaluate API), call `hs-static?` on the parsed AST and throw `"expression is not statically evaluable"` if false.
3. Replace 3 `SKIP` bodies in `spec/tests/test-hyperscript-behavioral.sx`:
```lisp
(deftest "throws on math expressions"
(assert (string? (eval-hs-error "1 + 2"))))
(deftest "throws on symbol references"
(assert (string? (eval-hs-error "x"))))
(deftest "throws on template strings"
(assert (string? (eval-hs-error "\"hello ${name}\""))))
```
4. Run `hs_test_run suite="hs-upstream-core/evalStatically"` — expect 8/8.
5. Run smoke 0195 — verify the 5 passing tests still pass.
6. Commit: `HS: evalStatically — static literal check, 3 tests (+3)`
## 7. Risk
Low-medium. The main risk is that `eval-hs` is used in many tests for non-static expressions and adding a static check to the shared path would break them. The fix must be gated — either a separate `eval-hs-static` helper or a flag parameter. The passing tests must not be affected.