erlang-runtime: add lib/erlang/runtime.sx + test.sh (55/55 pass)

Numeric tower (is-integer?/float?/number?, float/trunc/round/abs/max/min),
div/rem (quotient/remainder), bitwise (band/bor/bxor/bnot/bsl/bsr),
sets module (new/add/member/union/intersection/subtract/size/to-list/from-list),
re module (run/replace/replace-all/match-groups/split),
list BIFs (hd/tl/length/member/reverse/nth/foldl/foldr/seq/flatten/zip),
type conversions (integer-to-list, list-to-integer, atom-to-list, etc.),
ok/error tuple helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 21:21:39 +00:00
parent a8613656e9
commit 3c0a963229
2 changed files with 490 additions and 0 deletions

230
lib/erlang/runtime.sx Normal file
View File

@@ -0,0 +1,230 @@
;; lib/erlang/runtime.sx — Erlang BIFs and stdlib wrappers on SX primitives
;;
;; Provides Erlang-idiomatic wrappers. Thin where spec primitives match;
;; inline where Erlang semantics differ (e.g. rem sign, integer division).
;;
;; Primitives used from spec:
;; integer?/float? (Phase 2)
;; remainder/quotient (Phase 2 / Phase 15)
;; bitwise-and/or/xor/not (Phase 7)
;; arithmetic-shift (Phase 7)
;; make-set/set-add!/etc (Phase 18)
;; make-regexp/regexp-match/etc (Phase 20)
;; gcd (Phase 15)
;; ---------------------------------------------------------------------------
;; 1. Numeric tower — type predicates + conversions
;; ---------------------------------------------------------------------------
(define er-is-integer? integer?)
(define er-is-float? float?)
(define (er-is-number? x) (or (integer? x) (float? x)))
(define (er-is-atom? x) (= (type-of x) "symbol"))
(define er-is-list? list?)
(define er-is-binary? bytevector?)
;; Erlang float/1 coerces an integer to float
(define (er-float x) (* 1 x))
;; Erlang trunc/1 — truncate toward zero
(define er-trunc truncate)
;; Erlang round/1 — round to nearest integer
(define er-round round)
;; Erlang abs/1
(define er-abs abs)
;; Erlang max/min (BIFs in OTP 26)
(define (er-max a b) (if (>= a b) a b))
(define (er-min a b) (if (<= a b) a b))
;; ---------------------------------------------------------------------------
;; 2. Integer arithmetic — div + rem (Erlang semantics)
;; ---------------------------------------------------------------------------
;; Erlang div: integer division truncating toward zero
(define er-div quotient)
;; Erlang rem: remainder with sign of dividend (matches remainder primitive)
(define er-rem remainder)
;; Erlang gcd (non-standard BIF but useful)
(define er-gcd gcd)
;; ---------------------------------------------------------------------------
;; 3. Bitwise ops — band / bor / bxor / bnot / bsl / bsr
;; ---------------------------------------------------------------------------
(define er-band bitwise-and)
(define er-bor bitwise-or)
(define er-bxor bitwise-xor)
(define er-bnot bitwise-not)
;; bsl: bit shift left by N positions
(define (er-bsl x n) (arithmetic-shift x n))
;; bsr: bit shift right by N positions
(define (er-bsr x n) (arithmetic-shift x (- 0 n)))
;; ---------------------------------------------------------------------------
;; 4. Sets module — thin wrappers matching Erlang sets API
;; ---------------------------------------------------------------------------
(define er-sets-new make-set)
(define er-sets-add-element set-add!)
(define er-sets-is-element set-member?)
(define er-sets-del-element set-remove!)
(define er-sets-union set-union)
(define er-sets-intersection set-intersection)
(define er-sets-subtract set-difference)
(define er-sets-to-list set->list)
(define er-sets-from-list list->set)
(define (er-sets-size s) (len (set->list s)))
(define (er-sets-is-set? x) (set? x))
;; ---------------------------------------------------------------------------
;; 5. Regexp — re module wrappers
;; ---------------------------------------------------------------------------
;; er-re-run: returns match dict or nil (no match)
(define
(er-re-run subject pattern)
(regexp-match (make-regexp pattern) subject))
;; er-re-replace: replace first match
(define
(er-re-replace subject pattern replacement)
(regexp-replace (make-regexp pattern) subject replacement))
;; er-re-replace-all: global replace
(define
(er-re-replace-all subject pattern replacement)
(regexp-replace-all (make-regexp pattern) subject replacement))
;; er-re-match-groups: extract capture groups from a match result
(define (er-re-match-groups m) (if (= m nil) nil (get m :groups)))
;; er-re-split: split string on regexp delimiter
(define
(er-re-split subject pattern)
(let
((re (make-regexp pattern))
(ms (regexp-match-all (make-regexp pattern) subject)))
(if
(= (len ms) 0)
(list subject)
(letrec
((go (fn (matches pos acc) (if (= (len matches) 0) (append acc (list (substring subject pos (len subject)))) (let ((m (first matches)) (start (get (first matches) :start)) (end (get (first matches) :end))) (go (rest matches) end (append acc (list (substring subject pos start)))))))))
(go ms 0 (list))))))
;; ---------------------------------------------------------------------------
;; 6. List BIFs — hd/tl/length + lists module
;; ---------------------------------------------------------------------------
(define (er-hd lst) (first lst))
(define (er-tl lst) (rest lst))
(define (er-length lst) (len lst))
;; lists:member/2
(define
(er-lists-member elem lst)
(cond
((= (len lst) 0) false)
((= elem (first lst)) true)
(else (er-lists-member elem (rest lst)))))
;; lists:reverse/1
(define er-lists-reverse reverse)
;; lists:append/2
(define er-lists-append append)
;; lists:flatten/1
(define
(er-lists-flatten lst)
(cond
((= (len lst) 0) (list))
((list? (first lst))
(append (er-lists-flatten (first lst)) (er-lists-flatten (rest lst))))
(else (cons (first lst) (er-lists-flatten (rest lst))))))
;; lists:nth/2 — 1-indexed
(define (er-lists-nth n lst) (nth lst (- n 1)))
;; lists:map/2
(define er-lists-map map)
;; lists:filter/2
(define er-lists-filter filter)
;; lists:foldl/3 — (Fun, Acc0, List)
(define
(er-lists-foldl f acc lst)
(if
(= (len lst) 0)
acc
(er-lists-foldl f (f (first lst) acc) (rest lst))))
;; lists:foldr/3
(define
(er-lists-foldr f acc lst)
(if
(= (len lst) 0)
acc
(f (first lst) (er-lists-foldr f acc (rest lst)))))
;; lists:zip/2
(define
(er-lists-zip a b)
(if
(or (= (len a) 0) (= (len b) 0))
(list)
(cons (list (first a) (first b)) (er-lists-zip (rest a) (rest b)))))
;; lists:seq/2 — generate integer range (1-indexed like Erlang)
(define
(er-lists-seq from to)
(if
(> from to)
(list)
(cons from (er-lists-seq (+ from 1) to))))
;; ---------------------------------------------------------------------------
;; 7. Type conversion BIFs
;; ---------------------------------------------------------------------------
;; atom_to_list/1 — convert atom (symbol) to its name string
(define (er-atom-to-list a) (symbol->string a))
;; list_to_atom/1 — convert string to atom (symbol)
(define (er-list-to-atom s) (make-symbol s))
;; integer_to_list/1
(define (er-integer-to-list n) (str n))
;; list_to_integer/1
(define (er-list-to-integer s) (truncate (parse-number s)))
;; float_to_list/1
(define (er-float-to-list f) (str f))
;; list_to_float/1
(define (er-list-to-float s) (* 1 (parse-number s)))
;; integer_to_list/2 — with radix (e.g. 16 for hex)
(define (er-integer-to-list-radix n radix) (number->string n radix))
;; ---------------------------------------------------------------------------
;; 8. ok/error tuple helpers — Erlang idiom {ok, Val} / {error, Reason}
;; ---------------------------------------------------------------------------
(define (er-ok val) (list "ok" val))
(define (er-error reason) (list "error" reason))
(define
(er-is-ok? t)
(and (list? t) (= (len t) 2) (= (first t) "ok")))
(define
(er-is-error? t)
(and (list? t) (= (len t) 2) (= (first t) "error")))
(define (er-unwrap t) (nth t 1))

260
lib/erlang/test.sh Executable file
View File

@@ -0,0 +1,260 @@
#!/usr/bin/env bash
# lib/erlang/test.sh — smoke-test the Erlang runtime layer.
# Uses sx_server.exe epoch protocol.
#
# Usage:
# bash lib/erlang/test.sh
# bash lib/erlang/test.sh -v
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
PASS=0; FAIL=0; ERRORS=""
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/erlang/runtime.sx")
;; --- Numeric tower ---
(epoch 10)
(eval "(er-is-integer? 42)")
(epoch 11)
(eval "(er-is-integer? 3.14)")
(epoch 12)
(eval "(er-is-float? 3.14)")
(epoch 13)
(eval "(er-is-float? 42)")
(epoch 14)
(eval "(er-is-number? 42)")
(epoch 15)
(eval "(er-is-number? 3.14)")
(epoch 16)
(eval "(er-float 5)")
(epoch 17)
(eval "(er-trunc 3.9)")
(epoch 18)
(eval "(er-round 3.5)")
(epoch 19)
(eval "(er-abs -7)")
(epoch 20)
(eval "(er-max 3 7)")
(epoch 21)
(eval "(er-min 3 7)")
;; --- div + rem ---
(epoch 30)
(eval "(er-div 10 3)")
(epoch 31)
(eval "(er-div -10 3)")
(epoch 32)
(eval "(er-rem 10 3)")
(epoch 33)
(eval "(er-rem -10 3)")
(epoch 34)
(eval "(er-gcd 12 8)")
;; --- Bitwise ---
(epoch 40)
(eval "(er-band 12 10)")
(epoch 41)
(eval "(er-bor 12 10)")
(epoch 42)
(eval "(er-bxor 12 10)")
(epoch 43)
(eval "(er-bnot 0)")
(epoch 44)
(eval "(er-bsl 1 4)")
(epoch 45)
(eval "(er-bsr 16 2)")
;; --- Sets ---
(epoch 50)
(eval "(er-sets-is-set? (er-sets-new))")
(epoch 51)
(eval "(let ((s (er-sets-new))) (do (er-sets-add-element s 1) (er-sets-is-element s 1)))")
(epoch 52)
(eval "(er-sets-is-element (er-sets-new) 42)")
(epoch 53)
(eval "(er-sets-is-element (er-sets-from-list (list 1 2 3)) 2)")
(epoch 54)
(eval "(er-sets-size (er-sets-from-list (list 1 2 3)))")
(epoch 55)
(eval "(len (er-sets-to-list (er-sets-from-list (list 1 2 3))))")
;; --- Regexp ---
(epoch 60)
(eval "(not (= (er-re-run \"hello\" \"ll\") nil))")
(epoch 61)
(eval "(= (er-re-run \"hello\" \"xyz\") nil)")
(epoch 62)
(eval "(get (er-re-run \"hello\" \"ll\") :match)")
(epoch 63)
(eval "(er-re-replace \"hello\" \"l\" \"r\")")
(epoch 64)
(eval "(er-re-replace-all \"hello\" \"l\" \"r\")")
(epoch 65)
(eval "(er-re-match-groups (er-re-run \"hello world\" \"(\\w+)\\s+(\\w+)\"))")
(epoch 66)
(eval "(len (er-re-split \"a,b,c\" \",\"))")
;; --- List BIFs ---
(epoch 70)
(eval "(er-hd (list 1 2 3))")
(epoch 71)
(eval "(er-tl (list 1 2 3))")
(epoch 72)
(eval "(er-length (list 1 2 3))")
(epoch 73)
(eval "(er-lists-member 2 (list 1 2 3))")
(epoch 74)
(eval "(er-lists-member 9 (list 1 2 3))")
(epoch 75)
(eval "(er-lists-reverse (list 1 2 3))")
(epoch 76)
(eval "(er-lists-nth 2 (list 10 20 30))")
(epoch 77)
(eval "(er-lists-foldl + 0 (list 1 2 3 4 5))")
(epoch 78)
(eval "(er-lists-seq 1 5)")
(epoch 79)
(eval "(er-lists-flatten (list 1 (list 2 3) (list 4 (list 5))))")
;; --- Type conversions ---
(epoch 80)
(eval "(er-integer-to-list 42)")
(epoch 81)
(eval "(er-list-to-integer \"42\")")
(epoch 82)
(eval "(er-integer-to-list-radix 255 16)")
(epoch 83)
(eval "(er-atom-to-list (make-symbol \"hello\"))")
(epoch 84)
(eval "(= (type-of (er-list-to-atom \"foo\")) \"symbol\")")
;; --- ok/error tuples ---
(epoch 90)
(eval "(er-is-ok? (er-ok 42))")
(epoch 91)
(eval "(er-is-error? (er-error \"reason\"))")
(epoch 92)
(eval "(er-unwrap (er-ok 42))")
(epoch 93)
(eval "(er-is-ok? (er-error \"bad\"))")
EPOCHS
OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
check() {
local epoch="$1" desc="$2" expected="$3"
local actual
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1 || true)
if echo "$actual" | grep -q "^(ok-len"; then actual=""; fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " | head -1 || true)
fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(error $epoch " | head -1 || true)
fi
[ -z "$actual" ] && actual="<no output for epoch $epoch>"
if echo "$actual" | grep -qF -- "$expected"; then
PASS=$((PASS+1))
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
else
FAIL=$((FAIL+1))
ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
"
fi
}
# Numeric tower
check 10 "is-integer? 42" "true"
check 11 "is-integer? float" "false"
check 12 "is-float? 3.14" "true"
check 13 "is-float? int" "false"
check 14 "is-number? int" "true"
check 15 "is-number? float" "true"
check 16 "float 5" "5"
check 17 "trunc 3.9" "3"
check 18 "round 3.5" "4"
check 19 "abs -7" "7"
check 20 "max 3 7" "7"
check 21 "min 3 7" "3"
# div + rem
check 30 "div 10 3" "3"
check 31 "div -10 3" "-3"
check 32 "rem 10 3" "1"
check 33 "rem -10 3" "-1"
check 34 "gcd 12 8" "4"
# Bitwise
check 40 "band 12 10" "8"
check 41 "bor 12 10" "14"
check 42 "bxor 12 10" "6"
check 43 "bnot 0" "-1"
check 44 "bsl 1 4" "16"
check 45 "bsr 16 2" "4"
# Sets
check 50 "sets-new is-set?" "true"
check 51 "sets add+member" "true"
check 52 "member empty" "false"
check 53 "from-list member" "true"
check 54 "sets-size" "3"
check 55 "sets-to-list len" "3"
# Regexp
check 60 "re-run match" "true"
check 61 "re-run no match" "true"
check 62 "re-run match text" '"ll"'
check 63 "re-replace first" '"herlo"'
check 64 "re-replace-all" '"herro"'
check 65 "re-match-groups" '"hello"'
check 66 "re-split count" "3"
# List BIFs
check 70 "hd" "1"
check 71 "tl" "(2 3)"
check 72 "length" "3"
check 73 "member hit" "true"
check 74 "member miss" "false"
check 75 "reverse" "(3 2 1)"
check 76 "nth 2" "20"
check 77 "foldl sum" "15"
check 78 "seq 1..5" "(1 2 3 4 5)"
check 79 "flatten" "(1 2 3 4 5)"
# Type conversions
check 80 "integer-to-list" '"42"'
check 81 "list-to-integer" "42"
check 82 "integer-to-list hex" '"ff"'
check 83 "atom-to-list" '"hello"'
check 84 "list-to-atom" "true"
# ok/error
check 90 "ok? ok-tuple" "true"
check 91 "error? error-tuple" "true"
check 92 "unwrap ok" "42"
check 93 "ok? error-tuple" "false"
TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL lib/erlang tests passed"
else
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
echo "$ERRORS"
fi
[ $FAIL -eq 0 ]