ocaml: phase 6 Printf.sprintf %d/%s/%f/%c/%b/%% + global string_of_* (+5 tests, 492 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Replaces the stub sprintf in runtime.sx with a real implementation: walk fmt char-by-char accumulating a prefix; on recognised %X return a one-arg fn that formats the arg and recurses on the rest of fmt. The function self-curries to the spec count — there's no separate arity machinery, just a closure chain. Specs: %d (int), %s (string), %f (float), %c (char/string in our model), %b (bool), %% (literal). Unknown specs pass through. Same expression returns a string (no specs) or a function (>=1 spec) — OCaml proper would reject this; works fine in OCaml-on-SX's dynamic runtime. Also adds top-level aliases: string_of_int = _string_of_int string_of_float = _string_of_float string_of_bool = if b then "true" else "false" int_of_string = _int_of_string Printf.sprintf "x=%d" 42 = "x=42" Printf.sprintf "%s = %d" "answer" 42 = "answer = 42" Printf.sprintf "%d%%" 50 = "50%"
This commit is contained in:
@@ -424,8 +424,35 @@
|
|||||||
end ;;
|
end ;;
|
||||||
|
|
||||||
module Printf = struct
|
module Printf = struct
|
||||||
let sprintf fmt = fmt
|
(* sprintf walks fmt, accumulating prefix. When it sees a %X
|
||||||
let printf fmt = print_string fmt
|
spec, it returns a function of one arg that substitutes the
|
||||||
|
arg and recurses on the rest of fmt. With no specs, returns
|
||||||
|
the bare format string. Specs supported: %d %s %f %c %b
|
||||||
|
(and %% as a literal). Unknown specs are passed through. *)
|
||||||
|
let sprintf fmt =
|
||||||
|
let n = _string_length fmt in
|
||||||
|
let rec walk pos prefix =
|
||||||
|
if pos >= n then prefix
|
||||||
|
else if pos + 1 < n && _string_get fmt pos = \"%\" then
|
||||||
|
let spec = _string_get fmt (pos + 1) in
|
||||||
|
if spec = \"%\" then walk (pos + 2) (prefix ^ \"%\")
|
||||||
|
else if spec = \"d\" || spec = \"s\" || spec = \"f\"
|
||||||
|
|| spec = \"c\" || spec = \"b\" then
|
||||||
|
(fun arg ->
|
||||||
|
let s =
|
||||||
|
if spec = \"d\" then _string_of_int arg
|
||||||
|
else if spec = \"f\" then _string_of_float arg
|
||||||
|
else if spec = \"b\" then
|
||||||
|
(if arg then \"true\" else \"false\")
|
||||||
|
else arg
|
||||||
|
in
|
||||||
|
walk (pos + 2) (prefix ^ s))
|
||||||
|
else walk (pos + 1) (prefix ^ _string_get fmt pos)
|
||||||
|
else walk (pos + 1) (prefix ^ _string_get fmt pos)
|
||||||
|
in
|
||||||
|
walk 0 \"\"
|
||||||
|
|
||||||
|
let printf fmt = sprintf fmt
|
||||||
end ;;
|
end ;;
|
||||||
|
|
||||||
module Stack = struct
|
module Stack = struct
|
||||||
@@ -643,7 +670,13 @@
|
|||||||
| [] -> []
|
| [] -> []
|
||||||
| h :: t -> if mem h b then h :: inter t b else inter t b
|
| h :: t -> if mem h b then h :: inter t b else inter t b
|
||||||
end
|
end
|
||||||
end")
|
end ;;
|
||||||
|
|
||||||
|
let string_of_int n = _string_of_int n
|
||||||
|
let string_of_float f = _string_of_float f
|
||||||
|
let string_of_bool b = if b then \"true\" else \"false\"
|
||||||
|
let int_of_string s = _int_of_string s
|
||||||
|
")
|
||||||
|
|
||||||
(define ocaml-stdlib-loaded false)
|
(define ocaml-stdlib-loaded false)
|
||||||
(define ocaml-stdlib-env nil)
|
(define ocaml-stdlib-env nil)
|
||||||
|
|||||||
@@ -1210,6 +1210,18 @@ cat > "$TMPFILE" << 'EPOCHS'
|
|||||||
(epoch 4932)
|
(epoch 4932)
|
||||||
(eval "(ocaml-run \"try (assert false; 0) with _ -> 99\")")
|
(eval "(ocaml-run \"try (assert false; 0) with _ -> 99\")")
|
||||||
|
|
||||||
|
;; ── Printf.sprintf + global string_of_* ──────────────────────
|
||||||
|
(epoch 4940)
|
||||||
|
(eval "(ocaml-run \"Printf.sprintf \\\"hello\\\"\")")
|
||||||
|
(epoch 4941)
|
||||||
|
(eval "(ocaml-run \"Printf.sprintf \\\"x=%d\\\" 42\")")
|
||||||
|
(epoch 4942)
|
||||||
|
(eval "(ocaml-run \"Printf.sprintf \\\"%s = %d\\\" \\\"answer\\\" 42\")")
|
||||||
|
(epoch 4943)
|
||||||
|
(eval "(ocaml-run \"Printf.sprintf \\\"%d%%\\\" 50\")")
|
||||||
|
(epoch 4944)
|
||||||
|
(eval "(ocaml-run \"string_of_int 7 ^ \\\"-\\\" ^ string_of_bool true\")")
|
||||||
|
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|
||||||
OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||||
@@ -1919,6 +1931,13 @@ check 4930 "assert true; 42" '42'
|
|||||||
check 4931 "assert (x = 5); x + 1" '6'
|
check 4931 "assert (x = 5); x + 1" '6'
|
||||||
check 4932 "try (assert false; ...) with" '99'
|
check 4932 "try (assert false; ...) with" '99'
|
||||||
|
|
||||||
|
# ── Printf.sprintf ───────────────────────────────────────────────
|
||||||
|
check 4940 "sprintf no args" '"hello"'
|
||||||
|
check 4941 "sprintf one %d" '"x=42"'
|
||||||
|
check 4942 "sprintf %s = %d" '"answer = 42"'
|
||||||
|
check 4943 "sprintf %d%% literal percent" '"50%"'
|
||||||
|
check 4944 "string_of_int + string_of_b" '"7-true"'
|
||||||
|
|
||||||
TOTAL=$((PASS + FAIL))
|
TOTAL=$((PASS + FAIL))
|
||||||
if [ $FAIL -eq 0 ]; then
|
if [ $FAIL -eq 0 ]; then
|
||||||
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"
|
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"
|
||||||
|
|||||||
@@ -407,6 +407,18 @@ _Newest first._
|
|||||||
binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree *
|
binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree *
|
||||||
'a tree`) with insert + in-order traversal. Tests parametric ADT,
|
'a tree`) with insert + in-order traversal. Tests parametric ADT,
|
||||||
recursive match, List.append, List.fold_left.
|
recursive match, List.append, List.fold_left.
|
||||||
|
- 2026-05-09 Phase 6 — Printf.sprintf with %d/%s/%f/%c/%b/%% (+4
|
||||||
|
tests) and global `string_of_int`/`string_of_float`/`string_of_bool`
|
||||||
|
(+1 test). 492 total. sprintf walks fmt char-by-char accumulating
|
||||||
|
a prefix; on a recognised spec it returns a one-arg fn that formats
|
||||||
|
the arg and recurses on the rest of fmt — naturally curries to the
|
||||||
|
right arity since the spec count drives the chain. Dynamic typing
|
||||||
|
lets us return either a string (no specs) or a function (≥1 spec)
|
||||||
|
from the same expression, which OCaml proper would reject.
|
||||||
|
Examples:
|
||||||
|
Printf.sprintf "x=%d" 42 = "x=42"
|
||||||
|
Printf.sprintf "%s = %d" "answer" 42 = "answer = 42"
|
||||||
|
Printf.sprintf "%d%%" 50 = "50%"
|
||||||
- 2026-05-09 Phase 4 — `assert EXPR` (+3 tests, 487 total). Tokenizer
|
- 2026-05-09 Phase 4 — `assert EXPR` (+3 tests, 487 total). Tokenizer
|
||||||
already classified `assert` as a keyword; parse-prefix now handles
|
already classified `assert` as a keyword; parse-prefix now handles
|
||||||
it like `not` (advance, recur, wrap). Eval evaluates the operand and
|
it like `not` (advance, recur, wrap). Eval evaluates the operand and
|
||||||
|
|||||||
Reference in New Issue
Block a user