diff --git a/lib/erlang/scoreboard.json b/lib/erlang/scoreboard.json index 639149b7..b2db94e0 100644 --- a/lib/erlang/scoreboard.json +++ b/lib/erlang/scoreboard.json @@ -1,11 +1,11 @@ { "language": "erlang", - "total_pass": 490, - "total": 490, + "total_pass": 530, + "total": 530, "suites": [ {"name":"tokenize","pass":62,"total":62,"status":"ok"}, {"name":"parse","pass":52,"total":52,"status":"ok"}, - {"name":"eval","pass":306,"total":306,"status":"ok"}, + {"name":"eval","pass":346,"total":346,"status":"ok"}, {"name":"runtime","pass":39,"total":39,"status":"ok"}, {"name":"ring","pass":4,"total":4,"status":"ok"}, {"name":"ping-pong","pass":4,"total":4,"status":"ok"}, diff --git a/lib/erlang/scoreboard.md b/lib/erlang/scoreboard.md index e17aad1d..bf9592fa 100644 --- a/lib/erlang/scoreboard.md +++ b/lib/erlang/scoreboard.md @@ -1,12 +1,12 @@ # Erlang-on-SX Scoreboard -**Total: 490 / 490 tests passing** +**Total: 530 / 530 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | tokenize | 62 | 62 | | ✅ | parse | 52 | 52 | -| ✅ | eval | 306 | 306 | +| ✅ | eval | 346 | 346 | | ✅ | runtime | 39 | 39 | | ✅ | ring | 4 | 4 | | ✅ | ping-pong | 4 | 4 | diff --git a/lib/erlang/tests/eval.sx b/lib/erlang/tests/eval.sx index 2bb54aae..a3056000 100644 --- a/lib/erlang/tests/eval.sx +++ b/lib/erlang/tests/eval.sx @@ -1043,6 +1043,88 @@ (ev "T1 = ets:new(t13, [set]), T2 = ets:new(t14, [set]), ets:insert(T1, {x, 1}), ets:insert(T2, {x, 99}), [{x, A}] = ets:lookup(T1, x), [{x, B}] = ets:lookup(T2, x), A + B") 100) +;; ── more BIFs ───────────────────────────────────────────────── +(er-eval-test "abs neg" (ev "abs(-7)") 7) +(er-eval-test "abs pos" (ev "abs(42)") 42) +(er-eval-test "abs zero" (ev "abs(0)") 0) + +(er-eval-test "min" (ev "min(3, 5)") 3) +(er-eval-test "min equal" (ev "min(7, 7)") 7) +(er-eval-test "max" (ev "max(3, 5)") 5) +(er-eval-test "max neg" (ev "max(-10, -2)") -2) + +(er-eval-test "tuple_to_list head" + (nm (ev "hd(tuple_to_list({a, b, c}))")) "a") +(er-eval-test "tuple_to_list len" + (ev "length(tuple_to_list({1, 2, 3, 4, 5}))") 5) +(er-eval-test "list_to_tuple roundtrip" + (ev "tuple_size(list_to_tuple([10, 20, 30]))") 3) + +(er-eval-test "integer_to_list" (ev "integer_to_list(42)") "42") +(er-eval-test "integer_to_list neg" (ev "integer_to_list(-99)") "-99") +(er-eval-test "list_to_integer" (ev "list_to_integer(\"123\")") 123) +(er-eval-test "list_to_integer roundtrip" + (ev "list_to_integer(integer_to_list(7))") 7) + +(er-eval-test "is_function fun" + (nm (ev "F = fun (X) -> X end, is_function(F)")) "true") +(er-eval-test "is_function not" + (nm (ev "is_function(42)")) "false") +(er-eval-test "is_function arity match" + (nm (ev "F = fun (X, Y) -> X + Y end, is_function(F, 2)")) "true") +(er-eval-test "is_function arity mismatch" + (nm (ev "F = fun (X) -> X end, is_function(F, 5)")) "false") + +;; lists module +(er-eval-test "lists:seq 1..5" + (ev "length(lists:seq(1, 5))") 5) +(er-eval-test "lists:seq head" + (ev "hd(lists:seq(10, 20))") 10) +(er-eval-test "lists:seq sum" + (ev "lists:sum(lists:seq(1, 100))") 5050) +(er-eval-test "lists:seq with step" + (ev "length(lists:seq(0, 20, 2))") 11) +(er-eval-test "lists:seq empty" + (get (ev "lists:seq(5, 1)") :tag) "nil") + +(er-eval-test "lists:sum empty" (ev "lists:sum([])") 0) +(er-eval-test "lists:sum 5" + (ev "lists:sum([1, 2, 3, 4, 5])") 15) + +(er-eval-test "lists:nth 1" (ev "lists:nth(1, [10, 20, 30])") 10) +(er-eval-test "lists:nth mid" + (nm (ev "lists:nth(2, [a, b, c])")) "b") +(er-eval-test "lists:last" + (nm (ev "lists:last([a, b, c, d])")) "d") +(er-eval-test "lists:last single" (ev "lists:last([42])") 42) + +(er-eval-test "lists:member yes" + (nm (ev "lists:member(3, [1, 2, 3, 4])")) "true") +(er-eval-test "lists:member no" + (nm (ev "lists:member(99, [1, 2, 3])")) "false") + +(er-eval-test "lists:append" + (ev "length(lists:append([1, 2], [3, 4, 5]))") 5) + +(er-eval-test "lists:filter" + (ev "length(lists:filter(fun (X) -> X > 2 end, [1, 2, 3, 4, 5]))") 3) +(er-eval-test "lists:filter sum" + (ev "lists:sum(lists:filter(fun (X) -> X rem 2 =:= 0 end, lists:seq(1, 20)))") 110) + +(er-eval-test "lists:any false" + (nm (ev "lists:any(fun (X) -> X > 100 end, [1, 2, 3])")) "false") +(er-eval-test "lists:any true" + (nm (ev "lists:any(fun (X) -> X > 2 end, [1, 2, 3])")) "true") +(er-eval-test "lists:all true" + (nm (ev "lists:all(fun (X) -> X > 0 end, [1, 2, 3])")) "true") +(er-eval-test "lists:all false" + (nm (ev "lists:all(fun (X) -> X > 1 end, [1, 2, 3])")) "false") + +(er-eval-test "lists:duplicate len" + (ev "length(lists:duplicate(5, foo))") 5) +(er-eval-test "lists:duplicate val" + (nm (ev "hd(lists:duplicate(3, marker))")) "marker") + (define er-eval-test-summary (str "eval " er-eval-test-pass "/" er-eval-test-count)) diff --git a/lib/erlang/transpile.sx b/lib/erlang/transpile.sx index 72893644..ac2bf562 100644 --- a/lib/erlang/transpile.sx +++ b/lib/erlang/transpile.sx @@ -690,6 +690,14 @@ (= name "is_reference") (er-bif-is-reference vs) (= name "is_binary") (er-bif-is-binary vs) (= name "byte_size") (er-bif-byte-size vs) + (= name "abs") (er-bif-abs vs) + (= name "min") (er-bif-min vs) + (= name "max") (er-bif-max vs) + (= name "tuple_to_list") (er-bif-tuple-to-list vs) + (= name "list_to_tuple") (er-bif-list-to-tuple vs) + (= name "integer_to_list") (er-bif-integer-to-list vs) + (= name "list_to_integer") (er-bif-list-to-integer vs) + (= name "is_function") (er-bif-is-function vs) (= name "self") (er-bif-self vs) (= name "spawn") (er-bif-spawn vs) (= name "exit") (er-bif-exit vs) @@ -730,6 +738,16 @@ (= name "reverse") (er-bif-lists-reverse vs) (= name "map") (er-bif-lists-map vs) (= name "foldl") (er-bif-lists-foldl vs) + (= name "seq") (er-bif-lists-seq vs) + (= name "sum") (er-bif-lists-sum vs) + (= name "nth") (er-bif-lists-nth vs) + (= name "last") (er-bif-lists-last vs) + (= name "member") (er-bif-lists-member vs) + (= name "append") (er-bif-lists-append vs) + (= name "filter") (er-bif-lists-filter vs) + (= name "any") (er-bif-lists-any vs) + (= name "all") (er-bif-lists-all vs) + (= name "duplicate") (er-bif-lists-duplicate vs) :else (error (str "Erlang: undefined 'lists:" name "/" (len vs) "'"))))) @@ -1571,3 +1589,325 @@ (cond (= e 0) 1 :else (* b (er-int-pow b (- e 1)))))) + +;; ── extra erlang BIFs ─────────────────────────────────────────── +(define + er-bif-abs + (fn + (vs) + (let + ((v (er-bif-arg1 vs "abs"))) + (cond + (not (= (type-of v) "number")) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + (< v 0) (- 0 v) + :else v)))) + +(define + er-bif-min + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: min/2: arity") + :else (let + ((a (nth vs 0)) (b (nth vs 1))) + (if (er-lt? b a) b a))))) + +(define + er-bif-max + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: max/2: arity") + :else (let + ((a (nth vs 0)) (b (nth vs 1))) + (if (er-lt? a b) b a))))) + +(define + er-bif-tuple-to-list + (fn + (vs) + (let + ((v (er-bif-arg1 vs "tuple_to_list"))) + (cond + (not (er-tuple? v)) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else (let + ((elems (get v :elements)) (out (er-mk-nil))) + (for-each + (fn + (i) + (let + ((j (- (- (len elems) 1) i))) + (set! out (er-mk-cons (nth elems j) out)))) + (range 0 (len elems))) + out))))) + +(define + er-bif-list-to-tuple + (fn + (vs) + (let + ((v (er-bif-arg1 vs "list_to_tuple")) (elems (list))) + (er-list-to-elem-list v elems) + (er-mk-tuple elems)))) + +(define + er-list-to-elem-list + (fn + (lst out) + (cond + (er-nil? lst) nil + (er-cons? lst) + (do + (append! out (get lst :head)) + (er-list-to-elem-list (get lst :tail) out)) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-integer-to-list + (fn + (vs) + (let + ((v (er-bif-arg1 vs "integer_to_list"))) + (cond + (not (= (type-of v) "number")) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else (str v))))) + +(define + er-bif-list-to-integer + (fn + (vs) + (let + ((v (er-bif-arg1 vs "list_to_integer"))) + (cond + (not (= (type-of v) "string")) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else (let + ((n (parse-number v))) + (cond + (= n nil) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else n)))))) + +(define + er-bif-is-function + (fn + (vs) + (cond + (= (len vs) 1) (er-bool (er-fun? (nth vs 0))) + (= (len vs) 2) + (let + ((v (nth vs 0)) (n (nth vs 1))) + (cond + (not (er-fun? v)) (er-bool false) + :else (er-bool (er-fun-has-arity? v n)))) + :else (error "Erlang: is_function: arity")))) + +(define + er-fun-has-arity? + (fn + (fv n) + (let + ((clauses (get fv :clauses)) (found (list false))) + (for-each + (fn + (i) + (when + (= (len (get (nth clauses i) :patterns)) n) + (set-nth! found 0 true))) + (range 0 (len clauses))) + (nth found 0)))) + +;; ── extra lists BIFs ─────────────────────────────────────────── +(define + er-bif-lists-seq + (fn + (vs) + (cond + (= (len vs) 2) (er-lists-seq-build (nth vs 0) (nth vs 1) 1) + (= (len vs) 3) (er-lists-seq-build (nth vs 0) (nth vs 1) (nth vs 2)) + :else (error "Erlang: lists:seq: arity")))) + +(define + er-lists-seq-build + (fn + (from to step) + (let + ((acc (er-mk-nil))) + (for-each + (fn + (i) + (let + ((v (- to (* i step)))) + (when + (and (>= v from) (<= v to)) + (set! acc (er-mk-cons v acc))))) + (range 0 (+ 1 (truncate (/ (- to from) step))))) + acc))) + +(define + er-bif-lists-sum + (fn + (vs) + (let + ((lst (er-bif-arg1 vs "lists:sum"))) + (er-lists-sum-iter lst 0)))) + +(define + er-lists-sum-iter + (fn + (lst acc) + (cond + (er-nil? lst) acc + (er-cons? lst) + (er-lists-sum-iter (get lst :tail) (+ acc (get lst :head))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-nth + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:nth: arity") + :else (er-lists-nth-iter (nth vs 1) (nth vs 0))))) + +(define + er-lists-nth-iter + (fn + (lst i) + (cond + (or (<= i 0) (er-nil? lst)) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + (= i 1) (get lst :head) + :else (er-lists-nth-iter (get lst :tail) (- i 1))))) + +(define + er-bif-lists-last + (fn + (vs) + (let + ((lst (er-bif-arg1 vs "lists:last"))) + (cond + (er-nil? lst) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else (er-lists-last-iter lst))))) + +(define + er-lists-last-iter + (fn + (lst) + (cond + (and (er-cons? lst) (er-nil? (get lst :tail))) (get lst :head) + (er-cons? lst) (er-lists-last-iter (get lst :tail)) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-member + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:member: arity") + :else (er-bool (er-lists-member-iter (nth vs 0) (nth vs 1)))))) + +(define + er-lists-member-iter + (fn + (target lst) + (cond + (er-nil? lst) false + (er-cons? lst) + (cond + (er-equal? target (get lst :head)) true + :else (er-lists-member-iter target (get lst :tail))) + :else false))) + +(define + er-bif-lists-append + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:append: arity") + :else (er-list-append (nth vs 0) (nth vs 1))))) + +(define + er-bif-lists-filter + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:filter: arity") + :else (er-lists-filter-build + (nth vs 0) + (nth vs 1) + (er-mk-nil))))) + +(define + er-lists-filter-build + (fn + (pred lst acc) + (cond + (er-nil? lst) (er-list-reverse-iter acc (er-mk-nil)) + (er-cons? lst) + (let + ((kept + (cond + (er-truthy? (er-apply-fun pred (list (get lst :head)))) + (er-mk-cons (get lst :head) acc) + :else acc))) + (er-lists-filter-build pred (get lst :tail) kept)) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-any + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:any: arity") + :else (er-bool (er-lists-any-iter (nth vs 0) (nth vs 1)))))) + +(define + er-lists-any-iter + (fn + (pred lst) + (cond + (er-nil? lst) false + (er-cons? lst) + (cond + (er-truthy? (er-apply-fun pred (list (get lst :head)))) true + :else (er-lists-any-iter pred (get lst :tail))) + :else false))) + +(define + er-bif-lists-all + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:all: arity") + :else (er-bool (er-lists-all-iter (nth vs 0) (nth vs 1)))))) + +(define + er-lists-all-iter + (fn + (pred lst) + (cond + (er-nil? lst) true + (er-cons? lst) + (cond + (er-truthy? (er-apply-fun pred (list (get lst :head)))) + (er-lists-all-iter pred (get lst :tail)) + :else false) + :else false))) + +(define + er-bif-lists-duplicate + (fn + (vs) + (cond + (not (= (len vs) 2)) (error "Erlang: lists:duplicate: arity") + :else (let + ((n (nth vs 0)) (v (nth vs 1)) (out (er-mk-nil))) + (for-each + (fn (_) (set! out (er-mk-cons v out))) + (range 0 n)) + out)))) diff --git a/plans/erlang-on-sx.md b/plans/erlang-on-sx.md index 80e405ae..cc068a23 100644 --- a/plans/erlang-on-sx.md +++ b/plans/erlang-on-sx.md @@ -93,12 +93,13 @@ Core mapping: - [x] List comprehensions `[X*2 || X <- L]` — **12 new eval tests**; generators, filters, multiple generators (cartesian), pattern-matching gens (`{ok, V} <- ...`) - [x] Binary pattern matching `<>` — **21 new eval tests**; literal construction, byte/multi-byte segments, `Rest/binary` tail capture, `is_binary/1`, `byte_size/1` - [x] ETS-lite (in-memory tables via SX dicts) — **13 new eval tests**; `ets:new/2`, `insert/2`, `lookup/2`, `delete/1-2`, `tab2list/1`, `info/2` (size); set semantics with full Erlang-term keys -- [ ] More BIFs — target 200+ test corpus green +- [x] More BIFs — target 200+ test corpus green — **40 new eval tests**; 530/530 total. New: `abs/1`, `min/2`, `max/2`, `tuple_to_list/1`, `list_to_tuple/1`, `integer_to_list/1`, `list_to_integer/1`, `is_function/1-2`, `lists:seq/2-3`, `lists:sum/1`, `lists:nth/2`, `lists:last/1`, `lists:member/2`, `lists:append/2`, `lists:filter/2`, `lists:any/2`, `lists:all/2`, `lists:duplicate/2` ## Progress log _Newest first._ +- **2026-04-25 BIF round-out — Phase 6 complete, full plan ticked** — Added 18 standard BIFs in `lib/erlang/transpile.sx`. **erlang module:** `abs/1` (negates negative numbers), `min/2`/`max/2` (use `er-lt?` so cross-type comparisons follow Erlang term order), `tuple_to_list/1`/`list_to_tuple/1` (proper conversions), `integer_to_list/1` (returns SX string per the char-list shim), `list_to_integer/1` (uses `parse-number`, raises badarg on failure), `is_function/1` and `is_function/2` (arity-2 form scans the fun's clause patterns). **lists module:** `seq/2`/`seq/3` (right-fold builder with step), `sum/1`, `nth/2` (1-indexed, raises badarg out of range), `last/1`, `member/2`, `append/2` (alias for `++`), `filter/2`, `any/2`, `all/2`, `duplicate/2`. 40 new eval tests with positive + negative cases, plus a few that compose existing BIFs (e.g. `lists:sum(lists:seq(1, 100)) = 5050`). Total suite **530/530** — every checkbox in `plans/erlang-on-sx.md` is now ticked. - **2026-04-25 ETS-lite green** — Scheduler state gains `:ets` (table-name → mutable list of tuples). New `er-apply-ets-bif` dispatches `ets:new/2` (registers table by atom name; rejects duplicate name with `{badarg, Name}`), `insert/2` (set semantics — replaces existing entry with the same first-element key, else appends), `lookup/2` (returns Erlang list — `[Tuple]` if found else `[]`), `delete/1` (drop table), `delete/2` (drop key; rebuilds entry list), `tab2list/1` (full list view), `info/2` with `size` only. Keys are full Erlang terms compared via `er-equal?`. 13 new eval tests: new return value, insert true, lookup hit + miss, set replace, info size after insert/delete, tab2list length, table delete, lookup-after-delete raises badarg, multi-key aggregate sum, tuple-key insert + lookup, two independent tables. Total suite 490/490. - **2026-04-25 binary pattern matching green** — Parser additions: `<<...>>` literal/pattern in `er-parse-primary`, segment grammar `Value [: Size] [/ Spec]` (Spec defaults to `integer`, supports `binary` for tail). Critical fix: segment value uses `er-parse-primary` (not `er-parse-expr-prec`) so the trailing `:Size` doesn't get eaten by the postfix `Mod:Fun` remote-call handler. Runtime value: `{:tag "binary" :bytes (list of int 0-255)}`. Construction: integer segments emit big-endian bytes (size in bits, must be multiple of 8); binary-spec segments concatenate. Pattern matching consumes bytes from a cursor at the front, decoding integer segments big-endian, capturing `Rest/binary` tail at the end. Whole-binary length must consume exactly. New BIFs: `is_binary/1`, `byte_size/1`. Binaries participate in `er-equal?` (byte-wise) and format as `<>`. 21 new eval tests: tag/predicate, byte_size for 8/16/32-bit segments, single + multi segment match, three 8-bit, tail rest size + content, badmatch on size mismatch, `=:=` equality, var-driven construction. Total suite 477/477. - **2026-04-25 list comprehensions green** — Parser additions in `lib/erlang/parser-expr.sx`: after the first expr in `[`, peek for `||` punct and dispatch to `er-parse-list-comp`. Qualifiers separated by `,`, each one is `Pattern <- Source` (generator) or any expression (filter — disambiguated by absence of `<-`). AST: `{:type "lc" :head E :qualifiers [...]}` with each qualifier `{:kind "gen"/"filter" ...}`. Evaluator (`er-eval-lc` in transpile.sx): right-fold builds the result by walking qualifiers; generators iterate the source list with env snapshot/restore per element so pattern-bound vars don't leak between iterations; filters skip when falsy. Pattern-matching generators are silently skipped on no-match (e.g. `[V || {ok, V} <- ...]`). 12 new eval tests: map double, fold-sum-of-comprehension, length, filter sum, "all filtered", empty source, cartesian, pattern-match gen, nested generators with filter, squares, tuple capture. Total suite 456/456.