Fix VM correctness: get nil-safe, scope/context/collect! as primitives

- get primitive returns nil for type mismatches (list+string) instead
  of raising — matches JS/Python behavior, fixes find-nav-match errors
- scope-peek, collect!, collected, clear-collected! registered as real
  primitives in sx_primitives table (not just env bindings) so the CEK
  step-sf-context can find them via get-primitive
- step-sf-context checks scope-peek hashtable BEFORE walking CEK
  continuation — bridges aser's scope-push!/pop! with CEK's context
- context, emit!, emitted added to SPECIAL_FORM_NAMES and handled in
  aser-special (scope operations in aser rendering mode)
- sx-context NativeFn for VM-compiled code paths
- VM execution errors no longer mark functions as permanently failed —
  bytecode is correct, errors are from runtime data
- kbd, samp, var added to HTML_TAGS + sx-browser.js rebuilt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 09:33:18 +00:00
parent a716e3f745
commit 4734d38f3b
7 changed files with 131 additions and 22 deletions

View File

@@ -291,7 +291,8 @@
"defhandler" "defpage" "defquery" "defaction" "defrelation"
"begin" "do" "quote" "quasiquote"
"->" "set!" "letrec" "dynamic-wind" "defisland"
"deftype" "defeffect" "scope" "provide"))
"deftype" "defeffect" "scope" "provide"
"context" "emit!" "emitted"))
(define HO_FORM_NAMES
(list "map" "map-indexed" "filter" "reduce"
@@ -460,6 +461,27 @@
(scope-pop! prov-name)
result)
;; context — scope lookup (uses hashtable stack, not CEK kont)
(= name "context")
(let ((ctx-name (trampoline (eval-expr (first args) env)))
(default-val (if (>= (len args) 2)
(trampoline (eval-expr (nth args 1) env))
nil)))
(let ((val (scope-peek ctx-name)))
(if (nil? val) default-val val)))
;; emit! — scope accumulator
(= name "emit!")
(let ((emit-name (trampoline (eval-expr (first args) env)))
(emit-val (trampoline (eval-expr (nth args 1) env))))
(scope-emit! emit-name emit-val)
nil)
;; emitted — collect accumulated scope values
(= name "emitted")
(let ((emit-name (trampoline (eval-expr (first args) env))))
(or (scope-peek emit-name) (list)))
;; Everything else — evaluate normally
:else
(trampoline (eval-expr expr env))))))