From c6f397c3d99f23debe07d156bba49c8c254a5bc9 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 6 Jun 2026 08:02:36 +0000 Subject: [PATCH] erlang: register binary_to_list/1 + list_to_binary/1 BIFs (+9 ffi tests, 738/738) --- lib/erlang/runtime.sx | 59 ++++++++++++++++++++++++++++++++++++++ lib/erlang/scoreboard.json | 6 ++-- lib/erlang/scoreboard.md | 4 +-- lib/erlang/tests/ffi.sx | 45 +++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 5 deletions(-) diff --git a/lib/erlang/runtime.sx b/lib/erlang/runtime.sx index b4043b1e..f6db3733 100644 --- a/lib/erlang/runtime.sx +++ b/lib/erlang/runtime.sx @@ -1561,7 +1561,66 @@ (er-register-pure-bif! "crypto" "hash" 2 er-bif-crypto-hash) (er-register-pure-bif! "cid" "from_bytes" 1 er-bif-cid-from-bytes) (er-register-pure-bif! "cid" "to_string" 1 er-bif-cid-to-string) + +;; ── binary_to_list / list_to_binary (Step 3b — term codec) ────── +;; Standard Erlang semantics: +;; binary_to_list(<>) -> [B1, B2, ...] (Erlang cons of ints) +;; list_to_binary(IoList) -> <<...>> (flattens nested +;; iolists; elements are byte ints 0-255 or binaries) +;; Bad arg / out-of-range byte / non-iolist element -> error:badarg. + +(define er-bif-binary-to-list + (fn (vs) + (let ((v (nth vs 0))) + (cond + (not (er-binary? v)) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else + (let ((bs (get v :bytes)) (out (er-mk-nil))) + (for-each + (fn (i) + (set! out (er-mk-cons (nth bs (- (- (len bs) 1) i)) out))) + (range 0 (len bs))) + out))))) + +;; Walk an Erlang iolist, appending bytes to `acc` (a mutable SX list). +;; Accepts: nil, cons-of-X, binary, integer in 0..255. Anything else +;; signals failure by setting (nth fail 0) to true. +(define er-iolist-walk! + (fn (v acc fail) + (cond + (nth fail 0) nil + (er-nil? v) nil + (er-cons? v) + (do (er-iolist-walk! (get v :head) acc fail) + (er-iolist-walk! (get v :tail) acc fail)) + (er-binary? v) + (for-each + (fn (i) (append! acc (nth (get v :bytes) i))) + (range 0 (len (get v :bytes)))) + (= (type-of v) "number") + (cond + (and (>= v 0) (<= v 255)) (append! acc v) + :else (set-nth! fail 0 true)) + :else (set-nth! fail 0 true)))) + +(define er-bif-list-to-binary + (fn (vs) + (let ((v (nth vs 0)) (acc (list)) (fail (list false))) + (cond + (not (or (er-nil? v) (er-cons? v) (er-binary? v))) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else + (do + (er-iolist-walk! v acc fail) + (cond + (nth fail 0) + (raise (er-mk-error-marker (er-mk-atom "badarg"))) + :else (er-mk-binary acc))))))) + (er-register-bif! "file" "list_dir" 1 er-bif-file-list-dir) + (er-register-pure-bif! "erlang" "binary_to_list" 1 er-bif-binary-to-list) + (er-register-pure-bif! "erlang" "list_to_binary" 1 er-bif-list-to-binary) (er-mk-atom "ok"))) ;; Register everything at load time. diff --git a/lib/erlang/scoreboard.json b/lib/erlang/scoreboard.json index f5b6e981..fac8aff3 100644 --- a/lib/erlang/scoreboard.json +++ b/lib/erlang/scoreboard.json @@ -1,7 +1,7 @@ { "language": "erlang", - "total_pass": 729, - "total": 729, + "total_pass": 738, + "total": 738, "suites": [ {"name":"tokenize","pass":62,"total":62,"status":"ok"}, {"name":"parse","pass":52,"total":52,"status":"ok"}, @@ -12,7 +12,7 @@ {"name":"bank","pass":8,"total":8,"status":"ok"}, {"name":"echo","pass":7,"total":7,"status":"ok"}, {"name":"fib","pass":8,"total":8,"status":"ok"}, - {"name":"ffi","pass":28,"total":28,"status":"ok"}, + {"name":"ffi","pass":37,"total":37,"status":"ok"}, {"name":"vm","pass":78,"total":78,"status":"ok"} ] } diff --git a/lib/erlang/scoreboard.md b/lib/erlang/scoreboard.md index 75f3fe39..75cac040 100644 --- a/lib/erlang/scoreboard.md +++ b/lib/erlang/scoreboard.md @@ -1,6 +1,6 @@ # Erlang-on-SX Scoreboard -**Total: 729 / 729 tests passing** +**Total: 738 / 738 tests passing** | | Suite | Pass | Total | |---|---|---|---| @@ -13,7 +13,7 @@ | ✅ | bank | 8 | 8 | | ✅ | echo | 7 | 7 | | ✅ | fib | 8 | 8 | -| ✅ | ffi | 28 | 28 | +| ✅ | ffi | 37 | 37 | | ✅ | vm | 78 | 78 | diff --git a/lib/erlang/tests/ffi.sx b/lib/erlang/tests/ffi.sx index e08a31bf..29af1c9e 100644 --- a/lib/erlang/tests/ffi.sx +++ b/lib/erlang/tests/ffi.sx @@ -160,6 +160,51 @@ (ffi-nm (ffi-ev "element(2, file:list_dir(\"/no/such/dir/xyz\"))")) "enoent") +(er-ffi-test + "binary_to_list <<1,2,3>> length" + (ffi-ev "length(binary_to_list(<<1,2,3,4,5>>))") + 5) + +(er-ffi-test + "binary_to_list hd byte" + (ffi-ev "hd(binary_to_list(<<7,8,9>>))") + 7) + +(er-ffi-test + "binary_to_list empty -> []" + (ffi-nm (ffi-ev "case binary_to_list(<<>>) of [] -> empty end")) + "empty") + +(er-ffi-test + "list_to_binary flat list bytes" + (ffi-ev "byte_size(list_to_binary([1,2,3]))") + 3) + +(er-ffi-test + "list_to_binary nested iolist" + (ffi-ev "byte_size(list_to_binary([1, <<2,3>>, [4, [5]]]))") + 5) + +(er-ffi-test + "list_to_binary round-trip via binary_to_list" + (ffi-nm (ffi-ev "list_to_binary(binary_to_list(<<10,20,30>>)) =:= <<10,20,30>>")) + "true") + +(er-ffi-test + "binary_to_list non-binary -> error:badarg" + (ffi-nm (ffi-ev "try binary_to_list(42) catch error:badarg -> ok end")) + "ok") + +(er-ffi-test + "list_to_binary out-of-range byte -> error:badarg" + (ffi-nm (ffi-ev "try list_to_binary([300]) catch error:badarg -> ok end")) + "ok") + +(er-ffi-test + "list_to_binary non-iolist -> error:badarg" + (ffi-nm (ffi-ev "try list_to_binary(42) catch error:badarg -> ok end")) + "ok") + ;; ── Still deferred (no host primitive): httpc (HTTP client, v2), ;; sqlite-* (v2 indexes). Assert NOT registered so a future iteration ;; that wires them without updating this suite fails fast.