From d2c1400737972110431bbac0b36ebb3e657651fe Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 30 Jun 2026 13:51:49 +0000 Subject: [PATCH] erlang: lists sublist/2,3 + nthtail/2 + split/2 + droplast/1 (854/854) Slicing family in lib/erlang/lists-ext.sx. sublist lenient; nthtail and split strict (badarg when list shorter than N); droplast raises on []. lists_ext suite 70 -> 83. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/erlang/lists-ext.sx | 69 ++++++++++++++++++++++++++++++++++- lib/erlang/scoreboard.json | 6 +-- lib/erlang/scoreboard.md | 4 +- lib/erlang/tests/lists_ext.sx | 43 ++++++++++++++++++++++ plans/erlang-on-sx.md | 2 + 5 files changed, 118 insertions(+), 6 deletions(-) diff --git a/lib/erlang/lists-ext.sx b/lib/erlang/lists-ext.sx index 25852fd1..137903ac 100644 --- a/lib/erlang/lists-ext.sx +++ b/lib/erlang/lists-ext.sx @@ -422,6 +422,68 @@ (fn (vs) (er-ext-unzip (er-bif-arg1 vs "lists:unzip") (er-mk-nil) (er-mk-nil)))) +;; ── slicing (sublist / nthtail / split / droplast) ──────────────── +(define + er-ext-sublist2 + (fn (lst n) + (cond + (or (<= n 0) (er-nil? lst)) (er-mk-nil) + (er-cons? lst) + (er-mk-cons (get lst :head) (er-ext-sublist2 (get lst :tail) (- n 1))) + :else (er-mk-nil)))) + +;; lenient drop (used by sublist/3); never raises +(define + er-ext-drop-cons + (fn (lst n) + (cond + (or (<= n 0) (er-nil? lst)) lst + (er-cons? lst) (er-ext-drop-cons (get lst :tail) (- n 1)) + :else lst))) + +;; strict drop (used by nthtail/2 + split/2); raises if list too short +(define + er-ext-nthtail + (fn (n lst) + (cond + (<= n 0) lst + (er-cons? lst) (er-ext-nthtail (- n 1) (get lst :tail)) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-sublist + (fn (vs) + (cond + (= (len vs) 2) (er-ext-sublist2 (nth vs 0) (nth vs 1)) + (= (len vs) 3) + (er-ext-sublist2 + (er-ext-drop-cons (nth vs 0) (- (nth vs 1) 1)) + (nth vs 2)) + :else (error "Erlang: lists:sublist: wrong arity")))) + +(define + er-bif-lists-nthtail + (fn (vs) (er-ext-nthtail (nth vs 0) (nth vs 1)))) + +(define + er-bif-lists-split + (fn (vs) + (let ((n (nth vs 0)) (lst (nth vs 1))) + (er-mk-tuple + (list (er-ext-sublist2 lst n) (er-ext-nthtail n lst)))))) + +(define + er-ext-droplast + (fn (lst) + (cond + (and (er-cons? lst) (er-nil? (get lst :tail))) (er-mk-nil) + (er-cons? lst) (er-mk-cons (get lst :head) (er-ext-droplast (get lst :tail))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-droplast + (fn (vs) (er-ext-droplast (er-bif-arg1 vs "lists:droplast")))) + ;; ── register ────────────────────────────────────────────────────── ;; Hook into er-register-builtin-bifs! rather than registering once: ;; the registry can be reset + rebuilt mid-run (tests/runtime.sx does @@ -449,7 +511,12 @@ (er-register-pure-bif! "lists" "min" 1 er-bif-lists-min) (er-register-pure-bif! "lists" "zip" 2 er-bif-lists-zip) (er-register-pure-bif! "lists" "zipwith" 3 er-bif-lists-zipwith) - (er-register-pure-bif! "lists" "unzip" 1 er-bif-lists-unzip))) + (er-register-pure-bif! "lists" "unzip" 1 er-bif-lists-unzip) + (er-register-pure-bif! "lists" "sublist" 2 er-bif-lists-sublist) + (er-register-pure-bif! "lists" "sublist" 3 er-bif-lists-sublist) + (er-register-pure-bif! "lists" "nthtail" 2 er-bif-lists-nthtail) + (er-register-pure-bif! "lists" "split" 2 er-bif-lists-split) + (er-register-pure-bif! "lists" "droplast" 1 er-bif-lists-droplast))) (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 09730bbf..73626ba0 100644 --- a/lib/erlang/scoreboard.json +++ b/lib/erlang/scoreboard.json @@ -1,7 +1,7 @@ { "language": "erlang", - "total_pass": 841, - "total": 841, + "total_pass": 854, + "total": 854, "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":70,"total":70,"status":"ok"} + {"name":"lists_ext","pass":83,"total":83,"status":"ok"} ] } diff --git a/lib/erlang/scoreboard.md b/lib/erlang/scoreboard.md index 10d9b11f..e41f2df7 100644 --- a/lib/erlang/scoreboard.md +++ b/lib/erlang/scoreboard.md @@ -1,6 +1,6 @@ # Erlang-on-SX Scoreboard -**Total: 841 / 841 tests passing** +**Total: 854 / 854 tests passing** | | Suite | Pass | Total | |---|---|---|---| @@ -16,7 +16,7 @@ | ✅ | ffi | 37 | 37 | | ✅ | vm | 78 | 78 | | ✅ | send_after | 10 | 10 | -| ✅ | lists_ext | 70 | 70 | +| ✅ | lists_ext | 83 | 83 | Generated by `lib/erlang/conformance.sh`. diff --git a/lib/erlang/tests/lists_ext.sx b/lib/erlang/tests/lists_ext.sx index 7d4a9ab6..a5494371 100644 --- a/lib/erlang/tests/lists_ext.sx +++ b/lib/erlang/tests/lists_ext.sx @@ -267,3 +267,46 @@ (er-lx-test "zip/unzip roundtrip" (er-lx-nm "lists:unzip(lists:zip([1,2],[3,4])) =:= {[1,2],[3,4]}") "true") + +;; ── lists:sublist/2,3 ───────────────────────────────────────────── +(er-lx-test "sublist/2 first n" + (er-lx-nm "lists:sublist([1,2,3,4,5],3) =:= [1,2,3]") "true") + +(er-lx-test "sublist/2 over length" + (er-lx-nm "lists:sublist([1,2],5) =:= [1,2]") "true") + +(er-lx-test "sublist/2 zero" + (er-lx-nm "lists:sublist([1,2,3],0) =:= []") "true") + +(er-lx-test "sublist/3 mid" + (er-lx-nm "lists:sublist([1,2,3,4,5],2,3) =:= [2,3,4]") "true") + +(er-lx-test "sublist/3 to end" + (er-lx-nm "lists:sublist([1,2,3],2,10) =:= [2,3]") "true") + +;; ── lists:nthtail/2 ─────────────────────────────────────────────── +(er-lx-test "nthtail mid" + (er-lx-nm "lists:nthtail(2,[1,2,3,4]) =:= [3,4]") "true") + +(er-lx-test "nthtail zero" + (er-lx-nm "lists:nthtail(0,[1,2]) =:= [1,2]") "true") + +(er-lx-test "nthtail full" + (er-lx-nm "lists:nthtail(3,[1,2,3]) =:= []") "true") + +;; ── lists:split/2 ───────────────────────────────────────────────── +(er-lx-test "split mid" + (er-lx-nm "lists:split(2,[1,2,3,4,5]) =:= {[1,2],[3,4,5]}") "true") + +(er-lx-test "split zero" + (er-lx-nm "lists:split(0,[1,2]) =:= {[],[1,2]}") "true") + +(er-lx-test "split full" + (er-lx-nm "lists:split(3,[1,2,3]) =:= {[1,2,3],[]}") "true") + +;; ── lists:droplast/1 ────────────────────────────────────────────── +(er-lx-test "droplast" + (er-lx-nm "lists:droplast([1,2,3]) =:= [1,2]") "true") + +(er-lx-test "droplast single" + (er-lx-nm "lists:droplast([9]) =:= []") "true") diff --git a/plans/erlang-on-sx.md b/plans/erlang-on-sx.md index f2610b33..e4f147ce 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` slicing** — Added `sublist/2`, `sublist/3`, `nthtail/2`, `split/2`, `droplast/1` to `lib/erlang/lists-ext.sx`. `sublist` is lenient (clamps to list length); `nthtail/2` and `split/2` are strict (`badarg` when the list is shorter than N, matching the stdlib); `droplast/1` raises on `[]`. `lists_ext` suite 70→**83** (+13). Conformance **841 → 854/854**. loops/erlang only. + - **2026-06-30 stdlib hardening — `lists` zip family** — Added `zip/2`, `zipwith/3`, `unzip/1` to `lib/erlang/lists-ext.sx`. Length mismatch (zip/zipwith) and malformed/non-pair elements (unzip) raise `badarg` (port equivalent of Erlang's `function_clause`). `lists_ext` suite 62→**70** (+8, incl. a zip/unzip roundtrip). Conformance **833 → 841/841**. loops/erlang only. - **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.