From 5d33f8f20b513038081a4b3fa4b02057fa8f460c Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 05:24:37 +0000 Subject: [PATCH] ocaml: phase 6 Filename module + Char.compare/equal/escaped (+7 tests, 569 total) Filename module (forward-slash only, no Windows-separator detection): basename '/foo/bar/baz.ml' = 'baz.ml' dirname '/foo/bar/baz.ml' = '/foo/bar' extension 'baz.tar.gz' = '.gz' chop_extension 'hello.ml' = 'hello' concat 'a' 'b' = 'a/b' is_relative 'a/b' = true current_dir_name = '.', parent_dir_name = '..', dir_sep = '/' Char additions: equal a b = (a = b) compare a b = code(a) - code(b) escaped '\n' = '\\n' (likewise t, r, \\, ") --- lib/ocaml/runtime.sx | 63 ++++++++++++++++++++++++++++++++++++++++++++ lib/ocaml/test.sh | 29 ++++++++++++++++++++ plans/ocaml-on-sx.md | 6 +++++ 3 files changed, 98 insertions(+) diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index 4fab4505..62eee777 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -474,6 +474,69 @@ let is_alnum c = is_alpha c || is_digit c let is_whitespace c = c = \" \" || c = \"\\t\" || c = \"\\n\" || c = \"\\r\" + let equal a b = a = b + let compare a b = _char_code a - _char_code b + let escaped c = + if c = \"\\n\" then \"\\\\n\" + else if c = \"\\t\" then \"\\\\t\" + else if c = \"\\r\" then \"\\\\r\" + else if c = \"\\\\\" then \"\\\\\\\\\" + else if c = \"\\\"\" then \"\\\\\\\"\" + else c + end ;; + + module Filename = struct + (* Minimal Filename: basename / dirname / extension / concat / + chop_suffix. Forward-slash only — doesn't try to detect + Windows-style separators. *) + let _last_slash s = + let n = _string_length s in + let rec aux i = + if i < 0 then -1 + else if _string_get s i = \"/\" then i + else aux (i - 1) + in + aux (n - 1) + + let basename s = + let i = _last_slash s in + if i < 0 then s + else _string_sub s (i + 1) (_string_length s - i - 1) + + let dirname s = + let i = _last_slash s in + if i < 0 then \".\" + else if i = 0 then \"/\" + else _string_sub s 0 i + + let extension s = + let b = basename s in + let n = _string_length b in + let rec aux i = + if i < 0 then \"\" + else if _string_get b i = \".\" then + _string_sub b i (n - i) + else aux (i - 1) + in + aux (n - 1) + + let chop_extension s = + let ext = extension s in + let nx = _string_length ext in + if nx = 0 then s + else _string_sub s 0 (_string_length s - nx) + + let concat a b = + if _string_length a = 0 then b + else if _string_get a (_string_length a - 1) = \"/\" then a ^ b + else a ^ \"/\" ^ b + + let is_relative s = + _string_length s = 0 || _string_get s 0 <> \"/\" + + let current_dir_name = \".\" + let parent_dir_name = \"..\" + let dir_sep = \"/\" end ;; module Int = struct diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 8723d255..2baaaa56 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1404,6 +1404,24 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 5152) (eval "(ocaml-run \"let f ?x ~y = x + y in f ?x:1 ~y:2\")") +;; ── Filename module ────────────────────────────────────────── +(epoch 5160) +(eval "(ocaml-run \"Filename.basename \\\"/foo/bar/baz.ml\\\"\")") +(epoch 5161) +(eval "(ocaml-run \"Filename.dirname \\\"/foo/bar/baz.ml\\\"\")") +(epoch 5162) +(eval "(ocaml-run \"Filename.extension \\\"baz.tar.gz\\\"\")") +(epoch 5163) +(eval "(ocaml-run \"Filename.concat \\\"a\\\" \\\"b\\\"\")") +(epoch 5164) +(eval "(ocaml-run \"Filename.chop_extension \\\"hello.ml\\\"\")") + +;; ── Char.compare / equal / escaped ───────────────────────── +(epoch 5170) +(eval "(ocaml-run \"Char.compare \\\"b\\\" \\\"a\\\"\")") +(epoch 5171) +(eval "(ocaml-run \"Char.equal \\\"a\\\" \\\"a\\\"\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -2232,6 +2250,17 @@ check 5150 "f ~x:3 ~y:7 sum" '10' check 5151 "f ~x ~y punning" '20' check 5152 "f ?x:1 ~y:2 (no Some wrap)" '3' +# ── Filename module ───────────────────────────────────────────── +check 5160 "basename /foo/bar/baz.ml" '"baz.ml"' +check 5161 "dirname /foo/bar/baz.ml" '"/foo/bar"' +check 5162 "extension baz.tar.gz" '".gz"' +check 5163 "concat a b" '"a/b"' +check 5164 "chop_extension hello.ml" '"hello"' + +# ── Char.compare / equal ──────────────────────────────────────── +check 5170 "Char.compare b a" '1' +check 5171 "Char.equal a a" 'true' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL OCaml-on-SX tests passed" diff --git a/plans/ocaml-on-sx.md b/plans/ocaml-on-sx.md index 7a4264be..dd2dd8de 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,12 @@ _Newest first._ binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree`) with insert + in-order traversal. Tests parametric ADT, recursive match, List.append, List.fold_left. +- 2026-05-09 Phase 6 — Filename module + Char.compare/equal/escaped + (+7 tests, 569 total). Filename: basename, dirname, extension, + chop_extension, concat, is_relative + dir_sep / current_dir_name / + parent_dir_name constants. Forward-slash only, doesn't try to + detect Windows separators. Char additions: equal, compare (via + code subtraction), escaped (handles `\n`/`\t`/`\r`/`\\`/`\"`). - 2026-05-09 Phase 4 — basic labeled / optional argument syntax (label dropped, positional semantics) (+3 tests, 562 total). Three parser changes: