erlang: file:read_file/write_file/delete BIFs (+10 eval tests, 633/633)

This commit is contained in:
2026-05-14 20:14:31 +00:00
parent 3d092dd78e
commit 29fd70f17a
5 changed files with 134 additions and 11 deletions

View File

@@ -1304,6 +1304,74 @@
:else (error "Erlang: ets:info: arity"))))
;; ── file module (Phase 8 FFI) ────────────────────────────────────
;; Synchronous file IO. Filenames must be SX strings (or Erlang
;; binaries/char-code lists coercible to strings via er-source-to-string).
;; Returns `{ok, Binary}` / `ok` on success, `{error, Reason}` on failure
;; where Reason is one of `enoent`, `eacces`, `enotdir`, `posix_error`.
(define er-classify-file-error
(fn (msg)
(let ((s (str msg)))
(cond
(string-contains? s "No such") (er-mk-atom "enoent")
(string-contains? s "Permission denied") (er-mk-atom "eacces")
(string-contains? s "Not a directory") (er-mk-atom "enotdir")
(string-contains? s "Is a directory") (er-mk-atom "eisdir")
:else (er-mk-atom "posix_error")))))
(define er-bif-file-read-file
(fn (vs)
(let ((path (er-source-to-string (nth vs 0))))
(cond
(= path nil)
(er-mk-tuple (list (er-mk-atom "error") (er-mk-atom "badarg")))
:else
(let ((res (list nil)) (err (list nil)))
(guard (c (:else (set-nth! err 0 c)))
(set-nth! res 0 (file-read path)))
(cond
(not (= (nth err 0) nil))
(er-mk-tuple (list (er-mk-atom "error")
(er-classify-file-error (nth err 0))))
:else
(er-mk-tuple (list (er-mk-atom "ok")
(er-mk-binary (map char->integer (string->list (nth res 0))))))))))))
(define er-bif-file-write-file
(fn (vs)
(let ((path (er-source-to-string (nth vs 0)))
(data (er-source-to-string (nth vs 1))))
(cond
(or (= path nil) (= data nil))
(er-mk-tuple (list (er-mk-atom "error") (er-mk-atom "badarg")))
:else
(let ((err (list nil)))
(guard (c (:else (set-nth! err 0 c)))
(file-write path data))
(cond
(not (= (nth err 0) nil))
(er-mk-tuple (list (er-mk-atom "error")
(er-classify-file-error (nth err 0))))
:else (er-mk-atom "ok")))))))
(define er-bif-file-delete
(fn (vs)
(let ((path (er-source-to-string (nth vs 0))))
(cond
(= path nil)
(er-mk-tuple (list (er-mk-atom "error") (er-mk-atom "badarg")))
:else
(let ((err (list nil)))
(guard (c (:else (set-nth! err 0 c)))
(file-delete path))
(cond
(not (= (nth err 0) nil))
(er-mk-tuple (list (er-mk-atom "error")
(er-classify-file-error (nth err 0))))
:else (er-mk-atom "ok")))))))
;; ── builtin BIF registrations (Phase 8 migration) ────────────────
;; Populates `er-bif-registry` with every existing built-in BIF. Each
;; entry is keyed by "Module/Name/Arity"; multi-arity BIFs register
@@ -1394,6 +1462,10 @@
(er-register-bif! "code" "which" 1 er-bif-code-which)
(er-register-bif! "code" "is_loaded" 1 er-bif-code-is-loaded)
(er-register-bif! "code" "all_loaded" 0 er-bif-code-all-loaded)
;; file module
(er-register-bif! "file" "read_file" 1 er-bif-file-read-file)
(er-register-bif! "file" "write_file" 2 er-bif-file-write-file)
(er-register-bif! "file" "delete" 1 er-bif-file-delete)
(er-mk-atom "ok")))
;; Register everything at load time.

View File

@@ -1,11 +1,11 @@
{
"language": "erlang",
"total_pass": 623,
"total": 623,
"total_pass": 633,
"total": 633,
"suites": [
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
{"name":"parse","pass":52,"total":52,"status":"ok"},
{"name":"eval","pass":385,"total":385,"status":"ok"},
{"name":"eval","pass":395,"total":395,"status":"ok"},
{"name":"runtime","pass":93,"total":93,"status":"ok"},
{"name":"ring","pass":4,"total":4,"status":"ok"},
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},

View File

@@ -1,12 +1,12 @@
# Erlang-on-SX Scoreboard
**Total: 623 / 623 tests passing**
**Total: 633 / 633 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | tokenize | 62 | 62 |
| ✅ | parse | 52 | 52 |
| ✅ | eval | 385 | 385 |
| ✅ | eval | 395 | 395 |
| ✅ | runtime | 93 | 93 |
| ✅ | ring | 4 | 4 |
| ✅ | ping-pong | 4 | 4 |

View File

@@ -1340,6 +1340,55 @@
(er-eval-test "capstone soft_purge clean after hard = true"
(get (nth (get er-rt-cap-result :elements) 4) :name) "true")
;; ── Phase 8: file module BIFs ───────────────────────────────────
(er-modules-reset!)
;; write + read round-trip
(er-eval-test "file:write_file ok"
(nm (ev "file:write_file(\"/tmp/er-test-1.txt\", \"hello\")"))
"ok")
(er-eval-test "file:read_file ok tag"
(nm (ev "element(1, file:read_file(\"/tmp/er-test-1.txt\"))"))
"ok")
(er-eval-test "file:read_file payload is binary"
(ev "case file:read_file(\"/tmp/er-test-1.txt\") of {ok, B} -> is_binary(B) end")
(er-mk-atom "true"))
(er-eval-test "file:read_file content bytes"
(ev "case file:read_file(\"/tmp/er-test-1.txt\") of {ok, B} -> byte_size(B) end")
5)
;; missing file → {error, enoent}
(er-eval-test "file:read_file missing tag"
(nm (ev "element(1, file:read_file(\"/tmp/er-no-such-file-xyz\"))"))
"error")
(er-eval-test "file:read_file missing reason"
(nm (ev "element(2, file:read_file(\"/tmp/er-no-such-file-xyz\"))"))
"enoent")
;; delete
(er-eval-test "file:delete ok"
(nm (ev "file:write_file(\"/tmp/er-test-del.txt\", \"x\"), file:delete(\"/tmp/er-test-del.txt\")"))
"ok")
(er-eval-test "file:read_file after delete"
(nm (ev "file:write_file(\"/tmp/er-test-del2.txt\", \"x\"), file:delete(\"/tmp/er-test-del2.txt\"), element(2, file:read_file(\"/tmp/er-test-del2.txt\"))"))
"enoent")
;; write to inaccessible dir → {error, enoent}
(er-eval-test "file:write_file bad path"
(nm (ev "element(2, file:write_file(\"/tmp/no-such-dir-xyz/x\", \"y\"))"))
"enoent")
;; binary input round-trip (the bytes go through write)
(er-eval-test "file:write_file binary payload round-trip"
(ev "file:write_file(\"/tmp/er-test-2.bin\", <<1, 2, 3, 4, 5>>), case file:read_file(\"/tmp/er-test-2.bin\") of {ok, B} -> byte_size(B) end")
5)
(define
er-eval-test-summary
(str "eval " er-eval-test-pass "/" er-eval-test-count))