diff --git a/lib/erlang/lists-ext.sx b/lib/erlang/lists-ext.sx index a3e24577..4aaa6616 100644 --- a/lib/erlang/lists-ext.sx +++ b/lib/erlang/lists-ext.sx @@ -319,6 +319,53 @@ (er-mk-tuple (list (er-ext-takewhile pred lst) (er-ext-dropwhile pred lst)))))) +;; ── structural / aggregate (flatten / max / min) ────────────────── +(define + er-ext-flatten + (fn (lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (let ((h (get lst :head))) + (if (or (er-nil? h) (er-cons? h)) + (er-list-append (er-ext-flatten h) (er-ext-flatten (get lst :tail))) + (er-mk-cons h (er-ext-flatten (get lst :tail))))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-flatten + (fn (vs) (er-ext-flatten (er-bif-arg1 vs "lists:flatten")))) + +(define + er-ext-extreme + (fn (lst best lt?) + (cond + (er-nil? lst) best + (er-cons? lst) + (er-ext-extreme + (get lst :tail) + (if (lt? best (get lst :head)) (get lst :head) best) + lt?) + :else best))) + +(define + er-bif-lists-max + (fn (vs) + (let ((lst (er-bif-arg1 vs "lists:max"))) + (if (er-cons? lst) + (er-ext-extreme (get lst :tail) (get lst :head) + (fn (a b) (er-ext-lt? a b))) + (raise (er-mk-error-marker (er-mk-atom "badarg"))))))) + +(define + er-bif-lists-min + (fn (vs) + (let ((lst (er-bif-arg1 vs "lists:min"))) + (if (er-cons? lst) + (er-ext-extreme (get lst :tail) (get lst :head) + (fn (a b) (er-ext-lt? b a))) + (raise (er-mk-error-marker (er-mk-atom "badarg"))))))) + ;; ── register ────────────────────────────────────────────────────── ;; Hook into er-register-builtin-bifs! rather than registering once: ;; the registry can be reset + rebuilt mid-run (tests/runtime.sx does @@ -340,7 +387,10 @@ (er-register-pure-bif! "lists" "partition" 2 er-bif-lists-partition) (er-register-pure-bif! "lists" "takewhile" 2 er-bif-lists-takewhile) (er-register-pure-bif! "lists" "dropwhile" 2 er-bif-lists-dropwhile) - (er-register-pure-bif! "lists" "splitwith" 2 er-bif-lists-splitwith))) + (er-register-pure-bif! "lists" "splitwith" 2 er-bif-lists-splitwith) + (er-register-pure-bif! "lists" "flatten" 1 er-bif-lists-flatten) + (er-register-pure-bif! "lists" "max" 1 er-bif-lists-max) + (er-register-pure-bif! "lists" "min" 1 er-bif-lists-min))) (define er-ext-prev-register-builtins er-register-builtin-bifs!) (define er-register-builtin-bifs! diff --git a/lib/erlang/scoreboard.json b/lib/erlang/scoreboard.json index 35eff24f..4429b21c 100644 --- a/lib/erlang/scoreboard.json +++ b/lib/erlang/scoreboard.json @@ -1,7 +1,7 @@ { "language": "erlang", - "total_pass": 823, - "total": 823, + "total_pass": 833, + "total": 833, "suites": [ {"name":"tokenize","pass":62,"total":62,"status":"ok"}, {"name":"parse","pass":52,"total":52,"status":"ok"}, @@ -15,6 +15,6 @@ {"name":"ffi","pass":37,"total":37,"status":"ok"}, {"name":"vm","pass":78,"total":78,"status":"ok"}, {"name":"send_after","pass":10,"total":10,"status":"ok"}, - {"name":"lists_ext","pass":52,"total":52,"status":"ok"} + {"name":"lists_ext","pass":62,"total":62,"status":"ok"} ] } diff --git a/lib/erlang/scoreboard.md b/lib/erlang/scoreboard.md index b802f506..c3bd1749 100644 --- a/lib/erlang/scoreboard.md +++ b/lib/erlang/scoreboard.md @@ -1,6 +1,6 @@ # Erlang-on-SX Scoreboard -**Total: 823 / 823 tests passing** +**Total: 833 / 833 tests passing** | | Suite | Pass | Total | |---|---|---|---| @@ -16,7 +16,7 @@ | ✅ | ffi | 37 | 37 | | ✅ | vm | 78 | 78 | | ✅ | send_after | 10 | 10 | -| ✅ | lists_ext | 52 | 52 | +| ✅ | lists_ext | 62 | 62 | Generated by `lib/erlang/conformance.sh`. diff --git a/lib/erlang/tests/lists_ext.sx b/lib/erlang/tests/lists_ext.sx index b1b73cd9..b02b89ec 100644 --- a/lib/erlang/tests/lists_ext.sx +++ b/lib/erlang/tests/lists_ext.sx @@ -205,3 +205,36 @@ (er-lx-test "splitwith empty" (er-lx-nm "lists:splitwith(fun(_) -> true end, []) =:= {[],[]}") "true") + +;; ── lists:flatten/1 ─────────────────────────────────────────────── +(er-lx-test "flatten nested" + (er-lx-nm "lists:flatten([1,[2,[3,4]],5]) =:= [1,2,3,4,5]") "true") + +(er-lx-test "flatten already flat" + (er-lx-nm "lists:flatten([1,2,3]) =:= [1,2,3]") "true") + +(er-lx-test "flatten empty" + (er-lx-nm "lists:flatten([]) =:= []") "true") + +(er-lx-test "flatten deep empties" + (er-lx-nm "lists:flatten([[],[1],[[]]]) =:= [1]") "true") + +(er-lx-test "flatten length" + (erlang-eval-ast "length(lists:flatten([[1,2],[3],[4,5,6]]))") 6) + +;; ── lists:max/1 ─────────────────────────────────────────────────── +(er-lx-test "max ints" + (erlang-eval-ast "lists:max([3,1,4,1,5,9,2,6])") 9) + +(er-lx-test "max single" + (erlang-eval-ast "lists:max([7])") 7) + +(er-lx-test "max atoms term order" + (er-lx-nm "lists:max([a,c,b]) =:= c") "true") + +;; ── lists:min/1 ─────────────────────────────────────────────────── +(er-lx-test "min ints" + (erlang-eval-ast "lists:min([3,1,4,1,5])") 1) + +(er-lx-test "min mixed term order" + (er-lx-nm "lists:min([a,1,b]) =:= 1") "true") diff --git a/plans/erlang-on-sx.md b/plans/erlang-on-sx.md index b15c4181..0b83014f 100644 --- a/plans/erlang-on-sx.md +++ b/plans/erlang-on-sx.md @@ -159,6 +159,8 @@ The Phase 9 opcodes are registered, tested, and bridged SX↔OCaml, but inert: n _Newest first._ +- **2026-06-30 stdlib hardening — `lists` flatten/max/min** — Added `flatten/1` (deep recursive flatten via `er-list-append`), `max/1`, `min/1` (full term order via `er-ext-lt?`, `badarg` on empty) to `lib/erlang/lists-ext.sx`. Gotcha caught: `er-ext-lt?` returns a raw SX boolean, so the extreme-finder uses it directly in `if` rather than wrapping in `er-truthy?` (which only recognises Erlang bool atoms, not SX booleans — the first cut wrapped it and silently never updated the running best). `lists_ext` suite 52→**62** (+10). Conformance **823 → 833/833**. loops/erlang only. + - **2026-06-30 stdlib hardening — `lists` higher-order traversal** — Added `foldr/3`, `partition/2`, `takewhile/2`, `dropwhile/2`, `splitwith/2` to `lib/erlang/lists-ext.sx`, registered pure through the `er-register-builtin-bifs!` wrapper (consistent with the existing pure `map`/`filter`/`foldl`). `foldr` right-folds (order-preserving when consing); `partition` returns `{Satisfying, NotSatisfying}` order-preserved via `er-list-reverse-iter`; `splitwith` = `{takewhile, dropwhile}`. `lists_ext` suite 38→**52** (+14). Conformance **809 → 823/823**. loops/erlang only. - **2026-06-30 stdlib hardening — `lists` keylists** — Added the keylist family to `lib/erlang/lists-ext.sx`: `keyfind/3`, `keymember/3`, `keydelete/3`, `keyreplace/4`, `keystore/4`, `keytake/3`, `keysort/2`. All operate on lists of tuples keyed on element N (1-indexed), act on the first match only, and pass through non-tuples / tuples shorter than N. Key comparison is `==` (`er-equal?`) per the stdlib; `keysort/2` reuses the stable `er-ext-msort` + `er-ext-lt?` from the sort commit, comparing extracted keys. `keytake/3` returns `{value, Tuple, Rest}` / `false`. Registered through the same `er-register-builtin-bifs!` wrapper so they survive registry resets. `lists_ext` suite 17→**38** (+21: hit/miss/first-match-only/short-tuple-skip across all seven, keysort by elem 1 and 2 + stability). Conformance **788 → 809/809**. Test-harness note: `element(2, T)` returns an integer (no `:name`), so those two cases compare the raw number via `erlang-eval-ast` rather than `er-lx-nm`. loops/erlang only.