review quick-wins: JIT gate, crash guards, crit-2 signal-return, regen repair
Server (sx_server.ml):
- HTTP mode: JIT hook now opt-in via SX_SERVING_JIT, matching epoch mode
(was unconditional — live serving-JIT miscompiles J1/J2/J3 de-risked)
- command channel: malformed/non-ASCII line returns an error response
instead of killing the shared process (C1/C1b)
- response cache: soft error pages no longer cached (S4);
http_render_page returns (html, is_error)
Kernel spec + regen:
- crit-2: signal-return frame stored the saved kont under :f but the reader
looked up "saved-kont" — handler value became the whole program's result
and the covering test passed vacuously. Fixed; raise-continuable now also
resumes at the raise site (rest-k, not unwound-k), mirroring signal-condition
- quasiquote: R7RS longhand unquote-splicing aliased to splice-unquote
(used to serialize literally — silent zero-splice)
- guard: re-raise sentinel gensym'd per execution (was forgeable by any
(list '__guard-reraise__ x) value)
- do: IIFE-head form no longer misparses as a Scheme do-loop
- render: area/base/embed/param/track added to HTML_TAGS (were void-only
and rendered as Undefined symbol)
- REGEN REPAIR: checked-in sx_ref.ml carried hand-written additions that
every regeneration silently lost (let-values/define-values/delay/
delay-force registrations, AdtValue define-type) plus 5 regen blockers
(arrow-name mangling, 3-arg get, &rest defines, HO-position helper refs,
transpiler prim-table gaps). Moved into bootstrap.py FIXUPS/skips and the
transpiler prim table — regen is now reproducible, compiles, and tests
at baseline (CI Dockerfile.test steps 3-4 could not previously have
produced a compiling kernel)
Primitives:
- contains?: dict key-check arm per its spec doc
- expt: promotes to float on int63 overflow ((expt 2 100) returned 0)
- mcp_tree parity with sx_primitives: get (Integer indices + 3-arg default),
split (literal substring, was char-class — the historical gotcha lived
here), empty? on ""/{}, contains?, equal?, keyword-name, char-code
(Integer), parse-number (Integer-aware)
Python/docs:
- shared/sx/boundary.py: dead validation now logs a one-time WARNING instead
of silently no-oping (full revival gated: tier-1 declarations deleted and
SX_BOUNDARY_STRICT=1 is live in production compose)
- CLAUDE.md: canonical reference now points at spec/*.sx; island authoring
rules corrected (let IS sequential, bodies ARE implicit begin)
Verification: full suite 5762 passed / 274 failed — fail set byte-identical
to the pre-change baseline (273 in-progress hs-* + pre-existing r7rs radix
shadow). All repros verified fixed on both the native binary and the rebuilt
WASM browser kernel. Review findings: /tmp/sx-review/*.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1312,7 +1312,12 @@
|
||||
(= (type-of item) "list")
|
||||
(= (len item) 2)
|
||||
(= (type-of (first item)) "symbol")
|
||||
(= (symbol-name (first item)) "splice-unquote"))
|
||||
;; ,@ parses to splice-unquote; accept the R7RS longhand
|
||||
;; unquote-splicing too (it used to serialize literally —
|
||||
;; a silent zero-splice)
|
||||
(or
|
||||
(= (symbol-name (first item)) "splice-unquote")
|
||||
(= (symbol-name (first item)) "unquote-splicing")))
|
||||
(let
|
||||
((spliced (trampoline (eval-expr (nth item 1) env))))
|
||||
(if
|
||||
@@ -1395,7 +1400,9 @@
|
||||
(let
|
||||
((result (apply producer (list))))
|
||||
(if
|
||||
(and (dict? result) (get result :_values false))
|
||||
;; 2-arg get: absent key → nil (falsy) — the explicit false default
|
||||
;; was redundant AND the OCaml transpiler only emits 2-arg get
|
||||
(and (dict? result) (get result :_values))
|
||||
(apply consumer (get result :_list))
|
||||
(apply consumer (list result))))))
|
||||
|
||||
@@ -1415,7 +1422,7 @@
|
||||
(let
|
||||
((result (trampoline (eval-expr val-expr local))))
|
||||
(let
|
||||
((vs (if (and (dict? result) (get result :_values false)) (get result :_list) (list result))))
|
||||
((vs (if (and (dict? result) (get result :_values)) (get result :_list) (list result))))
|
||||
(for-each-indexed
|
||||
(fn
|
||||
(idx name)
|
||||
@@ -1442,7 +1449,7 @@
|
||||
(let
|
||||
((result (trampoline (eval-expr val-expr env))))
|
||||
(let
|
||||
((vs (if (and (dict? result) (get result :_values false)) (get result :_list) (list result))))
|
||||
((vs (if (and (dict? result) (get result :_values)) (get result :_list) (list result))))
|
||||
(for-each-indexed
|
||||
(fn (idx name) (env-bind! env (symbol-name name) (nth vs idx)))
|
||||
names)
|
||||
@@ -1699,7 +1706,9 @@
|
||||
(body (rest args))
|
||||
(var (first var-clauses))
|
||||
(clauses (rest var-clauses))
|
||||
(sentinel (make-symbol "__guard-reraise__")))
|
||||
;; gensym: fresh tag per guard execution — a user value shaped
|
||||
;; (list '__guard-reraise__ x) must not be mistaken for a re-raise
|
||||
(sentinel (gensym "__guard-reraise__")))
|
||||
(step-eval-list
|
||||
(list
|
||||
(quote let)
|
||||
@@ -1841,12 +1850,18 @@
|
||||
("io" (step-sf-io args env kont))
|
||||
("begin" (step-sf-begin args env kont))
|
||||
("do"
|
||||
;; do-loop only when it really looks like Scheme do:
|
||||
;; every element of the first form is a (var init [step])
|
||||
;; list AND a test-clause list follows. A begin-style
|
||||
;; (do ((fn (x) x) 5) ...) — IIFE head — used to misparse
|
||||
;; as a binding list.
|
||||
(if
|
||||
(and
|
||||
(not (empty? args))
|
||||
(>= (len args) 2)
|
||||
(list? (first args))
|
||||
(not (empty? (first args)))
|
||||
(list? (first (first args))))
|
||||
(every? (fn (b) (list? b)) (first args))
|
||||
(list? (nth args 1)))
|
||||
(let
|
||||
((bindings (first args))
|
||||
(test-clause (nth args 1))
|
||||
@@ -2056,7 +2071,9 @@
|
||||
(args env)
|
||||
(let ((thunk (make-lambda (list) (first args) env))) {:_iterative true :forced false :value nil :thunk thunk :_promise true})))
|
||||
|
||||
(define promise? (fn (v) (and (dict? v) (get v :_promise false))))
|
||||
;; 2-arg get throughout: absent key → nil (falsy) — explicit false/nil
|
||||
;; defaults were redundant AND the OCaml transpiler only emits 2-arg get
|
||||
(define promise? (fn (v) (and (dict? v) (get v :_promise))))
|
||||
|
||||
;; ═══════════════════════════════════════════════════════════════
|
||||
;; Part 11: Entry Points
|
||||
@@ -2075,12 +2092,12 @@
|
||||
(not (promise? p))
|
||||
p
|
||||
(if
|
||||
(get p :forced false)
|
||||
(get p :value nil)
|
||||
(get p :forced)
|
||||
(get p :value)
|
||||
(let
|
||||
((result (apply (get p :thunk nil) (list))))
|
||||
((result (apply (get p :thunk) (list))))
|
||||
(let
|
||||
((final (if (and (get p :_iterative false) (promise? result)) (force result) result)))
|
||||
((final (if (and (get p :_iterative) (promise? result)) (force result) result)))
|
||||
(dict-set! p :forced true)
|
||||
(dict-set! p :value final)
|
||||
final))))))
|
||||
@@ -2971,12 +2988,15 @@
|
||||
((registered
|
||||
(get (env-get env "*adt-registry*") type-name)))
|
||||
(when
|
||||
(and registered (not (some match-clause-is-else? clauses)))
|
||||
;; eta-expanded: a bare helper in HO position transpiles to
|
||||
;; cek_call on an OCaml function (type error); a lambda body
|
||||
;; calling it directly emits a plain application
|
||||
(and registered (not (some (fn (c) (match-clause-is-else? c)) clauses)))
|
||||
(let
|
||||
((clause-ctors
|
||||
(filter
|
||||
(fn (n) (not (nil? n)))
|
||||
(map match-clause-ctor-name clauses))))
|
||||
(map (fn (c) (match-clause-ctor-name c)) clauses))))
|
||||
(match-warn-non-exhaustive
|
||||
env type-name registered clause-ctors)))))))))
|
||||
|
||||
@@ -4507,8 +4527,12 @@
|
||||
rest-k)))))
|
||||
("restart" (make-cek-value value env rest-k))
|
||||
("signal-return"
|
||||
;; the frame constructor stores the caller's kont under :f
|
||||
;; (natively cf_f) — reading "saved-kont" returned nil and
|
||||
;; silently discarded the whole continuation outside the
|
||||
;; signal site (handler value became the program's result)
|
||||
(let
|
||||
((saved-kont (get frame "saved-kont")))
|
||||
((saved-kont (get frame "f")))
|
||||
(make-cek-value value (get frame "env") saved-kont)))
|
||||
("comp-trace" (make-cek-value value env rest-k))
|
||||
("cond-arrow"
|
||||
@@ -4565,8 +4589,12 @@
|
||||
(list condition)
|
||||
(if
|
||||
continuable?
|
||||
;; R7RS: the handler's value returns to the RAISE SITE,
|
||||
;; so save rest-k (the kont at raise time) — unwound-k
|
||||
;; has already dropped the frames between the raise
|
||||
;; site and the handler (mirrors step-sf-signal)
|
||||
(kont-push
|
||||
(make-signal-return-frame fenv unwound-k)
|
||||
(make-signal-return-frame fenv rest-k)
|
||||
unwound-k)
|
||||
(kont-push
|
||||
(make-raise-guard-frame fenv unwound-k)
|
||||
|
||||
@@ -160,7 +160,15 @@
|
||||
"template"
|
||||
"slot"
|
||||
"dialog"
|
||||
"menu"))
|
||||
"menu"
|
||||
;; void elements must ALSO be here — render routes a head symbol to
|
||||
;; the element renderer only when it's in HTML_TAGS; these five were
|
||||
;; only in VOID_ELEMENTS and fell through to "Undefined symbol"
|
||||
"area"
|
||||
"base"
|
||||
"embed"
|
||||
"param"
|
||||
"track"))
|
||||
|
||||
;; Self-closing tags (br, img, hr, etc.)
|
||||
(define
|
||||
|
||||
Reference in New Issue
Block a user