From 2d20f414981233a6af585db511390ce812af5a7d Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 30 Jun 2026 15:04:22 +0000 Subject: [PATCH] erlang: fold lists/proplists BIFs into transpile.sx + runtime.sx (completes 27dedf9b) 27dedf9b deleted lists-ext.sx but a botched git-add left the actual fold-in unstaged, so HEAD briefly referenced functions that weren't registered. This commit lands them: - transpile.sx: function bodies appended next to existing er-bif-lists-* - runtime.sx: registrations inside er-register-builtin-bifs! - conformance.sh: drop the lists-ext.sx load; keep the lists_ext suite Conformance 874/874 (verified on the combined tree before push). Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/erlang/conformance.sh | 1 - lib/erlang/runtime.sx | 36 +++ lib/erlang/transpile.sx | 653 ++++++++++++++++++++++++++++++++++++++ plans/erlang-on-sx.md | 2 + 4 files changed, 691 insertions(+), 1 deletion(-) diff --git a/lib/erlang/conformance.sh b/lib/erlang/conformance.sh index acc5e57c..7ba663d6 100755 --- a/lib/erlang/conformance.sh +++ b/lib/erlang/conformance.sh @@ -51,7 +51,6 @@ cat > "$TMPFILE" << 'EPOCHS' (load "lib/erlang/parser-module.sx") (load "lib/erlang/transpile.sx") (load "lib/erlang/runtime.sx") -(load "lib/erlang/lists-ext.sx") (load "lib/erlang/tests/tokenize.sx") (load "lib/erlang/tests/parse.sx") (load "lib/erlang/tests/eval.sx") diff --git a/lib/erlang/runtime.sx b/lib/erlang/runtime.sx index 41a507b7..b79858f6 100644 --- a/lib/erlang/runtime.sx +++ b/lib/erlang/runtime.sx @@ -1753,6 +1753,42 @@ (er-register-pure-bif! "lists" "any" 2 er-bif-lists-any) (er-register-pure-bif! "lists" "all" 2 er-bif-lists-all) (er-register-pure-bif! "lists" "duplicate" 2 er-bif-lists-duplicate) + (er-register-pure-bif! "lists" "sort" 1 er-bif-lists-sort) + (er-register-pure-bif! "lists" "sort" 2 er-bif-lists-sort) + (er-register-pure-bif! "lists" "usort" 1 er-bif-lists-usort) + (er-register-pure-bif! "lists" "keyfind" 3 er-bif-lists-keyfind) + (er-register-pure-bif! "lists" "keymember" 3 er-bif-lists-keymember) + (er-register-pure-bif! "lists" "keydelete" 3 er-bif-lists-keydelete) + (er-register-pure-bif! "lists" "keyreplace" 4 er-bif-lists-keyreplace) + (er-register-pure-bif! "lists" "keystore" 4 er-bif-lists-keystore) + (er-register-pure-bif! "lists" "keytake" 3 er-bif-lists-keytake) + (er-register-pure-bif! "lists" "keysort" 2 er-bif-lists-keysort) + (er-register-pure-bif! "lists" "foldr" 3 er-bif-lists-foldr) + (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" "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) + (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" "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) + (er-register-pure-bif! "lists" "flatmap" 2 er-bif-lists-flatmap) + (er-register-pure-bif! "lists" "filtermap" 2 er-bif-lists-filtermap) + (er-register-pure-bif! "lists" "mapfoldl" 3 er-bif-lists-mapfoldl) + (er-register-pure-bif! "lists" "search" 2 er-bif-lists-search) + (er-register-pure-bif! "proplists" "get_value" 2 er-bif-pl-get-value) + (er-register-pure-bif! "proplists" "get_value" 3 er-bif-pl-get-value) + (er-register-pure-bif! "proplists" "get_all_values" 2 er-bif-pl-get-all-values) + (er-register-pure-bif! "proplists" "is_defined" 2 er-bif-pl-is-defined) + (er-register-pure-bif! "proplists" "lookup" 2 er-bif-pl-lookup) + (er-register-pure-bif! "proplists" "delete" 2 er-bif-pl-delete) ;; io module — side-effecting (writes to io buffer) (er-register-bif! "io" "format" 1 er-bif-io-format) (er-register-bif! "io" "format" 2 er-bif-io-format) diff --git a/lib/erlang/transpile.sx b/lib/erlang/transpile.sx index 52b99c6c..bf349923 100644 --- a/lib/erlang/transpile.sx +++ b/lib/erlang/transpile.sx @@ -2039,4 +2039,657 @@ (range 0 (len ks))) out))) +;; ── extra lists + proplists BIFs (folded from lists-ext.sx) ── +;; ── cons <-> SX-list bridges ────────────────────────────────────── +(define + er-cons->sxlist + (fn (lst) + (cond + (er-nil? lst) (list) + (er-cons? lst) (cons (get lst :head) (er-cons->sxlist (get lst :tail))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) +(define + er-sxlist->cons + (fn (xs) + (if (= (len xs) 0) + (er-mk-nil) + (er-mk-cons (first xs) (er-sxlist->cons (rest xs)))))) + +;; ── merge sort over SX lists (stable) ───────────────────────────── +(define + er-ext-take + (fn (xs n) + (if (or (= n 0) (= (len xs) 0)) + (list) + (cons (first xs) (er-ext-take (rest xs) (- n 1)))))) + +(define + er-ext-drop + (fn (xs n) + (if (or (= n 0) (= (len xs) 0)) + xs + (er-ext-drop (rest xs) (- n 1))))) + +;; le? returns a truthy value (Erlang bool atom or SX bool) iff a +;; should sort at-or-before b. Taking from the left half first on a +;; true result keeps the sort stable. +(define + er-ext-merge + (fn (a b le?) + (cond + (= (len a) 0) b + (= (len b) 0) a + (er-truthy? (le? (first a) (first b))) + (cons (first a) (er-ext-merge (rest a) b le?)) + :else (cons (first b) (er-ext-merge a (rest b) le?))))) + +(define + er-ext-msort + (fn (xs le?) + (if (<= (len xs) 1) + xs + (let ((mid (quotient (len xs) 2))) + (er-ext-merge + (er-ext-msort (er-ext-take xs mid) le?) + (er-ext-msort (er-ext-drop xs mid) le?) + le?))))) + +;; Full Erlang term order. The shared er-lt? (transpile.sx) only +;; deep-compares numbers/atoms/strings and otherwise falls back to a +;; coarse type rank — so any two tuples (or two lists) compare as +;; order-equal there. er-ext-lt? adds the missing structural cases: +;; tuples by arity then elementwise, lists elementwise with a shorter +;; proper prefix sorting first. Cross-type cases delegate to er-lt?. +(define + er-ext-lt-seq + (fn (ea eb i) + (cond + (>= i (len ea)) false + (er-ext-lt? (nth ea i) (nth eb i)) true + (er-ext-lt? (nth eb i) (nth ea i)) false + :else (er-ext-lt-seq ea eb (+ i 1))))) + +(define + er-ext-lt? + (fn (a b) + (cond + (and (er-tuple? a) (er-tuple? b)) + (let ((ea (get a :elements)) (eb (get b :elements))) + (cond + (< (len ea) (len eb)) true + (> (len ea) (len eb)) false + :else (er-ext-lt-seq ea eb 0))) + (and (er-cons? a) (er-cons? b)) + (cond + (er-ext-lt? (get a :head) (get b :head)) true + (er-ext-lt? (get b :head) (get a :head)) false + :else (er-ext-lt? (get a :tail) (get b :tail))) + (and (er-nil? a) (er-cons? b)) true + (and (er-cons? a) (er-nil? b)) false + (and (er-nil? a) (er-nil? b)) false + :else (er-lt? a b)))) + +;; Default Erlang term order: a =< b == not (b < a). +(define + er-ext-term-le + (fn (a b) (er-bool (not (er-ext-lt? b a))))) + +;; ── lists:sort/1, lists:sort/2 ──────────────────────────────────── +(define + er-bif-lists-sort + (fn (vs) + (cond + (= (len vs) 1) + (er-sxlist->cons + (er-ext-msort (er-cons->sxlist (nth vs 0)) er-ext-term-le)) + (= (len vs) 2) + (let ((f (nth vs 0)) (lst (nth vs 1))) + (er-sxlist->cons + (er-ext-msort + (er-cons->sxlist lst) + (fn (a b) (er-apply-fun f (list a b)))))) + :else (error "Erlang: lists:sort: wrong arity")))) + +;; ── lists:usort/1 (sort then drop adjacent term-equal dups) ─────── +(define + er-ext-dedup + (fn (xs) + (cond + (= (len xs) 0) (list) + (= (len xs) 1) xs + (er-equal? (first xs) (nth xs 1)) (er-ext-dedup (rest xs)) + :else (cons (first xs) (er-ext-dedup (rest xs)))))) + +(define + er-bif-lists-usort + (fn (vs) + (let ((lst (er-bif-arg1 vs "lists:usort"))) + (er-sxlist->cons + (er-ext-dedup + (er-ext-msort (er-cons->sxlist lst) er-ext-term-le)))))) + +;; ── keylists (lists of tuples keyed on element N, 1-indexed) ────── +;; keyfind/keymember/keydelete/keyreplace/keystore/keytake/keysort. +;; Key comparison is == (er-equal?), matching the standard lib. Only +;; the FIRST matching tuple is acted on. Non-tuples / tuples shorter +;; than N never match and are passed through unchanged. +(define + er-ext-tup-elem + (fn (tup n) + (if (er-tuple? tup) + (let ((es (get tup :elements))) + (if (and (>= n 1) (<= n (len es))) (nth es (- n 1)) nil)) + nil))) + +(define + er-ext-key-match? + (fn (key n tup) + (and + (er-tuple? tup) + (>= n 1) + (<= n (len (get tup :elements))) + (er-equal? key (nth (get tup :elements) (- n 1)))))) + +(define + er-ext-keyfind + (fn (key n lst) + (cond + (er-nil? lst) (er-mk-atom "false") + (er-cons? lst) + (if (er-ext-key-match? key n (get lst :head)) + (get lst :head) + (er-ext-keyfind key n (get lst :tail))) + :else (er-mk-atom "false")))) + +(define + er-ext-keydelete + (fn (key n lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (if (er-ext-key-match? key n (get lst :head)) + (get lst :tail) + (er-mk-cons (get lst :head) (er-ext-keydelete key n (get lst :tail)))) + :else lst))) + +(define + er-ext-keyreplace + (fn (key n lst new) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (if (er-ext-key-match? key n (get lst :head)) + (er-mk-cons new (get lst :tail)) + (er-mk-cons (get lst :head) (er-ext-keyreplace key n (get lst :tail) new))) + :else lst))) + +(define + er-ext-keystore + (fn (key n lst new) + (cond + (er-nil? lst) (er-mk-cons new (er-mk-nil)) + (er-cons? lst) + (if (er-ext-key-match? key n (get lst :head)) + (er-mk-cons new (get lst :tail)) + (er-mk-cons (get lst :head) (er-ext-keystore key n (get lst :tail) new))) + :else lst))) + +(define + er-bif-lists-keyfind + (fn (vs) (er-ext-keyfind (nth vs 0) (nth vs 1) (nth vs 2)))) + +(define + er-bif-lists-keymember + (fn (vs) + (er-bool (not (er-atom? (er-ext-keyfind (nth vs 0) (nth vs 1) (nth vs 2))))))) + +(define + er-bif-lists-keydelete + (fn (vs) (er-ext-keydelete (nth vs 0) (nth vs 1) (nth vs 2)))) + +(define + er-bif-lists-keyreplace + (fn (vs) (er-ext-keyreplace (nth vs 0) (nth vs 1) (nth vs 2) (nth vs 3)))) + +(define + er-bif-lists-keystore + (fn (vs) (er-ext-keystore (nth vs 0) (nth vs 1) (nth vs 2) (nth vs 3)))) + +(define + er-bif-lists-keytake + (fn (vs) + (let ((key (nth vs 0)) (n (nth vs 1)) (lst (nth vs 2))) + (let ((hit (er-ext-keyfind key n lst))) + (if (er-atom? hit) + (er-mk-atom "false") + (er-mk-tuple + (list (er-mk-atom "value") hit (er-ext-keydelete key n lst)))))))) + +(define + er-bif-lists-keysort + (fn (vs) + (let ((n (nth vs 0)) (lst (nth vs 1))) + (er-sxlist->cons + (er-ext-msort + (er-cons->sxlist lst) + (fn (a b) + (er-bool + (not (er-ext-lt? (er-ext-tup-elem b n) (er-ext-tup-elem a n)))))))))) + +;; ── higher-order traversal (foldr / partition / *while) ─────────── +(define + er-ext-foldr + (fn (f acc lst) + (cond + (er-nil? lst) acc + (er-cons? lst) + (er-apply-fun f (list (get lst :head) (er-ext-foldr f acc (get lst :tail)))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-foldr + (fn (vs) (er-ext-foldr (nth vs 0) (nth vs 1) (nth vs 2)))) + +(define + er-ext-partition + (fn (pred lst yes no) + (cond + (er-nil? lst) + (er-mk-tuple + (list + (er-list-reverse-iter yes (er-mk-nil)) + (er-list-reverse-iter no (er-mk-nil)))) + (er-cons? lst) + (if (er-truthy? (er-apply-fun pred (list (get lst :head)))) + (er-ext-partition pred (get lst :tail) (er-mk-cons (get lst :head) yes) no) + (er-ext-partition pred (get lst :tail) yes (er-mk-cons (get lst :head) no))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-partition + (fn (vs) (er-ext-partition (nth vs 0) (nth vs 1) (er-mk-nil) (er-mk-nil)))) + +(define + er-ext-takewhile + (fn (pred lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (if (er-truthy? (er-apply-fun pred (list (get lst :head)))) + (er-mk-cons (get lst :head) (er-ext-takewhile pred (get lst :tail))) + (er-mk-nil)) + :else (er-mk-nil)))) + +(define + er-bif-lists-takewhile + (fn (vs) (er-ext-takewhile (nth vs 0) (nth vs 1)))) + +(define + er-ext-dropwhile + (fn (pred lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (if (er-truthy? (er-apply-fun pred (list (get lst :head)))) + (er-ext-dropwhile pred (get lst :tail)) + lst) + :else lst))) + +(define + er-bif-lists-dropwhile + (fn (vs) (er-ext-dropwhile (nth vs 0) (nth vs 1)))) + +(define + er-bif-lists-splitwith + (fn (vs) + (let ((pred (nth vs 0)) (lst (nth vs 1))) + (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"))))))) + +;; ── zip family (zip / zipwith / unzip) ──────────────────────────── +;; Length mismatch raises badarg (real Erlang raises function_clause; +;; badarg is the closest in-port equivalent). +(define + er-ext-zip + (fn (a b) + (cond + (and (er-nil? a) (er-nil? b)) (er-mk-nil) + (and (er-cons? a) (er-cons? b)) + (er-mk-cons + (er-mk-tuple (list (get a :head) (get b :head))) + (er-ext-zip (get a :tail) (get b :tail))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-zip + (fn (vs) (er-ext-zip (nth vs 0) (nth vs 1)))) + +(define + er-ext-zipwith + (fn (f a b) + (cond + (and (er-nil? a) (er-nil? b)) (er-mk-nil) + (and (er-cons? a) (er-cons? b)) + (er-mk-cons + (er-apply-fun f (list (get a :head) (get b :head))) + (er-ext-zipwith f (get a :tail) (get b :tail))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-zipwith + (fn (vs) (er-ext-zipwith (nth vs 0) (nth vs 1) (nth vs 2)))) + +(define + er-ext-unzip + (fn (lst as bs) + (cond + (er-nil? lst) + (er-mk-tuple + (list + (er-list-reverse-iter as (er-mk-nil)) + (er-list-reverse-iter bs (er-mk-nil)))) + (and (er-cons? lst) (er-tuple? (get lst :head))) + (let ((es (get (get lst :head) :elements))) + (if (= (len es) 2) + (er-ext-unzip (get lst :tail) + (er-mk-cons (nth es 0) as) + (er-mk-cons (nth es 1) bs)) + (raise (er-mk-error-marker (er-mk-atom "badarg"))))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-unzip + (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")))) + +;; ── more higher-order (flatmap / filtermap / mapfoldl / search) ─── +(define + er-ext-flatmap + (fn (f lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (er-list-append + (er-apply-fun f (list (get lst :head))) + (er-ext-flatmap f (get lst :tail))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-flatmap + (fn (vs) (er-ext-flatmap (nth vs 0) (nth vs 1)))) + +(define + er-ext-atom-true? + (fn (v) (and (er-atom? v) (= (get v :name) "true")))) + +(define + er-ext-filtermap + (fn (f lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (let ((r (er-apply-fun f (list (get lst :head))))) + (cond + (er-ext-atom-true? r) + (er-mk-cons (get lst :head) (er-ext-filtermap f (get lst :tail))) + (and + (er-tuple? r) + (= (len (get r :elements)) 2) + (er-ext-atom-true? (nth (get r :elements) 0))) + (er-mk-cons (nth (get r :elements) 1) (er-ext-filtermap f (get lst :tail))) + :else (er-ext-filtermap f (get lst :tail)))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-filtermap + (fn (vs) (er-ext-filtermap (nth vs 0) (nth vs 1)))) + +(define + er-ext-mapfoldl + (fn (f acc lst mapped) + (cond + (er-nil? lst) + (er-mk-tuple (list (er-list-reverse-iter mapped (er-mk-nil)) acc)) + (er-cons? lst) + (let ((r (er-apply-fun f (list (get lst :head) acc)))) + (let ((es (get r :elements))) + (er-ext-mapfoldl f (nth es 1) (get lst :tail) + (er-mk-cons (nth es 0) mapped)))) + :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))) + +(define + er-bif-lists-mapfoldl + (fn (vs) (er-ext-mapfoldl (nth vs 0) (nth vs 1) (nth vs 2) (er-mk-nil)))) + +(define + er-ext-search + (fn (pred lst) + (cond + (er-nil? lst) (er-mk-atom "false") + (er-cons? lst) + (if (er-truthy? (er-apply-fun pred (list (get lst :head)))) + (er-mk-tuple (list (er-mk-atom "value") (get lst :head))) + (er-ext-search pred (get lst :tail))) + :else (er-mk-atom "false")))) + +(define + er-bif-lists-search + (fn (vs) (er-ext-search (nth vs 0) (nth vs 1)))) + +;; ── proplists module ────────────────────────────────────────────── +;; A property list element is either a bare atom A (shorthand for +;; {A, true}) or a tuple whose first element is the key (value = its +;; second element, or true for a 1-tuple). Lookups use the FIRST match. +(define + er-ext-pl-key-of + (fn (e) + (cond + (er-atom? e) e + (and (er-tuple? e) (>= (len (get e :elements)) 1)) (nth (get e :elements) 0) + :else nil))) + +(define + er-ext-pl-val-of + (fn (e) + (cond + (and (er-tuple? e) (>= (len (get e :elements)) 2)) (nth (get e :elements) 1) + :else (er-mk-atom "true")))) + +(define + er-ext-pl-match? + (fn (key e) + (let ((k (er-ext-pl-key-of e))) + (and (not (= k nil)) (er-equal? key k))))) + +(define + er-ext-pl-get-value + (fn (key lst default) + (cond + (er-nil? lst) default + (er-cons? lst) + (if (er-ext-pl-match? key (get lst :head)) + (er-ext-pl-val-of (get lst :head)) + (er-ext-pl-get-value key (get lst :tail) default)) + :else default))) + +(define + er-bif-pl-get-value + (fn (vs) + (cond + (= (len vs) 2) + (er-ext-pl-get-value (nth vs 0) (nth vs 1) (er-mk-atom "undefined")) + (= (len vs) 3) + (er-ext-pl-get-value (nth vs 0) (nth vs 1) (nth vs 2)) + :else (error "Erlang: proplists:get_value: wrong arity")))) + +(define + er-ext-pl-all + (fn (key lst acc) + (cond + (er-nil? lst) (er-list-reverse-iter acc (er-mk-nil)) + (er-cons? lst) + (er-ext-pl-all key (get lst :tail) + (if (er-ext-pl-match? key (get lst :head)) + (er-mk-cons (er-ext-pl-val-of (get lst :head)) acc) + acc)) + :else (er-list-reverse-iter acc (er-mk-nil))))) + +(define + er-bif-pl-get-all-values + (fn (vs) (er-ext-pl-all (nth vs 0) (nth vs 1) (er-mk-nil)))) + +(define + er-ext-pl-defined? + (fn (key lst) + (cond + (er-nil? lst) false + (er-cons? lst) + (if (er-ext-pl-match? key (get lst :head)) + true + (er-ext-pl-defined? key (get lst :tail))) + :else false))) + +(define + er-bif-pl-is-defined + (fn (vs) (er-bool (er-ext-pl-defined? (nth vs 0) (nth vs 1))))) + +(define + er-ext-pl-lookup + (fn (key lst) + (cond + (er-nil? lst) (er-mk-atom "none") + (er-cons? lst) + (if (er-ext-pl-match? key (get lst :head)) + (let ((e (get lst :head))) + (if (er-tuple? e) e (er-mk-tuple (list e (er-mk-atom "true"))))) + (er-ext-pl-lookup key (get lst :tail))) + :else (er-mk-atom "none")))) + +(define + er-bif-pl-lookup + (fn (vs) (er-ext-pl-lookup (nth vs 0) (nth vs 1)))) + +(define + er-ext-pl-delete + (fn (key lst) + (cond + (er-nil? lst) (er-mk-nil) + (er-cons? lst) + (if (er-ext-pl-match? key (get lst :head)) + (er-ext-pl-delete key (get lst :tail)) + (er-mk-cons (get lst :head) (er-ext-pl-delete key (get lst :tail)))) + :else lst))) + +(define + er-bif-pl-delete + (fn (vs) (er-ext-pl-delete (nth vs 0) (nth vs 1)))) diff --git a/plans/erlang-on-sx.md b/plans/erlang-on-sx.md index 4505492f..1b087ecf 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 retire separate `lists-ext.sx` — fold stdlib BIFs into canonical files** — The 8 stdlib commits had lived in a standalone `lib/erlang/lists-ext.sx` (a workaround for this worktree's broken sx-tree write tools). Folded the function bodies into `transpile.sx` (appended, alongside the existing `er-bif-lists-*`) and moved the registrations **directly inside `er-register-builtin-bifs!`** in `runtime.sx` — so they now reach every erlang consumer (fed-sx, identity, …) that loads the runtime, not just the conformance harness. The `er-register-builtin-bifs!` **wrapper trick is gone** (no longer needed once registrations live inside the function the registry-reset re-runs). Deleted `lists-ext.sx` and its `(load …)` from `conformance.sh`; kept the `lists_ext` test suite + wiring. Byte-exact splice (no transcription), `sx_validate` clean, conformance **874/874** unchanged. Mirrors the same fold-in landed on the `architecture` branch (commit `39dbb00c`, equivalence verified: identical test suite + function bodies). loops/erlang only. + - **2026-06-30 stdlib hardening — `proplists` module** — Added `proplists:get_value/2,3`, `get_all_values/2`, `is_defined/2`, `lookup/2`, `delete/2` to `lib/erlang/lists-ext.sx` (header widened to "lists + proplists"). Property-list semantics: a bare atom `A` is shorthand for `{A, true}`, a tuple's first element is the key, lookups use the first match. `lookup` returns the tuple (or `{Key,true}`) or `none`; `get_value` defaults to `undefined`. The `lists_ext` suite (counter trio `er-lx-*`, now spanning both modules) 91→**103** (+12). Conformance **862 → 874/874**. loops/erlang only. - **2026-06-30 stdlib hardening — `lists` flatmap/filtermap/mapfoldl/search** — Added `flatmap/2`, `filtermap/2` (`true` keep / `false` drop / `{true, V}` transform), `mapfoldl/3` (returns `{MappedList, AccFinal}`), `search/2` (`{value, E}` | `false`) to `lib/erlang/lists-ext.sx`. `lists_ext` suite 83→**91** (+8). Conformance **854 → 862/862**. The `lists` module is now broadly covered (sort/usort/keylists/fold/partition/while/flatten/min/max/zip/slicing/flatmap/filtermap/mapfoldl/search on top of the originals). loops/erlang only.