diff --git a/lib/erlang/runtime.sx b/lib/erlang/runtime.sx index 5ef1a109..7f264400 100644 --- a/lib/erlang/runtime.sx +++ b/lib/erlang/runtime.sx @@ -1559,6 +1559,8 @@ (er-register-bif! "file" "delete" 1 er-bif-file-delete) ;; Phase 8 FFI — host-primitive BIFs (loops/fed-prims) (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) (er-mk-atom "ok"))) ;; Register everything at load time. diff --git a/lib/erlang/tests/ffi.sx b/lib/erlang/tests/ffi.sx index b3dbdf94..4809576e 100644 --- a/lib/erlang/tests/ffi.sx +++ b/lib/erlang/tests/ffi.sx @@ -106,9 +106,39 @@ "ok") (er-ffi-test - "cid:from_bytes unregistered" - (er-lookup-bif "cid" "from_bytes" 1) - nil) + "cid:from_bytes is_binary" + (ffi-nm (ffi-ev "is_binary(cid:from_bytes(<<97,98,99>>))")) + "true") + +(er-ffi-test + "cid:from_bytes deterministic" + (ffi-nm (ffi-ev "cid:from_bytes(<<97,98,99>>) =:= cid:from_bytes(<<97,98,99>>)")) + "true") + +(er-ffi-test + "cid:from_bytes distinct inputs distinct CIDs" + (ffi-nm (ffi-ev "cid:from_bytes(<<97,98,99>>) =/= cid:from_bytes(<<97,98,100>>)")) + "true") + +(er-ffi-test + "cid:from_bytes non-binary -> error:badarg" + (ffi-nm (ffi-ev "try cid:from_bytes(42) catch error:badarg -> ok end")) + "ok") + +(er-ffi-test + "cid:to_string is_binary" + (ffi-nm (ffi-ev "is_binary(cid:to_string({ok, 42}))")) + "true") + +(er-ffi-test + "cid:to_string deterministic" + (ffi-nm (ffi-ev "cid:to_string(foo) =:= cid:to_string(foo)")) + "true") + +(er-ffi-test + "cid:to_string distinct terms distinct CIDs" + (ffi-nm (ffi-ev "cid:to_string(foo) =/= cid:to_string(bar)")) + "true") (er-ffi-test "file:list_dir unregistered" diff --git a/plans/erlang-on-sx.md b/plans/erlang-on-sx.md index 138832ee..78483c78 100644 --- a/plans/erlang-on-sx.md +++ b/plans/erlang-on-sx.md @@ -116,7 +116,7 @@ Replace today's hardcoded BIF dispatch (`er-apply-bif`/`er-apply-remote-bif` in - [x] Migrate existing local + remote BIFs (length/hd/tl/lists:*/io:format/ets:*/etc.) onto the registry; delete the giant `cond` dispatch in `er-apply-bif`/`er-apply-remote-bif`. Conformance held at **600/600** after migration (baseline was 600, not the plan-text's 530 — the text was authored before Phase 7 work added rows). 67 builtin registrations across `erlang`/`lists`/`io`/`ets`/`code` modules; multi-arity BIFs (`is_function`, `spawn`, `exit`, `io:format`, `lists:seq`, `ets:delete`) register once per arity, all pointing at the same impl which dispatches on `(len vs)` internally. The four per-module cond dispatchers (`er-apply-lists-bif`, `er-apply-io-bif`, `er-apply-ets-bif`, `er-apply-code-bif`) are deleted. `er-apply-bif` and `er-apply-remote-bif` are now ~5-line registry lookups; user modules still win precedence over the registry. - [x] Term-marshalling helpers: `er-of-sx` (SX → Erlang) and `er-to-sx` (Erlang → SX). atom ↔ symbol, nil ↔ `()`, cons → list, tuple → list (one-way; tuples flatten), binary ↔ SX string, integer / float / boolean passthrough. **+23 runtime tests** (623/623 total). Erlang maps (`dict ↔ map`) deferred — Erlang map term not implemented in this port; will land when `#{}` syntax does. Pids, refs, funs pass through unchanged. SX strings on the way back become Erlang binaries (most useful FFI return shape). - [x] `crypto:hash/2` — **WIRED 2026-05-18** against `crypto-sha256`/`crypto-sha512`/`crypto-sha3-256` (loops/fed-prims). `crypto:hash(Type, Data)`: `Type` ∈ `sha256|sha512|sha3_256` atom; `Data` an Erlang binary/string/charlist (→ SX byte-string via `er-source-to-string`). Returns the **raw digest as an Erlang binary** (host hex → bytes via `er-hex->bytes`). Bad type / non-binary → `error:badarg`. 6 ffi tests (digest sizes 32/64, sha3 is_binary, deterministic, distinct, badarg). -- [ ] `cid:from_bytes/1`, `cid:to_string/1` — **BLOCKED** (needs `crypto:hash/2`). See Blockers. +- [x] `cid:from_bytes/1`, `cid:to_string/1` — **WIRED 2026-05-18**. `cid:from_bytes(Bin)` → CIDv1 raw-codec (0x55), sha2-256 multihash built in SX (`[0x12,0x20]++digest`) fed to `cid-from-bytes`; returned as an Erlang binary string. `cid:to_string(Term)` → canonical CIDv1 of the term's stable `er-format-value` string via `cid-from-sx` (cbor-encode rejects marshalled symbols, so `er-to-sx` is unencodable for compound terms — string form is total + deterministic). 7 ffi tests (is_binary, deterministic, distinct-inputs, non-binary badarg, to_string is_binary/deterministic/distinct). - [x] `file:read_file/1`, `file:write_file/2`, `file:delete/1` — **+10 eval tests** (633/633 total). Returns `{ok, Binary}` / `ok` / `{error, Reason}` where Reason is `enoent`/`eacces`/`enotdir`/`eisdir`/`posix_error` (classified from the SX `file-read`/`-write`/`-delete` exception string). Path accepts SX string, Erlang binary, or Erlang char-code list. `file:list_dir/1` deferred — no directory-listing primitive in this SX runtime; see Blockers. - [ ] `httpc:request/4` — **BLOCKED** (no HTTP client primitive). See Blockers. - [ ] `sqlite:open/1`, `sqlite:close/1`, `sqlite:exec/2`, `sqlite:query/2` — **BLOCKED** (no SQLite primitive). See Blockers.