diff --git a/lib/erlang/conformance.conf b/lib/erlang/conformance.conf index 8aef90e6..9d5f2f67 100644 --- a/lib/erlang/conformance.conf +++ b/lib/erlang/conformance.conf @@ -33,6 +33,7 @@ SUITES=( "ffi:lib/erlang/tests/ffi.sx:{:passed er-ffi-test-pass :failed (- er-ffi-test-count er-ffi-test-pass) :total er-ffi-test-count}" "vm:lib/erlang/tests/vm.sx:{:passed er-vm-test-pass :failed (- er-vm-test-count er-vm-test-pass) :total er-vm-test-count}" "send_after:lib/erlang/tests/send_after.sx:{:passed er-sa-test-pass :failed (- er-sa-test-count er-sa-test-pass) :total er-sa-test-count}" + "lists_ext:lib/erlang/tests/lists_ext.sx:{:passed er-lx-test-pass :failed (- er-lx-test-count er-lx-test-pass) :total er-lx-test-count}" ) # Preserve the historical scoreboard schema so consumers of diff --git a/lib/erlang/runtime.sx b/lib/erlang/runtime.sx index 4f379f10..46c2a569 100644 --- a/lib/erlang/runtime.sx +++ b/lib/erlang/runtime.sx @@ -2040,6 +2040,42 @@ "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) (er-register-bif! "io" "format" 1 er-bif-io-format) (er-register-bif! "io" "format" 2 er-bif-io-format) (er-register-bif! "ets" "new" 2 er-bif-ets-new) diff --git a/lib/erlang/scoreboard.json b/lib/erlang/scoreboard.json index 614cd84c..cb709dc0 100644 --- a/lib/erlang/scoreboard.json +++ b/lib/erlang/scoreboard.json @@ -1,7 +1,7 @@ { "language": "erlang", - "total_pass": 771, - "total": 771, + "total_pass": 874, + "total": 874, "suites": [ {"name":"tokenize","pass":62,"total":62,"status":"ok"}, {"name":"parse","pass":52,"total":52,"status":"ok"}, @@ -14,6 +14,7 @@ {"name":"fib","pass":8,"total":8,"status":"ok"}, {"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":"send_after","pass":10,"total":10,"status":"ok"}, + {"name":"lists_ext","pass":103,"total":103,"status":"ok"} ] } diff --git a/lib/erlang/scoreboard.md b/lib/erlang/scoreboard.md index d3c00f8c..d8958028 100644 --- a/lib/erlang/scoreboard.md +++ b/lib/erlang/scoreboard.md @@ -1,6 +1,6 @@ # Erlang-on-SX Scoreboard -**Total: 771 / 771 tests passing** +**Total: 874 / 874 tests passing** | | Suite | Pass | Total | |---|---|---|---| @@ -16,5 +16,6 @@ | ✅ | ffi | 37 | 37 | | ✅ | vm | 78 | 78 | | ✅ | send_after | 10 | 10 | +| ✅ | lists_ext | 103 | 103 | Generated by `lib/erlang/conformance.sh`. diff --git a/lib/erlang/tests/lists_ext.sx b/lib/erlang/tests/lists_ext.sx new file mode 100644 index 00000000..2e0fc806 --- /dev/null +++ b/lib/erlang/tests/lists_ext.sx @@ -0,0 +1,385 @@ +;; lists-ext tests — lists:sort/1, lists:sort/2, lists:usort/1. +;; Each case evaluates an Erlang expression that reduces to the bool +;; atom `true` (via =:= on the sorted result) and checks its name. + +(define er-lx-test-count 0) +(define er-lx-test-pass 0) +(define er-lx-test-fails (list)) + +(define + er-lx-test + (fn + (name actual expected) + (set! er-lx-test-count (+ er-lx-test-count 1)) + (if + (= actual expected) + (set! er-lx-test-pass (+ er-lx-test-pass 1)) + (append! er-lx-test-fails {:name name :expected expected :actual actual})))) + +;; eval an Erlang source string and return the result atom's name +(define er-lx-nm (fn (src) (get (erlang-eval-ast src) :name))) + +;; ── lists:sort/1 ────────────────────────────────────────────────── +(er-lx-test "sort/1 ascending" + (er-lx-nm "lists:sort([3,1,2]) =:= [1,2,3]") "true") + +(er-lx-test "sort/1 already sorted" + (er-lx-nm "lists:sort([1,2,3]) =:= [1,2,3]") "true") + +(er-lx-test "sort/1 empty" + (er-lx-nm "lists:sort([]) =:= []") "true") + +(er-lx-test "sort/1 singleton" + (er-lx-nm "lists:sort([7]) =:= [7]") "true") + +(er-lx-test "sort/1 keeps duplicates" + (er-lx-nm "lists:sort([3,1,2,1]) =:= [1,1,2,3]") "true") + +(er-lx-test "sort/1 length preserved" + (erlang-eval-ast "length(lists:sort([5,4,3,2,1]))") 5) + +(er-lx-test "sort/1 term order: number < atom" + (er-lx-nm "lists:sort([b,a,1]) =:= [1,a,b]") "true") + +(er-lx-test "sort/1 tuples elementwise" + (er-lx-nm "lists:sort([{2,a},{1,b},{1,a}]) =:= [{1,a},{1,b},{2,a}]") "true") + +;; ── lists:sort/2 ────────────────────────────────────────────────── +(er-lx-test "sort/2 ascending =<" + (er-lx-nm "lists:sort(fun(A,B) -> A =< B end, [3,1,2]) =:= [1,2,3]") "true") + +(er-lx-test "sort/2 descending >=" + (er-lx-nm "lists:sort(fun(A,B) -> A >= B end, [1,3,2]) =:= [3,2,1]") "true") + +(er-lx-test "sort/2 stable on equal keys" + (er-lx-nm + "lists:sort(fun({A,_},{B,_}) -> A =< B end, [{1,x},{1,y},{0,z}]) =:= [{0,z},{1,x},{1,y}]") + "true") + +(er-lx-test "sort/2 empty" + (er-lx-nm "lists:sort(fun(A,B) -> A =< B end, []) =:= []") "true") + +;; ── lists:usort/1 ───────────────────────────────────────────────── +(er-lx-test "usort/1 removes duplicates" + (er-lx-nm "lists:usort([3,1,2,1,3]) =:= [1,2,3]") "true") + +(er-lx-test "usort/1 empty" + (er-lx-nm "lists:usort([]) =:= []") "true") + +(er-lx-test "usort/1 all equal collapses to one" + (er-lx-nm "lists:usort([5,5,5]) =:= [5]") "true") + +(er-lx-test "usort/1 already unique" + (er-lx-nm "lists:usort([1,2,3]) =:= [1,2,3]") "true") + +(er-lx-test "usort/1 length after dedup" + (erlang-eval-ast "length(lists:usort([4,4,2,2,1,1,4]))") 3) + +;; ── lists:keyfind/3 ─────────────────────────────────────────────── +(er-lx-test "keyfind hit" + (erlang-eval-ast "element(2, lists:keyfind(b, 1, [{a,1},{b,2},{c,3}]))") 2) + +(er-lx-test "keyfind first match only" + (erlang-eval-ast "element(2, lists:keyfind(a, 1, [{a,1},{a,9}]))") 1) + +(er-lx-test "keyfind miss returns false" + (er-lx-nm "lists:keyfind(z, 1, [{a,1},{b,2}])") "false") + +(er-lx-test "keyfind on second element" + (er-lx-nm "element(1, lists:keyfind(2, 2, [{a,1},{b,2}]))") "b") + +(er-lx-test "keyfind skips short tuples" + (er-lx-nm "lists:keyfind(x, 2, [{x},{y,x}]) =:= {y,x}") "true") + +;; ── lists:keymember/3 ───────────────────────────────────────────── +(er-lx-test "keymember true" + (er-lx-nm "lists:keymember(b, 1, [{a,1},{b,2}])") "true") + +(er-lx-test "keymember false" + (er-lx-nm "lists:keymember(z, 1, [{a,1},{b,2}])") "false") + +;; ── lists:keydelete/3 ───────────────────────────────────────────── +(er-lx-test "keydelete removes first match" + (er-lx-nm "lists:keydelete(b, 1, [{a,1},{b,2},{c,3}]) =:= [{a,1},{c,3}]") "true") + +(er-lx-test "keydelete only first" + (er-lx-nm "lists:keydelete(a, 1, [{a,1},{a,2},{b,3}]) =:= [{a,2},{b,3}]") "true") + +(er-lx-test "keydelete miss unchanged" + (er-lx-nm "lists:keydelete(z, 1, [{a,1},{b,2}]) =:= [{a,1},{b,2}]") "true") + +;; ── lists:keyreplace/4 ──────────────────────────────────────────── +(er-lx-test "keyreplace hit" + (er-lx-nm + "lists:keyreplace(b, 1, [{a,1},{b,2},{c,3}], {b,99}) =:= [{a,1},{b,99},{c,3}]") + "true") + +(er-lx-test "keyreplace miss unchanged" + (er-lx-nm + "lists:keyreplace(z, 1, [{a,1}], {z,0}) =:= [{a,1}]") "true") + +;; ── lists:keystore/4 ────────────────────────────────────────────── +(er-lx-test "keystore replaces existing" + (er-lx-nm + "lists:keystore(b, 1, [{a,1},{b,2}], {b,99}) =:= [{a,1},{b,99}]") "true") + +(er-lx-test "keystore appends when absent" + (er-lx-nm + "lists:keystore(z, 1, [{a,1},{b,2}], {z,0}) =:= [{a,1},{b,2},{z,0}]") "true") + +;; ── lists:keytake/3 ─────────────────────────────────────────────── +(er-lx-test "keytake hit value tag" + (er-lx-nm "element(1, lists:keytake(b, 1, [{a,1},{b,2},{c,3}]))") "value") + +(er-lx-test "keytake hit tuple" + (er-lx-nm + "element(2, lists:keytake(b, 1, [{a,1},{b,2},{c,3}])) =:= {b,2}") "true") + +(er-lx-test "keytake hit rest" + (er-lx-nm + "element(3, lists:keytake(b, 1, [{a,1},{b,2},{c,3}])) =:= [{a,1},{c,3}]") "true") + +(er-lx-test "keytake miss false" + (er-lx-nm "lists:keytake(z, 1, [{a,1}])") "false") + +;; ── lists:keysort/2 ─────────────────────────────────────────────── +(er-lx-test "keysort by element 1" + (er-lx-nm + "lists:keysort(1, [{c,3},{a,1},{b,2}]) =:= [{a,1},{b,2},{c,3}]") "true") + +(er-lx-test "keysort by element 2" + (er-lx-nm + "lists:keysort(2, [{a,3},{b,1},{c,2}]) =:= [{b,1},{c,2},{a,3}]") "true") + +(er-lx-test "keysort stable on equal keys" + (er-lx-nm + "lists:keysort(1, [{a,1},{a,2},{a,3}]) =:= [{a,1},{a,2},{a,3}]") "true") + +;; ── lists:foldr/3 ───────────────────────────────────────────────── +(er-lx-test "foldr preserves order" + (er-lx-nm + "lists:foldr(fun(X,Acc) -> [X|Acc] end, [], [1,2,3]) =:= [1,2,3]") "true") + +(er-lx-test "foldr sum" + (erlang-eval-ast "lists:foldr(fun(X,A) -> X+A end, 0, [1,2,3,4])") 10) + +(er-lx-test "foldr empty returns acc" + (erlang-eval-ast "lists:foldr(fun(X,A) -> X+A end, 42, [])") 42) + +;; ── lists:partition/2 ───────────────────────────────────────────── +(er-lx-test "partition evens/odds" + (er-lx-nm + "lists:partition(fun(X) -> X rem 2 =:= 0 end, [1,2,3,4,5]) =:= {[2,4],[1,3,5]}") + "true") + +(er-lx-test "partition all satisfy" + (er-lx-nm "lists:partition(fun(_) -> true end, [1,2]) =:= {[1,2],[]}") "true") + +(er-lx-test "partition empty" + (er-lx-nm "lists:partition(fun(_) -> true end, []) =:= {[],[]}") "true") + +;; ── lists:takewhile/2 ───────────────────────────────────────────── +(er-lx-test "takewhile prefix" + (er-lx-nm "lists:takewhile(fun(X) -> X < 3 end, [1,2,3,4,1]) =:= [1,2]") "true") + +(er-lx-test "takewhile none" + (er-lx-nm "lists:takewhile(fun(X) -> X < 0 end, [1,2]) =:= []") "true") + +(er-lx-test "takewhile all" + (er-lx-nm "lists:takewhile(fun(X) -> X < 9 end, [1,2,3]) =:= [1,2,3]") "true") + +;; ── lists:dropwhile/2 ───────────────────────────────────────────── +(er-lx-test "dropwhile prefix" + (er-lx-nm "lists:dropwhile(fun(X) -> X < 3 end, [1,2,3,4,1]) =:= [3,4,1]") "true") + +(er-lx-test "dropwhile all" + (er-lx-nm "lists:dropwhile(fun(X) -> X < 9 end, [1,2,3]) =:= []") "true") + +(er-lx-test "dropwhile none" + (er-lx-nm "lists:dropwhile(fun(X) -> X < 0 end, [1,2]) =:= [1,2]") "true") + +;; ── lists:splitwith/2 ───────────────────────────────────────────── +(er-lx-test "splitwith" + (er-lx-nm + "lists:splitwith(fun(X) -> X < 3 end, [1,2,3,4,1]) =:= {[1,2],[3,4,1]}") "true") + +(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") + +;; ── lists:zip/2 ─────────────────────────────────────────────────── +(er-lx-test "zip pairs" + (er-lx-nm "lists:zip([a,b,c],[1,2,3]) =:= [{a,1},{b,2},{c,3}]") "true") + +(er-lx-test "zip empty" + (er-lx-nm "lists:zip([],[]) =:= []") "true") + +(er-lx-test "zip length" + (erlang-eval-ast "length(lists:zip([1,2],[3,4]))") 2) + +;; ── lists:zipwith/3 ─────────────────────────────────────────────── +(er-lx-test "zipwith sum" + (er-lx-nm + "lists:zipwith(fun(X,Y) -> X+Y end, [1,2,3], [10,20,30]) =:= [11,22,33]") + "true") + +(er-lx-test "zipwith tuple" + (er-lx-nm "lists:zipwith(fun(X,Y) -> {X,Y} end, [a], [1]) =:= [{a,1}]") "true") + +;; ── lists:unzip/1 ───────────────────────────────────────────────── +(er-lx-test "unzip" + (er-lx-nm "lists:unzip([{a,1},{b,2},{c,3}]) =:= {[a,b,c],[1,2,3]}") "true") + +(er-lx-test "unzip empty" + (er-lx-nm "lists:unzip([]) =:= {[],[]}") "true") + +(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") + +;; ── lists:flatmap/2 ─────────────────────────────────────────────── +(er-lx-test "flatmap duplicates" + (er-lx-nm "lists:flatmap(fun(X) -> [X,X] end, [1,2]) =:= [1,1,2,2]") "true") + +(er-lx-test "flatmap empty" + (er-lx-nm "lists:flatmap(fun(X) -> [X] end, []) =:= []") "true") + +;; ── lists:filtermap/2 ───────────────────────────────────────────── +(er-lx-test "filtermap transform" + (er-lx-nm + "lists:filtermap(fun(X) -> case X rem 2 of 0 -> {true, X*10}; _ -> false end end, [1,2,3,4]) =:= [20,40]") + "true") + +(er-lx-test "filtermap bool keep" + (er-lx-nm "lists:filtermap(fun(X) -> X > 2 end, [1,2,3,4]) =:= [3,4]") "true") + +;; ── lists:mapfoldl/3 ────────────────────────────────────────────── +(er-lx-test "mapfoldl map+acc" + (er-lx-nm + "lists:mapfoldl(fun(X,A) -> {X*2, A+X} end, 0, [1,2,3]) =:= {[2,4,6],6}") "true") + +(er-lx-test "mapfoldl empty" + (er-lx-nm "lists:mapfoldl(fun(X,A) -> {X,A} end, 5, []) =:= {[],5}") "true") + +;; ── lists:search/2 ──────────────────────────────────────────────── +(er-lx-test "search hit" + (er-lx-nm "lists:search(fun(X) -> X > 2 end, [1,2,3,4]) =:= {value,3}") "true") + +(er-lx-test "search miss" + (er-lx-nm "lists:search(fun(X) -> X > 9 end, [1,2,3])") "false") + +;; ── proplists:get_value/2,3 ─────────────────────────────────────── +(er-lx-test "pl get_value hit" + (erlang-eval-ast "proplists:get_value(b, [{a,1},{b,2}])") 2) + +(er-lx-test "pl get_value miss undefined" + (er-lx-nm "proplists:get_value(z, [{a,1}])") "undefined") + +(er-lx-test "pl get_value default" + (erlang-eval-ast "proplists:get_value(z, [{a,1}], 99)") 99) + +(er-lx-test "pl get_value bare atom is true" + (er-lx-nm "proplists:get_value(flag, [flag, {a,1}])") "true") + +(er-lx-test "pl get_value first occurrence" + (erlang-eval-ast "proplists:get_value(a, [{a,1},{a,2}])") 1) + +;; ── proplists:get_all_values/2 ──────────────────────────────────── +(er-lx-test "pl get_all_values" + (er-lx-nm + "proplists:get_all_values(a, [{a,1},{b,2},{a,3}]) =:= [1,3]") "true") + +;; ── proplists:is_defined/2 ──────────────────────────────────────── +(er-lx-test "pl is_defined true" + (er-lx-nm "proplists:is_defined(b, [{a,1},{b,2}])") "true") + +(er-lx-test "pl is_defined false" + (er-lx-nm "proplists:is_defined(z, [{a,1}])") "false") + +;; ── proplists:lookup/2 ──────────────────────────────────────────── +(er-lx-test "pl lookup hit" + (er-lx-nm "proplists:lookup(b, [{a,1},{b,2}]) =:= {b,2}") "true") + +(er-lx-test "pl lookup bare atom" + (er-lx-nm "proplists:lookup(flag, [flag]) =:= {flag,true}") "true") + +(er-lx-test "pl lookup miss" + (er-lx-nm "proplists:lookup(z, [{a,1}])") "none") + +;; ── proplists:delete/2 ──────────────────────────────────────────── +(er-lx-test "pl delete removes all" + (er-lx-nm "proplists:delete(a, [{a,1},{b,2},{a,3}]) =:= [{b,2}]") "true") diff --git a/lib/erlang/transpile.sx b/lib/erlang/transpile.sx index 6ed64b87..cc8dc057 100644 --- a/lib/erlang/transpile.sx +++ b/lib/erlang/transpile.sx @@ -2045,4 +2045,657 @@ (range 0 (len ks))) out))) +;; ── extra lists + proplists BIFs (folded from loops/erlang lists-ext) ── +;; ── 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))))