js-on-sx: for..of / for..in + more Array methods
Parser: jp-parse-for-stmt does 2-token lookahead for (var? ident (of|in) expr), emits (js-for-of-in kind ident iter body) else classic (js-for init cond step body). Transpile: wraps body in (call/cc (__break__) (let items (for-each (fn (ident) (call/cc (__continue__) body)) items))). Runtime: js-iterable-to-list normalizes list/string/dict for of iteration; js-string-to-list expands string to char list. 399/401 unit (+8), 148/148 slice unchanged.
This commit is contained in:
@@ -866,22 +866,48 @@
|
||||
jp-parse-for-stmt
|
||||
(fn
|
||||
(st)
|
||||
(do
|
||||
(jp-advance! st)
|
||||
(jp-expect! st "punct" "(")
|
||||
(jp-advance! st)
|
||||
(jp-expect! st "punct" "(")
|
||||
(let
|
||||
((has-decl false) (decl-kind nil))
|
||||
(cond
|
||||
((jp-at? st "keyword" "var")
|
||||
(begin (set! has-decl true) (set! decl-kind "var")))
|
||||
((jp-at? st "keyword" "let")
|
||||
(begin (set! has-decl true) (set! decl-kind "let")))
|
||||
((jp-at? st "keyword" "const")
|
||||
(begin (set! has-decl true) (set! decl-kind "const")))
|
||||
(else nil))
|
||||
(let
|
||||
((init (jp-parse-for-init st)))
|
||||
(let
|
||||
((cond-ast (if (jp-at? st "punct" ";") nil (jp-parse-assignment st))))
|
||||
(do
|
||||
(jp-expect! st "punct" ";")
|
||||
(let
|
||||
((step (if (jp-at? st "punct" ")") nil (jp-parse-assignment st))))
|
||||
(do
|
||||
(jp-expect! st "punct" ")")
|
||||
((ident-off (if has-decl 1 0)))
|
||||
(cond
|
||||
((and (= (get (jp-peek-at st ident-off) :type) "ident") (or (and (= (get (jp-peek-at st (+ ident-off 1)) :type) "keyword") (= (get (jp-peek-at st (+ ident-off 1)) :value) "of")) (and (= (get (jp-peek-at st (+ ident-off 1)) :type) "keyword") (= (get (jp-peek-at st (+ ident-off 1)) :value) "in"))))
|
||||
(begin
|
||||
(when has-decl (jp-advance! st))
|
||||
(let
|
||||
((ident (get (jp-peek st) :value)))
|
||||
(jp-advance! st)
|
||||
(let
|
||||
((body (jp-parse-stmt st)))
|
||||
(list (quote js-for) init cond-ast step body))))))))))
|
||||
((iter-kind (get (jp-peek st) :value)))
|
||||
(jp-advance! st)
|
||||
(let
|
||||
((iter (jp-parse-assignment st)))
|
||||
(jp-expect! st "punct" ")")
|
||||
(let
|
||||
((body (jp-parse-stmt st)))
|
||||
(list (quote js-for-of-in) iter-kind ident iter body)))))))
|
||||
(else
|
||||
(let
|
||||
((init (cond (has-decl (jp-parse-var-stmt st decl-kind)) ((jp-at? st "punct" ";") (begin (jp-advance! st) nil)) (else (let ((e (jp-parse-assignment st))) (jp-expect! st "punct" ";") (list (quote js-exprstmt) e))))))
|
||||
(let
|
||||
((cond-ast (if (jp-at? st "punct" ";") nil (jp-parse-assignment st))))
|
||||
(jp-expect! st "punct" ";")
|
||||
(let
|
||||
((step (if (jp-at? st "punct" ")") nil (jp-parse-assignment st))))
|
||||
(jp-expect! st "punct" ")")
|
||||
(let
|
||||
((body (jp-parse-stmt st)))
|
||||
(list (quote js-for) init cond-ast step body)))))))))))
|
||||
|
||||
(define
|
||||
jp-parse-for-init
|
||||
|
||||
@@ -1585,6 +1585,29 @@
|
||||
(dict-set! p "value" reason)
|
||||
(js-promise-flush-callbacks! p))))))
|
||||
|
||||
(define
|
||||
js-iterable-to-list
|
||||
(fn
|
||||
(v)
|
||||
(cond
|
||||
((list? v) v)
|
||||
((= (type-of v) "string") (js-string-to-list v 0 (list)))
|
||||
((dict? v)
|
||||
(let
|
||||
((result (list)))
|
||||
(for-each (fn (k) (append! result (get v k))) (keys v))
|
||||
result))
|
||||
(else (list)))))
|
||||
|
||||
(define
|
||||
js-string-to-list
|
||||
(fn
|
||||
(s i acc)
|
||||
(if
|
||||
(>= i (len s))
|
||||
acc
|
||||
(begin (append! acc (char-at s i)) (js-string-to-list s (+ i 1) acc)))))
|
||||
|
||||
(define
|
||||
js-object-keys
|
||||
(fn
|
||||
|
||||
@@ -1027,6 +1027,14 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 2104)
|
||||
(eval "(js-eval \"[1,2,1,2].indexOf(2, 2)\")")
|
||||
|
||||
;; ── Phase 11.forofin: for..of / for..in ─────────────────────────
|
||||
(epoch 2200)
|
||||
(eval "(js-eval \"var s=0; for (var x of [1,2,3]) s=s+x; s\")")
|
||||
(epoch 2201)
|
||||
(eval "(js-eval \"var r=''; for (var k in {a:1, b:2}) r=r+k; r.length\")")
|
||||
(epoch 2202)
|
||||
(eval "(js-eval \"var s=''; for (var c of 'abc') s=s+c; s\")")
|
||||
|
||||
EPOCHS
|
||||
|
||||
|
||||
@@ -1578,6 +1586,11 @@ check 2102 "fill(0)" '"0,0,0"'
|
||||
check 2103 "fill(0,1,3)" '"1,0,0,4"'
|
||||
check 2104 "indexOf with start" '3'
|
||||
|
||||
# ── Phase 11.forofin ───────────────────────────────────────────
|
||||
check 2200 "for-of array" '6'
|
||||
check 2201 "for-in object keys count" '2'
|
||||
check 2202 "for-of string" '"abc"'
|
||||
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "✓ $PASS/$TOTAL JS-on-SX tests passed"
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
(nth ast 2)
|
||||
(nth ast 3)
|
||||
(nth ast 4)))
|
||||
((js-tag? ast "js-for-of-in")
|
||||
(js-transpile-for-of-in
|
||||
(nth ast 1)
|
||||
(nth ast 2)
|
||||
(nth ast 3)
|
||||
(nth ast 4)))
|
||||
((js-tag? ast "js-return") (js-transpile-return (nth ast 1)))
|
||||
((js-tag? ast "js-break") (js-transpile-break))
|
||||
((js-tag? ast "js-continue") (js-transpile-continue))
|
||||
@@ -643,6 +649,42 @@
|
||||
(for-each (fn (stmt) (append! forms (js-transpile stmt))) body)
|
||||
(cons (js-sym "begin") forms))))
|
||||
|
||||
(define
|
||||
js-transpile-for-of-in
|
||||
(fn
|
||||
(iter-kind ident iter-ast body-ast)
|
||||
(let
|
||||
((ident-sym (js-sym ident))
|
||||
(iter-sx (js-transpile iter-ast))
|
||||
(body-sx (js-transpile body-ast))
|
||||
(items-sym (js-sym "__js_items__")))
|
||||
(list
|
||||
(js-sym "call/cc")
|
||||
(list
|
||||
(js-sym "fn")
|
||||
(list (js-sym "__break__"))
|
||||
(list
|
||||
(js-sym "let")
|
||||
(list
|
||||
(list
|
||||
items-sym
|
||||
(if
|
||||
(= iter-kind "of")
|
||||
(list (js-sym "js-iterable-to-list") iter-sx)
|
||||
(list (js-sym "js-object-keys") iter-sx))))
|
||||
(list
|
||||
(js-sym "for-each")
|
||||
(list
|
||||
(js-sym "fn")
|
||||
(list ident-sym)
|
||||
(list
|
||||
(js-sym "call/cc")
|
||||
(list
|
||||
(js-sym "fn")
|
||||
(list (js-sym "__continue__"))
|
||||
body-sx)))
|
||||
items-sym)))))))
|
||||
|
||||
(define
|
||||
js-param-sym
|
||||
(fn
|
||||
|
||||
@@ -189,6 +189,8 @@ Append-only record of completed iterations. Loop writes one line per iteration:
|
||||
|
||||
- 2026-04-23 — **JSON.stringify + JSON.parse.** Shipped a recursive-descent parser and serializer in SX. `js-json-stringify` dispatches on `type-of` for primitives, lists, dicts. `js-json-parse` uses a state dict `{:s src :i idx}` mutated in-place by helpers (`js-json-skip-ws!`, `js-json-parse-value`, `-string`, `-number`, `-array`, `-object`). String parser handles `\n \t \r \" \\ \/` escapes. Number parser collects digits/signs/e+E/. then delegates to `js-to-number`. Array and object loops recursively call parse-value. JSON wired into `js-global`. 10 new tests (stringify primitives/arrays/objects, parse primitives/string/array/object), **391/393** (381→+10). Conformance unchanged.
|
||||
|
||||
- 2026-04-23 — **Array.prototype flat/fill; indexOf start arg; for..of/for..in.** Array: `flat(depth=1)` uses `js-list-flat-loop` (recursive flatten), `fill(value, start?, end?)` mutates in-place then returns self via `js-list-fill-loop`. Fixed `indexOf` to honor the `fromIndex` second argument. Parser: `jp-parse-for-stmt` now does a 2-token lookahead — if it sees `(var? ident (of|in) expr)` it emits `(js-for-of-in kind ident iter body)`, else falls back to classic `for(;;)`. Transpile: `js-transpile-for-of-in` wraps body in `(call/cc (fn (__break__) (let ((__js_items__ <normalized-source>)) (for-each (fn (ident) (call/cc (fn (__continue__) body))) items))))`. For `of` it normalizes via `js-iterable-to-list` (list → self, string → char list, dict → values). For `in` it iterates over `js-object-keys`. `break` / `continue` already route to the call/cc bindings. 8 new tests (flat, fill variants, indexOf with start, for-of array/string, for-in dict), **399/401** (391→+8). Conformance unchanged. Gotcha: **SX `cond` clauses evaluate only the last expr of a body. `(cond ((test) (set! a 1) (set! b 2)) …)` silently drops the first set!.** Must wrap multi-stmt clause bodies in `(begin …)`. First pass on the for-stmt rewrite had multi-expr cond clauses that silently did nothing — broke all existing for-loop tests, not just the new ones.
|
||||
|
||||
## Phase 3-5 gotchas
|
||||
|
||||
Worth remembering for later phases:
|
||||
|
||||
Reference in New Issue
Block a user