to_counts builds a 256-slot int array of character frequencies:
let to_counts s =
let counts = Array.make 256 0 in
for i = 0 to String.length s - 1 do
let c = Char.code s.[i] in
counts.(c) <- counts.(c) + 1
done;
counts
same_counts compares two arrays element-by-element via for loop +
bool ref. is_anagram composes them.
Four pairs:
listen ~ silent true
hello !~ world false
anagram ~ nagaram true
abc !~ abcd false (length differs)
sum 2
Exercises Array.make + arr.(i) + arr.(i) <- v + nested for loops +
Char.code + s.[i].
57 baseline programs total.
Defines a user exception with int payload:
exception Negative of int
let safe_sqrt n =
if n < 0 then raise (Negative n)
else <integer sqrt via while loop>
let try_sqrt n =
try safe_sqrt n with
| Negative x -> -x
try_sqrt 16 -> 4
try_sqrt 25 -> 5
try_sqrt -7 -> 7 (handler returns -(-7) = 7)
try_sqrt 100 -> 10
sum -> 26
Tests exception declaration with int payload, raise with carry, and
try-with arm pattern-matching the constructor with payload binding.
56 baseline programs total.
Defines a parametric tree:
type 'a tree = Leaf of 'a | Node of 'a tree list
let rec flatten t =
match t with
| Leaf x -> [x]
| Node ts -> List.concat (List.map flatten ts)
Test tree has 3 levels of nesting:
Node [Leaf 1; Node [Leaf 2; Leaf 3];
Node [Node [Leaf 4]; Leaf 5; Leaf 6];
Leaf 7]
flattens to [1;2;3;4;5;6;7] -> sum = 28.
Tests parametric ADT, mutual recursion via map+self, List.concat.
55 baseline programs total.
Two-line baseline:
let rec gcd a b = if b = 0 then a else gcd b (a mod b)
let lcm a b = a * b / gcd a b
gcd 36 48 = 12
lcm 4 6 = 12
lcm 12 18 = 36
sum = 60
Tests mod arithmetic and the integer-division fix from iteration 94
(without truncate-toward-zero, 'lcm 4 6 = 4 * 6 / 2 = 12.0' rather
than the expected 12).
54 baseline programs total.
zip walks both lists in lockstep, truncating at the shorter. unzip
uses tuple-pattern destructuring on the recursive result.
let pairs = zip [1;2;3;4] [10;20;30;40] in
let (xs, ys) = unzip pairs in
List.fold_left (+) 0 xs * List.fold_left (+) 0 ys
= 10 * 100
= 1000
Exercises:
- tuple-cons patterns in match scrutinee: 'match (xs, ys) with'
- tuple constructor in return value: '(a :: la, b :: lb)'
- the iter-98 let-tuple destructuring: 'let (la, lb) = unzip rest'
53 baseline programs total.
Recursive 4-arm match on (a, b) tuples threading a carry:
match (a, b) with
| ([], []) -> if carry = 0 then [] else [carry]
| (x :: xs, []) -> (s mod 10) :: aux xs [] (s / 10) where s = x + carry
| ([], y :: ys) -> ...
| (x :: xs, y :: ys) -> ... where s = x + y + carry
Little-endian digit lists. Three tests:
[9;9;9] + [1] = [0;0;0;1] (=1000, digit sum 1)
[5;6;7] + [8;9;1] = [3;6;9] (=963, digit sum 18)
[9;9;9;9;9;9;9;9] + [1] length 9 (carry propagates 8x)
Sum = 1 + 18 + 9 = 28.
Exercises tuple-pattern match on nested list-cons with the integer
arithmetic and carry-threading idiom typical of multi-precision
implementations.
52 baseline programs total.
Recursive ADT with three constructors (Num/Add/Mul). simp does
bottom-up rewrite using algebraic identities:
x + 0 -> x
0 + x -> x
x * 0 -> 0
0 * x -> 0
x * 1 -> x
1 * x -> x
constant folding for Num + Num and Num * Num
Uses tuple pattern in nested match: 'match (simp a, simp b) with'.
Add (Mul (Num 3, Num 5), Add (Num 0, Mul (Num 1, Num 7)))
-> simp -> Add (Num 15, Num 7)
-> eval -> 22
51 baseline programs total.
Triple-nested for loop with row-major indexing:
for i = 0 to n - 1 do
for j = 0 to n - 1 do
for k = 0 to n - 1 do
c.(i * n + j) <- c.(i * n + j) + a.(i * n + k) * b.(k * n + j)
done
done
done
For 3x3 matrices A=[[1..9]] and B=[[9..1]], the resulting C has sum
621. Tests deeply nested for loops on Array, Array.make + arr.(i) +
arr.(i) <- v + Array.fold_left.
50 baseline programs total — milestone.
Iterative binary search on a sorted int array:
let bsearch arr target =
let n = Array.length arr in
let lo = ref 0 and hi = ref (n - 1) in
let found = ref (-1) in
while !lo <= !hi && !found = -1 do
let mid = (!lo + !hi) / 2 in
if arr.(mid) = target then found := mid
else if arr.(mid) < target then lo := mid + 1
else hi := mid - 1
done;
!found
For [1;3;5;7;9;11;13;15;17;19;21]:
bsearch a 13 = 6
bsearch a 5 = 2
bsearch a 100 = -1
sum = 7
Exercises Array.of_list + arr.(i) + multi-let 'let lo = ... and
hi = ...' + while + multi-arm if/else if/else.
49 baseline programs total.
Two-pointer palindrome check:
let is_palindrome s =
let n = String.length s in
let rec check i j =
if i >= j then true
else if s.[i] <> s.[j] then false
else check (i + 1) (j - 1)
in
check 0 (n - 1)
Tests on six strings:
racecar = true
hello = false
abba = true
'' = true (vacuously, i >= j on entry)
'a' = true
'ab' = false
Sum = 4.
Uses s.[i] <> s.[j] (string-get + structural inequality), recursive
2-arg pointer advancement, and a multi-clause if/else if/else for
the three cases.
48 baseline programs total.
Bottom-up dynamic programming. dp[i] = minimum coins to make
amount i.
let dp = Array.make (target + 1) (target + 1) in (* sentinel *)
dp.(0) <- 0;
for i = 1 to target do
List.iter (fun c ->
if c <= i && dp.(i - c) + 1 < dp.(i) then
dp.(i) <- dp.(i - c) + 1
) coins
done
Sentinel 'target + 1' means impossible — any real solution uses at
most 'target' coins.
coin_change [1; 5; 10; 25] 67 = 6 (= 25+25+10+5+1+1)
Exercises Array.make + arr.(i) + arr.(i) <- v + nested
for/List.iter + guard 'c <= i'.
47 baseline programs total.
Kadane's algorithm in O(n):
let max_subarray xs =
let max_so_far = ref min_int in
let cur = ref 0 in
List.iter (fun x ->
cur := max x (!cur + x);
max_so_far := max !max_so_far !cur
) xs;
!max_so_far
For [-2;1;-3;4;-1;2;1;-5;4] the optimal subarray is [4;-1;2;1] = 6.
Exercises min_int (iter 94), max as global, ref / ! / :=, and
List.iter with two side-effecting steps in one closure body.
46 baseline programs total.
next_row prepends 1, walks adjacent pairs (x, y) emitting x+y,
appends a final 1:
let rec next_row prev =
let rec aux a =
match a with
| [_] -> [1]
| x :: y :: rest -> (x + y) :: aux (y :: rest)
| [] -> []
in
1 :: aux prev
row n iterates next_row n times starting from [1] using a ref +
'for _ = 1 to n do r := next_row !r done'.
row 10 = [1;10;45;120;210;252;210;120;45;10;1]
List.nth (row 10) 5 = 252 = C(10, 5)
Exercises three-arm match including [_] singleton wildcard, x :: y
:: rest binding, and the for-loop with wildcard counter. 45 baseline
programs total.
Run-length encoding via tail-recursive 4-arg accumulator:
let rle xs =
let rec aux xs cur n acc =
match xs with
| [] -> List.rev ((cur, n) :: acc)
| h :: t ->
if h = cur then aux t cur (n + 1) acc
else aux t h 1 ((cur, n) :: acc)
in
match xs with
| [] -> []
| h :: t -> aux t h 1 []
rle [1;1;1;2;2;3;3;3;3;1;1] = [(1,3);(2,2);(3,4);(1,2)]
sum of counts = 11 (matches input length)
The sum-of-counts test verifies that the encoding preserves total
length — drops or duplicates would diverge.
44 baseline programs total.
Defines a recursive str_contains that walks the haystack with
String.sub to find a needle substring. Real OCaml's String.contains
only accepts a single char, so this baseline implements its own
substring search to stay portable.
let rec str_contains s sub i =
if i + sl > nl then false
else if String.sub s i sl = sub then true
else str_contains s sub (i + 1)
count_matching splits text on newlines, folds with the predicate.
'the quick brown fox\nfox runs fast\nthe dog\nfoxes are clever'
needle = 'fox'
matches = 3 (lines 1, 2, 4 — 'foxes' contains 'fox')
43 baseline programs total.
The binop precedence table already had land/lor/lxor/lsl/lsr/asr
(iter 0 setup) but eval-op fell through to 'unknown operator' for
all of them. SX doesn't expose host bitwise primitives, so each is
implemented in eval.sx via arithmetic on the host:
land/lor/lxor: mask & shift loop, accumulating 1<<k digits
lsl k: repeated * 2 k times
lsr k: repeated floor (/ 2) k times
asr: aliased to lsr (no sign extension at our bit width)
bits.ml baseline: popcount via 'while m > 0 do if m land 1 = 1 then
... ; m := m lsr 1 done'. Sum of popcount(1023, 5, 1024, 0xff) = 10
+ 2 + 1 + 8 = 21.
5 land 3 = 1
5 lor 3 = 7
5 lxor 3 = 6
1 lsl 8 = 256
256 lsr 4 = 16
41 baseline programs total.
Classic Ackermann function:
let rec ack m n =
if m = 0 then n + 1
else if n = 0 then ack (m - 1) 1
else ack (m - 1) (ack m (n - 1))
ack(3, 4) = 125, expanding to ~6700 evaluator frames — a useful
stress test of the call stack and control transfer. Real OCaml
evaluates this in milliseconds; ours takes ~2 minutes on a
contended host but completes correctly.
40 baseline programs total.
Stack-based RPN evaluator:
let eval_rpn tokens =
let stack = Stack.create () in
List.iter (fun tok ->
if tok is operator then
let b = Stack.pop stack in
let a = Stack.pop stack in
Stack.push (apply tok a b) stack
else
Stack.push (int_of_string tok) stack
) tokens;
Stack.pop stack
For tokens [3 4 + 2 * 5 -]:
3 4 + -> 7
7 2 * -> 14
14 5 - -> 9
Exercises Stack.create / push / pop, mixed branch on string
equality, multi-arm if/else if for operator dispatch, int_of_string
for token parsing.
39 baseline programs total.
Newton's method for square root:
let sqrt_newton x =
let g = ref 1.0 in
for _ = 1 to 20 do
g := (!g +. x /. !g) /. 2.0
done;
!g
20 iterations is more than enough to converge for x=2 — result is
~1.414213562. Multiplied by 1000 and int_of_float'd: 1414.
First baseline exercising:
- for _ = 1 to N do ... done (wildcard loop variable)
- pure float arithmetic with +. /.
- the int_of_float truncate-toward-zero fix from iter 117
38 baseline programs total.
Classic doubly-recursive solution returning the move count:
hanoi n from to via =
if n = 0 then 0
else hanoi (n-1) from via to + 1 + hanoi (n-1) via to from
For n = 10, returns 2^10 - 1 = 1023.
Exercises 4-arg recursion, conditional base case, and tail-position
addition. Uses 'to_' instead of 'to' for the destination param to
avoid collision with the 'to' keyword in for-loops — the OCaml
conventional workaround.
37 baseline programs total.
validate_int returns Left msg on empty / non-digit, Right
(int_of_string s) on a digit-only string. process folds inputs with a
tuple accumulator (errs, sum), branching on the result.
['12'; 'abc'; '5'; ''; '100'; 'x']
-> 3 errors (abc, '', x), valid sum = 12+5+100 = 117
-> errs * 100 + sum = 417
Exercises:
- Either constructors used bare (Left/Right without 'Either.'
qualification)
- char range comparison: c >= '0' && c <= '9'
- tuple-pattern destructuring on let-binding (iter 98)
- recursive helper defined inside if-else
- List.fold_left with tuple accumulator
36 baseline programs total.
First baseline using Map.Make on a string-keyed map:
module StringOrd = struct
type t = string
let compare = String.compare
end
module SMap = Map.Make (StringOrd)
let count_words text =
let words = String.split_on_char ' ' text in
List.fold_left (fun m w ->
let n = match SMap.find_opt w m with
| Some n -> n
| None -> 0
in
SMap.add w (n + 1) m
) SMap.empty words
For 'the quick brown fox jumps over the lazy dog' ('the' appears
twice), SMap.cardinal -> 8.
Complements bag.ml (Hashtbl-based) and unique_set.ml (Set.Make)
with a sorted Map view of the same kind of counting problem. 35
baseline programs total.
Either module (mirrors OCaml 4.12+ stdlib):
left x / right x
is_left / is_right
find_left / find_right (return Option)
map_left / map_right (single-side mappers)
fold lf rf e (case dispatch)
equal eq_l eq_r a b
compare cmp_l cmp_r a b (Left < Right)
Constructors are bare 'Left x' / 'Right x' (OCaml 4.12+ exposes them
directly without an explicit type-decl).
Hashtbl.copy:
build a fresh cell with _hashtbl_create
walk _hashtbl_to_list and re-add each (k, v)
mutating one copy doesn't touch the other
(Hashtbl.length t + Hashtbl.length t2 = 3 after fork-and-add
verifies that adds to t2 don't appear in t)
Defines a JSON-like algebraic data type:
type json =
| JNull
| JBool of bool
| JInt of int
| JStr of string
| JList of json list
Recursively serialises to a string via match-on-constructor, then
measures the length:
JList [JInt 1; JBool true; JNull; JStr 'hi'; JList [JInt 2; JInt 3]]
-> '[1,true,null,"hi",[2,3]]' length 24
Exercises:
- five-constructor ADT (one nullary, three single-arg, one list-arg)
- recursive match
- String.concat ',' (List.map to_string xs)
- string-cat with embedded escaped quotes
34 baseline programs total.
In-place Fisher-Yates shuffle using:
Random.init 42 deterministic seed
let a = Array.of_list xs
for i = n - 1 downto 1 do reverse iteration
let j = Random.int (i + 1)
let tmp = a.(i) in
a.(i) <- a.(j);
a.(j) <- tmp
done
Sum is invariant under permutation, so the test value (55 for
[1..10] = 1+2+...+10) verifies the shuffle is a valid permutation
regardless of which permutation the seed yields.
Exercises Random.init / Random.int + Array.of_list / to_list /
length / arr.(i) / arr.(i) <- v + downto loop + multi-statement
sequencing within for-body.
33 baseline programs total.
pi_leibniz.ml: Leibniz formula for pi.
pi/4 = 1 - 1/3 + 1/5 - 1/7 + ...
pi ~= 4 * sum_{k=0}^{n-1} (-1)^k / (2k+1)
For n=1000, pi ~= 3.140593. Multiply by 100 and int_of_float -> 314.
Side-quest: int_of_float was wrongly defined as identity in
iteration 94. Fixed to:
let int_of_float f =
if f < 0.0 then _float_ceil f else _float_floor f
(truncate toward zero, mirroring real OCaml's int_of_float). The
identity definition was a stub from when integer/float dispatch was
not yet split — now they're separate, the stub is wrong.
Float.to_int still uses floor since OCaml's docs say the result is
unspecified for nan / out-of-range; close enough for our scope.
32 baseline programs total.
Both take an inner predicate / comparator and walk both lists in
lockstep:
equal eq a b short-circuits on first mismatch
compare cmp a b -1 if a is a strict prefix
1 if b is
0 if both empty
otherwise first non-zero element comparison
Mirrors real OCaml's signatures.
List.equal (=) [1;2;3] [1;2;3] = true
List.equal (=) [1;2;3] [1;2;4] = false
List.compare compare [1;2;3] [1;2;4] = -1
List.compare compare [1;2] [1;2;3] = -1
List.compare compare [] [] = 0
Bool module:
equal a b = a = b
compare a b = 0 if equal, 1 if a, -1 if b (false < true)
to_string 'true' / 'false'
of_string s = s = 'true'
not_ wraps host not
to_int true=1, false=0
Option additions (take eq/cmp parameter for the inner value):
equal eq a b None=None, otherwise eq the inner values
compare cmp a b None < Some _; otherwise cmp inner
Option.equal (=) (Some 1) (Some 1) = true
Option.equal (=) (Some 1) None = false
Option.compare compare (Some 5) (Some 3) = 1
bag.ml: split a sentence on spaces, count each word in a Hashtbl,
return the maximum count via Hashtbl.fold.
count_words 'the quick brown fox jumps over the lazy dog the fox'
-> Hashtbl with 'the' = 3 as the max
-> 3
Exercises String.split_on_char + Hashtbl.find_opt/replace +
Hashtbl.fold (k v acc -> ...). Together with frequency.ml from
iter 84 we now have two Hashtbl-counting baselines exercising
slightly different idioms. 29 baseline programs total.
String additions:
equal a b = a = b
compare a b = -1 / 0 / 1 via host < / >
cat a b = a ^ b
empty = '' (constant)
Defines:
type frac = { num : int; den : int }
let rec gcd a b = if b = 0 then a else gcd b (a mod b)
let make n d = (* canonicalise: gcd-reduce and
force den > 0 *)
let add x y = make (x.num * y.den + y.num * x.den) (x.den * y.den)
let mul x y = make (x.num * y.num) (x.den * y.den)
Test:
let r = add (make 1 2) (make 1 3) in (* 5/6 *)
let s = mul (make 2 3) (make 3 4) in (* 1/2 *)
let t = add r s in (* 5/6 + 1/2 = 4/3 *)
t.num + t.den (* = 7 *)
Exercises records, recursive gcd, mod, abs, integer division (the
truncate-toward-zero semantics from iter 94 are essential here —
make would diverge from real OCaml's behaviour with float division).
28 baseline programs total.
Real OCaml's Seq.t is 'unit -> Cons of elt * Seq.t | Nil' — a lazy
thunk that lets you build infinite sequences. Ours is just a list,
which gives the right shape for everything in baseline programs that
don't rely on laziness (taking from infinite sequences would force
memory).
API: empty, cons, return, is_empty, iter, iteri, map, filter,
filter_map, fold_left, length, take, drop, append, to_list,
of_list, init, unfold.
unfold takes a step fn 'acc -> Option (elt * acc)' and threads
through until it returns None:
Seq.fold_left (+) 0
(Seq.unfold (fun n -> if n > 4 then None
else Some (n, n + 1))
1)
= 1 + 2 + 3 + 4 = 10
First baseline that exercises the functor pipeline end to end:
module IntOrd = struct
type t = int
let compare a b = a - b
end
module IntSet = Set.Make (IntOrd)
let unique_count xs =
let s = List.fold_left (fun s x -> IntSet.add x s) IntSet.empty xs in
IntSet.cardinal s
Counts unique elements in [3;1;4;1;5;9;2;6;5;3;5;8;9;7;9]:
{1,2,3,4,5,6,7,8,9} -> 9
The input has 15 elements with 9 unique values. The 'type t = int'
declaration in IntOrd is required by real OCaml; OCaml-on-SX is
dynamic and would accept it without, but we include it for source
fidelity. 27 baseline programs total.
Functors were already wired through ocaml-make-functor in eval.sx
(curried host closure consuming module dicts) but had no explicit
tests for the user-defined Ord application path. This commit adds
three smoke tests that confirm:
module IntOrd = struct let compare a b = a - b end
module S = Set.Make (IntOrd)
S.elements (fold-add [5;1;3;1;5]) sums to 9 (dedupe + sort)
S.mem 2 (S.add 1 (S.add 2 (S.add 3 S.empty))) = true
M.cardinal (M.add 1 'a' (M.add 2 'b' M.empty)) = 2
The Ord parameter is properly threaded through the functor body —
elements are sorted in compare order and dedupe works.
Three parser changes:
1. at-app-start? returns true on op '~' or '?' so the app loop
keeps consuming labeled args.
2. The app arg parser handles:
~name:VAL drop label, parse VAL as the arg
?name:VAL same
~name punning -- treat as (:var name)
?name same
3. try-consume-param! drops '~' or '?' and treats the following
ident as a regular positional param name.
Caveats:
- Order in the call must match definition order; we don't reorder
by label name.
- Optional args don't auto-wrap in Some, so the function body sees
the raw value for ?x:V.
Lets us write idiomatic-looking OCaml even though the runtime is
positional underneath:
let f ~x ~y = x + y in f ~x:3 ~y:7 = 10
let x = 4 in let y = 5 in f ~x ~y = 20 (punning)
let f ?x ~y = x + y in f ?x:1 ~y:2 = 3
User-implemented mergesort that exercises features added across the
last few iterations:
let rec split lst = match lst with
| x :: y :: rest ->
let (a, b) = split rest in (* iter 98 let-tuple destruct *)
(x :: a, y :: b)
| ...
let rec merge xs ys = match xs with
| x :: xs' ->
match ys with (* nested match-in-match *)
| y :: ys' -> ...
...
List.fold_left (+) 0 (sort [...]) (* iter 89 (op) section *)
Sum of [3;1;4;1;5;9;2;6;5;3;5] = 44 regardless of order, so the
result is also a smoke test of the implementation correctness — if
merge_sort drops or duplicates an element the sum diverges. 26
baseline programs total.
parse-decl-let lives in the outer ocaml-parse-program scope and does
not have access to parse-pattern (which is local to ocaml-parse).
Source-slicing approach instead:
1. detect '(IDENT, ...)' in collect-params
2. scan tokens to the matching ')' (tracking nested parens)
3. slice the pattern source string from src
4. push (synth_name, pat_src) onto tuple-srcs
Then after collecting params, the rhs source string gets wrapped with
'match SN with PAT_SRC -> (RHS_SRC)' for each tuple-param,
innermost-first, and the final string is fed through ocaml-parse.
End result is the same AST shape as the iteration-102 inner-let
case: a function whose body destructures a synthetic name.
let f (a, b) = a + b ;; f (3, 7) = 10
let g x (a, b) = x + a + b ;; g 1 (2, 3) = 6
let h (a, b) (c, d) = a * b + c * d
;; h (1, 2) (3, 4) = 14
Mirrors iteration 101's parse-fun change inside parse-let's
parse-one!:
- same '(IDENT, ...)' detection on collect-params
- same __pat_N synth name for the function param
- same innermost-first match-wrapping
Difference: for inner-let the wrapping is applied to the rhs of the
let-binding (which is the function value), not directly to a fun
body.
let f (a, b) = a + b in f (3, 7) = 10
let g x (a, b) = x + a + b in g 1 (2, 3) = 6
let h (a, b) (c, d) = a * b + c * d
in h (1, 2) (3, 4) = 14
parse-fun's collect-params now detects '(IDENT, ...)' as a
tuple-pattern parameter (lookahead at peek-tok-at 1/2 distinguishes
from '(x : T)' and '()' cases that try-consume-param! already
handles). For each tuple param it:
1. parse-pattern to get the full pattern AST
2. generate a synthetic __pat_N name as the actual fun parameter
3. push (synth_name, pattern) onto tuple-binds
After parsing the body, wraps it innermost-first with one
'match __pat_N with PAT -> ...' per tuple-param. The user-visible
result is a (:fun (params...) body) where params are all simple
names but the body destructures.
Also retroactively simplifies Hashtbl.keys/values from
'fun pair -> match pair with (k, _) -> k' to plain
'fun (k, _) -> k', closing the iteration-99 workaround.
(fun (a, b) -> a + b) (3, 7) = 10
List.map (fun (a, b) -> a * b)
[(1, 2); (3, 4); (5, 6)] = [2; 12; 30]
List.map (fun (k, _) -> k)
[("a", 1); ("b", 2)] = ["a"; "b"]
(fun a (b, c) d -> a + b + c + d) 1 (2, 3) 4 = 10
Linear-congruential PRNG with mutable seed (_state ref). API:
init s seed the PRNG
self_init () default seed (1)
int bound 0 <= n < bound
bool () fair coin
float bound uniform in [0, bound)
bits () 30 bits
Stepping rule:
state := (state * 1103515245 + 12345) mod 2147483647
result := |state| mod bound
Same seed reproduces the same sequence. Real OCaml's Random uses
Lagged Fibonacci; ours is simpler but adequate for shuffles and
Monte Carlo demos in baseline programs.
Random.init 42; Random.int 100 = 48
Random.init 1; Random.int 10 = 0
Two new host primitives:
_hashtbl_remove t k -> dissoc the key from the underlying dict
_hashtbl_clear t -> reset the cell to {}
Eight new OCaml-syntax helpers in runtime.sx Hashtbl module:
bindings t = _hashtbl_to_list t
keys t = List.map (fun (k, _) -> k) (...)
values t = List.map (fun (_, v) -> v) (...)
to_seq t = bindings t
to_seq_keys / to_seq_values
remove / clear / reset
The keys/values implementations use a 'fun pair -> match pair with
(k, _) -> k' indirection because parse-fun does not currently allow
tuple patterns directly on parameters. Same restriction we worked
around in iteration 98's let-pattern desugaring.
Also: a detour attempting to add top-level 'let (a, b) = expr'
support was started but reverted — parse-decl-let in the outer
ocaml-parse-program scope does not have access to parse-pattern
(which is local to ocaml-parse). Will need a slice + re-parse trick
later.
When 'let' is followed by '(', parse-let now reads a full pattern
(via the existing parse-pattern used by match), expects '=', then
'in', and desugars to:
let PATTERN = EXPR in BODY => match EXPR with PATTERN -> BODY
This reuses the entire pattern-matching machinery, so any pattern
the match parser accepts works here too — paren-tuples, nested
tuples, cons patterns, list patterns. No 'rec' allowed for pattern
bindings (real OCaml's restriction).
let (a, b) = (1, 2) in a + b = 3
let (a, b, c) = (10, 20, 30) in a + b + c = 60
let pair = (5, 7) in
let (x, y) = pair in x * y = 35
Also retroactively cleaned up Printf's iter-97 width-pos packing
hack ('width * 1000000 + spec_pos') — it's now
'let (width, spec_pos) = parse_width_loop after_flags in ...' like
real OCaml.
The Printf walker now parses optional flags + width digits between
'%' and the spec letter:
- left-align (default is right-align)
0 zero-pad (default is space-pad; only honoured when not left-aligned)
Nd... decimal width digits (any number)
After formatting the argument into a base string with the existing
spec dispatch (%d/%i/%u/%s/%f/%c/%b/%x/%X/%o), the result is padded
to the requested width.
Workaround: width and spec_pos are returned packed as
width * 1000000 + spec_pos
because the parser does not yet support tuple destructuring in let
('let (a, b) = expr in body' fails with 'expected ident'). TODO: lift
that limitation; for now the encoding round-trips losslessly for any
practical width.
Printf.sprintf '%5d' 42 = ' 42'
Printf.sprintf '%-5d|' 42 = '42 |'
Printf.sprintf '%05d' 42 = '00042'
Printf.sprintf '%4s' 'hi' = ' hi'
Printf.sprintf 'hi=%-3d, hex=%04x' 9 15 = 'hi=9 , hex=000f'
The previous List.sort was O(n^2) insertion sort. Replaced with a
straightforward mergesort:
split lst -> alternating-take into ([odd], [even])
merge xs ys -> classic two-finger merge under cmp
sort cmp xs -> base cases [], [x]; otherwise split + recursive
sort on each half + merge
Tuple destructuring on the split result is expressed via nested
match — let-tuple-destructuring would be cleaner but works today.
This benefits sort_uniq (which calls sort first), Set.Make.add via
sort etc., and any user program using List.sort. Stable_sort is
already aliased to sort.