261 Commits

Author SHA1 Message Date
bed374c9e1 ocaml: phase 5.1 tarjan_scc.ml baseline (8-node digraph, 4 SCCs)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Tarjan's strongly-connected components in a single DFS using
index/lowlink:

  graph (8 nodes, directed):
    0 -> 1 -> 2 -> 0   (3-cycle)
    2 -> 3
    3 -> 4
    4 -> 5 -> 6 -> 4   (3-cycle)
    4 -> 7

  SCCs: {0,1,2}, {3}, {4,5,6}, {7}  =  4 components

Module-level ref + array state (index_arr, lowlink, on_stack,
stack, scc_count). When lowlink(v) = index(v), pop from stack
until v is removed; that's a complete SCC.

Tests: recursive function with module-level mutable state,
nested begin/end branches inside List.iter closure, inner
`let rec pop ()` traversing a ref-of-list, pattern match on
[] / h :: rest cons-list shape.

154 baseline programs total.
2026-05-10 07:06:29 +00:00
b4571f0f9f ocaml: phase 5.1 lev_iter.ml baseline (sum of 5 edit distances = 16)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Iterative Levenshtein DP with rolling 1D arrays for O(min(m,n))
space. Distances:

  kitten    -> sitting    : 3
  saturday  -> sunday     : 3
  abc       -> abc        : 0
  ""        -> abcde      : 5
  intention -> execution  : 5
  ----------------------------
  total                   : 16

Complementary to the existing levenshtein.ml which uses the
exponential recursive form (only sums tiny strings); this one is
the practical iterative variant used for real ED.

Tests the recently-fixed <- with bare `if` rhs:

  curr.(j) <- (if m1 < c then m1 else c) + 1

153 baseline programs total.
2026-05-10 06:53:38 +00:00
0ef26b20f3 ocaml: phase 5.1 binary_heap.ml baseline (min-heap sort 9 vals -> 123456789)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Array-backed binary min-heap with explicit size tracking via ref:

  let push a size x =
    a.(!size) <- x; size := !size + 1; sift_up a (!size - 1)

  let pop a size =
    let m = a.(0) in
    size := !size - 1;
    a.(0) <- a.(!size);
    sift_down a !size 0;
    m

Push [9;4;7;1;8;3;5;2;6], pop nine times -> 1,2,3,4,5,6,7,8,9.
Fold-as-decimal: ((((((((1*10+2)*10+3)*10+4)*10+5)*10+6)*10+7)*10+8)*10+9 = 123456789.

Tests recursive sift_up + sift_down, in-place array swap,
parent/lchild/rchild index arithmetic, combined push/pop session
with refs.

152 baseline programs total.
2026-05-10 06:43:46 +00:00
19d0ef0f38 ocaml: phase 5.1 rolling_hash.ml baseline (Rabin-Karp, 6 "abc" matches)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Polynomial rolling hash mod 1000003 with base 257:
  - precompute base^(m-1)
  - slide window updating hash in O(1) per step
  - verify hash match with O(m) memcmp to skip false positives

  rolling_match "abcabcabcabcabcabc" "abc" = 6

Six non-overlapping copies of "abc" at positions 0,3,6,9,12,15.

Tests `for _ = 0 to m - 2 do … done` unused loop variable
(uses underscore wildcard pattern), Char.code arithmetic, mod
arithmetic with intermediate negative subtractions, complex nested
if/begin branching with inner break-via-flag.

151 baseline programs total.
2026-05-10 06:34:13 +00:00
1dd350d592 ocaml: phase 5.1 huffman.ml baseline (Huffman tree WPL = 224)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Classic CLRS Huffman code example. ADT:

  type tree = Leaf of int * char | Node of int * tree * tree

Build by repeatedly merging two lightest trees (sorted-list pq):

  let rec build_tree lst = match lst with
    | [t] -> t
    | a :: b :: rest ->
      let merged = Node (weight a + weight b, a, b) in
      build_tree (insert merged rest)

  weighted path length (= total Huffman bits):
    leaves {(5,a) (9,b) (12,c) (13,d) (16,e) (45,f)} -> 224

Tests sum-typed ADT with mixed arities, `function` keyword
pattern matching, recursive sorted insert, depth-counting recursion.

150 baseline programs total.
2026-05-10 06:21:06 +00:00
4fdf6980da ocaml: parser accepts if/match/let/fun as rhs of <- and :=
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Previously `a.(i) <- if c then x else y` failed with
"unexpected token keyword if" because parse-binop-rhs called
parse-prefix for the rhs, which doesn't accept if/match/let/fun.

Real OCaml allows full expressions on the rhs of <-/:=. Fix:
special-case prec-1 ops in parse-binop-rhs to call parse-expr-no-seq
instead of parse-prefix. The recursive parse-binop-rhs with
min-prec restored after picks up any further chained <- (since both
ops are right-associative with no higher-prec binops above them).

Manacher baseline updated to use bare `if` on rhs of <-,
removing the parens workaround from iter 235. 607/607 regressions
remain clean.
2026-05-10 06:11:57 +00:00
cccef832d9 ocaml: phase 5.1 manacher.ml baseline (longest palindrome "babadaba" = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Manacher's algorithm: insert # separators (length 2n+1) to unify
odd/even cases, then maintain palindrome radii p[] alongside a
running (center, right) pair to skip work via mirror reflection.
Linear time.

  manacher "babadaba" = 7   (* witness: "abadaba", positions 1..7 *)

Note: requires parenthesizing the if-expression on the rhs of <-:

  p.(i) <- (if pm < v then pm else v)

Real OCaml parses bare `if` at <-rhs since the rhs is at expr
level; our parser places <-rhs at binop level which doesn't include
`if` / `match` / `let`. Workaround until we relax the binop
RHS grammar.

149 baseline programs total.
2026-05-10 05:58:05 +00:00
526ffbb5f0 ocaml: phase 5.1 floyd_warshall.ml baseline (4-node APSP, dist(0,3)=9)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Floyd-Warshall all-pairs shortest path with triple-nested for-loop:

  for k = 0 to n - 1 do
    for i = 0 to n - 1 do
      for j = 0 to n - 1 do
        if d.(i).(k) + d.(k).(j) < d.(i).(j) then
          d.(i).(j) <- d.(i).(k) + d.(k).(j)
      done
    done
  done

Graph (4 nodes, directed):
  0->1 weight 5, 0->3 weight 10, 1->2 weight 3, 2->3 weight 1

Direct edge 0->3 = 10, but path 0->1->2->3 = 5+3+1 = 9.

Tests 2D array via Array.init with closure, nested .(i).(j) read
+ write, triple-nested for, in-place mutation under aliasing.

148 baseline programs total.
2026-05-10 05:41:02 +00:00
99f321f532 ocaml: phase 5.1 mst_kruskal.ml baseline (5-node MST weight 11)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Kruskal's minimum spanning tree using path-compressing union-find:

  edges (w, u, v):
    (1, 0, 1) (2, 1, 2) (3, 0, 3) (4, 2, 3) (5, 3, 4) (6, 0, 4)

After sorting by weight and greedily unioning:
  pick (1,0,1) -> components: {0,1} {2} {3} {4}
  pick (2,1,2) -> {0,1,2} {3} {4}
  pick (3,0,3) -> {0,1,2,3} {4}
  skip (4,2,3) -- already connected
  pick (5,3,4) -> {0,1,2,3,4}
  skip (6,0,4) -- already connected

  MST weight = 1 + 2 + 3 + 5 = 11

Tests List.sort with 3-tuple destructuring lambda, compare on int,
Array.init with closure, in-place array mutation in find, boolean
union returning true iff merge happened.

147 baseline programs total.
2026-05-10 05:21:14 +00:00
dfd89d998e ocaml: phase 5.1 trie.ml baseline (prefix tree, 6/9 word lookups match)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Mutable-record trie with linked-list children:

  type trie = {
    mutable terminal : bool;
    mutable children : (char * trie) list
  }

Insert {cat, car, card, cart, dog, doge}; lookup 9 words. Hits are
exactly the inserted set: cat, car, card, cart, dog, doge = 6.
Misses: ca (prefix not terminal), dogs (extends 'dog' but no 'dogs'
node), x (no path).

Tests:
  - recursive type definition with self-referential field
  - mutable record fields with .field <- v
  - Option pattern matching (Some / None)
  - tuple-cons pattern (k, v) :: rest

146 baseline programs total.
2026-05-10 05:11:12 +00:00
74d8ade089 ocaml: phase 5.1 count_inversions.ml baseline (12 inversions via merge sort)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Modified merge sort that counts inversions during the merge step:
when an element from the right half is selected, the remaining
elements of the left half (mid - i + 1) all form inversions with
that right element.

  count_inv [|8; 4; 2; 1; 3; 5; 7; 6|] = 12

Inversions of [8;4;2;1;3;5;7;6]:
  with 8: (8,4)(8,2)(8,1)(8,3)(8,5)(8,7)(8,6) = 7
  with 4: (4,2)(4,1)(4,3) = 3
  with 2: (2,1) = 1
  with 7: (7,6) = 1
                                        total = 12

Tests: let rec ... and ... mutual recursion, while + ref + array
mutation, in-place sort with auxiliary scratch array.

145 baseline programs total.
2026-05-10 05:01:08 +00:00
872302ede1 ocaml: phase 5.1 topo_sort.ml baseline (6-node DAG, all 6 ordered)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
Kahn's algorithm BFS topological sort:

  let topo_sort n adj =
    let in_deg = Array.make n 0 in
    for i = 0 to n - 1 do
      List.iter (fun j -> in_deg.(j) <- in_deg.(j) + 1) adj.(i)
    done;
    let q = Queue.create () in
    for i = 0 to n - 1 do
      if in_deg.(i) = 0 then Queue.push i q
    done;
    let count = ref 0 in
    while not (Queue.is_empty q) do
      let u = Queue.pop q in
      count := !count + 1;
      List.iter (fun v ->
        in_deg.(v) <- in_deg.(v) - 1;
        if in_deg.(v) = 0 then Queue.push v q
      ) adj.(u)
    done;
    !count

Graph: 0->{1,2}; 1->{3}; 2->{3,4}; 3->{5}; 4->{5}; 5.
Acyclic, so all 6 nodes can be ordered.

Tests Queue.{create,push,pop,is_empty}, mutable array via closure.

144 baseline programs total.
2026-05-10 04:51:15 +00:00
57a63826e3 ocaml: phase 5.1 knapsack.ml baseline (0/1 knapsack, cap=8 -> 36)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 15s
Standard 1D 0/1 knapsack DP with reverse inner loop:

  let knapsack values weights cap =
    let n = Array.length values in
    let dp = Array.make (cap + 1) 0 in
    for i = 0 to n - 1 do
      let v = values.(i) and w = weights.(i) in
      for c = cap downto w do
        let take = dp.(c - w) + v in
        if take > dp.(c) then dp.(c) <- take
      done
    done;
    dp.(cap)

  values: [|6; 10; 12; 15; 20|]
  weights: [|1; 2;  3;  4;  5|]
  knapsack v w 8 = 36   (* take items with weights 1, 2, 5 *)

Tests for-downto + array literal access in the same hot loop.

143 baseline programs total.
2026-05-10 04:38:59 +00:00
7a67637826 ocaml: phase 5.1 lcs.ml baseline (LCS of "ABCBDAB" and "BDCAB" = 4)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Classic 2D DP for longest common subsequence, optimized to use
two rolling 1D arrays (prev / curr) for O(min(m,n)) space:

  for i = 1 to m do
    for j = 1 to n do
      if s1.[i-1] = s2.[j-1] then curr.(j) <- prev.(j-1) + 1
      else if prev.(j) >= curr.(j-1) then curr.(j) <- prev.(j)
      else curr.(j) <- curr.(j-1)
    done;
    for j = 0 to n do prev.(j) <- curr.(j) done
  done;
  prev.(n)

  lcs "ABCBDAB" "BDCAB" = 4

Two valid LCS witnesses: BCAB and BDAB.

Avoids Array.make_matrix (not in our runtime) by manual rolling.

142 baseline programs total.
2026-05-10 04:29:58 +00:00
42a506faff ocaml: phase 5.1 dijkstra.ml baseline (5-node SSSP, dist(0,4) = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Array-based O(n^2) Dijkstra on a small directed weighted graph:

  edges = [|
    [(1, 4); (2, 1)];   (* 0 -> 1 (w=4), 2 (w=1) *)
    [(3, 1)];           (* 1 -> 3 (w=1)         *)
    [(1, 2); (3, 5)];   (* 2 -> 1 (w=2), 3 (w=5) *)
    [(4, 3)];           (* 3 -> 4 (w=3)         *)
    []                  (* 4 sink              *)
  |]

Optimal path 0->2->1->3->4 has weight 1+2+1+3 = 7.

Tests: array-of-list-of-int-pair literal, List.iter with tuple
destructuring closure, in-place dist mutation, nested for + ref.

141 baseline programs total.
2026-05-10 04:20:47 +00:00
713d506bb8 ocaml: phase 5.1 kmp.ml baseline (5 occurrences of "abab" in haystack)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Knuth-Morris-Pratt linear-time string search:
  - kmp_table builds failure function in O(|pattern|)
  - kmp_search scans text once in O(|text|), counting matches
  - After a hit, k := t.(n-1) so overlapping matches still count

  kmp_search "abababcabababcababcc" "abab" = 5

Hits at positions 0, 2, 7, 9, 14 (overlapping at 0/2 and 7/9).

Tests: nested while-inside-for, char inequality (.<>), pat.[i]
string indexing, Array.make 0, combined string + array indexing.

140 baseline programs total.
2026-05-10 04:08:53 +00:00
bcaa41d1ae ocaml: phase 5.1 union_find.ml baseline (10 nodes, 6 unions, 4 components)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Disjoint-set union with path compression:

  let make_uf n = Array.init n (fun i -> i)
  let rec find p x =
    if p.(x) = x then x
    else begin let r = find p p.(x) in p.(x) <- r; r end
  let union p x y =
    let rx = find p x in let ry = find p y in
    if rx <> ry then p.(rx) <- ry

After unioning (0,1), (2,3), (4,5), (6,7), (0,2), (4,6):
  {0,1,2,3} {4,5,6,7} {8} {9}  --> 4 components.

Tests Array.init with closure, recursive find, in-place .(i)<-r.

139 baseline programs total.
2026-05-10 03:59:56 +00:00
edbb03e205 ocaml: phase 5.1 quickselect.ml baseline (median of 9 elements = 5)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Hoare quickselect with Lomuto partition: recursively narrows the
range to whichever side contains the kth index. Mutates the array
in place via .(i)<-v. The median (k=4) of [7;2;9;1;5;6;3;8;4] is 5.

  let rec quickselect arr lo hi k =
    if lo = hi then arr.(lo)
    else begin
      let pivot = arr.(hi) in
      let i = ref lo in
      for j = lo to hi - 1 do
        if arr.(j) < pivot then begin
          let t = arr.(!i) in
          arr.(!i) <- arr.(j); arr.(j) <- t;
          i := !i + 1
        end
      done;
      ...
    end

Exercises array literal syntax + in-place mutation in the same
program, ensuring [|...|] yields a mutable backing.

138 baseline programs total.
2026-05-10 03:50:59 +00:00
551ed44f7f ocaml: phase 5.1 array literals [|...|] + lis.ml baseline (LIS = 6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
Added parser support for OCaml array literal syntax:

  [| e1; e2; ...; en |]  -->  Array.of_list [e1; e2; ...; en]
  [||]                   -->  Array.of_list []

Desugaring keeps the array representation unchanged (ref-of-list)
since Array.of_list is a no-op constructor for that backing.

Tokenizer emits [, |, |, ] as separate ops; parser detects [ followed
by | and enters array-literal mode, terminating on |].

Baseline lis.ml exercises the syntax:

  let lis arr =
    let n = Array.length arr in
    let dp = Array.make n 1 in
    for i = 1 to n - 1 do
      for j = 0 to i - 1 do
        if arr.(j) < arr.(i) && dp.(j) + 1 > dp.(i) then
          dp.(i) <- dp.(j) + 1
      done
    done;
    let best = ref 0 in
    for i = 0 to n - 1 do
      if dp.(i) > !best then best := dp.(i)
    done;
    !best

  lis [|10; 22; 9; 33; 21; 50; 41; 60; 80|] = 6

137 baseline programs total.
2026-05-10 03:41:19 +00:00
76de0a20f8 ocaml: phase 5.1 josephus.ml baseline (n=50 k=3, survivor at position 11)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Classic Josephus problem solved with the standard recurrence:

  let rec josephus n k =
    if n = 1 then 0
    else (josephus (n - 1) k + k) mod n

  josephus 50 3 + 1 = 11

50 people stand in a circle, every 3rd is eliminated; the last
survivor is at position 11 (1-indexed). Tests recursion + mod.

136 baseline programs total.
2026-05-10 03:22:29 +00:00
353dcb67d6 ocaml: phase 5.1 partition_count.ml baseline (p(15) = 176)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Counts integer partitions via classic DP:

  let partition_count n =
    let dp = Array.make (n + 1) 0 in
    dp.(0) <- 1;
    for k = 1 to n do
      for i = k to n do
        dp.(i) <- dp.(i) + dp.(i - k)
      done
    done;
    dp.(n)

  partition_count 15 = 176

Tests Array.make, .(i)<-/.(i) array access, nested for-loops, refs.

135 baseline programs total.
2026-05-10 03:13:36 +00:00
36e02c906a ocaml: phase 5.1 pythagorean.ml baseline (primitive Pythagorean triples with hyp <= 100 = 16)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Uses Euclid's formula: for coprime m > k of opposite parity, the
triple (m^2 - k^2, 2mk, m^2 + k^2) is a primitive Pythagorean.

  let count_primitive_triples n =
    let c = ref 0 in
    for m = 2 to 50 do
      let kk = ref 1 in
      while !kk < m do
        if (m - !kk) mod 2 = 1 && gcd m !kk = 1 then begin
          let h = m * m + !kk * !kk in
          if h <= n then c := !c + 1
        end;
        kk := !kk + 1
      done
    done;
    !c

  count_primitive_triples 100 = 16

The 16 triples include the classics (3,4,5), (5,12,13), (8,15,17),
(7,24,25), and end with (65,72,97).

134 baseline programs total.
2026-05-10 03:02:17 +00:00
5c1b4349aa ocaml: phase 5.1 harshad.ml baseline (count Niven/Harshad numbers <= 100 = 33)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
A Harshad (or Niven) number is divisible by its digit sum:

  let count_harshad limit =
    let c = ref 0 in
    for n = 1 to limit do
      if n mod (digit_sum n) = 0 then c := !c + 1
    done;
    !c

  count_harshad 100 = 33

All single-digit numbers (1..9) qualify trivially. Plus 10, 12, 18,
20, 21, 24, 27, 30, 36, 40, 42, 45, 48, 50, 54, 60, 63, 70, 72, 80,
81, 84, 90, 100 (24 more) = 33 total under 100.

133 baseline programs total.
2026-05-10 02:46:20 +00:00
e23aa9c273 ocaml: phase 5.1 reverse_int.ml baseline (digit-reverse sum = 54329)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Walks digits via mod 10 / div 10, accumulating the reversed value:

  let reverse_int n =
    let m = ref n in
    let r = ref 0 in
    while !m > 0 do
      r := !r * 10 + !m mod 10;
      m := !m / 10
    done;
    !r

  reverse 12345 + reverse 100 + reverse 7
  = 54321 + 1 + 7
  = 54329

Trailing zeros collapse (reverse 100 = 1, not 001).

132 baseline programs total.
2026-05-10 02:36:37 +00:00
da54c3ea53 ocaml: phase 5.1 bowling.ml baseline (10-pin bowling score, sample game = 167)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Walks the pin-knockdown list applying strike/spare bonuses through
a 10-frame counter:

  strike (10):           score 10 + next 2 throws,  advance i+1
  spare  (a + b = 10):   score 10 + next 1 throw,   advance i+2
  open  (a + b < 10):    score a + b,                advance i+2

Frame ten special-cases are handled implicitly: the input includes
bonus throws naturally and the while-loop terminates after frame 10.

  bowling_score [10; 7; 3; 9; 0; 10; 0; 8; 8; 2; 0; 6;
                 10; 10; 10; 8; 1]
  = 20+19+9+18+8+10+6+30+28+19
  = 167

131 baseline programs total.
2026-05-10 02:26:10 +00:00
63901931c4 ocaml: phase 5.1 tail_factorial.ml baseline (12! via tail-recursion = 479001600)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
Single-helper tail-recursive loop threading an accumulator:

  let factorial n =
    let rec go n acc =
      if n <= 1 then acc
      else go (n - 1) (n * acc)
    in
    go n 1

  factorial 12 = 479_001_600

Companion to factorial.ml (10! = 3628800 via doubly-recursive
style); same answer-shape, different evaluator stress: this version
has constant stack depth.

130 baseline programs total — milestone.
2026-05-10 02:05:09 +00:00
e77a2d3a81 ocaml: phase 5.1 zerosafe.ml baseline (Option-chained safe division, sum = 28)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
safe_div returns None on division by zero; safe_chain stitches two
divisions, propagating None on either failure:

  let safe_div a b =
    if b = 0 then None else Some (a / b)

  let safe_chain a b c =
    match safe_div a b with
    | None -> None
    | Some q -> safe_div q c

Test:
  safe_chain 100 2 5    = Some 10
  safe_chain 100 0 5    = None  -> -1
  safe_chain 50 5 0     = None  -> -1
  safe_chain 1000 10 5  = Some 20
  10 - 1 - 1 + 20 = 28

Tests Option chaining + match-on-result with sentinel default.
Demonstrates the canonical 'fail-early on None' pattern.

129 baseline programs total.
2026-05-10 01:49:23 +00:00
836e01dbb4 ocaml: phase 5.1 number_words.ml baseline (letter count of 1..19 spelled out = 106)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
19-arm match returning the English word for each number 1..19, then
sum String.length:

  let number_to_words n =
    match n with
    | 1 -> 'one' | 2 -> 'two' | ... | 19 -> 'nineteen'
    | _ -> ''

  total_letters 19 = 36 + 70 = 106
                    (1-9)  (10-19)

Real PE17 covers 1..1000 (answer 21124) but needs more elaborate
number-to-words logic (compounds, 'and', 'thousand'). 1..19 keeps
the program small while exercising literal-pattern match dispatch
on many arms.

128 baseline programs total.
2026-05-10 01:39:25 +00:00
fb0e83d3a1 ocaml: phase 5.1 palindrome_sum.ml baseline (sum of 3-digit palindromes = 49500)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
let palindrome_sum lo hi =
    let total = ref 0 in
    for n = lo to hi do
      if is_pal n then total := !total + n
    done;
    !total

  palindrome_sum 100 999 = 49500

There are 90 three-digit palindromes (form aba; 9 choices for a, 10
for b). Average value 550, sum 49500.

Companion to palindrome.ml (predicate-only) and paren_depth.ml.

127 baseline programs total.
2026-05-10 01:29:52 +00:00
0b79d4d4b4 ocaml: phase 5.1 triangle_div.ml baseline (first triangle with >10 divisors = 120)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
PE12 with target = 10:

  let count_divisors n =
    let c = ref 0 in
    let i = ref 1 in
    while !i * !i <= n do
      if n mod !i = 0 then begin
        c := !c + 1;
        if !i * !i <> n then c := !c + 1
      end;
      i := !i + 1
    done;
    !c

  let first_triangle_with_divs target =
    walk triangles T(n) = T(n-1) + n until count_divisors T > target

T(15) = 120 has 16 divisors — first to exceed 10. Real PE12 uses
target 500 (answer 76576500); 10 stays well under budget.

126 baseline programs total.
2026-05-10 01:17:11 +00:00
58ea001f12 ocaml: phase 5.1 perfect.ml baseline (count perfect numbers <= 500 = 3)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s
Perfect numbers = those where the proper-divisor sum equals n. Three
exist under 500: 6, 28, 496. (8128 is the next; 33550336 the one
after that.)

Same div_sum machinery as euler21_small.ml / abundant.ml (the
trial-division up to sqrt-n).

Original 10000 limit timed out at 10 minutes under contention (496
itself takes thousands of trials at the inner loop). 500 stays under
budget while still finding all three small perfects.

125 baseline programs total — milestone.
2026-05-10 01:02:18 +00:00
da96a79104 ocaml: phase 5.1 abundant.ml baseline (count abundant numbers < 100 = 21)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
A number n is abundant if its proper-divisor sum exceeds n. Reuses
the trial-division div_sum helper:

  let count_abundant n =
    let c = ref 0 in
    for i = 12 to n - 1 do
      if div_sum i > i then c := !c + 1
    done;
    !c

  count_abundant 100 = 21

Abundant numbers under 100, starting at 12, 18, 20, 24, 30, 36, 40,
42, 48, 54, 56, 60, 66, 70, 72, 78, 80, 84, 88, 90, 96 -> 21.

Companion to euler21_small.ml (amicable). The classification:
  perfect:  d(n) = n   (e.g. 6, 28)
  abundant: d(n) > n   (e.g. 12, 18)
  deficient:d(n) < n   (everything else)

124 baseline programs total.
2026-05-10 00:36:29 +00:00
ed8aaf8af7 ocaml: phase 5.1 euler36.ml baseline (sum of double-base palindromes <= 1000 = 1772)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Numbers that read the same in base 10 and base 2:

  1, 3, 5, 7, 9, 33, 99, 313, 585, 717
  sum = 1772

Implementation:
  pal_dec n        check decimal palindrome via index walk
  to_binary n      build binary string via mod 2 / div 2 stack
  pal_bin n        check binary palindrome
  euler36 limit    scan 1..limit-1, sum where both palindromes

Real PE36 uses 10^6 (answer 872187). 1000 takes ~9 minutes on
contended host but stays within reasonable budget for the
spec-level evaluator.

123 baseline programs total.
2026-05-10 00:26:46 +00:00
37f7405dcf ocaml: phase 5.1 euler40_small.ml baseline (Champernowne digit product = 15)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
Build the Champernowne string '12345678910111213...' until at
least 1500 chars; product of digits at positions 1, 10, 100, 1000
is 1 * 1 * 5 * 3 = 15.

Initial implementation timed out: 'String.length (Buffer.contents
buf) < 1500' rebuilt the full string each iteration (O(n^2) in our
spec-level evaluator). Fixed by tracking length separately from
the Buffer:

  let len = ref 0 in
  while !len < 1500 do
    let s = string_of_int !i in
    Buffer.add_string buf s;
    len := !len + String.length s;
    i := !i + 1
  done

Real PE40 uses positions up to 10^6 (answer 210); 1000 keeps under
budget while exercising the same string-build + char-pick pattern.

122 baseline programs total.
2026-05-10 00:09:57 +00:00
4e6a345342 ocaml: phase 5.1 euler34_small.ml baseline (factorions <= 2000 = 145)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Number that equals the sum of factorials of its digits:
  145 = 1! + 4! + 5! = 1 + 24 + 120

Implementation:
  fact n           iterative factorial
  digit_fact_sum n walk digits, sum fact(digit)
  euler34 limit    scan 3..limit, accumulate matches

The only other factorion is 40585 = 4!+0!+5!+8!+5!. Real PE34 sums
both (= 40730); 2000 keeps under our search budget.

121 baseline programs total.
2026-05-09 23:50:25 +00:00
21dbd195d5 ocaml: phase 5.1 euler29_small.ml baseline (distinct a^b for 2<=a,b<=5 = 15)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Compute every power a^b for a, b in [2..N] and count distinct
values. Hashtbl as a set with unit-payload (iter-168 idiom):

  let euler29 n =
    let h = Hashtbl.create 64 in
    for a = 2 to n do
      for b = 2 to n do
        let p = ref 1 in
        for _ = 1 to b do p := !p * a done;
        Hashtbl.replace h !p ()
      done
    done;
    Hashtbl.length h

For N=5: 16 powers minus one duplicate (4^2 = 2^4 = 16) -> 15.

Real PE29 uses N=100 (answer 9183).

120 baseline programs total — milestone.
2026-05-09 23:37:36 +00:00
87f9a84365 ocaml: phase 5.1 euler21_small.ml baseline (sum of amicable numbers <= 300 = 504)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
div_sum computes proper divisor sum via trial division up to sqrt(n):

  let div_sum n =
    let s = ref 1 in
    let i = ref 2 in
    while !i * !i <= n do
      if n mod !i = 0 then begin
        s := !s + !i;
        let q = n / !i in
        if q <> !i then s := !s + q
      end;
      i := !i + 1
    done;
    if n = 1 then 0 else !s

Outer loop finds amicable pairs (a, b) with d(a) = b, d(b) = a,
a != b. Only pair under 300 is (220, 284); 220 + 284 = 504.

Real PE21 uses 10000 (answer 31626). 300 keeps the run under
budget while exercising the same divisor-sum trick.

119 baseline programs total.
2026-05-09 23:27:15 +00:00
46e49dc947 ocaml: phase 5.1 euler30_cube.ml baseline (sum of digit-cube narcissistic numbers <= 999 = 1301)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Numbers equal to the sum of cubes of their digits:
  153 = 1 + 125 + 27
  370 = 27 + 343 + 0
  371 = 27 + 343 + 1
  407 = 64 + 0 + 343
  sum = 1301

Implementation:
  pow_digit_sum n p   walk digits of n, accumulate d^p
  euler30 p limit     scan 2..limit and sum where pow_digit_sum n p = n

Real PE30 uses 5th powers (answer 443839); the cube version
exercises the same algorithm in a smaller search space.

118 baseline programs total.
2026-05-09 23:17:00 +00:00
ea7120751d ocaml: phase 5.1 euler28.ml baseline (sum of diagonals in 7x7 spiral = 261)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
For each layer 1..(n-1)/2, the four corners of an Ulam spiral are
spaced 2*layer apart. Step k four times per layer, accumulate:

  let euler28 n =
    let s = ref 1 in
    let k = ref 1 in
    for layer = 1 to (n - 1) / 2 do
      let step = 2 * layer in
      for _ = 1 to 4 do
        k := !k + step;
        s := !s + !k
      done
    done;
    !s

  euler28 7 = 1 + (3+5+7+9) + (13+17+21+25) + (31+37+43+49) = 261

Real PE28 uses 1001x1001 (answer 669171001); 7x7 is fast.

117 baseline programs total.
2026-05-09 23:03:06 +00:00
89a807a1ed ocaml: phase 5.1 euler14.ml baseline (longest Collatz under 100, starting n = 97)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
collatz_len walks n through n/2 if even, 3n+1 if odd, counting
steps. Outer loop scans 2..N tracking the best length and arg-best:

  let euler14 limit =
    let best = ref 0 in
    let best_n = ref 0 in
    for n = 2 to limit do
      let l = collatz_len n in
      if l > !best then begin
        best := l;
        best_n := n
      end
    done;
    !best_n

  euler14 100 = 97   (97 generates a 118-step chain)

Real PE14 uses limit = 1_000_000 (answer 837799); 100 exercises the
same algorithm in <2 minutes on our contended host.

116 baseline programs total.
2026-05-09 22:53:27 +00:00
391a2d0c4f ocaml: phase 5.1 euler16.ml baseline (digit sum of 2^15 = 26)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Computes 2^n via for-loop multiplication, then walks the digits via
mod 10 / div 10:

  let euler16 n =
    let p = ref 1 in
    for _ = 1 to n do p := !p * 2 done;
    let sum = ref 0 in
    let m = ref !p in
    while !m > 0 do
      sum := !sum + !m mod 10;
      m := !m / 10
    done;
    !sum

  euler16 15 = 3 + 2 + 7 + 6 + 8 = 26  (= digit sum of 32768)

Real PE16 asks for 2^1000 which exceeds float precision; 2^15 stays
safe and exercises the same digit-decomposition pattern.

115 baseline programs total.
2026-05-09 22:43:45 +00:00
5959989324 ocaml: phase 5.1 euler25.ml baseline (first 12-digit Fibonacci index = 55)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Iteratively grows two refs while the larger is below 10^(n-1),
counting iterations:

  let euler25 n =
    let a = ref 1 in
    let b = ref 1 in
    let i = ref 2 in
    let target = ref 1 in
    for _ = 1 to n - 1 do target := !target * 10 done;
    while !b < !target do
      let c = !a + !b in
      a := !b;
      b := c;
      i := !i + 1
    done;
    !i

  euler25 12 = 55   (F(55) = 139_583_862_445, 12 digits)

Real PE25 asks for 1000 digits (answer 4782); 12 keeps within
safe-int while exercising the identical algorithm.

114 baseline programs total — 200 iterations landed.
2026-05-09 22:31:27 +00:00
320d78a993 ocaml: phase 5.1 euler3.ml baseline (largest prime factor of 13195 = 29)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
PE3's worked example. Trial-division streaming: when the current
factor divides m, divide and update largest; otherwise bump factor:

  let largest_prime_factor n =
    let m = ref n in
    let factor = ref 2 in
    let largest = ref 0 in
    while !m > 1 do
      if !m mod !factor = 0 then begin
        largest := !factor;
        m := !m / !factor
      end else factor := !factor + 1
    done;
    !largest

  largest_prime_factor 13195 = 29  (= 5 * 7 * 13 * 29)

The full PE3 number 600851475143 exceeds JS safe-int (2^53 ≈ 9e15
in float terms; 6e11 is fine but the intermediate 'i mod !factor'
on the way to 6857 can overflow precision). 13195 keeps the program
portable across hosts.

113 baseline programs total.
2026-05-09 22:21:16 +00:00
2a01758f28 ocaml: phase 5.1 euler7.ml baseline (100th prime = 541)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Scaled-down PE7 (real version asks for the 10001st prime = 104743).
Trial-division within an outer while loop searching forward from 2,
short-circuited via bool ref:

  let nth_prime n =
    let count = ref 0 in
    let i = ref 1 in
    let result = ref 0 in
    while !count < n do
      i := !i + 1;
      let p = ref true in
      let j = ref 2 in
      while !j * !j <= !i && !p do
        if !i mod !j = 0 then p := false;
        j := !j + 1
      done;
      if !p then begin
        count := !count + 1;
        if !count = n then result := !i
      end
    done;
    !result

  nth_prime 100 = 541

100 keeps the run under our 3-minute budget while exercising the
same algorithm.

112 baseline programs total.
2026-05-09 22:11:11 +00:00
533be5b36b ocaml: phase 5.1 euler4_small.ml baseline (largest 2-digit palindrome product = 9009)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Scaled-down Project Euler #4. Real version uses 3-digit numbers
yielding 906609 = 913 * 993; that's an 810k-iteration nested loop
that times out under our contended-host spec-level evaluator.

The 2-digit version (10..99) is fast enough and tests the same
algorithm:
  9009 = 91 * 99   (the only 2-digit-product palindrome > 9000)

Implementation:
  is_pal n     index-walk comparing s.[i] to s.[len-1-i]
  euler4 lo hi nested for with running max + early-skip via
                'p > !m && is_pal p' short-circuit

111 baseline programs total.
2026-05-09 21:59:23 +00:00
853504642f ocaml: phase 5.1 euler10.ml baseline (sum of primes <= 100 = 1060, scaled-down PE10)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Sieve of Eratosthenes followed by a sum loop:

  let sieve_sum n =
    let s = Array.make (n + 1) true in
    s.(0) <- false;
    s.(1) <- false;
    for i = 2 to n do
      if s.(i) then begin
        let j = ref (i * i) in
        while !j <= n do
          s.(!j) <- false;
          j := !j + i
        done
      end
    done;
    let total = ref 0 in
    for i = 2 to n do
      if s.(i) then total := !total + i
    done;
    !total

Real PE10 asks for sum below 2,000,000; that's a ~2-3 second loop in
native OCaml but minutes-to-hours under our contended-host
spec-level evaluator. 100 keeps the run under 3 minutes while still
exercising the same algorithm.

110 baseline programs total.
2026-05-09 21:46:16 +00:00
00ffba9306 ocaml: phase 5.1 euler5.ml baseline (smallest multiple of 1..20 = 232792560)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Iteratively takes lcm of running result with i:

  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
  let euler5 n =
    let r = ref 1 in
    for i = 2 to n do
      r := lcm !r i
    done;
    !r

  euler5 20 = 232792560
            = 2^4 * 3^2 * 5 * 7 * 11 * 13 * 17 * 19

Tests gcd_lcm composition (iter 140) on a fresh problem.

109 baseline programs total.
2026-05-09 21:35:59 +00:00
cecde8733a ocaml: phase 5.1 partition.ml baseline (stable partition, evens*100 + odds = 3025)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Two ref lists accumulating in reverse, then List.rev'd — preserves
original order:

  let partition pred xs =
    let yes = ref [] in
    let no = ref [] in
    List.iter (fun x ->
      if pred x then yes := x :: !yes
      else no := x :: !no
    ) xs;
    (List.rev !yes, List.rev !no)

  partition (fun x -> x mod 2 = 0) [1..10]
  -> ([2;4;6;8;10], [1;3;5;7;9])

  evens sum * 100 + odds sum = 30 * 100 + 25 = 3025

Tests higher-order predicate, tuple return, and iter-98 let-tuple
destructuring on the call site.

108 baseline programs total.
2026-05-09 21:26:31 +00:00
c16a8f2d53 ocaml: phase 5.1 is_prime.ml baseline (count primes <= 100 = 25)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Trial division up to sqrt(n) with early-exit via bool ref:

  let is_prime n =
    if n < 2 then false
    else
      let p = ref true in
      let i = ref 2 in
      while !i * !i <= n && !p do
        if n mod !i = 0 then p := false;
        i := !i + 1
      done;
      !p

Outer count_primes loops 2..n calling is_prime, accumulating count.
Returns 25 — the canonical prime-counting function pi(100).

107 baseline programs total.
2026-05-09 21:16:40 +00:00
d4eb57fa07 ocaml: phase 5.1 catalan.ml baseline (Catalan number C(5) = 42)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
DP recurrence:

  C(0) = 1
  C(n) = sum_{j=0}^{n-1} C(j) * C(n-1-j)

  let catalan n =
    let dp = Array.make (n + 1) 0 in
    dp.(0) <- 1;
    for i = 1 to n do
      for j = 0 to i - 1 do
        dp.(i) <- dp.(i) + dp.(j) * dp.(i - 1 - j)
      done
    done;
    dp.(n)

C(5) = 42 — also the count of distinct binary trees with 5 internal
nodes, balanced paren strings of length 10, monotonic lattice paths,
etc.

106 baseline programs total.
2026-05-09 21:06:10 +00:00
73917745a0 ocaml: phase 5.1 fizz_classifier.ml baseline (FizzBuzz with polymorphic variants, 1..30 weighted = 540)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
Two functions:
  classify n   maps i to a polymorphic variant
                  FizzBuzz | Fizz | Buzz | Num of int
  score x      pattern-matches the variant to a weight
                  FizzBuzz=100, Fizz=10, Buzz=5, Num n=n

For i in 1..30:
  FizzBuzz at 15, 30:                  2 * 100 = 200
  Fizz at 3,6,9,12,18,21,24,27:        8 *  10 =  80
  Buzz at 5,10,20,25:                  4 *   5 =  20
  Num: rest (16 numbers)                       = 240
                                          total = 540

Exercises polymorphic-variant match (iter 87) including a
payload-bearing 'Num n' arm.

105 baseline programs total.
2026-05-09 20:56:31 +00:00
c8206e718a ocaml: phase 5.1 max_product3.ml baseline (max product of 3, with negatives -> 300)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Sort, then compare two candidates:
  p1 = product of three largest values
  p2 = product of two smallest (potentially negative) values and the largest

For [-10;-10;1;3;2]:
  sorted    = [-10;-10;1;2;3]
  p1        = 3 * 2 * 1                = 6
  p2        = (-10) * (-10) * 3        = 300
  max       = 300

Tests List.sort + Array.of_list + arr.(n-i) end-walk + candidate-pick
via if-then-else.

104 baseline programs total.
2026-05-09 20:46:42 +00:00
288c0f8c3e ocaml: phase 5.1 euler9.ml baseline (Project Euler #9, abc = 31875000)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Find the unique Pythagorean triple with a + b + c = 1000 and
return their product.

The naive triple loop timed out under host contention (10-minute
cap exceeded with ~333 * 999 ~= 333k inner iterations of complex
checks). Rewritten with algebraic reduction:

  a + b + c = 1000  AND  a^2 + b^2 = c^2
  =>  b = (500000 - 1000 * a) / (1000 - a)

so only the outer a-loop is needed (333 iterations). Single-pass
form:

  for a = 1 to 333 do
    let num = 500000 - 1000 * a in
    let den = 1000 - a in
    if num mod den = 0 then begin
      let b = num / den in
      if b > a then
        let c = 1000 - a - b in
        if c > b then result := a * b * c
    end
  done

Triple (200, 375, 425), product 31875000.

103 baseline programs total.
2026-05-09 20:36:43 +00:00
2c7246e11d ocaml: phase 5.1 euler6.ml baseline (Project Euler #6, sum^2 - sum_sq for 1..100 = 25164150)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Project Euler #6: difference between square of sum and sum of squares
for 1..100.

  let euler6 n =
    let sum = ref 0 in
    let sum_sq = ref 0 in
    for i = 1 to n do
      sum := !sum + i;
      sum_sq := !sum_sq + i * i
    done;
    !sum * !sum - !sum_sq

  euler6 100 = 5050^2 - 338350 = 25502500 - 338350 = 25164150

102 baseline programs total.
2026-05-09 20:16:49 +00:00
4840a9f660 ocaml: phase 5.1 euler2.ml baseline (Project Euler #2, even Fib <= 4M = 4613732)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Sum of even-valued Fibonacci numbers up to 4,000,000:

  let euler2 limit =
    let a = ref 1 in
    let b = ref 2 in
    let sum = ref 0 in
    while !a <= limit do
      if !a mod 2 = 0 then sum := !sum + !a;
      let c = !a + !b in
      a := !b;
      b := c
    done;
    !sum

Sequence: 1, 2, 3, 5, 8, 13, 21, 34, ... Only every third term
(2, 8, 34, 144, ...) is even. Sum below 4M: 4613732.

101 baseline programs total.
2026-05-09 20:05:45 +00:00
53968c2480 ocaml: phase 5.1 euler1.ml baseline (Project Euler #1, multiples of 3 or 5 below 1000 = 233168)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Project Euler #1: sum of all multiples of 3 or 5 below 1000.

  let euler1 limit =
    let sum = ref 0 in
    for i = 1 to limit - 1 do
      if i mod 3 = 0 || i mod 5 = 0 then sum := !sum + i
    done;
    !sum

  euler1 1000 = 233168

Trivial DSL exercise but symbolically meaningful: this is the 100th
baseline program.

100 baseline programs total — milestone.
2026-05-09 19:56:58 +00:00
3759aad7a6 ocaml: phase 5.1 anagram_groups.ml baseline (group by canonical anagram, 3 groups)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
canonical builds a sorted-by-frequency string representation:

  let canonical s =
    let chars = Array.make 26 0 in
    for i = 0 to String.length s - 1 do
      let k = Char.code s.[i] - Char.code 'a' in
      if k >= 0 && k < 26 then chars.(k) <- chars.(k) + 1
    done;
    expand into a-z order via a Buffer

For 'eat', 'tea', 'ate' -> all canonicalise to 'aet'. For 'tan',
'nat' -> 'ant'. For 'bat' -> 'abt'.

group_anagrams folds the input, accumulating per-key string lists;
final answer is Hashtbl.length (number of distinct groups):

  ['eat'; 'tea'; 'tan'; 'ate'; 'nat'; 'bat']  -> 3 groups

99 baseline programs total.
2026-05-09 19:47:21 +00:00
14575a9cd7 ocaml: phase 5.1 monotonic.ml baseline (monotonicity check, 4/5 inputs monotonic)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Tracks two bool refs (inc, dec). For each pair of consecutive
elements: if h < prev clear inc, if h > prev clear dec. Returns
inc OR dec at the end:

  let is_monotonic xs =
    match xs with
    | [] -> true
    | [_] -> true
    | _ ->
      let inc = ref true in
      let dec = ref true in
      let rec walk prev rest = ... in
      (match xs with h :: t -> walk h t | [] -> ());
      !inc || !dec

Five test cases:
  [1;2;3;4]    inc only        true
  [4;3;2;1]    dec only        true
  [1;2;1]      neither          false
  [5;5;5]      both (constant)  true
  []            empty            true (vacuous)
  sum = 4

98 baseline programs total.
2026-05-09 19:37:11 +00:00
be13f2daba ocaml: phase 5.1 majority_vote.ml baseline (Boyer-Moore majority, [3;3;4;2;4;4;2;4;4] = 4)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
O(n) time / O(1) space majority vote algorithm:

  let majority xs =
    let cand = ref 0 in
    let count = ref 0 in
    List.iter (fun x ->
      if !count = 0 then begin
        cand := x;
        count := 1
      end else if x = !cand then count := !count + 1
      else count := !count - 1
    ) xs;
    !cand

The candidate is updated to the current element whenever count
reaches zero. When a strict majority exists, this guarantees the
result.

  majority [3;3;4;2;4;4;2;4;4] = 4   (5 of 9, > n/2)

97 baseline programs total.
2026-05-09 19:27:14 +00:00
810f61a1c1 ocaml: phase 5.1 adler32.ml baseline (Adler-32 of 'Wikipedia' = 300286872 = 0x11E60398)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
Two running sums modulo 65521:

  a = (1 + sum of bytes)            mod 65521
  b = sum of running 'a' values     mod 65521
  checksum = b * 65536 + a

  let adler32 s =
    let a = ref 1 in
    let b = ref 0 in
    let m = 65521 in
    for i = 0 to String.length s - 1 do
      a := (!a + Char.code s.[i]) mod m;
      b := (!b + !a) mod m
    done;
    !b * 65536 + !a

For 'Wikipedia': 0x11E60398 = 300286872 (the canonical test value).

Tests for-loop accumulating two refs together, modular arithmetic,
and Char.code on s.[i].

96 baseline programs total.
2026-05-09 19:18:01 +00:00
37a514d566 ocaml: phase 5.1 gray_code.ml baseline (4-bit reflected Gray code, sum+len = 136)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
Single-formula generation:

  gray[i] = i lxor (i lsr 1)

For n = 4, generates 16 values, each differing from its neighbour
by one bit. Output is a permutation of 0..15, so its sum equals the
natural-sequence sum 120; +16 entries -> 136.

Tests lsl / lxor / lsr together (the iter-127 bitwise ops) plus
Array.make / Array.fold_left.

95 baseline programs total.
2026-05-09 19:08:19 +00:00
7e838bb62b ocaml: phase 5.1 max_run.ml baseline (longest consecutive run, 4+1+0 = 5)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Walks list keeping a previous-value reference; increments cur on
match, resets to 1 otherwise. Uses 'Some y when y = x' guard pattern
in match for the prev-value comparison:

  let max_run xs =
    let max_so_far = ref 0 in
    let cur = ref 0 in
    let last = ref None in
    List.iter (fun x ->
      (match !last with
       | Some y when y = x -> cur := !cur + 1
       | _ -> cur := 1);
      last := Some x;
      if !cur > !max_so_far then max_so_far := !cur
    ) xs;
    !max_so_far

Three test cases:
  [1;1;2;2;2;2;3;3;1;1;1]   max run = 4 (the 2's)
  [1;2;3;4;5]                max run = 1
  []                          max run = 0
  sum = 5

Tests 'when' guard pattern in match arm + Option ref + ref-mutation
sequence inside List.iter closure body.

94 baseline programs total.
2026-05-09 18:58:32 +00:00
b2ff367c6b ocaml: phase 5.1 subseq_check.ml baseline (subsequence test, 3/5 yes)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Two-pointer walk:

  let is_subseq s t =
    let i = ref 0 in
    let j = ref 0 in
    while !i < n && !j < m do
      if s.[!i] = t.[!j] then i := !i + 1;
      j := !j + 1
    done;
    !i = n

advance i only on match; always advance j. Pattern matches if i
reaches n.

Five test cases:
  'abc'  in 'ahbgdc'    yes
  'axc'  in 'ahbgdc'    no  (no x in t)
  ''     in 'anything'  yes (empty trivially)
  'abc'  in 'abc'       yes
  'abcd' in 'abc'       no  (s longer)
  sum = 3

93 baseline programs total.
2026-05-09 18:49:00 +00:00
17a7a91d73 ocaml: phase 5.1 merge_intervals.ml baseline (LeetCode #56, total length 9+3 = 12)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
Sort intervals by start, then sweep maintaining a current (cs, ce)
window — extend ce if next start <= ce, else push current and start
fresh:

  let merge_intervals xs =
    let sorted = List.sort (fun (a, _) (b, _) -> a - b) xs in
    let rec aux acc cur xs =
      match xs with
      | [] -> List.rev (cur :: acc)
      | (s, e) :: rest ->
        let (cs, ce) = cur in
        if s <= ce then aux acc (cs, max e ce) rest
        else aux (cur :: acc) (s, e) rest
    in
    match sorted with
    | [] -> []
    | h :: rest -> aux [] h rest

  [(1,3);(2,6);(8,10);(15,18);(5,9)]
  -> [(1,10); (15,18)]
  total length = 9 + 3 = 12

Tests List.sort with custom comparator using tuple patterns, plus
tuple destructuring in lambda + let-tuple from accumulator + match
arms.

92 baseline programs total.
2026-05-09 18:39:46 +00:00
df6efeb68e ocaml: phase 5.1 hamming.ml baseline (Hamming distance, 3+2-1 = 4)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Counts position-wise differences between two strings of equal
length; returns -1 sentinel for length mismatch:

  let hamming s t =
    if String.length s <> String.length t then -1
    else
      let d = ref 0 in
      for i = 0 to String.length s - 1 do
        if s.[i] <> t.[i] then d := !d + 1
      done;
      !d

Three test cases:
  'karolin'  vs 'kathrin'    3   (positions 2,3,4)
  '1011101'  vs '1001001'    2   (positions 2,4)
  'abc'      vs 'abcd'      -1   (length mismatch)
  sum                        4

91 baseline programs total.
2026-05-09 18:27:50 +00:00
60e3ce1c96 ocaml: phase 5.1 xor_cipher.ml baseline (XOR roll-key encryption, round-trip = 601)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
For each character, XOR with the corresponding key char (key cycled
via 'i mod kn'):

  let xor_cipher key text =
    let buf = Buffer.create n in
    for i = 0 to n - 1 do
      let c = Char.code text.[i] in
      let k = Char.code key.[i mod kn] in
      Buffer.add_string buf (String.make 1 (Char.chr (c lxor k)))
    done;
    Buffer.contents buf

XOR is its own inverse, so encrypt + decrypt with the same key yields
the original. Test combines:
  - String.length decoded            = 6
  - decoded = 'Hello!'                  -> 1
  - 6 * 100 + 1                          = 601

Tests Char.code + Char.chr round-trip, the iter-127 lxor operator,
Buffer.add_string + String.make 1, and key-cycling via mod.

90 baseline programs total.
2026-05-09 18:19:02 +00:00
eb621240d7 ocaml: phase 5.1 simpson_int.ml baseline (Simpson 1/3 rule, integral_0^1 x^2 -> 10000)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Composite Simpson's 1/3 rule with 100 panels:

  let simpson f a b n =
    let h = (b -. a) /. float_of_int n in
    let sum = ref (f a +. f b) in
    for i = 1 to n - 1 do
      let x = a +. float_of_int i *. h in
      let coef = if i mod 2 = 0 then 2.0 else 4.0 in
      sum := !sum +. coef *. f x
    done;
    h *. !sum /. 3.0

The 1-4-2-4-...-4-1 coefficient pattern is implemented via even/odd
index dispatch. Endpoints get coefficient 1.

For x^2 over [0, 1], exact value is 1/3 ~= 0.33333. Scaled by 30000
gives 9999.99..., int_of_float -> 10000.

Tests higher-order function (passing the integrand 'fun x -> x *. x'),
float arithmetic in for-loop, and float_of_int for index->x conversion.

89 baseline programs total.
2026-05-09 18:09:33 +00:00
e8a0c86de0 ocaml: phase 5.1 int_sqrt.ml baseline (Newton integer sqrt, 12+14+1000+1 = 1027)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Newton's method on integers, converging when y >= x:

  let isqrt n =
    if n < 2 then n
    else
      let x = ref n in
      let y = ref ((!x + 1) / 2) in
      while !y < !x do
        x := !y;
        y := (!x + n / !x) / 2
      done;
      !x

Test cases:
  isqrt 144      = 12     (perfect square)
  isqrt 200      = 14     (floor of sqrt(200) ~= 14.14)
  isqrt 1000000  = 1000
  isqrt 2        = 1
  sum            = 1027

Companion to newton_sqrt.ml (iter 124, float Newton). Tests integer
division semantics from iter 94 and a while-until-convergence loop.

88 baseline programs total.
2026-05-09 17:55:07 +00:00
4eeb7e59b4 ocaml: phase 5.1 grid_paths.ml baseline (count paths in 4x6 grid = 210)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
DP filling a flattened 2D array:

  dp.(0, 0)         = 1
  dp.(i, j)         = dp.(i-1, j) + dp.(i, j-1)
  index             = i * (n+1) + j

For a 4x6 grid (5x7 dp matrix), the count is C(10, 4) = 210.

Tests Array as 2D via row-major flatten + nested for + multi-step
conditional access (above/left guarded by 'if i > 0' / 'if j > 0').

87 baseline programs total.
2026-05-09 17:45:52 +00:00
f1df5b1b72 ocaml: phase 5.1 fib_doubling.ml baseline (Fibonacci by doubling, fib(40) = 102334155)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Uses the identities:
  F(2k)   = F(k) * (2 * F(k+1) - F(k))
  F(2k+1) = F(k)^2 + F(k+1)^2

to compute Fibonacci in O(log n) recursive depth instead of O(n).

  let rec fib_pair n =
    if n = 0 then (0, 1)
    else
      let (a, b) = fib_pair (n / 2) in
      let c = a * (2 * b - a) in
      let d = a * a + b * b in
      if n mod 2 = 0 then (c, d)
      else (d, c + d)

Each call returns the pair (F(n), F(n+1)). fib(40) = 102334155 fits
in JS safe-int (< 2^53). Tests tuple returns with let-tuple
destructuring + recursion on n / 2.

86 baseline programs total.
2026-05-09 17:36:24 +00:00
254ef0daff ocaml: phase 5.1 merge_two.ml baseline (merge two sorted lists, length*sum = 441)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Standard two-finger merge with nested match-in-match:

  let rec merge xs ys =
    match xs with
    | [] -> ys
    | x :: xs' ->
      match ys with
      | [] -> xs
      | y :: ys' ->
        if x <= y then x :: merge xs' (y :: ys')
        else y :: merge (x :: xs') ys'

Used as a building block in merge_sort.ml (iter 104) but called out
as its own baseline here.

  merge [1;4;7;10] [2;3;5;8;9]   = [1;2;3;4;5;7;8;9;10]
  length 9, sum 49, product 441.

85 baseline programs total.
2026-05-09 17:24:53 +00:00
b6e723fc3e ocaml: phase 5.1 pow_mod.ml baseline (modular exponentiation, sum = 738639)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Fast exponentiation by squaring with modular reduction:

  let rec pow_mod base exp m =
    if exp = 0 then 1
    else if exp mod 2 = 0 then
      let half = pow_mod base (exp / 2) m in
      (half * half) mod m
    else
      (base * pow_mod base (exp - 1) m) mod m

Even exponent halves and squares (O(log n)); odd decrements and
multiplies. mod-reduction at each step keeps intermediates bounded.

  pow_mod 2 30 1000003 + pow_mod 3 20 13 + pow_mod 5 17 100 = 738639

84 baseline programs total.
2026-05-09 17:15:47 +00:00
2e84492d96 ocaml: phase 5.1 tree_depth.ml baseline (binary tree depth, longest path = 4)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Same 'tree = Leaf | Node of int * tree * tree' ADT as iter-159
max_path_tree.ml, but the recursion ignores the value:

  let rec depth t = match t with
    | Leaf -> 0
    | Node (_, l, r) ->
      let dl = depth l in
      let dr = depth r in
      1 + (if dl > dr then dl else dr)

For the test tree:
        1
       /       2   3
     /     4   5
       /
      8

longest path is 1 -> 2 -> 5 -> 8, depth = 4.

Tests wildcard pattern in constructor 'Node (_, l, r)', two nested
let-bindings in match arm, inline if-as-expression for max.

83 baseline programs total.
2026-05-09 17:06:10 +00:00
1bde4e834f ocaml: phase 5.1 stable_unique.ml baseline (Hashtbl dedupe preserving order, 8+38 = 46)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
Walk input with Hashtbl.mem + Hashtbl.add seen x () (unit-payload
turns the table into a set); on first occurrence cons to the result
list; reverse at the end:

  let stable_unique xs =
    let seen = Hashtbl.create 8 in
    let result = ref [] in
    List.iter (fun x ->
      if not (Hashtbl.mem seen x) then begin
        Hashtbl.add seen x ();
        result := x :: !result
      end
    ) xs;
    List.rev !result

For [3;1;4;1;5;9;2;6;5;3;5;8;9]:
  result = [3;1;4;5;9;2;6;8]   (input order, dupes dropped)
  length = 8, sum = 38         total = 46

Tests Hashtbl as a set abstraction (unit-payload), the rev-build
idiom, and 'not (Hashtbl.mem seen x)' membership negation.

82 baseline programs total.
2026-05-09 16:56:46 +00:00
554ef48c63 ocaml: phase 5.1 run_decode.ml baseline (RLE decode, expansion sum = 21)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Inverse of run_length.ml from iteration 130. Takes a list of
(value, count) tuples and expands:

  let rec rle_decode pairs =
    match pairs with
    | [] -> []
    | (x, n) :: rest ->
      let rec rep k = if k = 0 then [] else x :: rep (k - 1) in
      rep n @ rle_decode rest

  rle_decode [(1,3); (2,2); (3,4); (1,2)]
  = [1;1;1; 2;2; 3;3;3;3; 1;1]
  sum = 3 + 4 + 12 + 2 = 21.

Tests tuple-cons pattern, inner-let recursion, list concat (@), and
the 'List.fold_left (+) 0' invariant on encoding round-trips.

81 baseline programs total.
2026-05-09 16:47:56 +00:00
b7b841821c ocaml: phase 5.1 peano.ml baseline (Peano arithmetic, 5*6 = 30)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Defines unary Peano numerals with two recursive functions for
arithmetic:

  type peano = Zero | Succ of peano

  let rec plus a b = match a with
    | Zero -> b
    | Succ a' -> Succ (plus a' b)

  let rec mul a b = match a with
    | Zero -> Zero
    | Succ a' -> plus b (mul a' b)

mul is defined inductively: mul Zero _ = Zero; mul (Succ a) b =
b + (a * b).

  to_int (mul (from_int 5) (from_int 6)) = 30

The result is a Peano value with 30 nested Succ wrappers; to_int
unrolls them to a host int. Tests recursive ADT with a single-arg
constructor + four mutually-defined recursive functions (no rec/and
needed since each is defined separately).

80 baseline programs total — milestone.
2026-05-09 16:38:09 +00:00
2129e04bfd ocaml: phase 5.1 count_change.ml baseline (ways to make 50c from [1;2;5;10;25] = 406)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
Companion to coin_change.ml (min coins). Counts distinct multisets
via the unbounded-knapsack DP:

  let count_ways coins target =
    let dp = Array.make (target + 1) 0 in
    dp.(0) <- 1;
    List.iter (fun c ->
      for i = c to target do
        dp.(i) <- dp.(i) + dp.(i - c)
      done
    ) coins;
    dp.(target)

Outer loop over coins, inner DP relaxes dp.(i) += dp.(i - c). The
order matters — coin in outer, amount in inner — to count multisets
rather than ordered sequences.

count_ways [1; 2; 5; 10; 25] 50 = 406.

79 baseline programs total.
2026-05-09 16:28:15 +00:00
89726ed6c2 ocaml: phase 5.1 paren_depth.ml baseline (max nesting depth, 3+3+1 = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
One-pass walk tracking current depth and a high-water mark:

  let max_depth s =
    let d = ref 0 in
    let m = ref 0 in
    for i = 0 to String.length s - 1 do
      if s.[i] = '(' then begin
        d := !d + 1;
        if !d > !m then m := !d
      end
      else if s.[i] = ')' then d := !d - 1
    done;
    !m

Three inputs:
  '((1+2)*(3-(4+5)))'   3   (innermost (4+5) at depth 3)
  '(((deep)))'           3
  '()()()'               1   (no nesting)
  sum                    7

Tests for-loop char comparison s.[i] = '(' and the high-water-mark
idiom with two refs.

78 baseline programs total.
2026-05-09 16:13:05 +00:00
5d71be364e ocaml: phase 5.1 pancake_sort.ml baseline (in-place pancake sort, 9 flips -> 910)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Each pass:
  1. find_max in [0..size-1]
  2. if max not at the right end, flip max to position 0 (if needed)
  3. flip the size-prefix to push max to the end

Inner 'flip k' reverses prefix [0..k] using two pointer refs lo/hi.
Inner 'find_max k' walks 1..k tracking the max-position.

  pancake_sort [3;1;4;1;5;9;2;6]
  = 9 flips * 100 + a.(0) + a.(n-1)
  = 9 * 100 + 1 + 9
  = 910

The output combines flip count and sorted endpoints, so the test
verifies both that the sort terminates and that it sorts correctly.

Tests two inner functions closing over the same Array, ref-based
two-pointer flip, and downto loop with conditional flip dispatch.

77 baseline programs total.
2026-05-09 16:03:22 +00:00
ce013fa138 ocaml: phase 5.1 fib_mod.ml baseline (Fibonacci mod prime, fib(100) mod 1000003 = 391360)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Iterative two-ref Fibonacci with modular reduction every step:

  let fib_mod n m =
    let a = ref 0 in
    let b = ref 1 in
    for _ = 1 to n do
      let c = (!a + !b) mod m in
      a := !b;
      b := c
    done;
    !a

The 100th Fibonacci is 354_224_848_179_261_915_075, well past JS
safe-int (2^53). Modular reduction every step keeps intermediate
values within int53 precision so the answer is exact in our
runtime. fib(100) mod 1000003 = 391360.

76 baseline programs total.
2026-05-09 15:53:47 +00:00
07de86365e ocaml: phase 5.1 luhn.ml baseline (Luhn check-digit, 2/4 inputs valid)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Walks digits right-to-left, doubles every other starting from the
second-from-right; if a doubled value > 9, subtract 9. Sum must be
divisible by 10:

  let luhn s =
    let n = String.length s in
    let total = ref 0 in
    for i = 0 to n - 1 do
      let d = Char.code s.[n - 1 - i] - Char.code '0' in
      let v = if i mod 2 = 1 then
        let dd = d * 2 in
        if dd > 9 then dd - 9 else dd
      else d
      in
      total := !total + v
    done;
    !total mod 10 = 0

Test cases:
  '79927398713'        valid
  '79927398710'        invalid
  '4532015112830366'   valid (real Visa test)
  '1234567890123456'   invalid
  sum = 2

Tests right-to-left index walk via 'n - 1 - i', Char.code '0'
arithmetic for digit conversion, and nested if-then-else.

75 baseline programs total.
2026-05-09 15:42:01 +00:00
5b38f4d499 ocaml: phase 5.1 triangle.ml baseline (Pascal-shape min path sum, 2+3+5+1 = 11)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Bottom-up DP minimum-path through a triangle:

       2
      3 4
     6 5 7
    4 1 8 3

  let min_path_triangle rows =
    initialise dp from last row;
    for r = n - 2 downto 0 do
      for c = 0 to row_len - 1 do
        dp.(c) <- row.(c) + min(dp.(c), dp.(c+1))
      done
    done;
    dp.(0)

The optimal path 2 -> 3 -> 5 -> 1 sums to 11.

Tests downto loop, Array.of_list inside loop body, nested arr.(i)
reads + writes, and inline if-then-else for min.

74 baseline programs total.
2026-05-09 15:32:11 +00:00
a3a93c20b8 ocaml: phase 5.1 max_path_tree.ml baseline (max root-to-leaf sum, 1+3+7 = 11)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Recursive ADT for binary trees:

  type tree = Leaf | Node of int * tree * tree

  let rec max_path t =
    match t with
    | Leaf -> 0
    | Node (v, l, r) ->
      let lp = max_path l in
      let rp = max_path r in
      v + (if lp > rp then lp else rp)

For the test tree:
       1
      /      2   3
    / \   \
   4   5   7

paths sum:    1+2+4=7, 1+2+5=8, 1+3+7=11.  max = 11.

Tests 3-arg Node constructor with positional arg destructuring, two
nested let-bindings, and if-then-else as an inline expression.

73 baseline programs total.
2026-05-09 15:22:28 +00:00
30b237a891 ocaml: phase 5.1 mod_inverse.ml baseline (extended Euclidean, inverse sum = 27)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Extended Euclidean returns a triple (gcd, x, y) such that
a*x + b*y = gcd:

  let rec ext_gcd a b =
    if b = 0 then (a, 1, 0)
    else
      let (g, x1, y1) = ext_gcd b (a mod b) in
      (g, y1, x1 - (a / b) * y1)

  let mod_inverse a m =
    let (_, x, _) = ext_gcd a m in
    ((x mod m) + m) mod m

Three invariants checked:

  inv(3, 11)  = 4      (3*4  = 12  = 1 mod 11)
  inv(5, 26)  = 21     (5*21 = 105 = 1 mod 26)
  inv(7, 13)  = 2      (7*2  = 14  = 1 mod 13)
  sum         = 27

Tests recursive triple-tuple return, tuple-pattern destructuring on
let-binding (with wildcard for unused fields), and nested
let-binding inside the recursive call site.

72 baseline programs total.
2026-05-09 15:11:46 +00:00
667dfcfd7c ocaml: phase 5.1 hist.ml baseline (Hashtbl int histogram, total * max = 75)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Three small functions:

  hist xs       build a Hashtbl of count-by-value
  max_value h   Hashtbl.fold to find the max bin
  total h       Hashtbl.fold to sum all bins

For the 15-element list [1;2;3;1;4;5;1;2;6;7;1;8;9;1;0]:
  total      = 15
  max_value  =  5 (the number 1 appears 5 times)
  product    = 75

Companion to bag.ml (string keys) and frequency.ml (char keys) —
same Hashtbl.fold + Hashtbl.find_opt pattern, exercised on int
keys this time.

71 baseline programs total.
2026-05-09 15:02:13 +00:00
7f8bf5f455 ocaml: phase 5.1 mortgage.ml baseline (monthly payment, 200k @ 5% / 30y = $1073)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Standard amortising-mortgage formula:

  payment = P * r * (1 + r)^n / ((1 + r)^n - 1)

where r = annual_rate / 12, n = years * 12.

  let payment principal annual_rate years =
    let r = annual_rate /. 12.0 in
    let n = years * 12 in
    let pow_r = ref 1.0 in
    for _ = 1 to n do pow_r := !pow_r *. (1.0 +. r) done;
    principal *. r *. !pow_r /. (!pow_r -. 1.0)

For 200,000 at 5% over 30 years: monthly payment ~= $1073.64,
int_of_float -> 1073.

Manual (1+r)^n via for-loop instead of Float.pow keeps the program
portable to any environment where pow is restricted.

Tests float arithmetic precedence, for-loop accumulation in a float
ref, int_of_float on the result.

70 baseline programs total — milestone.
2026-05-09 14:52:13 +00:00
a98d683e60 ocaml: phase 5.1 group_consec.ml baseline (group consecutive equals, 5*10+3 = 53)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s
Inner 'collect cur acc tail' walks the tail while head matches
'cur', accumulating into 'acc'. Returns (rev acc, remaining) on
first mismatch. Outer 'group' recurses on the remaining list.

  group [1;1;2;2;2;3;1;1;4]
   = [[1;1]; [2;2;2]; [3]; [1;1]; [4]]

  List.length groups        = 5
  List.length (gs.(1))      = 3
  5 * 10 + 3                = 53

Tests nested recursion (inner aux + outer recursion), tuple
destructuring 'let (g, tail) = ...' inside the outer match arm,
and List.nth.

69 baseline programs total.
2026-05-09 14:40:22 +00:00
a2f3c533b8 ocaml: phase 5.1 zigzag.ml baseline (interleave two lists, sum 1..10 = 55)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
One-liner that swaps the lists on every recursive call:

  let rec zigzag xs ys =
    match xs with
    | [] -> ys
    | x :: xs' -> x :: zigzag ys xs'

This works because each call emits the head of xs and recurses with
ys as the new xs and the rest of xs as the new ys.

  zigzag [1;3;5;7;9] [2;4;6;8;10] = [1;2;3;4;5;6;7;8;9;10]   sum = 55

Tests recursive list cons + arg-swap idiom that is concise but
non-obvious to readers expecting symmetric-handling.

68 baseline programs total.
2026-05-09 14:30:55 +00:00
0f2eb45f5c ocaml: phase 5.1 prefix_sum.ml baseline (precomputed sums + range queries, 14+25+27 = 66)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Two utility functions:

  prefix_sums xs   builds Array of len n+1 such that
                   arr.(i) = sum of xs[0..i-1]
  range_sum p lo hi = p.(hi+1) - p.(lo)

For [3;1;4;1;5;9;2;6;5;3]:
  range_sum 0 4   = 14   (3+1+4+1+5)
  range_sum 5 9   = 25   (9+2+6+5+3)
  range_sum 2 7   = 27   (4+1+5+9+2+6)
  sum             = 66

Tests List.iter mutating Array indexed by a ref counter, plus the
classic prefix-sum technique for O(1) range queries.

67 baseline programs total.
2026-05-09 14:21:24 +00:00
1c40fec8fa ocaml: phase 5.1 tic_tac_toe.ml baseline (3x3 winner check, X wins top row = 1)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s
Board as 9-element flat int array, 0=empty, 1=X, 2=O. Three
predicate functions:

  check_row b r       check_col b c       check_diag b

each return the winning player's mark or 0. Main 'winner' loops
i = 0..2 calling row(i)/col(i) then check_diag, threading via a
result ref.

Test board:
  X X X
  . O .
  . . O

X wins on row 0 -> winner returns 1.

Tests Array.of_list with row-major 'b.(r * 3 + c)' indexing,
multi-fn collaboration, and structural equality on int values.

66 baseline programs total.
2026-05-09 14:00:49 +00:00
b94a47a9a9 ocaml: phase 5.1 subset_sum.ml baseline (count subsets of [1..8] summing to 10 = 8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
Pure recursion — at each element, take it or don't:

  let rec count_subsets xs target =
    match xs with
    | [] -> if target = 0 then 1 else 0
    | x :: rest ->
      count_subsets rest target
      + count_subsets rest (target - x)

For [1;2;3;4;5;6;7;8] target 10, the recursion tree has 2^8 = 256
leaves. Returns 8 — number of subsets summing to 10:

  1+2+3+4, 1+2+7, 1+3+6, 1+4+5, 2+3+5, 2+8, 3+7, 4+6 = 8

Tests doubly-recursive list traversal pattern with single-arg list
+ accumulator-via-target.

65 baseline programs total.
2026-05-09 13:46:40 +00:00
7de014cd75 ocaml: phase 5.1 hailstone.ml baseline (Collatz length from 27 = 111 steps)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Iterative Collatz / hailstone sequence:

  let collatz_length n =
    let m = ref n in
    let count = ref 0 in
    while !m > 1 do
      if !m mod 2 = 0 then m := !m / 2
      else m := 3 * !m + 1;
      count := !count + 1
    done;
    !count

27 is the famous 'long-running' Collatz starter. Reaches a peak of
9232 mid-sequence and takes 111 steps to bottom out at 1.

64 baseline programs total.
2026-05-09 13:30:46 +00:00
0eef5bc8e6 ocaml: phase 5.1 twosum.ml baseline (LeetCode #1 one-pass hashmap, index sum = 5)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Walks list with List.iteri, checking if target - x is already in
the hashtable; if yes, the earlier index plus current is the
answer; otherwise record the current pair.

  twosum [2;7;11;15] 9   = (0, 1)   2+7
  twosum [3;2;4]     6   = (1, 2)   2+4
  twosum [3;3]       6   = (0, 1)   3+3

Sum of i+j over each pair: 1 + 3 + 1 = 5.

Tests Hashtbl.find_opt + add (the iter-99 cleanup), List.iteri, and
tuple destructuring on let-binding (iter 98 'let (i, j) = twosum
... in').

63 baseline programs total.
2026-05-09 13:15:05 +00:00
50981a2a9b ocaml: phase 5.1 bisect.ml baseline (root-finding, sqrt(2)*100 = 141)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
Bisection method searching for f(x) = 0 in [lo, hi] over 50
iterations:

  let bisect f lo hi =
    let lo = ref lo and hi = ref hi in
    for _ = 1 to 50 do
      let mid = (!lo +. !hi) /. 2.0 in
      if f mid = 0.0 || f !lo *. f mid < 0.0 then hi := mid
      else lo := mid
    done;
    !lo

Solving x^2 - 2 = 0 in [1, 2] via 'bisect (fun x -> x *. x -. 2.0)
1.0 2.0' converges to ~1.41421356... -> int_of_float (r *. 100) =
141.

Tests:
  - higher-order function passing
  - multi-let 'let lo = ref ... and hi = ref ...'
  - float arithmetic
  - int_of_float truncate-toward-zero (iter 117)

62 baseline programs total.
2026-05-09 13:02:17 +00:00
05487b497d ocaml: phase 5.1 base_n.ml baseline (int to base-N string, length sum = 17)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
36-character digit alphabet '0..9A..Z' supports any base 2..36. Loop
divides the magnitude by base and prepends the digit:

  while !m > 0 do
    acc := String.make 1 digits.[!m mod base] ^ !acc;
    m := !m / base
  done

Special-cases n = 0 -> '0' and prepends '-' for negatives.

Test cases (length, since the strings differ in alphabet):
  255 hex      'FF'              2
  1024 binary  '10000000000'    11
  100 dec      '100'             3
  0 any base   '0'               1
  sum                            17

Combines digits.[i] (string indexing) + String.make 1 ch + String
concatenation in a loop.

61 baseline programs total.
2026-05-09 12:52:55 +00:00
af38d98583 ocaml: phase 5.1 prime_factors.ml baseline (trial-division, 360 factor sum = 17)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Three refs threading through a while loop:
  m       remaining quotient
  d       current divisor
  result  accumulator (built in reverse, List.rev at end)

  while !m > 1 do
    if !m mod !d = 0 then begin
      result := !d :: !result;
      m := !m / !d
    end else
      d := !d + 1
  done

360 = 2^3 * 3^2 * 5 factors to [2;2;2;3;3;5], sum 17.

60 baseline programs total — milestone.
2026-05-09 12:44:02 +00:00
f5122a9a5d ocaml: phase 5.1 atm.ml baseline (mutable record + exception + try/with, balance = 120)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s
Models a bank account using a mutable record + a user exception:

  type account = { mutable balance : int }
  exception Insufficient

  let withdraw acct amt =
    if amt > acct.balance then raise Insufficient
    else acct.balance <- acct.balance - amt

Sequence:
  start             100
  deposit 50        150
  withdraw 30       120
  withdraw 200      raises Insufficient
  handler returns   acct.balance        (= 120, transaction rolled back)

Combines mutable record fields, user exception declaration,
try-with-bare-pattern, and verifies that a raise in the middle of a
sequence doesn't leave a partial mutation.

59 baseline programs total.
2026-05-09 12:34:36 +00:00
097c7f4590 ocaml: phase 5.1 bf_full.ml baseline (full Brainfuck with [] loops, +++[.-] = 6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Extends the iter-92 brainfuck.ml subset interpreter with bracket
matching:

  '['  if mem[ptr] = 0, jump past matching ']'
       (forward depth-counting scan: '[' increments depth, ']' decrements)
  ']'  if mem[ptr] <> 0, jump back to matching '['
       (backward depth-counting scan)

Test program '+++[.-]':
  +++   set cell 0 = 3
  [     enter loop (cell != 0)
    .   acc += cell
    -   cell -= 1
  ]     loop while cell != 0
  result: acc = 3 + 2 + 1 = 6

Tests deeply nested while loops, mutable pc / ptr / acc, multi-arm
if/else if dispatch on chars + nested begin/end blocks for loop
body conditionals.

58 baseline programs total.
2026-05-09 12:24:48 +00:00
5c587c0f61 ocaml: phase 5.1 anagram_check.ml baseline (char-frequency array, 2/4 anagrams)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
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.
2026-05-09 12:14:32 +00:00
acc8b01ddb ocaml: phase 5.1 exception_user.ml baseline (user exception with payload, 4+5+7+10 = 26)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s
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.
2026-05-09 12:04:42 +00:00
027678f31e ocaml: phase 5.1 flatten_tree.ml baseline (parametric ADT flatten, sum 1..7 = 28)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
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.
2026-05-09 11:52:19 +00:00
cca3a28206 ocaml: phase 5.1 gcd_lcm.ml baseline (Euclidean gcd + lcm, 12+12+36 = 60)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
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.
2026-05-09 11:42:52 +00:00
b8dfc080dd ocaml: phase 5.1 zip_unzip.ml baseline (zip/unzip round-trip, sum-product = 1000)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
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.
2026-05-09 11:33:30 +00:00
ac19b7aced ocaml: phase 5.1 bigint_add.ml baseline (digit-list bignum add, 1+18+9 = 28)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 11:19:23 +00:00
aa0a7fa1a2 ocaml: phase 5.1 expr_simp.ml baseline (symbolic simplifier, eval(simp e) = 22)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
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.
2026-05-09 11:09:23 +00:00
bafa2410e4 ocaml: phase 5.1 mat_mul.ml baseline (3x3 row-major matrix multiply, sum = 621)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
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.
2026-05-09 11:00:00 +00:00
a91ff62730 ocaml: phase 5.1 bsearch.ml baseline (binary search, position sum = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
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.
2026-05-09 10:40:49 +00:00
073ea44fdb ocaml: phase 5.1 palindrome.ml baseline (two-pointer check, 4/6 inputs match)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 10:31:22 +00:00
aee7226b9c ocaml: phase 5.1 coin_change.ml baseline (DP, 67c with [1;5;10;25] = 6 coins)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
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.
2026-05-09 10:21:11 +00:00
b3d5da5361 ocaml: phase 5.1 kadane.ml baseline (max subarray sum = 6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 10:07:12 +00:00
da6d8e39c9 ocaml: phase 5.1 pascal.ml baseline (Pascal triangle row 10 middle = C(10,5) = 252)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
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.
2026-05-09 09:57:18 +00:00
32aba1823d ocaml: phase 5.1 run_length.ml baseline (RLE, sum-of-counts = 11)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
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.
2026-05-09 09:47:25 +00:00
3be2dc6e78 ocaml: phase 5.1 grep_count.ml baseline (substring-aware line filter, 3 matches)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
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.
2026-05-09 09:34:40 +00:00
b0cbdaf713 ocaml: phase 5.1 pretty_table.ml baseline (Buffer + Printf widths, len = 64)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Builds a 4-row scoreboard via Buffer + Printf.sprintf:

  Buffer.add_string buf (Printf.sprintf '%-10s %4d\n' name score)

Each row is exactly 16 chars regardless of actual name/score length:
  10 name padding + 1 space + 4 score padding + 1 newline.
4 rows -> 64 chars total.

Combines:
  - Buffer.add_string + Printf.sprintf
  - %-Ns left-justified string and %Nd right-justified int width
  - List.iter on tuple-pattern args (iter 101 fun (a, b))

42 baseline programs total.
2026-05-09 09:24:41 +00:00
aaaf054441 ocaml: phase 4 bitwise ops land/lor/lxor/lsl/lsr/asr + bits.ml baseline (+5 tests, 607 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 09:15:00 +00:00
70b9b4f6cf ocaml: phase 5.1 ackermann.ml baseline (ack(3, 4) = 125)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 08:50:12 +00:00
095bb62ef9 ocaml: phase 5.1 rpn.ml baseline (Reverse Polish Notation evaluator, [3 4 + 2 * 5 -] = 9)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
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.
2026-05-09 08:39:56 +00:00
13fb1bd7a9 ocaml: phase 5.1 newton_sqrt.ml baseline (Newton's method, sqrt(2)*1000 = 1414)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
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.
2026-05-09 08:29:01 +00:00
39f4c7a9a8 ocaml: phase 5.1 hanoi.ml baseline (Tower of Hanoi move count, n=10 -> 1023)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
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.
2026-05-09 08:21:19 +00:00
1a828d5b9f ocaml: phase 5.1 validate.ml baseline (Either-based validation, 3 errs * 100 + 117 = 417)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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.
2026-05-09 08:11:07 +00:00
5c70747ac7 ocaml: phase 5.1 word_freq.ml baseline (Map.Make on String, distinct = 8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
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.
2026-05-09 08:01:21 +00:00
c272b1ea04 ocaml: phase 6 Either module + Hashtbl.copy (+4 tests, 602 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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)
2026-05-09 07:50:24 +00:00
9a8bbff5b2 ocaml: phase 5.1 json_pretty.ml baseline (recursive ADT to string, len = 24)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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.
2026-05-09 07:41:01 +00:00
75a1adbbd5 ocaml: phase 5.1 shuffle.ml baseline (Fisher-Yates with deterministic Random)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
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.
2026-05-09 07:31:33 +00:00
90418c120b ocaml: phase 5.1 pi_leibniz.ml baseline + int_of_float fix (1000 terms x 100 = 314)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 07:19:52 +00:00
e42ff3b1f6 ocaml: phase 6 Float module fleshed out (+6 tests, 598 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
New Float members:
  zero / one / minus_one
  abs / neg
  add / sub / mul / div     (lift host '+.' '-.' '*.' '/.')
  max / min                 (if-based)
  equal / compare           (Float.compare returns -1 / 0 / 1)
  to_int                    (host floor)
  of_int                    (identity in dynamic runtime)
  of_string                 (delegates to _int_of_string)

Aligns Float with Int's API and lets baselines use Float.add /
Float.compare / etc without lifting the symbols themselves.

  Float.add 3.5 4.5         = 8
  Float.compare 2.5 5.0     = -1
  Float.abs -3.7            = 3.7
  Float.max 3.14 2.71       = 3.14
2026-05-09 07:09:29 +00:00
97a8c06690 ocaml: phase 5.1 balance.ml baseline (paren/bracket/brace balance via Stack)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
is_balanced walks a string; on each char:
  '(', '[', '{'  -> Stack.push c
  ')', ']', '}'  -> require stack non-empty AND top = expected opener,
                     else mark ok = false
  others         -> skip

At end: !ok && Stack.is_empty stack.

Five test cases:
  '({[abc]d}e)'  -> true
  '(a]'           -> false  (no matching opener)
  '{[}]'          -> false  (mismatched closer)
  '(())'          -> true
  ''              -> true

Sum of (if balanced then 1 else 0) -> 3.

Exercises:
  Stack.create / push / pop / is_empty
  s.[!i] string indexing
  while loop + bool ref short-circuit
  multi-arm if/else if/else if dispatch

31 baseline programs total.
2026-05-09 06:59:22 +00:00
0c3b5d21fa ocaml: phase 5.1 safe_div.ml baseline + Result.equal/compare/iter_error (+3 tests, 592 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
safe_div.ml: integer division returning Result. Sum-safe folds pairs,
skipping the Error branches.

  [(10,2); (20,4); (30,0); (50,5)]   ->  5 + 5 + 0 + 10 = 20

Result module additions (mirroring real OCaml's signatures):

  equal eq_ok eq_err a b
  compare cmp_ok cmp_err a b      Ok < Error (i.e. Ok x compared to
                                  Error e returns -1)
  iter_error f r

  Result.equal (=) (=) (Ok 1) (Ok 1)              = true
  Result.compare compare compare (Ok 5) (Ok 3)    = 1
  Result.compare compare compare (Ok 1) (Error _) = -1

30 baseline programs total.
2026-05-09 06:47:47 +00:00
98ba772acd ocaml: phase 6 List.equal / List.compare (+5 tests, 589 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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
2026-05-09 06:35:42 +00:00
4d32c80a99 ocaml: phase 6 Bool module + Option.equal/Option.compare (+5 tests, 584 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
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
2026-05-09 06:26:33 +00:00
ddd1e40d00 ocaml: phase 5.1 bag.ml baseline + String.equal/compare/cat/empty (+3 tests, 579 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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)
2026-05-09 06:15:03 +00:00
7ca5bfbb70 ocaml: phase 5.1 fraction.ml baseline (rational arithmetic, 4/3 -> num+den=7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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.
2026-05-09 06:05:31 +00:00
2d519461c4 ocaml: phase 6 Seq module (eager, list-backed) (+4 tests, 576 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
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
2026-05-09 05:56:10 +00:00
24416f8cef ocaml: phase 5.1 unique_set.ml baseline (Set.Make + IntOrd, count = 9)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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.
2026-05-09 05:44:35 +00:00
ec12b721e8 ocaml: phase 4 Set.Make / Map.Make functor application smoke tests (+3 tests, 572 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
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.
2026-05-09 05:35:19 +00:00
5d33f8f20b ocaml: phase 6 Filename module + Char.compare/equal/escaped (+7 tests, 569 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
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, \\, ")
2026-05-09 05:24:37 +00:00
7773c40337 ocaml: phase 4 basic labeled / optional argument syntax (label dropped) (+3 tests, 562 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
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
2026-05-09 05:12:34 +00:00
7c40506571 ocaml: phase 5.1 merge_sort.ml baseline (user mergesort, sum=44)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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.
2026-05-09 05:00:50 +00:00
82ffc695a5 ocaml: phase 4 top-level 'let f (a, b) = body' tuple-param decl (+3 tests, 559 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
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
2026-05-09 04:51:11 +00:00
b526d81a4c ocaml: phase 4 'let f (a, b) = body' tuple-param on inner-let (+3 tests, 556 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
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
2026-05-09 04:36:33 +00:00
64f4f10c32 ocaml: phase 4 'fun (a, b) -> body' tuple-param destructuring (+4 tests, 553 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
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
2026-05-09 04:25:18 +00:00
8ca3ef342d ocaml: phase 6 Random module (deterministic LCG) (+4 tests, 549 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
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
2026-05-09 04:12:16 +00:00
41190c6d23 ocaml: phase 6 Hashtbl.keys/values/bindings/remove/clear (+4 tests, 545 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
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.
2026-05-09 03:59:20 +00:00
dab8718289 ocaml: phase 4 'let PATTERN = expr in body' tuple destructuring (+3 tests, 541 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
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.
2026-05-09 03:40:38 +00:00
7e64695a74 ocaml: phase 6 Printf width specifiers %5d/%-5d/%05d/%4s (+5 tests, 538 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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'
2026-05-09 03:25:50 +00:00
cb14a07413 ocaml: phase 6 Printf %i/%u/%x/%X/%o + int_to_hex/octal host primitives (+5 tests, 533 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Three new host primitives in eval.sx:
  _int_to_hex_lower n  -> string of hex digits (lowercase)
  _int_to_hex_upper n  -> string of hex digits (uppercase)
  _int_to_octal    n   -> string of octal digits

Each builds the digit string by repeated floor(n / base) + mod,
prepending the digit at each step. Negative numbers prefix '-' so the
output round-trips through int_of_string with a sign.

Printf walker now fans out:
  %d, %i, %u  -> _string_of_int
  %f          -> _string_of_float
  %x          -> _int_to_hex_lower
  %X          -> _int_to_hex_upper
  %o          -> _int_to_octal
  %s, %c, %b  -> existing handling

  Printf.sprintf '%x' 255          = 'ff'
  Printf.sprintf '%X' 4096         = '1000'
  Printf.sprintf '%o' 8            = '10'
  Printf.sprintf '%x %X %o' 255 4096 8 = 'ff 1000 10'
2026-05-09 03:12:28 +00:00
8188a82a58 ocaml: phase 6 List.sort upgraded to mergesort (+3 tests, 528 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
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.
2026-05-09 03:01:28 +00:00
a0e8b64f5c ocaml: phase 4 integer division semantics + Int module + max_int/min_int (+5 tests, 525 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Three things in this commit:

1. Integer / is now truncate-toward-zero on ints, IEEE on floats. The
   eval-op handler for '/' checks (number? + (= (round x) x)) on both
   sides; if both integral, applies host floor/ceil based on sign;
   otherwise falls through to host '/'.

2. Fixes Int.rem, which was returning 0 because (a - b * (a / b))
   was using float division: 17 - 5 * 3.4 = 0.0. Now Int.rem 17 5 = 2.

3. Int module fleshed out:
   max_int / min_int / zero / one / minus_one,
   succ / pred / neg, add / sub / mul / div / rem,
   equal, compare.

Also adds globals: max_int, min_int, abs_float, float_of_int,
int_of_float (the latter two are identity in our dynamic runtime).

  17 / 5         = 3
  -17 / 5        = -3   (trunc toward zero)
  Int.rem 17 5   = 2
  Int.compare 5 3 = 1
2026-05-09 02:50:21 +00:00
55fe1e4468 ocaml: phase 6 Array.sort/sub/append/exists/for_all/mem (+5 tests, 520 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Eight new Array functions, all in OCaml syntax inside runtime.sx,
delegating to the corresponding List operation on the cell's
underlying list:

  sort cmp a    -> a := List.sort cmp !a    (* mutates the cell *)
  stable_sort   = sort
  fast_sort     = sort
  append a b    -> ref (List.append !a !b)
  sub a pos n   -> ref (take n (drop pos !a))
  exists p      -> List.exists p !a
  for_all p     -> List.for_all p !a
  mem x a       -> List.mem x !a

Round-trip:
  let a = Array.of_list [3;1;4;1;5;9;2;6] in
  Array.sort compare a;
  Array.to_list a    = [1;1;2;3;4;5;6;9]
2026-05-09 02:35:55 +00:00
f68ea63e46 ocaml: phase 5.1 brainfuck.ml baseline (subset interpreter)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Five '+++++.' groups, cumulative accumulator 5+10+15+20+25 = 75.

This is a brainfuck *subset* — only > < + - . (no [ ] looping). That's
intentional: the goal is to stress imperative idioms that the recently
added Array module + array indexing syntax + s.[i] make ergonomic, all
in one program.

Exercises:
  Array.make 256 0
  arr.(!ptr)
  arr.(!ptr) <- arr.(!ptr) + 1
  prog.[!pc]
  ref / ! / :=
  while + nested if/else if/else if for op dispatch

25 baseline programs total.
2026-05-09 02:24:45 +00:00
a66b262267 ocaml: phase 5.1 sieve.ml baseline (Sieve of Eratosthenes)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Counts primes <= 50, expected 15.

Stresses the recently-added Array module + the new array-indexing
syntax together with nested control flow:

  let sieve = Array.make (n + 1) true in
  sieve.(0) <- false;
  sieve.(1) <- false;
  for i = 2 to n do
    if sieve.(i) then begin
      let j = ref (i * i) in
      while !j <= n do
        sieve.(!j) <- false;
        j := !j + i
      done
    end
  done;
  ...

Exercises: Array.make, arr.(i), arr.(i) <- v, nested for/while,
begin..end blocks, ref/!/:=, integer arithmetic. 24 baseline
programs total.
2026-05-09 02:16:18 +00:00
073588812a ocaml: phase 4 'arr.(i)' and 'arr.(i) <- v' array indexing (+3 tests, 515 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
parse-atom-postfix's '.()' branch now disambiguates between let-open
and array-get based on whether the head is a module path (':con' or
':field' chain rooted in ':con'). Module paths still emit
(:let-open M EXPR); everything else emits (:array-get ARR I).

Eval handles :array-get by reading the cell's underlying list at
index. The '<-' assignment handler now also accepts :array-get lhs
and rewrites the cell with one position changed.

Idiomatic OCaml array code now works:

  let a = Array.make 5 0 in
  for i = 0 to 4 do a.(i) <- i * i done;
  a.(3) + a.(4)               = 25

  let a = Array.init 4 (fun i -> i + 1) in
  a.(0) + a.(1) + a.(2) + a.(3)  = 10

  List.(length [1;2;3])         = 3   (* unchanged: List is a module *)
2026-05-09 02:08:21 +00:00
1ed3216ba6 ocaml: phase 6 Array module + (op) operator sections (+6 tests, 512 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Array module (runtime.sx, OCaml syntax):
  Backed by a 'ref of list'. make/length/get/init build the cell;
  set rewrites the underlying list with one cell changed (O(n) but
  works for short arrays in baseline programs). Includes
  iter/iteri/map/mapi/fold_left/to_list/of_list/copy/blit/fill.

(op) operator sections (parser.sx, parse-atom):
  When the token after '(' is a binop (any op with non-zero
  precedence in the binop table) and the next token is ')', emit
  (:fun ('a' 'b') (:op OP a b)) — i.e. (+)  becomes fun a b -> a + b.
  Recognises every binop including 'mod', 'land', '^', '@', '::',
  etc.

Lets us write:
  List.fold_left (+) 0 [1;2;3;4;5]    = 15
  let f = ( * ) in f 6 7              = 42
  List.map ((-) 10) [1;2;3]           = [9;8;7]
  let a = Array.make 5 7 in
  Array.set a 2 99;
  Array.fold_left (+) 0 a             = 127
2026-05-09 01:59:13 +00:00
5618dd1ef5 ocaml: phase 5.1 csv.ml baseline (split + int_of_string + fold_left)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Inline CSV-like text:
  a,1,extra
  b,2,extra
  c,3,extra
  d,4,extra

Two-stage String.split_on_char: first on '\n' for rows, then on ','
for fields per row. List.fold_left accumulates int_of_string of the
second field across rows. Result = 1+2+3+4 = 10.

Exercises char escapes inside string literals ('\n'), nested
String.split_on_char, List.fold_left with a non-trivial closure body,
and int_of_string. 23 baseline programs total.
2026-05-09 01:47:27 +00:00
19497c9fba ocaml: phase 4 polymorphic variants confirmation (+3 tests, 506 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Tokenizer already classified backtick-uppercase as a ctor identical
to a nominal one, but it had never been exercised by the suite. This
commit adds three smoke tests confirming that nullary, n-ary, and
list-of-polyvariant patterns all match:

  let x = polyvar(Foo) in match x with polyvar(Foo) -> 1 | polyvar(Bar) -> 2

  let x = polyvar(Pair) (5, 7) in
  match x with polyvar(Pair) (a, b) -> a + b | _ -> 0

  List.map (fun x -> match x with polyvar(On) -> 1 | polyvar(Off) -> 0)
           [polyvar(On); polyvar(Off); polyvar(On)]

(In the actual SX, polyvar(X) is the literal backtick-X — backticks
in this commit message are escaped to avoid shell interpretation.)
Since OCaml-on-SX is dynamic, there's no structural row inference,
but matching by tag works.
2026-05-09 01:38:09 +00:00
a34cfe69dc ocaml: phase 6 List.sort_uniq + List.find_map (+2 tests, 503 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
sort_uniq:
  Sort with the user comparator, then walk the sorted list dropping
  any element equal to its predecessor. Output is sorted and unique.

  List.sort_uniq compare [3;1;2;1;3;2;4]  =  [1;2;3;4]

find_map:
  Walk until the user fn returns Some v; return that. If all None,
  return None.

  List.find_map (fun x -> if x > 5 then Some (x * 2) else None)
                [1;2;3;6;7]
  = Some 12

Both defined in OCaml syntax in runtime.sx — no host primitive
needed since they're pure list traversals over existing operations.
2026-05-09 01:29:02 +00:00
8af3630625 ocaml: phase 6 String.iter/iteri/fold_left/fold_right/to_seq/of_seq (+3 tests, 501 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Six new String functions, all in OCaml syntax inside runtime.sx:

  iter      : index-walk with side-effecting f
  iteri     : iter with index
  fold_left : thread accumulator left-to-right
  fold_right: thread accumulator right-to-left
  to_seq    : return a char list (lazy in real OCaml; eager here)
  of_seq    : concat a char list back to a string

Round-trip:
  String.of_seq (List.rev (String.to_seq "hello"))   = "olleh"

Note: real OCaml's Seq is lazy. We return a plain list because the
existing stdlib already provides exhaustive list operations and we
don't yet have lazy sequences. If a baseline needs Seq.unfold or
similar, we'll graduate to a proper Seq module then.
2026-05-09 01:19:28 +00:00
34d518d555 ocaml: phase 5.1 frequency.ml baseline + Format module alias (+2 tests, 498 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
frequency.ml exercises the recently-added Hashtbl.iter / fold +
Hashtbl.find_opt + s.[i] indexing + for-loop together: build a
char-count table for 'abracadabra' then take the max via
Hashtbl.fold. Expected = 5 (a x 5). Total 25 baseline programs.

Format module added as a thin alias of Printf — sprintf, printf, and
asprintf all delegate to Printf.sprintf. The dynamic runtime doesn't
distinguish boxes/breaks, so format strings work the same as in
Printf and most Format-using OCaml programs now compile.
2026-05-09 01:11:53 +00:00
9907c1c58c ocaml: phase 4 'lazy EXPR' + Lazy.force (+2 tests, 496 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Tokenizer already had 'lazy' as a keyword. This commit wires it through:

  parser  : parse-prefix emits (:lazy EXPR), like the existing 'assert'
            handler.
  eval    : creates a one-element cell with state ('Thunk' expr env).
  host    : _lazy_force flips the cell to ('Forced' v) on first call
            and returns the cached value thereafter.
  runtime : module Lazy = struct let force lz = _lazy_force lz end.

Memoisation confirmed by tracking a side-effect counter through two
forces of the same lazy:

  let counter = ref 0 in
  let lz = lazy (counter := !counter + 1; 42) in
  let a = Lazy.force lz in
  let b = Lazy.force lz in
  (a + b) * 100 + !counter        = 8401   (= 84*100 + 1)
2026-05-09 01:03:40 +00:00
207dfc60ad ocaml: phase 6 Hashtbl.iter / Hashtbl.fold (+2 tests, 494 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
New host primitive _hashtbl_to_list returns the entries as a list of
OCaml tuples — ('tuple' k v) form, matching the AST representation
that the pattern-match VM (:ptuple) expects. Without that exact
shape, '(k, v) :: rest' patterns fail to match.

Hashtbl.iter / Hashtbl.fold in runtime walk that list with the user
fn. This closes a long-standing gap: previously Hashtbl was opaque
once values were written (we could only find_opt one key at a time).

  let t = Hashtbl.create 4 in
  Hashtbl.add t "a" 1; Hashtbl.add t "b" 2; Hashtbl.add t "c" 3;
  Hashtbl.fold (fun _ v acc -> acc + v) t 0   = 6
2026-05-09 00:53:32 +00:00
1b38f89055 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
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%"
2026-05-09 00:42:35 +00:00
14b52cfaa7 ocaml: phase 4 'assert EXPR' (+3 tests, 487 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Tokenizer already classified 'assert' as a keyword; this commit wires
it through:
  parser  : parse-prefix dispatches like 'not' — advance, recur, wrap
            as (:assert EXPR).
  eval    : evaluate operand; nil on truthy, host-error 'Assert_failure'
            on false. Caught cleanly by existing try/with.

  assert true; 42                          = 42
  let x = 5 in assert (x = 5); x + 1       = 6
  try (assert false; 0) with _ -> 99       = 99
2026-05-09 00:32:35 +00:00
bd2cd8aad1 ocaml: phase 5.1 levenshtein.ml baseline (no-memo edit distance, sum=11)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
Recursive Levenshtein edit distance with no memoization (the test
strings are short enough for the exponential-without-memo version to
fit in <2 minutes on contended hosts). Sums distances for five short
pairs:

  ('abc','abx') + ('ab','ba') + ('abc','axyc') + ('','abcd') + ('ab','')
   = 1 + 2 + 2 + 4 + 2 = 11

Exercises:
  * curried four-arg recursion
  * s.[i] equality test (char comparison)
  * min nested twice for the three-way recurrence
  * mixed empty-string base cases
2026-05-09 00:23:58 +00:00
0234ae329e ocaml: phase 5.1 caesar.ml baseline (ROT13 + s.[i] + Char ops)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Side-quests required to land caesar.ml:

1. Top-level 'let r = expr in body' is now an expression decl, not a
   broken decl-let. ocaml-parse-program's dispatch now checks
   has-matching-in? at every top-level let; if matched, slices via
   skip-let-rhs-boundary (which already opens depth on a leading let
   with matching in) and ocaml-parse on the slice, wrapping as :expr.

2. runtime.sx: added String.make / String.init / String.map. Used by
   caesar.ml's encode = String.init n (fun i -> shift_char s.[i] k).

3. baseline run.sh per-program timeout 240->480s (system load on the
   shared host frequently exceeds 240s for large baselines).

caesar.ml exercises:
  * the new top-level let-in expression dispatch
  * s.[i] string indexing
  * Char.code / Char.chr round-trip math
  * String.init with a closure that captures k

Test value: Char.code r.[0] + Char.code r.[4] after ROT13(ROT13('hello')) = 104 + 111 = 215.
2026-05-09 00:13:11 +00:00
f895a118fb ocaml: phase 4 's.[i]' string indexing syntax (+3 tests, 484 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
parse-atom-postfix now dispatches three cases after consuming '.':

  .field  -> existing field/module access
  .(EXPR) -> existing local-open
  .[EXPR] -> new string-get syntax  (this commit)

Eval reduces (:string-get S I) to host (nth S I), which already returns
a one-character string for OCaml's char model.

Lets us write idiomatic OCaml string traversal:

  let s = "hi" in
  let n = ref 0 in
  for i = 0 to String.length s - 1 do
    n := !n + Char.code s.[i]
  done;
  !n  (* = 209 *)
2026-05-08 23:58:37 +00:00
bc4f4a5477 ocaml: phase 5.1 roman.ml baseline + top-level 'let () = expr'
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Side-quest emerged from adding roman.ml baseline (Roman numeral greedy
encoding): top-level 'let () = expr' was unsupported because
ocaml-parse-program's parse-decl-let consumed an ident strictly. Now
parse-decl-let recognises a leading '()' as a unit binding and
synthesises a __unit_NN name (matching how parse-let already handles
inner-let unit patterns).

roman.ml exercises:
  * tuple list literal [(int * string); ...]
  * recursive pattern match on tuple-cons
  * String.length + List.fold_left
  * the new top-level let () support (sanity in a comment, even though
    the program ends with a bare expression for the test harness)

Bumped lib/ocaml/test.sh server timeout 180->360s — the recent surge in
test count plus a CPU-contended host was crowding out the sole epoch
reaching the deeper smarts.
2026-05-08 23:40:36 +00:00
982e9680fe ocaml: phase 4 'M.(expr)' local-open expression form (+3 tests, 481 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
In parse-atom-postfix, after consuming '.', if the next token is '(',
parse the inner expression and emit (:let-open M EXPR) instead of
:field. Cleanly composes with the existing :let-open evaluator and
loops to allow chained dot postfixes.

  List.(length [1;2;3])                = 3
  List.(map (fun x -> x + 1) [1;2;3])   = [2;3;4]
  Option.(map (fun x -> x * 10) (Some 4)) = Some 40
2026-05-08 21:43:38 +00:00
6dc535dde3 ocaml: phase 4 'let open M in body' local opens (+3 tests, 478 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Parser detects 'let open' as a separate let-form, parses M as a path
(Ctor(.Ctor)*) directly via inline AST construction (no source slicing
since cur-pos is only available in ocaml-parse-program), and emits
(:let-open PATH BODY).

Eval resolves the path to a module dict and merges its bindings into
the env for body evaluation. Now:

  let open List in map (fun x -> x * 2) [1;2;3]   = [2;4;6]
  let open Option in map (fun x -> x + 1) (Some 5) = Some 6
2026-05-08 21:33:14 +00:00
0530120bc7 ocaml: phase 4 def-mut / def-rec-mut inside modules (+2 tests, 475 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
ocaml-eval-module now handles :def-mut and :def-rec-mut decls so
'module M = struct let rec a n = ... and b n = ... end' works. The
def-rec-mut version uses cell-based mutual recursion exactly as the
top-level version.
2026-05-08 21:14:07 +00:00
6d9ac1e55a ocaml: phase 5.1 bfs.ml baseline (20/20 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 20s
Graph BFS using Queue + Hashtbl visited-set + List.assoc_opt + List.iter.
Returns 6 for a graph where A reaches B/C/D/E/F. Demonstrates 4 stdlib
modules (Queue, Hashtbl, List) cooperating in a real algorithm.
2026-05-08 21:05:32 +00:00
a4ef9a8ec9 ocaml: phase 1 type annotations on let / (e : T) (+4 tests, 473 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
let NAME [PARAMS] : T = expr and (expr : T) parse and skip the type
source. Runtime no-op since SX is dynamic. Works in inline let,
top-level let, and parenthesised expressions:

  let x : int = 5 ;; x + 1                        -> 6
  let f (x : int) : int = x + 1 in f 41           -> 42
  (5 : int)                                       -> 5
  ((1 + 2) : int) * 3                             -> 9
2026-05-08 20:58:50 +00:00
ce75bd6848 ocaml: phase 1+5.1 type aliases + poly_stack baseline (+3 tests, 469 / 19 baseline)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
Parser: in parse-decl-type, dispatch on the post-= token:
  '|' or Ctor   -> sum type
  '{'           -> record type
  otherwise     -> type alias (skip to boundary)
AST (:type-alias NAME PARAMS) with body discarded. Runtime no-op since
SX has no nominal types.

poly_stack.ml baseline exercises:
  module type ELEMENT = sig type t val show : t -> string end
  module IntElem = struct type t = int let show x = ... end
  module Make (E : ELEMENT) = struct ... use E.show ... end
  module IntStack = Make(IntElem)

Demonstrates the substrate handles signature decls + abstract types +
functor parameter with sig constraint.
2026-05-08 20:49:26 +00:00
c7d8b7dd62 ocaml: phase 2+3 'when' guard in try/with (+3 tests, 467 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
parse-try now consumes optional 'when GUARD-EXPR' before -> and emits
(:case-when PAT GUARD BODY). Eval try clause loop dispatches on case /
case-when and falls through on guard false — same semantics as match.

Examples:
  try raise (E 5) with | E n when n > 0 -> n | _ -> 0   = 5
  try raise (E (-3)) with | E n when n > 0 -> n | _ -> 0 = 0
  try raise (E 5) with | E n when n > 100 -> n | E n -> n + 1000  = 1005
2026-05-08 20:36:02 +00:00
029c1783f4 ocaml: phase 1+3 'when' guard in 'function | pat -> body' (+3 tests, 464 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
parse-function now consumes optional 'when GUARD-EXPR' before -> and
emits (:case-when PAT GUARD BODY) — same handling as match clauses.
function-style sign extraction now works:
  (function | n when n > 0 -> 1 | n when n < 0 -> -1 | _ -> 0)
2026-05-08 20:26:28 +00:00
b92a98fb45 ocaml: refresh scoreboard (480/480 across 15 suites incl. 18 baseline programs)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
2026-05-08 20:12:35 +00:00
8fab20c8bc ocaml: phase 5.1 anagrams.ml baseline (18/18 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s
Group anagrams by canonical (sorted-chars) key using Hashtbl +
List.sort. Demonstrates char-by-char traversal via String.get + for-loop +
ref accumulator + Hashtbl as a multi-valued counter.
2026-05-08 19:57:09 +00:00
de8b1dd681 ocaml: phase 5.1 lambda_calc.ml baseline (17/17 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Untyped lambda calculus interpreter inside OCaml-on-SX:
  type term = Var | Abs of string * term | App | Num of int
  type value = VNum of int | VClos of string * term * env
  let rec eval env t = match t with ...

(\x.\y.x) 7 99 = 7. The substrate handles two ADTs, recursive eval,
closure-based env, and pattern matching all written as a single
self-contained OCaml program — strong validation.
2026-05-08 19:49:08 +00:00
ce81ce2e95 ocaml: phase 6 Char predicates (+7 tests, 461 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
Char.is_digit / is_alpha / is_alnum / is_whitespace / is_upper /
is_lower / is_space — all written in OCaml using Char.code + ASCII
range checks.
2026-05-08 19:42:00 +00:00
8c7ad62b44 ocaml: phase 5 HM def-mut + def-rec-mut at top level (+3 tests, 454 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
ocaml-type-of-program now handles :def-mut (sequential generalize) and
:def-rec-mut (pre-bind tvs, infer rhs, unify, generalize all, infer
body — same algorithm as the inline let-rec-mut version).

Mutual top-level recursion now type-checks:
  let rec even n = ... and odd n = ...;; even 10           : Bool
  let rec map f xs = ... and length lst = ...;; map :
                              ('a -> 'b) -> 'a list -> 'b list
2026-05-08 19:19:17 +00:00
fff8fe2dc8 ocaml: phase 5.1 memo_fib.ml baseline (16/16 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Memoized fibonacci using Hashtbl.find_opt + Hashtbl.add.
fib(25) = 75025. Demonstrates mutable Hashtbl through the OCaml
stdlib API in real recursive code.
2026-05-08 19:10:49 +00:00
360a3ed51f ocaml: phase 5.1 queens.ml baseline (15/15 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
4-queens via recursive backtracking + List.fold_left. Returns 2 (the
two solutions of 4-queens). Per-program timeout in run.sh bumped to
240s — the tree-walking interpreter is slow on heavy recursion but
correct.

The substrate handles full backtracking + safe-check recursion +
list-driven candidate enumeration end-to-end.
2026-05-08 19:04:04 +00:00
50a219b688 ocaml: phase 5.1 mutable_record.ml baseline (14/14 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
Counter-style record with two mutable fields. Validates the new
r.f <- v field mutation end-to-end through type decl + record literal
+ field access + field assignment + sequence operator.

  type counter = { mutable count : int; mutable last : int }
  let bump c = c.count <- c.count + 1 ; c.last <- c.count

After 5 bumps: count=5, last=5, sum=10.
2026-05-08 18:43:19 +00:00
d9979eaf6c ocaml: phase 2 mutable record fields r.f <- v (+4 tests, 451 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
<- added to op-table at level 1 (same as :=). Eval short-circuits on
<- to mutate the lhs's field via host SX dict-set!. The lhs must be a
:field expression; otherwise raises.

Tested:
  let r = { x = 1; y = 2 } in r.x <- 5; r.x       (5)
  let r = { x = 0 } in for i = 1 to 5 do r.x <- r.x + i done; r.x  (15)
  let r = { name = ...; age = 30 } in r.name <- "Alice"; r.name

The 'mutable' keyword in record type decls is parsed-and-discarded;
runtime semantics: every field is mutable. Phase 2 closes this gap
without changing the dict-based record representation.
2026-05-08 18:35:31 +00:00
66da0e5b84 ocaml: phase 1+3 record type declarations (+3 tests, 447 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
type r = { x : int; mutable y : string } parses to
(:type-def-record NAME PARAMS FIELDS) with FIELDS each (NAME) or
(:mutable NAME). Parser dispatches on { after = to parse field list.
Field-type sources are skipped (HM registration TBD). Runtime no-op
since records already work as dynamic dicts.
2026-05-08 18:26:34 +00:00
f070bddb0e ocaml: phase 5.1 conformance.sh integrates baseline (458/458 across 15 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
bash lib/ocaml/conformance.sh now runs lib/ocaml/baseline/run.sh and
aggregates pass/fail counts under a 'baseline' suite. Full-suite
scoreboard now reports both unit-test results and end-to-end OCaml
program runs in a single artifact.
2026-05-08 18:12:23 +00:00
0858986877 ocaml: phase 5.1 btree.ml baseline (13/13 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
Polymorphic binary search tree with insert + in-order traversal.
Exercises parametric ADT (type 'a tree = Leaf | Node of 'a * 'a tree
* 'a tree), recursive match, List.append, List.fold_left.
2026-05-08 17:52:49 +00:00
d8f1882b50 ocaml: phase 5.1 fizzbuzz.ml baseline (12/12 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
Classic fizzbuzz using ref-cell accumulator, for-loop, mod, if/elseif
chain, String.concat, Int.to_string. Output verified via String.length
of the comma-joined result for n=15: 57.
2026-05-08 17:44:07 +00:00
0bc6dbd233 ocaml: phase 2+6 print primitives wire to host display (+2 tests, 444 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
print_string / print_endline / print_int / print_newline now route to
SX display primitive (not the non-existent print/println). print_endline
appends '\n'.

let _ = expr ;; at top level confirmed working via the
wildcard-param parser.
2026-05-08 17:37:00 +00:00
cabf5dc9c3 ocaml: phase 5 HM let-mut / let-rec-mut (+3 tests, 442 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
ocaml-infer-let-mut: each rhs inferred in parent env, generalized
sequentially before adding to body env.

ocaml-infer-let-rec-mut: pre-bind all names with fresh tvs; infer
each rhs against the joint env, unify each with its tv, then
generalize all and infer body.

Mutual recursion now type-checks:
  let rec even n = if n = 0 then true else odd (n - 1)
  and odd n = if n = 0 then false else even (n - 1)
  in even   : Int -> Bool
2026-05-08 17:28:27 +00:00
4909ebe2ad ocaml: phase 6 Option/Result/Bytes extensions (+9 tests, 439 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Option: join, to_result, some, none.
Result: value, iter, fold.
Bytes: length, get, of_string, to_string, concat, sub — thin alias of
String (SX has no separate immutable byte type).

Ordering fix: Bytes module placed after String so its closures capture
String in scope. Earlier draft put Bytes before String which made
String.* lookups fail with 'not a record/module' (treated as nullary
ctor).
2026-05-08 17:19:16 +00:00
f05d405bac ocaml: phase 6 Stack + Queue modules (+5 tests, 430 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
Stack: ref-holding-list LIFO. push/pop/top/length/is_empty/clear.
Queue: two-list (front, back) amortised O(1) queue. push/pop/length/
is_empty/clear. Both in OCaml syntax in runtime.sx.
2026-05-08 17:03:32 +00:00
ffa74399fd ocaml: phase 5.1 calc.ml baseline (11/11 pass) + inline let-rec-and parser fix
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Recursive-descent calculator parses '(1 + 2) * 3 + 4' = 13. Two parser
bugs fixed:

1. parse-let now handles inline 'let rec a () = ... and b () = ... in
   body' via new (:let-rec-mut BINDINGS BODY) and (:let-mut BINDINGS
   BODY) AST shapes; eval handles both.

2. has-matching-in? lookahead no longer stops at 'and' — 'and' is
   internal to let-rec, not a decl boundary. Without this fix, the
   inner 'let rec a () = ... and b () = ...' inside a let-decl rhs
   would have been treated as the start of a new top-level decl.

Baseline exercises mutually-recursive functions, while-loops, ref-cell
imperative parsing, and ADT-based AST construction.
2026-05-08 16:53:44 +00:00
ecdd90345e ocaml: refresh scoreboard (426/426 across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
2026-05-08 16:18:21 +00:00
2f271fa6a6 ocaml: phase 1+6 Buffer + parser !x in app args (+3 tests, 425 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
Parser fix: at-app-start? and parse-app's loop recognise prefix !
as a deref of the next app arg. So 'List.rev !b' parses as
'(:app List.rev (:deref b))' instead of stalling at !.

Buffer module backed by a ref holding string list:
  create _ = ref []
  add_string b s = b := s :: !b
  contents b = String.concat "" (List.rev !b)
  add_char/length/clear/reset
2026-05-08 16:16:52 +00:00
dbe3c6c203 ocaml: phase 5.1 word_count.ml baseline (10/10 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Uses Map.Make(StrOrd) + List.fold_left to count word frequencies;
exercises the full functor pipeline with a real-world idiom:

  let inc_count m word =
    match StrMap.find_opt word m with
    | None -> StrMap.add word 1 m
    | Some n -> StrMap.add word (n + 1) m
  let count words = List.fold_left inc_count StrMap.empty words

10/10 baseline programs pass.
2026-05-08 16:11:03 +00:00
404c908a9a ocaml: phase 6 Map/Set extensions iter/fold/filter/union/inter (+4 tests, 422 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
Map.Make: iter, fold, map, filter, is_empty added.
Set.Make: iter, fold, filter, is_empty, union, inter added.

All written in OCaml inside the existing functor bodies. Tested:
  IntMap.fold (fun k v acc -> acc + v) m 0           = 30
  IntSet.elements (IntSet.union {1,2} {2,3})         = [1; 2; 3]
  IntSet.elements (IntSet.inter {1,2,3} {2,3,4})     = [2; 3]
2026-05-08 16:02:45 +00:00
b297c83b1d ocaml: refresh scoreboard (419/419 across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
2026-05-08 15:51:36 +00:00
85867e329b ocaml: phase 6 Map.Make / Set.Make functors (+4 tests, 418 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
Both written in OCaml inside lib/ocaml/runtime.sx:

  module Map = struct module Make (Ord) = struct
    let empty = []
    let add k v m = ... (* sorted insert via Ord.compare *)
    let find_opt / find / mem / remove / bindings / cardinal
  end end

  module Set = struct module Make (Ord) = struct
    let empty = []
    let mem / add / remove / elements / cardinal
  end end

Sorted association list / sorted list backing — linear ops but
correct. Strong substrate-validation: Map.Make is a non-trivial
functor implemented entirely on top of the OCaml-on-SX evaluator.
2026-05-08 15:50:22 +00:00
cd93b11328 ocaml: phase 6 Sys module constants (+5 tests, 414 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
os_type="SX", word_size=64, max_array_length, max_string_length,
executable_name="ocaml-on-sx", big_endian=false, unix=true,
win32=false, cygwin=false. Constants-only for now — argv/getenv_opt/
command would need host platform integration.
2026-05-08 15:46:33 +00:00
4bca2cacff ocaml: phase 5 parse ctor arg types in user type-defs (+3 tests, 409 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 36s
ocaml-hm-parse-type-src recognises primitive type names (int/bool/
string/float/unit), tyvars 'a, and simple parametric T list / T option.
Replaces the previous int-by-default placeholder in
ocaml-hm-register-type-def!.

So 'type tag = TStr of string | TInt of int' correctly registers
TStr : string -> tag and TInt : int -> tag. Pattern-match on tag
gives proper field types in the body. Multi-arg / function types
still fall back to a fresh tv.
2026-05-08 15:43:16 +00:00
d61ee088c5 ocaml: refresh scoreboard (407/407 across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
2026-05-08 15:35:28 +00:00
f40dfbbeb5 ocaml: phase 6 String extensions (+6 tests, 406 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
ends_with, contains, trim, split_on_char, replace_all, index_of —
wrap host SX primitives via new _string_* builtins. String module
now substantively covers OCaml's Stdlib.String.
2026-05-08 15:34:18 +00:00
9f05e24c52 ocaml: phase 6 List.take/drop/filter_map/flat_map (+6 tests, 400 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
Common functional helpers written in OCaml. flat_map / concat_map
share an implementation. 400-test milestone.
2026-05-08 15:30:29 +00:00
86343345dc ocaml: phase 1+3 or-patterns (P1 | P2 | ...) parens-only (+5 tests, 394 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Parser: when | follows a pattern inside parens, build (:por ALT1 ALT2
...). Eval: try alternatives, succeed on first match. Top-level |
remains the clause separator — parens-only avoids ambiguity without
lookahead.

Examples now work:
  match n with | (1 | 2 | 3) -> 100 | _ -> 0
  match c with | (Red | Green) -> 1 | Blue -> 2
2026-05-08 15:22:34 +00:00
ad252088c3 ocaml: phase 4 module type S = sig … end parser (+3 tests, 389 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
module type S = sig DECLS end is parsed-and-discarded — sig..end
balanced skipping in parse-decl-module-type. AST (:module-type-def
NAME). Runtime no-op (signatures are type-level only).

Allows real OCaml programs with module type decls to parse and run
without stripping the sig blocks.
2026-05-08 15:11:45 +00:00
76ccbfbab6 ocaml: refresh scoreboard (387/387 across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
2026-05-08 15:07:55 +00:00
98049d5458 ocaml: phase 1+3 record patterns { f = pat } (+4 tests, 386 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
Parser: { f1 = pat; f2 = pat; ... } in pattern position emits
(:precord (FIELDNAME PAT)...). Mixed with the existing { in
expression position via the at-pattern-atom? whitelist.

Eval: :precord matches against a dict; required fields must be present
and each pat must match the field's value. Can mix literal+var:
'match { x = 1; y = y } with | { x = 1; y = y } -> y' matches only
when x is 1.
2026-05-08 15:06:44 +00:00
0cf5c8f219 ocaml: phase 5.1 expr_eval.ml baseline (9/9 pass)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
A tiny arithmetic-expression evaluator using:
  type expr = Lit of int | Add of expr*expr | Mul of expr*expr | Neg of expr
  let rec eval e = match e with | Lit n -> n | Add (a,b) -> ...

Exercises type-decl + multi-arg ctor + recursive match end-to-end.
Per-program timeout in run.sh bumped to 120s.
2026-05-08 15:01:04 +00:00
9f539ab392 ocaml: phase 3 polymorphic variants (+4 tests, 382 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
Tokenizer recognises backtick followed by an upper ident, emitting a
ctor token identical to a nominal ctor. Parser and evaluator treat
polyvariants as ctors — same tagged-list runtime. So:

  `Red                   -> ("Red")
  `Some 42               -> ("Some" 42)
  match `Red with | `Red -> 1 | `Green -> 2 | `Blue -> 3
                          -> 1
  `Pair (1,2)            -> ("Pair" 1 2) (with tuple-arg flatten)

Proper row types in HM deferred.
2026-05-08 14:03:09 +00:00
986b15c0e5 ocaml: phase 6 Float module: sqrt/sin/cos/pow/floor/ceil/round/pi (+6 tests, 378 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Wraps host SX math primitives via _float_* builtins. Float.pi is a
Float literal in the OCaml-side module.
2026-05-08 13:58:52 +00:00
ee002f2e02 ocaml: phase 1/5/6 float arithmetic +./-./*./. (+5 tests, 372 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Tokenizer: +. -. *. /. (with -. avoiding clash with negative float
literals). Parser table places dotted ops at int-precedence levels.
Eval routes to host SX +/-/*//. HM types them Float->Float->Float;
literal floats now infer as Float (was Int).

OCaml-style 1.5 +. 2.5 : Float works end-to-end through tokenize +
parse + eval + infer.
2026-05-08 13:55:04 +00:00
16df48ff74 ocaml: phase 6 List.combine/split/iter2/fold_left2/map2 (+4, 367 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
Mechanical pair-walk OCaml implementations. failwith on length
mismatch matches Stdlib semantics. List module now covers 30+
functions.
2026-05-08 13:48:48 +00:00
dac9cf124f ocaml: refresh scoreboard (364/364 across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
2026-05-08 13:45:29 +00:00
46d0eb258e ocaml: phase 5.1 baseline 8/8 — quicksort + exceptions + closures
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 38s
Added 3 baseline programs:
- closures.ml — curried make_adder; verifies closure capture
- quicksort.ml — recursive sort using List.filter + List.append, sums result
- exception_handle.ml — exception NegArg of int + raise + try/with

All 8/8 baseline programs pass through ocaml-run-program. Combined the
suite exercises: let-rec, modules, refs, for-loops, pattern matching,
exceptions, lambdas, list ops (map/filter/append/fold), arithmetic.

run.sh streamlined to one sx_server invocation per program. End-to-end
runtime ≈2 min.
2026-05-08 13:44:28 +00:00
de7be332c8 ocaml: phase 5.1 baseline OCaml programs (5/5 pass) + lookahead boundary
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
lib/ocaml/baseline/{factorial,list_ops,option_match,module_use,sum_squares}.ml
exercised through ocaml-run-program (file-read F). lib/ocaml/baseline/
run.sh runs them and compares against expected.json — all 5 pass.

To make module_use.ml (with nested let-in) parse, parser's
skip-let-rhs-boundary! now uses has-matching-in? lookahead: a let at
depth 0 in a let-decl rhs opens a nested block IFF a matching in
exists before any decl-keyword. Without that in, the let is a new
top-level decl (preserves test 274 'let x = 1 let y = 2').

This is the first piece of Phase 5.1 'vendor a slice of OCaml
testsuite' — handcrafted fixtures for now, real testsuite TBD.
2026-05-08 13:33:24 +00:00
756d5fba64 ocaml: phase 5 HM with user type declarations (+6 tests, 363 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
ocaml-hm-ctors is now a mutable list cell; user type-defs register
their constructors via ocaml-hm-register-type-def!. New
ocaml-type-of-program processes top-level decls in order:
- type-def: register ctors with the scheme inferred from PARAMS+CTORS
- def/def-rec: generalize and bind in the type env
- exception-def: no-op for typing
- expr: return inferred type

Examples:
  type color = Red | Green | Blue;; Red : color
  type shape = Circle of int | Square of int;;
  let area s = match s with
    | Circle r -> r * r
    | Square s -> s * s;;
  area : shape -> Int

Caveat: ctor arg types parsed as raw source strings; registry defaults
to int for any single-arg ctor. Proper type-source parsing pending.
2026-05-08 13:12:07 +00:00
5bc7895ce0 ocaml: phase 5 HM let-rec + cons / append op types (+6 tests, 357 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
ocaml-infer-let-rec pre-binds the function name to a fresh tv before
inferring rhs (which may recursively call the name), unifies the
inferred rhs type with the tv, generalizes, then infers body.

Builtin env types :: : 'a -> 'a list -> 'a list and @ : 'a list ->
'a list -> 'a list — needed because :op compiles to (:app (:app (:var
OP) L) R) and previously these var lookups failed.

Examples now infer:
  let rec fact n = if ... in fact : Int -> Int
  let rec len lst = ... in len    : 'a list -> Int
  let rec map f xs = ... in map   : ('a -> 'b) -> 'a list -> 'b list
  1 :: [2; 3]                      : Int list
  let rec sum lst = ... in sum [1;2;3] : Int

Scoreboard refreshed: 358/358 across 14 suites.
2026-05-08 13:08:51 +00:00
81247eb6ea ocaml: phase 5 HM ctor inference for option/result (+7 tests, 351 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
ocaml-hm-ctor-env registers None/Some : 'a -> 'a option, Ok/Error :
'a -> ('a, 'b) result. :con NAME instantiates the scheme; :pcon NAME
ARG-PATS walks arg patterns through the constructor's arrow type,
unifying each.

Pretty-printer renders 'Int option' and '(Int, 'b) result'.

Examples now infer:
  fun x -> Some x : 'a -> 'a option
  match Some 5 with | None -> 0 | Some n -> n : Int
  fun o -> match o with | None -> 0 | Some n -> n : Int option -> Int
  Ok 1 : (Int, 'b) result
  Error "oops" : ('a, String) result

User type-defs would extend the registry — pending.
2026-05-08 13:05:22 +00:00
d2bf0c0d00 ocaml: phase 5 HM pattern-match inference (+5 tests, 344 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
ocaml-infer-pat covers :pwild, :pvar, :plit, :pcons, :plist, :ptuple,
:pas. Returns {:type T :env ENV2 :subst S} where ENV2 has the pattern's
bound names threaded through.

ocaml-infer-match unifies each clause's pattern type with the scrutinee,
runs the body in the env extended with pattern bindings, and unifies
all body types via a fresh result tv.

Examples:
  fun lst -> match lst with | [] -> 0 | h :: _ -> h : Int list -> Int
  match (1, 2) with | (a, b) -> a + b              : Int

Constructor patterns (:pcon) fall through to a fresh tv for now —
proper handling needs a ctor type registry from 'type' declarations.
2026-05-08 13:02:15 +00:00
202ea9cf5f ocaml: phase 6 List.sort + compare (+7 tests, 339 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
compare is a host builtin returning -1/0/1 (Stdlib.compare semantics)
deferred to host SX </>. List.sort is insertion-sort in OCaml: O(n²)
but works correctly. List.stable_sort = sort.

Tested: ascending int sort, descending via custom comparator (b - a),
empty list, string sort.
2026-05-08 12:59:50 +00:00
812aa75d43 ocaml: phase 6 Hashtbl (+6 tests, 332 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
Backing store is a one-element list cell holding a SX dict; keys
coerced to strings via str so int/string keys work uniformly. API:
create, add, replace, find, find_opt, mem, length.

_hashtbl_create / _hashtbl_add / _hashtbl_replace / _hashtbl_find_opt /
_hashtbl_mem / _hashtbl_length primitives wired in eval.sx; OCaml-side
Hashtbl module wraps them in lib/ocaml/runtime.sx.
2026-05-08 12:57:22 +00:00
6d7197182e ocaml: phase 5 HM tuple + list types (+7 tests, 326 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Tuple type (hm-con "*" TYPES); list type (hm-con "list" (TYPE)).
ocaml-infer-tuple threads substitution through each item left-to-right.
ocaml-infer-list unifies all items with a fresh 'a (giving 'a list for
empty []).

Pretty-printer renders 'Int * Int' for tuples and 'Int list' for lists,
matching standard OCaml notation.

Examples:
  fun x y -> (x, y) : 'a -> 'b -> 'a * 'b
  fun x -> [x; x] : 'a -> 'a list
  []                : 'a list
2026-05-08 12:54:15 +00:00
a0abdcf520 ocaml: refresh scoreboard (320/320 across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
2026-05-08 12:50:39 +00:00
88c02c7c73 ocaml: phase 6 expanded stdlib (+15 tests, 319 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
List: concat/flatten, init, find/find_opt, partition, mapi/iteri,
assoc/assoc_opt. Option: iter/fold/to_list. Result: get_ok/get_error/
map_error/to_option.

Fixed skip-to-boundary! in parser to track let..in / begin..end /
struct..end / for/while..done nesting via a depth counter — without
this, nested-let inside a top-level decl body trips over the
decl-boundary detector. Stdlib functions like List.init / mapi / iteri
use begin..end to make their nested-let intent explicit.
2026-05-08 12:49:23 +00:00
bc557a5ad2 ocaml: phase 3 exception declarations (+4 tests, 304 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
exception NAME [of TYPE] parses to (:exception-def NAME [ARG-SRC]).
Runtime is a no-op: raise/match already work on tagged ctor values, so
'exception E of int;; try raise (E 5) with | E n -> n' end-to-end with
zero new eval logic.
2026-05-08 12:37:58 +00:00
d8f6250962 ocaml: phase 3 type declarations (+5 tests, 300 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
Parser: type [PARAMS] NAME = | Ctor [of T1 [* T2]*] | ...
- PARAMS: optional 'a or ('a, 'b) tyvar list
- AST: (:type-def NAME PARAMS CTORS) with each CTOR (NAME ARG-SOURCES)
- Argument types captured as raw source strings (treated opaquely at
  runtime since ctor dispatch is dynamic)

Runtime is a no-op — constructors and pattern matching already work
dynamically. Phase 5 will use these decls to register ctor types for
HM checking.
2026-05-08 12:32:39 +00:00
851e0585cf ocaml: phase 3 'as' alias + 'when' guard in match (+6 tests, 295 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
Pattern parser top wraps cons-pat with 'as ident' -> (:pas PAT NAME).
Match clause parser consumes optional 'when GUARD-EXPR' before -> and
emits (:case-when PAT GUARD BODY) instead of :case.

Eval: :pas matches inner pattern then binds the alias name; case-when
checks the guard after a successful match and falls through to the next
clause if the guard is false.

Or-patterns deferred — ambiguous with clause separator without
parens-only support.
2026-05-08 12:28:07 +00:00
7fb65cd26a ocaml: phase 1+2 records {x=1;y=2} + with-update (+6 tests, 289 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Parser: { f = e; f = e; ... } -> (:record (F E)...). { base with f = e;
... } -> (:record-update BASE (F E)...). Eval builds a dict from field
bindings; record-update merges the new fields over the base dict — the
same dict representation already used for modules.

{ also added to at-app-start? so records are valid arg atoms. Field
access via the existing :field postfix unifies record/module access.

Record patterns deferred to a later iteration.
2026-05-08 09:26:24 +00:00
9473911cf3 ocaml: phase 5.1 conformance.sh + scoreboard (283 tests across 14 suites)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
lib/ocaml/conformance.sh runs the full test suite, classifies each
result by description prefix into one of 14 suites (tokenize, parser,
eval-core, phase2-refs/loops/function/exn, phase3-adt, phase4-modules,
phase5-hm, phase6-stdlib, let-and, phase1-params, misc), and emits
scoreboard.json + scoreboard.md.

Per the briefing: "Once the scoreboard exists (Phase 5.1), it is your
north star." Real OCaml testsuite vendoring deferred — needs more
stdlib + ADT decls to make .ml files runnable.
2026-05-08 09:23:06 +00:00
74b80e6b0e ocaml: phase 1 unit/wildcard params + 180s timeout (+5 tests, 283 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s
Parser: try-consume-param! handles ident, wildcard _ (fresh __wild_N
name), unit () (fresh __unit_N), typed (x : T) (skips signature).
parse-fun and parse-let (inline) reuse the helper; top-level
parse-decl-let inlines a similar test.

test.sh timeout bumped from 60s to 180s — the growing suite was hitting
the cap and reporting spurious failures.
2026-05-08 09:21:06 +00:00
c8bfd22786 ocaml: phase 6 String/Char/Int/Float/Printf modules (+13 tests, 278 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Host primitives _string_length / _string_sub / _char_code / etc. exposed
in the base env (underscore-prefixed to avoid user clash). lib/ocaml/
runtime.sx wraps them into OCaml-syntax modules: String (length, get,
sub, concat, uppercase/lowercase_ascii, starts_with), Char (code, chr,
lowercase/uppercase_ascii), Int (to_string, of_string, abs, max, min),
Float.to_string, Printf stubs.

Also added print_string / print_endline / print_int IO builtins.
2026-05-08 09:10:06 +00:00
26863242a0 ocaml: phase 5 HM type inference — closes lib-guest step 8 (+14 tests, 265 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
OCaml-on-SX is the deferred second consumer for lib/guest/hm.sx step 8.
lib/ocaml/infer.sx assembles Algorithm W on top of the shipped algebra:

- Var: lookup + hm-instantiate.
- Fun: fresh-tv per param, auto-curried via recursion.
- App: unify against hm-arrow, fresh-tv for result.
- Let: generalize rhs over (ftv(t) - ftv(env)) — let-polymorphism.
- If: unify cond with Bool, both branches with each other.
- Op (+, =, <, etc.): builtin signatures (int*int->int monomorphic,
  =/<> polymorphic 'a->'a->bool).

Tests pass for: literals, fun x -> x : 'a -> 'a, let id ... id 5/id true,
fun f x -> f (f x) : ('a -> 'a) -> 'a -> 'a (twice).

Pending: tuples, lists, pattern matching, let-rec, modules in HM.
2026-05-08 09:02:25 +00:00
4c6790046c ocaml: phase 2 let..and.. mutual recursion (+3 tests, 251 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Parser collects multiple bindings via 'and', emitting (:def-rec-mut
BINDINGS) for let-rec chains and (:def-mut BINDINGS) for non-rec.
Single bindings keep the existing (:def …) / (:def-rec …) shapes.

Eval (def-rec-mut): allocate placeholder cell per binding, build joint
env where each name forwards through its cell, then evaluate each rhs
against the joint env and fill the cells. Even/odd mutual-rec works.
2026-05-08 08:53:53 +00:00
19f1cad11d ocaml: phase 6 stdlib slice (List/Option/Result, +23 tests, 248 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
lib/ocaml/runtime.sx defines the stdlib in OCaml syntax (not SX): every
function exercises the parser, evaluator, match engine, and module
machinery built in earlier phases. Loaded once via ocaml-load-stdlib!,
cached in ocaml-stdlib-env, layered under user code via ocaml-base-env.

List: length, rev, rev_append, map, filter, fold_left/right, append,
iter, mem, for_all, exists, hd, tl, nth.
Option: map, bind, value, get, is_none, is_some.
Result: map, bind, is_ok, is_error.

Substrate validation: this stdlib is a nontrivial OCaml program — its
mere existence proves the substrate works.
2026-05-08 08:49:44 +00:00
5603ecc3a6 ocaml: phase 4 functors + module aliases (+5 tests, 225 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Parser: module F (M) (N) ... = struct DECLS end -> (:functor-def NAME
PARAMS DECLS). module N = expr (non-struct) -> (:module-alias NAME
BODY-SRC). Functor params accept (P) or (P : Sig) — signatures
parsed-and-skipped via skip-optional-sig.

Eval: ocaml-make-functor builds curried host-SX closures from module
dicts to a module dict. ocaml-resolve-module-path extended for :app so
F(A), F(A)(B), and Outer.Inner all resolve to dicts.

Phase 4 LOC ~290 cumulative (still well under 2000).
2026-05-08 08:44:54 +00:00
d45e653a87 ocaml: phase 4 open / include (+5 tests, 220 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
Parser: open Path and include Path top-level decls; Path is Ctor (.Ctor)*.
Eval resolves via ocaml-resolve-module-path (same :con-as-module-lookup
escape hatch used by :field). open extends the env with the module's
bindings; include also merges into the surrounding module's exports
(when inside a struct...end).

Path resolver lets M.Sub.x work for nested modules. Phase 4 LOC ~165.
2026-05-08 08:39:13 +00:00
317f93b2af ocaml: phase 4 modules + field access (+11 tests, 215 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
module M = struct DECLS end parsed by sub-tokenising the body source
between struct and the matching end (nesting tracked via struct/begin/
sig/end). Field access is a postfix layer above parse-atom, binding
tighter than application: f r.x -> (:app f (:field r "x")).

Eval (:module-def NAME DECLS) builds a dict via ocaml-eval-module
running decls in a sub-env. (:field EXPR NAME) looks up dict fields,
treating (:con NAME) heads as module-name lookups instead of nullary
ctors so M.x works with M as a module.

Phase 4 LOC so far: ~110 lines (well under 2000 budget).
2026-05-08 08:33:34 +00:00
6a1f63f0d1 ocaml: phase 2 try/with + raise (+6 tests, 204 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
Parser: try EXPR with | pat -> handler | ... -> (:try EXPR CLAUSES).
Eval delegates to SX guard with else matching the raised value against
clause patterns; re-raises on no-match. raise/failwith/invalid_arg
shipped as builtins. failwith "msg" raises ("Failure" msg) so
| Failure msg -> ... patterns match.
2026-05-08 08:20:11 +00:00
937342bbf0 ocaml: phase 2 function | pat -> body (+4 tests, 198 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Sugar for fun + match. AST (:function CLAUSES) -> unary closure that
runs ocaml-match-clauses on its arg. let rec recognises :function as a
recursive rhs and ties the knot via cell, so

  let rec map f = function | [] -> [] | h::t -> f h :: map f t

works. ocaml-match-eval refactored to share clause-walk with function.
2026-05-08 08:15:38 +00:00
9b8b0b4325 ocaml: phase 2 for/while loops (+5 tests, 194 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
Parser: for i = lo to|downto hi do body done, while cond do body done.
AST: (:for NAME LO HI :ascend|:descend BODY) and (:while COND BODY).
Eval re-binds the loop var per iteration; both forms evaluate to unit.
2026-05-08 08:11:13 +00:00
a11f3c33b6 ocaml: phase 2 references ref/!/:= (+6 tests, 189 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 55s
ref is a builtin boxing its arg in a one-element list. Prefix ! parses
to (:deref ...) and reads via (nth cell 0). := joins the binop
precedence table at level 1 right-assoc and mutates via set-nth!.
Closures share the underlying cell.
2026-05-08 08:07:26 +00:00
9b833a9442 ocaml: phase 3 pattern matching + constructors (+18 tests, 183 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
Constructor app: (:app (:con NAME) arg) -> (NAME …args). Tuple args
flatten so Pair(a,b) -> ("Pair" a b), matching the parser's pattern
flatten. Standalone (:con NAME) -> (NAME) nullary.

Pattern matcher: :pwild, :pvar, :plit, :pcon (head + arity), :pcons
(decompose), :plist (length match), :ptuple (after tuple tag). Match
walks clauses until first success; runtime error on exhaustion.
Recursive list functions (len, sum, fact) work end-to-end.
2026-05-08 08:02:56 +00:00
4dca583ee3 ocaml: phase 2 evaluator slice (+42 tests, 165 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
ocaml-eval walks the AST and yields SX values. ocaml-run / ocaml-run-program
wrap parse + eval. Coverage: atoms, vars, app (curried), 22 binary ops,
prefix - and not, if/seq/tuple/list, fun (auto-curried via host SX
lambdas), let, let-rec (mutable-cell knot for recursive functions).
Initial env: not/succ/pred/abs/max/min/fst/snd/ignore. Tests: arithmetic,
comparison, string concat, closures, fact 5 / fib 10 / sum 100,
top-level decls, |> pipe.
2026-05-08 07:57:20 +00:00
a6ab944c39 ocaml: phase 1 sequence operator ; (+10 tests, 123 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Two-phase grammar: parse-expr-no-seq (prior entry) + parse-expr wraps
it with ;-chaining. List bodies keep parse-expr-no-seq so ; remains a
separator inside [...]. Match clause bodies use the seq variant and stop
at | — real OCaml semantics. Trailing ; before end/)/|/in/then/else/eof
permitted.
2026-05-08 07:48:52 +00:00
9102e57d89 ocaml: phase 1 match/with + pattern parser (+9 tests, 113 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
Patterns: wildcard, literal, var, ctor (nullary + arg, flattens tuple
args so Pair(a,b) -> (:pcon "Pair" PA PB)), tuple, list literal, cons
:: (right-assoc), unit. Match: leading | optional, (:match SCRUT
CLAUSES) with each clause (:case PAT BODY). Body parsed via parse-expr
because | is below level-1 binop precedence.
2026-05-08 07:29:02 +00:00
9648dac88d ocaml: phase 1 top-level decls (+9 tests, 104 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
ocaml-parse-program: program = decls + bare exprs, ;;-separated.
Each decl is (:def …), (:def-rec …), or (:expr …). Body parsing
re-feeds the source slice through ocaml-parse — shared-state refactor
deferred.
2026-05-08 07:25:11 +00:00
9a090c6e42 ocaml: phase 1 expression parser (+37 tests, 95 total) — consumes lib/guest/pratt.sx
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 55s
Atoms (literals/var/con/unit/list), application (left-assoc), prefix - / not,
29-op precedence table via pratt-op-lookup (incl. keyword-spelled mod/land/
lor/lxor/lsl/lsr/asr), tuples, parens, if/then/else, fun, let, let rec
with function shorthand. AST follows Haskell-on-SX (:int / :op / :fun / etc).
2026-05-07 23:26:48 +00:00
85b7fed4fc ocaml: phase 1 tokenizer (+58 tests) — consumes lib/guest/lex.sx
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
Idents, ctors, 51 keywords, numbers (int/float/hex/exp/underscored),
strings + chars with escapes, type variables, 26 op/punct tokens, and
nested (* ... *) block comments. Tests via epoch protocol against
sx_server.exe.
2026-05-07 23:04:40 +00:00
1eb9d0f8d2 merge: loops/apl — Phase 8 quick-wins, named fns, multi-axis, trains, perf
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
2026-05-07 19:46:21 +00:00
f182d04e6a GUEST-plan: log step 8 partial — algebra + literal rule, assembly deferred
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:45:23 +00:00
ab2c40c14c GUEST: step 8 — lib/guest/hm.sx Hindley-Milner foundations
Ships the algebra for HM-style type inference, riding on
lib/guest/match.sx (terms + unify) and ast.sx (canonical AST):

  • Type constructors: hm-tv, hm-arrow, hm-con, hm-int, hm-bool, hm-string
  • Schemes: hm-scheme / hm-monotype + accessors
  • Free type-vars: hm-ftv, hm-ftv-scheme, hm-ftv-env
  • Substitution: hm-apply, hm-apply-scheme, hm-apply-env, hm-compose
  • Generalize / Instantiate (with shared fresh-tv counter)
  • hm-fresh-tv (counter is a (list N) the caller threads)
  • hm-infer-literal (the only fully-closed inference rule)

24 self-tests in lib/guest/tests/hm.sx covering every function above.

The lambda / app / let inference rules — the substitution-threading
core of Algorithm W — intentionally live in HOST CODE rather than the
kit, because each host's AST shape and substitution-threading idiom
differ subtly enough that forcing one shared assembly here proved
brittle in practice (an earlier inline-assembled hm-infer faulted with
"Not callable: nil" only when defined in the kit, despite working when
inline-eval'd or in a separate file — a load/closure interaction not
worth chasing inside this step's budget). The host gets the algebra
plus a spec; assembly stays close to the AST it reasons over.

PARTIAL — algebra + literal rule shipped; full Algorithm W deferred
to host consumers (haskell/infer.sx, lib/ocaml/types.sx when
OCaml-on-SX Phase 5 lands per the brief's sequencing note). Haskell
infer.sx untouched; haskell scoreboard still 156/156 baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:45:10 +00:00
d3c34b46b9 GUEST-plan: claim step 8 — hm.sx
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:35:05 +00:00
80dac0051d apl: perf — fix quadratic append in permutations, restore queens(8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
apl-permutations was doing (append acc <new-perms>) which is
O(|acc|) and acc grows ~N! big — total cost O(N!²).

Swapped to (append <new-perms> acc) — append is O(|first|)
so cost is O((n+1)·N!_prev) per layer, total O(N!).  q(7)
went from 32s to 12s; q(8)=92 now finishes well within the
300s timeout, so the queens(8) test is restored.

497/497.  Phase 8 complete.
2026-05-07 19:33:09 +00:00
b661318a45 apl: train/fork notation (f g h) and (g h) (+6 tests, 496/496)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
Parser: when a parenthesised subexpression contains only function
segments (>= 2), collect-segments-loop now emits a :train AST node
instead of treating it as a value-producing expression.

Resolver: apl-resolve-{monadic,dyadic} handle :train.
- monadic 2-train (atop):  (g h)⍵ = g (h ⍵)
- monadic 3-train (fork):  (f g h)⍵ = (f ⍵) g (h ⍵)
- dyadic 2-train:          ⍺(g h)⍵ = g (⍺ h ⍵)
- dyadic 3-train:          ⍺(f g h)⍵ = (⍺ f ⍵) g (⍺ h ⍵)

apl-run "(+/÷≢) 1 2 3 4 5"  → 3   (mean)
apl-run "(- ⌊) 5"           → -5  (atop)
apl-run "2 (+ × -) 5"       → -21 (dyadic fork)
apl-run "(⌈/-⌊/) 3 1 4 …"   → 8   (range)
2026-05-07 19:02:17 +00:00
47d9d07f2e GUEST-plan: log step 7 partial — kit + synthetic, haskell port deferred
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:55:48 +00:00
d75c61d408 GUEST: step 7 — lib/guest/layout.sx off-side / layout-sensitive lexer
Configurable layout pass that inserts virtual open / close / separator
tokens based on indentation. Supports both styles the brief calls out:

  • Haskell-flavour: layout opens AFTER a reserved keyword
    (let/where/do/of) and resolves to the next token's column. Module
    prelude wraps the whole input in an implicit block. Explicit `{`
    after the keyword suppresses virtual layout.

  • Python-flavour: layout opens via an :open-trailing-fn predicate
    fired AFTER the trigger token (e.g. trailing `:`) — and resolves
    to the column of the next token, which in real source is on a
    fresh line. No module prelude.

Public entry: (layout-pass cfg tokens). Token shape: dict with at
least :type :value :line :col; everything else passes through. Newline
filler tokens are NOT used — line-break detection is via :line.

lib/guest/tests/layout.sx — 6 tests covering both flavours:
  haskell-do-block / haskell-explicit-brace / haskell-do-inline /
  haskell-module-prelude / python-if-block / python-nested.

Per the brief's gotcha note ("Don't ship lib/guest/layout.sx unless
the haskell scoreboard equals baseline") — haskell/layout.sx is left
UNTOUCHED. The kit isn't yet a drop-in replacement for the full
Haskell 98 algorithm (Note 5, multi-stage pre-pass, etc.) and forcing
a port would risk the 156 currently passing programs. Haskell
scoreboard remains at 156/156 baseline because no haskell file
changed. The synthetic Python-ish fixture is the second consumer per
the brief's wording.

PARTIAL — kit + synthetic fixture shipped; haskell port deferred until
the kit grows the missing Haskell-98 wrinkles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:55:38 +00:00
a677585639 apl: programs-e2e + ⌿/⍀ glyph fix (+15 tests, 490/490)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
programs-e2e.sx exercises the classic-algorithm shapes from
lib/apl/tests/programs/*.apl via the full pipeline (apl-run on
embedded source strings).  Tests include factorial-via-∇,
triangular numbers, sum-of-squares, prime-mask building blocks
(divisor counts via outer mod), named-fn composition,
dyadic max-of-two, and a single Newton sqrt step.

The original one-liners (e.g. primes' inline ⍵←⍳⍵) need parser
features we haven't built (compress-as-fn, inline assign) — the
e2e tests use multi-statement equivalents.  No file-reading
primitive in OCaml SX, so source is embedded.

Side-fix: ⌿ (first-axis reduce) and ⍀ (first-axis scan) were
silently skipped by the tokenizer — added to apl-glyph-set
and apl-parse-op-glyphs.
2026-05-07 18:31:57 +00:00
c04f38a1ba apl: multi-axis bracket A[I;J] / A[I;] / A[;J] (+8 tests, 475/475)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
Parser: split-bracket-content splits inner tokens on :semi at
depth 0; maybe-bracket emits (:bracket arr axis-exprs...) for
multi-axis access, with :all marker for empty axes.

Runtime: apl-bracket-multi enumerates index combinations via
apl-cartesian (helper) and produces sub-array. Scalar axes
collapse from result shape; vector / nil axes contribute their
length.

apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[2;2]"  → 5
apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[1;]"   → 1 2 3
apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[;2]"   → 2 5 8
apl-run "M ← (2 3) ⍴ ⍳6 ⋄ M[1 2;1 2]" → 2x2 sub-block
2026-05-07 17:56:24 +00:00
b13819c50c apl: named function definitions f ← {…} (+7 tests, 467/467)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Parser: apl-collect-fn-bindings pre-scans stmt-groups for
`name ← { ... }` patterns and populates apl-known-fn-names.
is-fn-tok? consults this list; collect-segments-loop emits
(:fn-name nm) for known names so they parse as functions.

Resolver: apl-resolve-{monadic,dyadic} handle :fn-name by
looking up env, asserting the binding is a dfn, returning
a closure that dispatches to apl-call-dfn{-m,}.

Recursion still works: `fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5` → 120.
2026-05-07 17:33:41 +00:00
d9cf00f287 apl: quick-wins bundle — decimals + ⎕← + strings (+10 tests, 460/460)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
Three small unblockers in one iteration:
- tokenizer: read-digits! now consumes optional ".digits" suffix,
  so 3.7 and ¯2.5 are single number tokens.
- tokenizer: ⎕ followed by ← emits a single :name "⎕←" token
  (instead of splitting on the assign glyph).  Parser registers
  ⎕← in apl-quad-fn-names; apl-monadic-fn maps to apl-quad-print.
- eval-ast: :str AST nodes evaluate to char arrays.  Single-char
  strings become rank-0 scalars; multi-char become rank-1 vectors
  of single-char strings.
2026-05-07 17:26:37 +00:00
0c0ed0605a plans: Phase 8 — quick-wins, named fns, multi-axis brackets, .apl-as-tests, trains, perf
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
2026-05-07 17:20:47 +00:00
183 changed files with 14541 additions and 149 deletions

View File

@@ -25,8 +25,9 @@
; Glyph classification sets ; Glyph classification sets
; ============================================================ ; ============================================================
(define apl-parse-op-glyphs (define
(list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@")) apl-parse-op-glyphs
(list "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
(define (define
apl-parse-fn-glyphs apl-parse-fn-glyphs
@@ -82,22 +83,48 @@
"⍎" "⍎"
"⍕")) "⍕"))
(define apl-quad-fn-names (list "⎕FMT")) (define apl-quad-fn-names (list "⎕FMT" "⎕←"))
(define (define apl-known-fn-names (list))
apl-parse-op-glyph?
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
; ============================================================ ; ============================================================
; Token accessors ; Token accessors
; ============================================================ ; ============================================================
(define
apl-collect-fn-bindings
(fn
(stmt-groups)
(set! apl-known-fn-names (list))
(for-each
(fn
(toks)
(when
(and
(>= (len toks) 3)
(= (tok-type (nth toks 0)) :name)
(= (tok-type (nth toks 1)) :assign)
(= (tok-type (nth toks 2)) :lbrace))
(set!
apl-known-fn-names
(cons (tok-val (nth toks 0)) apl-known-fn-names))))
stmt-groups)))
(define
apl-parse-op-glyph?
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
(define (define
apl-parse-fn-glyph? apl-parse-fn-glyph?
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs))) (fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
(define tok-type (fn (tok) (get tok :type))) (define tok-type (fn (tok) (get tok :type)))
; ============================================================
; Collect trailing operators starting at index i
; Returns {:ops (op ...) :end new-i}
; ============================================================
(define tok-val (fn (tok) (get tok :value))) (define tok-val (fn (tok) (get tok :value)))
(define (define
@@ -107,8 +134,8 @@
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok))))) (and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
; ============================================================ ; ============================================================
; Collect trailing operators starting at index i ; Build a derived-fn node by chaining operators left-to-right
; Returns {:ops (op ...) :end new-i} ; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
; ============================================================ ; ============================================================
(define (define
@@ -119,15 +146,17 @@
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok))) (and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
(and (and
(= (tok-type tok) :name) (= (tok-type tok) :name)
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names))))) (or
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)
(some (fn (q) (= q (tok-val tok))) apl-known-fn-names))))))
; ============================================================
; Find matching close bracket/paren/brace
; Returns the index of the matching close token
; ============================================================
(define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list)))) (define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list))))
; ============================================================
; Build a derived-fn node by chaining operators left-to-right
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
; ============================================================
(define (define
collect-ops-loop collect-ops-loop
(fn (fn
@@ -143,8 +172,10 @@
{:end i :ops acc}))))) {:end i :ops acc})))))
; ============================================================ ; ============================================================
; Find matching close bracket/paren/brace ; Segment collection: scan tokens left-to-right, building
; Returns the index of the matching close token ; a list of {:kind "val"/"fn" :node ast} segments.
; Operators following function glyphs are merged into
; derived-fn nodes during this pass.
; ============================================================ ; ============================================================
(define (define
@@ -163,12 +194,20 @@
(find-matching-close-loop tokens start open-type close-type 1))) (find-matching-close-loop tokens start open-type close-type 1)))
; ============================================================ ; ============================================================
; Segment collection: scan tokens left-to-right, building ; Build tree from segment list
; a list of {:kind "val"/"fn" :node ast} segments. ;
; Operators following function glyphs are merged into ; The segments are in left-to-right order.
; derived-fn nodes during this pass. ; APL evaluates right-to-left, so the LEFTMOST function is
; the outermost (last-evaluated) node.
;
; Patterns:
; [val] → val node
; [fn val ...] → (:monad fn (build-tree rest))
; [val fn val ...] → (:dyad fn val (build-tree rest))
; [val val ...] → (:vec val1 val2 ...) — strand
; ============================================================ ; ============================================================
; Find the index of the first function segment (returns -1 if none)
(define (define
find-matching-close-loop find-matching-close-loop
(fn (fn
@@ -208,21 +247,9 @@
collect-segments collect-segments
(fn (tokens) (collect-segments-loop tokens 0 (list)))) (fn (tokens) (collect-segments-loop tokens 0 (list))))
; ============================================================ ; Build an array node from 0..n value segments
; Build tree from segment list ; If n=1 → return that segment's node
; ; If n>1 → return (:vec node1 node2 ...)
; The segments are in left-to-right order.
; APL evaluates right-to-left, so the LEFTMOST function is
; the outermost (last-evaluated) node.
;
; Patterns:
; [val] → val node
; [fn val ...] → (:monad fn (build-tree rest))
; [val fn val ...] → (:dyad fn val (build-tree rest))
; [val val ...] → (:vec val1 val2 ...) — strand
; ============================================================
; Find the index of the first function segment (returns -1 if none)
(define (define
collect-segments-loop collect-segments-loop
(fn (fn
@@ -242,24 +269,38 @@
((= tt :str) ((= tt :str)
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)}))) (collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
((= tt :name) ((= tt :name)
(if (cond
(some (fn (q) (= q tv)) apl-quad-fn-names) ((some (fn (q) (= q tv)) apl-quad-fn-names)
(let
((op-result (collect-ops tokens (+ i 1))))
(let (let
((ops (get op-result :ops)) (ni (get op-result :end))) ((op-result (collect-ops tokens (+ i 1))))
(let (let
((fn-node (build-derived-fn (list :fn-glyph tv) ops))) ((ops (get op-result :ops))
(collect-segments-loop (ni (get op-result :end)))
tokens (let
ni ((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
(append acc {:kind "fn" :node fn-node}))))) (collect-segments-loop
(let tokens
((br (maybe-bracket (list :name tv) tokens (+ i 1)))) ni
(collect-segments-loop (append acc {:kind "fn" :node fn-node}))))))
tokens ((some (fn (q) (= q tv)) apl-known-fn-names)
(nth br 1) (let
(append acc {:kind "val" :node (nth br 0)}))))) ((op-result (collect-ops tokens (+ i 1))))
(let
((ops (get op-result :ops))
(ni (get op-result :end)))
(let
((fn-node (build-derived-fn (list :fn-name tv) ops)))
(collect-segments-loop
tokens
ni
(append acc {:kind "fn" :node fn-node}))))))
(else
(let
((br (maybe-bracket (list :name tv) tokens (+ i 1))))
(collect-segments-loop
tokens
(nth br 1)
(append acc {:kind "val" :node (nth br 0)}))))))
((= tt :lparen) ((= tt :lparen)
(let (let
((end (find-matching-close tokens (+ i 1) :lparen :rparen))) ((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
@@ -267,11 +308,23 @@
((inner-tokens (slice tokens (+ i 1) end)) ((inner-tokens (slice tokens (+ i 1) end))
(after (+ end 1))) (after (+ end 1)))
(let (let
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after))) ((inner-segs (collect-segments inner-tokens)))
(collect-segments-loop (if
tokens (and
(nth br 1) (>= (len inner-segs) 2)
(append acc {:kind "val" :node (nth br 0)})))))) (every? (fn (s) (= (get s :kind) "fn")) inner-segs))
(let
((train-node (cons :train (map (fn (s) (get s :node)) inner-segs))))
(collect-segments-loop
tokens
after
(append acc {:kind "fn" :node train-node})))
(let
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after)))
(collect-segments-loop
tokens
(nth br 1)
(append acc {:kind "val" :node (nth br 0)}))))))))
((= tt :lbrace) ((= tt :lbrace)
(let (let
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace))) ((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
@@ -346,9 +399,12 @@
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0))) (define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
; Build an array node from 0..n value segments
; If n=1 → return that segment's node ; ============================================================
; If n>1 → return (:vec node1 node2 ...) ; Split token list on statement separators (diamond / newline)
; Only splits at depth 0 (ignores separators inside { } or ( ) )
; ============================================================
(define (define
find-first-fn-loop find-first-fn-loop
(fn (fn
@@ -370,10 +426,9 @@
(get (first segs) :node) (get (first segs) :node)
(cons :vec (map (fn (s) (get s :node)) segs))))) (cons :vec (map (fn (s) (get s :node)) segs)))))
; ============================================================ ; ============================================================
; Split token list on statement separators (diamond / newline) ; Parse a dfn body (tokens between { and })
; Only splits at depth 0 (ignores separators inside { } or ( ) ) ; Handles guard expressions: cond : expr
; ============================================================ ; ============================================================
(define (define
@@ -408,11 +463,6 @@
split-statements split-statements
(fn (tokens) (split-statements-loop tokens (list) (list) 0))) (fn (tokens) (split-statements-loop tokens (list) (list) 0)))
; ============================================================
; Parse a dfn body (tokens between { and })
; Handles guard expressions: cond : expr
; ============================================================
(define (define
split-statements-loop split-statements-loop
(fn (fn
@@ -467,6 +517,10 @@
((stmt-groups (split-statements tokens))) ((stmt-groups (split-statements tokens)))
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts))))) (let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
; ============================================================
; Parse a single statement (assignment or expression)
; ============================================================
(define (define
parse-dfn-stmt parse-dfn-stmt
(fn (fn
@@ -483,12 +537,17 @@
(parse-apl-expr body-tokens))) (parse-apl-expr body-tokens)))
(parse-stmt tokens))))) (parse-stmt tokens)))))
; ============================================================
; Parse an expression from a flat token list
; ============================================================
(define (define
find-top-level-colon find-top-level-colon
(fn (tokens i) (find-top-level-colon-loop tokens i 0))) (fn (tokens i) (find-top-level-colon-loop tokens i 0)))
; ============================================================ ; ============================================================
; Parse a single statement (assignment or expression) ; Main entry point
; parse-apl: string → AST
; ============================================================ ; ============================================================
(define (define
@@ -508,10 +567,6 @@
((and (= tt :colon) (= depth 0)) i) ((and (= tt :colon) (= depth 0)) i)
(true (find-top-level-colon-loop tokens (+ i 1) depth))))))) (true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
; ============================================================
; Parse an expression from a flat token list
; ============================================================
(define (define
parse-stmt parse-stmt
(fn (fn
@@ -526,11 +581,6 @@
(parse-apl-expr (slice tokens 2))) (parse-apl-expr (slice tokens 2)))
(parse-apl-expr tokens)))) (parse-apl-expr tokens))))
; ============================================================
; Main entry point
; parse-apl: string → AST
; ============================================================
(define (define
parse-apl-expr parse-apl-expr
(fn (fn
@@ -547,13 +597,52 @@
((tokens (apl-tokenize src))) ((tokens (apl-tokenize src)))
(let (let
((stmt-groups (split-statements tokens))) ((stmt-groups (split-statements tokens)))
(if (begin
(= (len stmt-groups) 0) (apl-collect-fn-bindings stmt-groups)
nil
(if (if
(= (len stmt-groups) 1) (= (len stmt-groups) 0)
(parse-stmt (first stmt-groups)) nil
(cons :program (map parse-stmt stmt-groups)))))))) (if
(= (len stmt-groups) 1)
(parse-stmt (first stmt-groups))
(cons :program (map parse-stmt stmt-groups)))))))))
(define
split-bracket-loop
(fn
(tokens current acc depth)
(if
(= (len tokens) 0)
(append acc (list current))
(let
((tok (first tokens)) (more (rest tokens)))
(let
((tt (tok-type tok)))
(cond
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
(split-bracket-loop
more
(append current (list tok))
acc
(+ depth 1)))
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
(split-bracket-loop
more
(append current (list tok))
acc
(- depth 1)))
((and (= tt :semi) (= depth 0))
(split-bracket-loop
more
(list)
(append acc (list current))
depth))
(else
(split-bracket-loop more (append current (list tok)) acc depth))))))))
(define
split-bracket-content
(fn (tokens) (split-bracket-loop tokens (list) (list) 0)))
(define (define
maybe-bracket maybe-bracket
@@ -569,8 +658,17 @@
((inner-tokens (slice tokens (+ after 1) end)) ((inner-tokens (slice tokens (+ after 1) end))
(next-after (+ end 1))) (next-after (+ end 1)))
(let (let
((idx-expr (parse-apl-expr inner-tokens))) ((sections (split-bracket-content inner-tokens)))
(let (if
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node))) (= (len sections) 1)
(maybe-bracket indexed tokens next-after))))) (let
((idx-expr (parse-apl-expr inner-tokens)))
(let
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node)))
(maybe-bracket indexed tokens next-after)))
(let
((axis-exprs (map (fn (toks) (if (= (len toks) 0) :all (parse-apl-expr toks))) sections)))
(let
((indexed (cons :bracket (cons val-node axis-exprs))))
(maybe-bracket indexed tokens next-after)))))))
(list val-node after)))) (list val-node after))))

View File

@@ -883,7 +883,7 @@
(let (let
((sub (apl-permutations (- n 1)))) ((sub (apl-permutations (- n 1))))
(reduce (reduce
(fn (acc p) (append acc (apl-insert-everywhere n p))) (fn (acc p) (append (apl-insert-everywhere n p) acc))
(list) (list)
sub))))) sub)))))
@@ -985,6 +985,38 @@
(some (fn (c) (= c 0)) codes) (some (fn (c) (= c 0)) codes)
(some (fn (c) (= c (nth e 1))) codes))))) (some (fn (c) (= c (nth e 1))) codes)))))
(define
apl-cartesian
(fn
(lists)
(if
(= (len lists) 0)
(list (list))
(let
((rest-prods (apl-cartesian (rest lists))))
(reduce
(fn (acc x) (append acc (map (fn (p) (cons x p)) rest-prods)))
(list)
(first lists))))))
(define
apl-bracket-multi
(fn
(axes arr)
(let
((shape (get arr :shape)) (ravel (get arr :ravel)))
(let
((rank (len shape)) (strides (apl-strides shape)))
(let
((axis-info (map (fn (i) (let ((a (nth axes i))) (cond ((= a nil) {:idxs (range 0 (nth shape i)) :scalar? false}) ((= (len (get a :shape)) 0) {:idxs (list (- (first (get a :ravel)) apl-io)) :scalar? true}) (else {:idxs (map (fn (x) (- x apl-io)) (get a :ravel)) :scalar? false})))) (range 0 rank))))
(let
((cells (apl-cartesian (map (fn (a) (get a :idxs)) axis-info))))
(let
((result-ravel (map (fn (cell) (let ((flat (reduce + 0 (map (fn (i) (* (nth cell i) (nth strides i))) (range 0 rank))))) (nth ravel flat))) cells)))
(let
((result-shape (filter (fn (x) (>= x 0)) (map (fn (i) (let ((a (nth axis-info i))) (if (get a :scalar?) -1 (len (get a :idxs))))) (range 0 rank)))))
(make-array result-shape result-ravel)))))))))
(define (define
apl-reduce apl-reduce
(fn (fn

View File

@@ -39,6 +39,7 @@ cat > "$TMPFILE" << 'EPOCHS'
(load "lib/apl/tests/idioms.sx") (load "lib/apl/tests/idioms.sx")
(load "lib/apl/tests/eval-ops.sx") (load "lib/apl/tests/eval-ops.sx")
(load "lib/apl/tests/pipeline.sx") (load "lib/apl/tests/pipeline.sx")
(load "lib/apl/tests/programs-e2e.sx")
(epoch 4) (epoch 4)
(eval "(list apl-test-pass apl-test-fail)") (eval "(list apl-test-pass apl-test-fail)")
EPOCHS EPOCHS

View File

@@ -178,3 +178,137 @@
"apl-run \"(5)[3] × 7\" → 21" "apl-run \"(5)[3] × 7\" → 21"
(mkrv (apl-run "(5)[3] × 7")) (mkrv (apl-run "(5)[3] × 7"))
(list 21)) (list 21))
(apl-test "decimal: 3.7 → 3.7" (mkrv (apl-run "3.7")) (list 3.7))
(apl-test "decimal: ¯2.5 → -2.5" (mkrv (apl-run "¯2.5")) (list -2.5))
(apl-test "decimal: 1.5 + 2.5 → 4" (mkrv (apl-run "1.5 + 2.5")) (list 4))
(apl-test "decimal: ⌊3.7 → 3" (mkrv (apl-run "⌊ 3.7")) (list 3))
(apl-test "decimal: ⌈3.7 → 4" (mkrv (apl-run "⌈ 3.7")) (list 4))
(apl-test
"⎕← scalar passthrough"
(mkrv (apl-run "⎕← 42"))
(list 42))
(apl-test
"⎕← vector passthrough"
(mkrv (apl-run "⎕← 1 2 3"))
(list 1 2 3))
(apl-test
"string: 'abc' → 3-char vector"
(mkrv (apl-run "'abc'"))
(list "a" "b" "c"))
(apl-test "string: 'a' is rank-0 scalar" (mksh (apl-run "'a'")) (list))
(apl-test "string: 'hello' shape (5)" (mksh (apl-run "'hello'")) (list 5))
(apl-test
"named-fn: f ← {+⍵} ⋄ 3 f 4 → 7"
(mkrv (apl-run "f ← {+⍵} ⋄ 3 f 4"))
(list 7))
(apl-test
"named-fn monadic: sq ← {⍵×⍵} ⋄ sq 7 → 49"
(mkrv (apl-run "sq ← {⍵×⍵} ⋄ sq 7"))
(list 49))
(apl-test
"named-fn dyadic: hyp ← {((×)+⍵×⍵)} ⋄ 3 hyp 4 → 25"
(mkrv (apl-run "hyp ← {((×)+⍵×⍵)} ⋄ 3 hyp 4"))
(list 25))
(apl-test
"named-fn: dbl ← {⍵+⍵} ⋄ dbl 5"
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ dbl 5"))
(list 2 4 6 8 10))
(apl-test
"named-fn factorial via ∇ recursion"
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
(list 120))
(apl-test
"named-fn used twice in expr: dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"))
(list 14))
(apl-test
"named-fn with vector arg: neg ← {-⍵} ⋄ neg 1 2 3"
(mkrv (apl-run "neg ← {-⍵} ⋄ neg 1 2 3"))
(list -1 -2 -3))
(apl-test
"multi-axis: M[2;2] → center"
(mkrv (apl-run "M ← (3 3) 9 ⋄ M[2;2]"))
(list 5))
(apl-test
"multi-axis: M[1;] → first row"
(mkrv (apl-run "M ← (3 3) 9 ⋄ M[1;]"))
(list 1 2 3))
(apl-test
"multi-axis: M[;2] → second column"
(mkrv (apl-run "M ← (3 3) 9 ⋄ M[;2]"))
(list 2 5 8))
(apl-test
"multi-axis: M[1 2;1 2] → 2x2 block"
(mkrv (apl-run "M ← (2 3) 6 ⋄ M[1 2;1 2]"))
(list 1 2 4 5))
(apl-test
"multi-axis: M[1 2;1 2] shape (2 2)"
(mksh (apl-run "M ← (2 3) 6 ⋄ M[1 2;1 2]"))
(list 2 2))
(apl-test
"multi-axis: M[;] full matrix"
(mkrv (apl-run "M ← (2 2) 10 20 30 40 ⋄ M[;]"))
(list 10 20 30 40))
(apl-test
"multi-axis: M[1;] shape collapsed"
(mksh (apl-run "M ← (3 3) 9 ⋄ M[1;]"))
(list 3))
(apl-test
"multi-axis: select all rows of column 3"
(mkrv (apl-run "M ← (4 3) 1 2 3 4 5 6 7 8 9 10 11 12 ⋄ M[;3]"))
(list 3 6 9 12))
(apl-test
"train: mean = (+/÷≢) on 1..5"
(mkrv (apl-run "(+/÷≢) 1 2 3 4 5"))
(list 3))
(apl-test
"train: mean of 2 4 6 8 10"
(mkrv (apl-run "(+/÷≢) 2 4 6 8 10"))
(list 6))
(apl-test
"train 2-atop: (- ⌊) 5 → -5"
(mkrv (apl-run "(- ⌊) 5"))
(list -5))
(apl-test
"train 3-fork dyadic: 2(+×-)5 → -21"
(mkrv (apl-run "2 (+ × -) 5"))
(list -21))
(apl-test
"train: range = (⌈/-⌊/) on vector"
(mkrv (apl-run "(⌈/-⌊/) 3 1 4 1 5 9 2 6"))
(list 8))
(apl-test
"train: mean of 10 has shape ()"
(mksh (apl-run "(+/÷≢) 10"))
(list))

View File

@@ -0,0 +1,96 @@
; End-to-end tests of the classic-program archetypes — running APL
; source through the full pipeline (tokenize → parse → eval-ast → runtime).
;
; These mirror the algorithms documented in lib/apl/tests/programs/*.apl
; but use forms our pipeline supports today (named functions instead of
; the inline ⍵← rebinding idiom; multi-stmt over single one-liners).
(define mkrv (fn (arr) (get arr :ravel)))
(define mksh (fn (arr) (get arr :shape)))
; ---------- factorial via ∇ recursion (cf. n-queens style) ----------
(apl-test
"e2e: factorial 5! = 120"
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
(list 120))
(apl-test
"e2e: factorial 7! = 5040"
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 7"))
(list 5040))
(apl-test
"e2e: factorial via ×/N (no recursion)"
(mkrv (apl-run "fact ← {×/⍳⍵} ⋄ fact 6"))
(list 720))
; ---------- sum / triangular numbers (sum-1..N) ----------
(apl-test
"e2e: triangular(10) = 55"
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 10"))
(list 55))
(apl-test
"e2e: triangular(100) = 5050"
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 100"))
(list 5050))
; ---------- sum of squares ----------
(apl-test
"e2e: sum-of-squares 1..5 = 55"
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss 5"))
(list 55))
(apl-test
"e2e: sum-of-squares 1..10 = 385"
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss 10"))
(list 385))
; ---------- divisor-counting (prime-sieve building blocks) ----------
(apl-test
"e2e: divisor counts 1..5 via outer mod"
(mkrv (apl-run "P ← 5 ⋄ +⌿ 0 = P ∘.| P"))
(list 1 2 2 3 2))
(apl-test
"e2e: divisor counts 1..10"
(mkrv (apl-run "P ← 10 ⋄ +⌿ 0 = P ∘.| P"))
(list 1 2 2 3 2 4 2 4 3 4))
(apl-test
"e2e: prime-mask 1..10 (count==2)"
(mkrv (apl-run "P ← 10 ⋄ 2 = +⌿ 0 = P ∘.| P"))
(list 0 1 1 0 1 0 1 0 0 0))
; ---------- monadic primitives chained ----------
(apl-test
"e2e: sum of |abs| = 15"
(mkrv (apl-run "+/|¯1 ¯2 ¯3 ¯4 ¯5"))
(list 15))
(apl-test
"e2e: max of squares 1..6"
(mkrv (apl-run "⌈/(6)×6"))
(list 36))
; ---------- nested named functions ----------
(apl-test
"e2e: compose dbl and sq via two named fns"
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ sq ← {⍵×⍵} ⋄ sq dbl 3"))
(list 36))
(apl-test
"e2e: max-of-two as named dyadic fn"
(mkrv (apl-run "mx ← {⍺⌈⍵} ⋄ 5 mx 3"))
(list 5))
(apl-test
"e2e: sqrt-via-newton 1 step from 1 → 2.5"
(mkrv (apl-run "step ← {(⍵+⍺÷⍵)÷2} ⋄ 4 step 1"))
(list 2.5))

View File

@@ -252,6 +252,8 @@
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40)) (apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
(apl-test "queens 8 → 92 solutions" (mkrv (apl-queens 8)) (list 92))
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6) (apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24) (apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)

View File

@@ -2,7 +2,7 @@
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠" (list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
"≢" "≡" "∊" "∧" "" "⍱" "⍲" "," "⍪" "" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆" "≢" "≡" "∊" "∧" "" "⍱" "⍲" "," "⍪" "" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
"" "∩" "" "⍸" "⌷" "⍋" "⍒" "⊥" "" "⊣" "⊢" "⍎" "⍕" "" "∩" "" "⍸" "⌷" "⍋" "⍒" "⊥" "" "⊣" "⊢" "⍎" "⍕"
"" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯")) "" "⍵" "∇" "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
(define apl-glyph? (define apl-glyph?
(fn (ch) (fn (ch)
@@ -138,12 +138,22 @@
(begin (begin
(consume! "¯") (consume! "¯")
(let ((digits (read-digits! ""))) (let ((digits (read-digits! "")))
(tok-push! :num (- 0 (parse-int digits 0)))) (if (and (< pos src-len) (= (cur-byte) ".")
(< (+ pos 1) src-len) (apl-digit? (nth source (+ pos 1))))
(begin (advance!)
(let ((frac (read-digits! "")))
(tok-push! :num (- 0 (string->number (str digits "." frac))))))
(tok-push! :num (- 0 (parse-int digits 0)))))
(scan!))) (scan!)))
((apl-digit? ch) ((apl-digit? ch)
(begin (begin
(let ((digits (read-digits! ""))) (let ((digits (read-digits! "")))
(tok-push! :num (parse-int digits 0))) (if (and (< pos src-len) (= (cur-byte) ".")
(< (+ pos 1) src-len) (apl-digit? (nth source (+ pos 1))))
(begin (advance!)
(let ((frac (read-digits! "")))
(tok-push! :num (string->number (str digits "." frac)))))
(tok-push! :num (parse-int digits 0))))
(scan!))) (scan!)))
((= ch "'") ((= ch "'")
(begin (begin
@@ -155,7 +165,9 @@
(let ((start pos)) (let ((start pos))
(begin (begin
(if (cur-sw? "⎕") (consume! "⎕") (advance!)) (if (cur-sw? "⎕") (consume! "⎕") (advance!))
(read-ident-cont!) (if (and (< pos src-len) (cur-sw? "←"))
(consume! "←")
(read-ident-cont!))
(tok-push! :name (slice source start pos)) (tok-push! :name (slice source start pos))
(scan!)))) (scan!))))
(true (true

View File

@@ -40,6 +40,7 @@
((= g "⍋") apl-grade-up) ((= g "⍋") apl-grade-up)
((= g "⍒") apl-grade-down) ((= g "⍒") apl-grade-down)
((= g "⎕FMT") apl-quad-fmt) ((= g "⎕FMT") apl-quad-fmt)
((= g "⎕←") apl-quad-print)
(else (error "no monadic fn for glyph"))))) (else (error "no monadic fn for glyph")))))
(define (define
@@ -97,6 +98,15 @@
((tag (first node))) ((tag (first node)))
(cond (cond
((= tag :num) (apl-scalar (nth node 1))) ((= tag :num) (apl-scalar (nth node 1)))
((= tag :str)
(let
((s (nth node 1)))
(if
(= (len s) 1)
(apl-scalar s)
(make-array
(list (len s))
(map (fn (i) (slice s i (+ i 1))) (range 0 (len s)))))))
((= tag :vec) ((= tag :vec)
(let (let
((items (rest node))) ((items (rest node)))
@@ -139,6 +149,16 @@
(apl-eval-ast rhs env))))) (apl-eval-ast rhs env)))))
((= tag :program) (apl-eval-stmts (rest node) env)) ((= tag :program) (apl-eval-stmts (rest node) env))
((= tag :dfn) node) ((= tag :dfn) node)
((= tag :bracket)
(let
((arr-expr (nth node 1)) (axis-exprs (rest (rest node))))
(let
((arr (apl-eval-ast arr-expr env))
(axes
(map
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
axis-exprs)))
(apl-bracket-multi axes arr))))
(else (error (list "apl-eval-ast: unknown node tag" tag node))))))) (else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
(define (define
@@ -419,6 +439,36 @@
((f (apl-resolve-dyadic inner env))) ((f (apl-resolve-dyadic inner env)))
(fn (arr) (apl-commute f arr)))) (fn (arr) (apl-commute f arr))))
(else (error "apl-resolve-monadic: unsupported op"))))) (else (error "apl-resolve-monadic: unsupported op")))))
((= tag :fn-name)
(let
((nm (nth fn-node 1)))
(let
((bound (get env nm)))
(if
(and
(list? bound)
(> (len bound) 0)
(= (first bound) :dfn))
(fn (arg) (apl-call-dfn-m bound arg))
(error "apl-resolve-monadic: name not bound to dfn")))))
((= tag :train)
(let
((fns (rest fn-node)))
(let
((n (len fns)))
(cond
((= n 2)
(let
((g (apl-resolve-monadic (nth fns 0) env))
(h (apl-resolve-monadic (nth fns 1) env)))
(fn (arg) (g (h arg)))))
((= n 3)
(let
((f (apl-resolve-monadic (nth fns 0) env))
(g (apl-resolve-dyadic (nth fns 1) env))
(h (apl-resolve-monadic (nth fns 2) env)))
(fn (arg) (g (f arg) (h arg)))))
(else (error "monadic train arity not 2 or 3"))))))
(else (error "apl-resolve-monadic: unknown fn-node tag")))))) (else (error "apl-resolve-monadic: unknown fn-node tag"))))))
(define (define
@@ -442,6 +492,18 @@
((f (apl-resolve-dyadic inner env))) ((f (apl-resolve-dyadic inner env)))
(fn (a b) (apl-commute-dyadic f a b)))) (fn (a b) (apl-commute-dyadic f a b))))
(else (error "apl-resolve-dyadic: unsupported op"))))) (else (error "apl-resolve-dyadic: unsupported op")))))
((= tag :fn-name)
(let
((nm (nth fn-node 1)))
(let
((bound (get env nm)))
(if
(and
(list? bound)
(> (len bound) 0)
(= (first bound) :dfn))
(fn (a b) (apl-call-dfn bound a b))
(error "apl-resolve-dyadic: name not bound to dfn")))))
((= tag :outer) ((= tag :outer)
(let (let
((inner (nth fn-node 2))) ((inner (nth fn-node 2)))
@@ -455,6 +517,24 @@
((f (apl-resolve-dyadic f-node env)) ((f (apl-resolve-dyadic f-node env))
(g (apl-resolve-dyadic g-node env))) (g (apl-resolve-dyadic g-node env)))
(fn (a b) (apl-inner f g a b))))) (fn (a b) (apl-inner f g a b)))))
((= tag :train)
(let
((fns (rest fn-node)))
(let
((n (len fns)))
(cond
((= n 2)
(let
((g (apl-resolve-monadic (nth fns 0) env))
(h (apl-resolve-dyadic (nth fns 1) env)))
(fn (a b) (g (h a b)))))
((= n 3)
(let
((f (apl-resolve-dyadic (nth fns 0) env))
(g (apl-resolve-dyadic (nth fns 1) env))
(h (apl-resolve-dyadic (nth fns 2) env)))
(fn (a b) (g (f a b) (h a b)))))
(else (error "dyadic train arity not 2 or 3"))))))
(else (error "apl-resolve-dyadic: unknown fn-node tag")))))) (else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {}))) (define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))

180
lib/guest/hm.sx Normal file
View File

@@ -0,0 +1,180 @@
;; lib/guest/hm.sx — Hindley-Milner type-inference foundations.
;;
;; Builds on lib/guest/match.sx (terms + unify) and ast.sx (canonical
;; AST shapes). This file ships the ALGEBRA — types, schemes, free
;; type-vars, generalize / instantiate, substitution composition — so a
;; full Algorithm W (or J) can be assembled on top either inside this
;; file or in a host-specific consumer (haskell/infer.sx,
;; lib/ocaml/types.sx, …).
;;
;; Per the brief the second consumer for this step is OCaml-on-SX
;; Phase 5 (paired sequencing). Until that lands, the algebra is the
;; deliverable; the host-flavoured assembly (lambda / app / let
;; inference rules with substitution threading) lives in the host.
;;
;; Types
;; -----
;; A type is a canonical match.sx term — type variables use mk-var,
;; type constructors use mk-ctor:
;; (hm-tv NAME) type variable
;; (hm-arrow A B) A -> B
;; (hm-con NAME ARGS) named n-ary constructor
;; (hm-int) / (hm-bool) / (hm-string) primitive constructors
;;
;; Schemes
;; -------
;; (hm-scheme VARS TYPE) ∀ VARS . TYPE
;; (hm-monotype TYPE) empty quantifier
;; (hm-scheme? S) (hm-scheme-vars S) (hm-scheme-type S)
;;
;; Free type variables
;; -------------------
;; (hm-ftv TYPE) names occurring in TYPE
;; (hm-ftv-scheme S) free names (minus quantifiers)
;; (hm-ftv-env ENV) free across an env (name -> scheme)
;;
;; Substitution
;; ------------
;; (hm-apply SUBST TYPE) substitute through a type
;; (hm-apply-scheme SUBST S) leaves bound vars alone
;; (hm-apply-env SUBST ENV)
;; (hm-compose S2 S1) apply S1 then S2
;;
;; Generalize / Instantiate
;; ------------------------
;; (hm-generalize TYPE ENV) → scheme over ftv(t) - ftv(env)
;; (hm-instantiate SCHEME COUNTER) → fresh-var instance
;; (hm-fresh-tv COUNTER) → (:var "tN"), bumps COUNTER
;;
;; Inference (literal only — the rest of Algorithm W lives in the host)
;; --------------------------------------------------------------------
;; (hm-infer-literal EXPR) → {:subst {} :type T}
;;
;; A complete Algorithm W consumes this kit by assembling lambda / app
;; / let rules in the host language file.
(define hm-tv (fn (name) (list :var name)))
(define hm-con (fn (name args) (list :ctor name args)))
(define hm-arrow (fn (a b) (hm-con "->" (list a b))))
(define hm-int (fn () (hm-con "Int" (list))))
(define hm-bool (fn () (hm-con "Bool" (list))))
(define hm-string (fn () (hm-con "String" (list))))
(define hm-scheme (fn (vars t) (list :scheme vars t)))
(define hm-monotype (fn (t) (hm-scheme (list) t)))
(define hm-scheme? (fn (s) (and (list? s) (not (empty? s)) (= (first s) :scheme))))
(define hm-scheme-vars (fn (s) (nth s 1)))
(define hm-scheme-type (fn (s) (nth s 2)))
(define
hm-fresh-tv
(fn (counter)
(let ((n (first counter)))
(begin
(set-nth! counter 0 (+ n 1))
(hm-tv (str "t" (+ n 1)))))))
(define
hm-ftv-acc
(fn (t acc)
(cond
((is-var? t)
(if (some (fn (n) (= n (var-name t))) acc) acc (cons (var-name t) acc)))
((is-ctor? t)
(let ((a acc))
(begin
(for-each (fn (x) (set! a (hm-ftv-acc x a))) (ctor-args t))
a)))
(:else acc))))
(define hm-ftv (fn (t) (hm-ftv-acc t (list))))
(define
hm-ftv-scheme
(fn (s)
(let ((qs (hm-scheme-vars s))
(all (hm-ftv (hm-scheme-type s))))
(filter (fn (n) (not (some (fn (q) (= q n)) qs))) all))))
(define
hm-ftv-env
(fn (env)
(let ((acc (list)))
(begin
(for-each
(fn (k)
(for-each
(fn (n)
(when (not (some (fn (m) (= m n)) acc))
(set! acc (cons n acc))))
(hm-ftv-scheme (get env k))))
(keys env))
acc))))
(define hm-apply (fn (subst t) (walk* t subst)))
(define
hm-apply-scheme
(fn (subst s)
(let ((qs (hm-scheme-vars s))
(d {}))
(begin
(for-each
(fn (k)
(when (not (some (fn (q) (= q k)) qs))
(dict-set! d k (get subst k))))
(keys subst))
(hm-scheme qs (walk* (hm-scheme-type s) d))))))
(define
hm-apply-env
(fn (subst env)
(let ((d {}))
(begin
(for-each
(fn (k) (dict-set! d k (hm-apply-scheme subst (get env k))))
(keys env))
d))))
(define
hm-compose
(fn (s2 s1)
(let ((d {}))
(begin
(for-each (fn (k) (dict-set! d k (walk* (get s1 k) s2))) (keys s1))
(for-each
(fn (k) (when (not (has-key? d k)) (dict-set! d k (get s2 k))))
(keys s2))
d))))
(define
hm-generalize
(fn (t env)
(let ((tvars (hm-ftv t))
(evars (hm-ftv-env env)))
(let ((qs (filter (fn (n) (not (some (fn (m) (= m n)) evars))) tvars)))
(hm-scheme qs t)))))
(define
hm-instantiate
(fn (s counter)
(let ((qs (hm-scheme-vars s))
(subst {}))
(begin
(for-each
(fn (q) (set! subst (assoc subst q (hm-fresh-tv counter))))
qs)
(walk* (hm-scheme-type s) subst)))))
;; Literal inference — the only AST kind whose typing rule is closed
;; in the kit. Lambda / app / let live in host code so the host's own
;; AST conventions stay untouched.
(define
hm-infer-literal
(fn (expr)
(let ((v (ast-literal-value expr)))
(cond
((number? v) {:subst {} :type (hm-int)})
((string? v) {:subst {} :type (hm-string)})
((boolean? v) {:subst {} :type (hm-bool)})
(:else (error (str "hm-infer-literal: unknown kind: " v)))))))

145
lib/guest/layout.sx Normal file
View File

@@ -0,0 +1,145 @@
;; lib/guest/layout.sx — configurable off-side / layout-sensitive lexer.
;;
;; Inserts virtual open / close / separator tokens based on indentation.
;; Configurable enough to encode either the Haskell 98 layout rule (let /
;; where / do / of opens a virtual brace at the next token's column) or
;; a Python-ish indent / dedent rule (a colon at the end of a line opens
;; a block at the next non-blank line's column).
;;
;; Token shape (input + output)
;; ----------------------------
;; Each token is a dict {:type :value :line :col …}. The kit reads
;; only :type / :value / :line / :col and passes everything else
;; through. The input stream MUST be free of newline filler tokens
;; (preprocess them away with your tokenizer) — line breaks are detected
;; by comparing :line of consecutive tokens.
;;
;; Config
;; ------
;; :open-keywords list of strings; a token whose :value matches
;; opens a new layout block at the next token's
;; column (Haskell: let/where/do/of).
;; :open-trailing-fn (fn (tok) -> bool) — alternative trigger that
;; fires AFTER the token is emitted. Use for
;; Python-style trailing `:`.
;; :open-token / :close-token / :sep-token
;; templates {:type :value} merged with :line and
;; :col when virtual tokens are emitted.
;; :explicit-open? (fn (tok) -> bool) — if the next token after a
;; trigger satisfies this, suppress virtual layout
;; for that block (Haskell: `{`).
;; :module-prelude? if true, wrap whole input in an implicit block
;; at the first token's column (Haskell yes,
;; Python no).
;;
;; Public entry
;; ------------
;; (layout-pass cfg tokens) -> tokens with virtual layout inserted.
(define
layout-mk-virtual
(fn (template line col)
(assoc (assoc template :line line) :col col)))
(define
layout-is-open-kw?
(fn (tok open-kws)
(and (= (get tok :type) "reserved")
(some (fn (k) (= k (get tok :value))) open-kws))))
(define
layout-pass
(fn (cfg tokens)
(let ((open-kws (get cfg :open-keywords))
(trailing-fn (get cfg :open-trailing-fn))
(open-tmpl (get cfg :open-token))
(close-tmpl (get cfg :close-token))
(sep-tmpl (get cfg :sep-token))
(mod-prelude? (get cfg :module-prelude?))
(expl?-fn (get cfg :explicit-open?))
(out (list))
(stack (list))
(n (len tokens))
(i 0)
(prev-line -1)
(pending-open false)
(just-opened false))
(define
emit-closes-while-greater
(fn (col line)
(when (and (not (empty? stack)) (> (first stack) col))
(do
(append! out (layout-mk-virtual close-tmpl line col))
(set! stack (rest stack))
(emit-closes-while-greater col line)))))
(define
emit-pending-open
(fn (line col)
(do
(append! out (layout-mk-virtual open-tmpl line col))
(set! stack (cons col stack))
(set! pending-open false)
(set! just-opened true))))
(define
layout-step
(fn ()
(when (< i n)
(let ((tok (nth tokens i)))
(let ((line (get tok :line)) (col (get tok :col)))
(cond
(pending-open
(cond
((and (not (= expl?-fn nil)) (expl?-fn tok))
(do
(set! pending-open false)
(append! out tok)
(set! prev-line line)
(set! i (+ i 1))
(layout-step)))
(:else
(do
(emit-pending-open line col)
(layout-step)))))
(:else
(let ((on-fresh-line? (and (> prev-line 0) (> line prev-line))))
(do
(when on-fresh-line?
(let ((stack-before stack))
(begin
(emit-closes-while-greater col line)
(when (and (not (empty? stack))
(= (first stack) col)
(not just-opened)
;; suppress separator if a dedent fired
;; — the dedent is itself the separator
(= (len stack) (len stack-before)))
(append! out (layout-mk-virtual sep-tmpl line col))))))
(set! just-opened false)
(append! out tok)
(set! prev-line line)
(set! i (+ i 1))
(cond
((layout-is-open-kw? tok open-kws)
(set! pending-open true))
((and (not (= trailing-fn nil)) (trailing-fn tok))
(set! pending-open true)))
(layout-step))))))))))
(begin
;; Module prelude: implicit layout block at the first token's column.
(when (and mod-prelude? (> n 0))
(let ((tok (nth tokens 0)))
(do
(append! out (layout-mk-virtual open-tmpl (get tok :line) (get tok :col)))
(set! stack (cons (get tok :col) stack))
(set! just-opened true))))
(layout-step)
;; EOF: close every remaining block.
(define close-rest
(fn ()
(when (not (empty? stack))
(do
(append! out (layout-mk-virtual close-tmpl 0 0))
(set! stack (rest stack))
(close-rest)))))
(close-rest)
out))))

89
lib/guest/tests/hm.sx Normal file
View File

@@ -0,0 +1,89 @@
;; lib/guest/tests/hm.sx — exercises lib/guest/hm.sx algebra.
(define ghm-test-pass 0)
(define ghm-test-fail 0)
(define ghm-test-fails (list))
(define
ghm-test
(fn (name actual expected)
(if (= actual expected)
(set! ghm-test-pass (+ ghm-test-pass 1))
(begin
(set! ghm-test-fail (+ ghm-test-fail 1))
(append! ghm-test-fails {:name name :expected expected :actual actual})))))
;; ── Type constructors ─────────────────────────────────────────────
(ghm-test "tv" (hm-tv "a") (list :var "a"))
(ghm-test "int" (hm-int) (list :ctor "Int" (list)))
(ghm-test "arrow" (ctor-head (hm-arrow (hm-int) (hm-bool))) "->")
(ghm-test "arrow-args-len" (len (ctor-args (hm-arrow (hm-int) (hm-bool)))) 2)
;; ── Schemes ───────────────────────────────────────────────────────
(ghm-test "scheme-vars" (hm-scheme-vars (hm-scheme (list "a") (hm-tv "a"))) (list "a"))
(ghm-test "monotype-vars" (hm-scheme-vars (hm-monotype (hm-int))) (list))
(ghm-test "scheme?-yes" (hm-scheme? (hm-monotype (hm-int))) true)
(ghm-test "scheme?-no" (hm-scheme? (hm-int)) false)
;; ── Fresh tyvars ──────────────────────────────────────────────────
(ghm-test "fresh-1"
(let ((c (list 0))) (var-name (hm-fresh-tv c))) "t1")
(ghm-test "fresh-bumps"
(let ((c (list 5))) (begin (hm-fresh-tv c) (first c))) 6)
;; ── Free type variables ──────────────────────────────────────────
(ghm-test "ftv-int" (hm-ftv (hm-int)) (list))
(ghm-test "ftv-tv" (hm-ftv (hm-tv "a")) (list "a"))
(ghm-test "ftv-arrow"
(len (hm-ftv (hm-arrow (hm-tv "a") (hm-arrow (hm-tv "b") (hm-tv "a"))))) 2)
(ghm-test "ftv-scheme-quantified"
(hm-ftv-scheme (hm-scheme (list "a") (hm-arrow (hm-tv "a") (hm-tv "b")))) (list "b"))
(ghm-test "ftv-env"
(let ((env (assoc {} "f" (hm-monotype (hm-arrow (hm-tv "x") (hm-tv "y"))))))
(len (hm-ftv-env env))) 2)
;; ── Substitution / apply / compose ───────────────────────────────
(ghm-test "apply-tv"
(hm-apply (assoc {} "a" (hm-int)) (hm-tv "a")) (hm-int))
(ghm-test "apply-arrow"
(ctor-head
(hm-apply (assoc {} "a" (hm-int))
(hm-arrow (hm-tv "a") (hm-tv "b")))) "->")
(ghm-test "compose-1-then-2"
(var-name
(hm-apply
(hm-compose (assoc {} "b" (hm-tv "c")) (assoc {} "a" (hm-tv "b")))
(hm-tv "a"))) "c")
;; ── Generalize / Instantiate ─────────────────────────────────────
;; forall a. a -> a instantiated twice yields fresh vars each time
(ghm-test "generalize-id"
(len (hm-scheme-vars (hm-generalize (hm-arrow (hm-tv "a") (hm-tv "a")) {}))) 1)
(ghm-test "generalize-skips-env"
;; ftv(t)={a,b}, ftv(env)={a}, qs={b}
(let ((env (assoc {} "x" (hm-monotype (hm-tv "a")))))
(len (hm-scheme-vars
(hm-generalize (hm-arrow (hm-tv "a") (hm-tv "b")) env)))) 1)
(ghm-test "instantiate-fresh"
(let ((s (hm-scheme (list "a") (hm-arrow (hm-tv "a") (hm-tv "a"))))
(c (list 0)))
(let ((t1 (hm-instantiate s c)) (t2 (hm-instantiate s c)))
(not (= (var-name (first (ctor-args t1)))
(var-name (first (ctor-args t2)))))))
true)
;; ── Inference (literal only) ─────────────────────────────────────
(ghm-test "infer-int"
(ctor-head (get (hm-infer-literal (ast-literal 42)) :type)) "Int")
(ghm-test "infer-string"
(ctor-head (get (hm-infer-literal (ast-literal "hi")) :type)) "String")
(ghm-test "infer-bool"
(ctor-head (get (hm-infer-literal (ast-literal true)) :type)) "Bool")
(define ghm-tests-run!
(fn ()
{:passed ghm-test-pass
:failed ghm-test-fail
:total (+ ghm-test-pass ghm-test-fail)}))

180
lib/guest/tests/layout.sx Normal file
View File

@@ -0,0 +1,180 @@
;; lib/guest/tests/layout.sx — synthetic Python-ish off-side fixture.
;;
;; Exercises lib/guest/layout.sx with a config different from Haskell's
;; (no module-prelude, layout opens via trailing `:` not via reserved
;; keyword) to prove the kit isn't Haskell-shaped.
(define glayout-test-pass 0)
(define glayout-test-fail 0)
(define glayout-test-fails (list))
(define
glayout-test
(fn (name actual expected)
(if (= actual expected)
(set! glayout-test-pass (+ glayout-test-pass 1))
(begin
(set! glayout-test-fail (+ glayout-test-fail 1))
(append! glayout-test-fails {:name name :expected expected :actual actual})))))
;; Convenience: build a token from {type value line col}.
(define
glayout-tok
(fn (ty val line col)
{:type ty :value val :line line :col col}))
;; Project a token list to ((type value) ...) for compact comparison.
(define
glayout-shape
(fn (toks)
(map (fn (t) (list (get t :type) (get t :value))) toks)))
;; ── Haskell-flavour: keyword opens block ─────────────────────────
(define
glayout-haskell-cfg
{:open-keywords (list "let" "where" "do" "of")
:open-trailing-fn nil
:open-token {:type "vlbrace" :value "{"}
:close-token {:type "vrbrace" :value "}"}
:sep-token {:type "vsemi" :value ";"}
:module-prelude? false
:explicit-open? (fn (tok) (= (get tok :type) "lbrace"))})
;; do
;; a
;; b
;; c ← outside the do-block
(glayout-test "haskell-do-block"
(glayout-shape
(layout-pass
glayout-haskell-cfg
(list (glayout-tok "reserved" "do" 1 1)
(glayout-tok "ident" "a" 2 3)
(glayout-tok "ident" "b" 3 3)
(glayout-tok "ident" "c" 4 1))))
(list (list "reserved" "do")
(list "vlbrace" "{")
(list "ident" "a")
(list "vsemi" ";")
(list "ident" "b")
(list "vrbrace" "}")
(list "ident" "c")))
;; Explicit `{` after `do` suppresses virtual layout.
(glayout-test "haskell-explicit-brace"
(glayout-shape
(layout-pass
glayout-haskell-cfg
(list (glayout-tok "reserved" "do" 1 1)
(glayout-tok "lbrace" "{" 1 4)
(glayout-tok "ident" "a" 1 6)
(glayout-tok "rbrace" "}" 1 8))))
(list (list "reserved" "do")
(list "lbrace" "{")
(list "ident" "a")
(list "rbrace" "}")))
;; Single-statement do-block on the same line.
(glayout-test "haskell-do-inline"
(glayout-shape
(layout-pass
glayout-haskell-cfg
(list (glayout-tok "reserved" "do" 1 1)
(glayout-tok "ident" "a" 1 4))))
(list (list "reserved" "do")
(list "vlbrace" "{")
(list "ident" "a")
(list "vrbrace" "}")))
;; Module-prelude: wrap whole input in implicit layout block at first
;; tok's column.
(glayout-test "haskell-module-prelude"
(glayout-shape
(layout-pass
(assoc glayout-haskell-cfg :module-prelude? true)
(list (glayout-tok "ident" "x" 1 1)
(glayout-tok "ident" "y" 2 1)
(glayout-tok "ident" "z" 3 1))))
(list (list "vlbrace" "{")
(list "ident" "x")
(list "vsemi" ";")
(list "ident" "y")
(list "vsemi" ";")
(list "ident" "z")
(list "vrbrace" "}")))
;; ── Python-flavour: trailing `:` opens block ─────────────────────
(define
glayout-python-cfg
{:open-keywords (list)
:open-trailing-fn (fn (tok) (and (= (get tok :type) "punct")
(= (get tok :value) ":")))
:open-token {:type "indent" :value "INDENT"}
:close-token {:type "dedent" :value "DEDENT"}
:sep-token {:type "newline" :value "NEWLINE"}
:module-prelude? false
:explicit-open? nil})
;; if x:
;; a
;; b
;; c
(glayout-test "python-if-block"
(glayout-shape
(layout-pass
glayout-python-cfg
(list (glayout-tok "reserved" "if" 1 1)
(glayout-tok "ident" "x" 1 4)
(glayout-tok "punct" ":" 1 5)
(glayout-tok "ident" "a" 2 5)
(glayout-tok "ident" "b" 3 5)
(glayout-tok "ident" "c" 4 1))))
(list (list "reserved" "if")
(list "ident" "x")
(list "punct" ":")
(list "indent" "INDENT")
(list "ident" "a")
(list "newline" "NEWLINE")
(list "ident" "b")
(list "dedent" "DEDENT")
(list "ident" "c")))
;; Nested Python-style blocks.
;; def f():
;; if x:
;; a
;; b
(glayout-test "python-nested"
(glayout-shape
(layout-pass
glayout-python-cfg
(list (glayout-tok "reserved" "def" 1 1)
(glayout-tok "ident" "f" 1 5)
(glayout-tok "punct" "(" 1 6)
(glayout-tok "punct" ")" 1 7)
(glayout-tok "punct" ":" 1 8)
(glayout-tok "reserved" "if" 2 5)
(glayout-tok "ident" "x" 2 8)
(glayout-tok "punct" ":" 2 9)
(glayout-tok "ident" "a" 3 9)
(glayout-tok "ident" "b" 4 5))))
(list (list "reserved" "def")
(list "ident" "f")
(list "punct" "(")
(list "punct" ")")
(list "punct" ":")
(list "indent" "INDENT")
(list "reserved" "if")
(list "ident" "x")
(list "punct" ":")
(list "indent" "INDENT")
(list "ident" "a")
(list "dedent" "DEDENT")
(list "ident" "b")
(list "dedent" "DEDENT")))
(define glayout-tests-run!
(fn ()
{:passed glayout-test-pass
:failed glayout-test-fail
:total (+ glayout-test-pass glayout-test-fail)}))

View File

@@ -0,0 +1,23 @@
let div_sum n =
let s = ref 1 in
let i = ref 2 in
while !i * !i <= n do
if n mod !i = 0 then begin
s := !s + !i;
let q = n / !i in
if q <> !i then s := !s + q
end;
i := !i + 1
done;
if n = 1 then 0 else !s
let count_abundant n =
let c = ref 0 in
for i = 12 to n - 1 do
if div_sum i > i then c := !c + 1
done;
!c
;;
count_abundant 100

View File

@@ -0,0 +1,8 @@
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

View File

@@ -0,0 +1,13 @@
let adler32 s =
let a = ref 1 in
let b = ref 0 in
let m = 65521 in
for i = 0 to String.length s - 1 do
a := (!a + Char.code s.[i]) mod m;
b := (!b + !a) mod m
done;
!b * 65536 + !a
;;
adler32 "Wikipedia"

View File

@@ -0,0 +1,23 @@
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
let same_counts a b =
let result = ref true in
for i = 0 to 255 do
if a.(i) <> b.(i) then result := false
done;
!result
let is_anagram s t = same_counts (to_counts s) (to_counts t)
;;
(if is_anagram "listen" "silent" then 1 else 0) +
(if is_anagram "hello" "world" then 1 else 0) +
(if is_anagram "anagram" "nagaram" then 1 else 0) +
(if is_anagram "abc" "abcd" then 1 else 0)

View File

@@ -0,0 +1,29 @@
let canonical s =
let chars = Array.make 26 0 in
for i = 0 to String.length s - 1 do
let k = Char.code s.[i] - Char.code 'a' in
if k >= 0 && k < 26 then chars.(k) <- chars.(k) + 1
done;
let buf = Buffer.create 26 in
for i = 0 to 25 do
for _ = 1 to chars.(i) do
Buffer.add_string buf (String.make 1 (Char.chr (i + Char.code 'a')))
done
done;
Buffer.contents buf
let group_anagrams xs =
let h = Hashtbl.create 8 in
List.iter (fun s ->
let k = canonical s in
let cur = match Hashtbl.find_opt h k with
| Some xs -> xs
| None -> []
in
Hashtbl.replace h k (s :: cur)
) xs;
Hashtbl.length h
;;
group_anagrams ["eat"; "tea"; "tan"; "ate"; "nat"; "bat"]

View File

@@ -0,0 +1,26 @@
(* Baseline: count anagram groups using Hashtbl + sort *)
(* Sort the chars in a string to get its anagram-equivalence key *)
let canonical s =
let n = String.length s in
let chars = ref [] in
for i = 0 to n - 1 do
chars := (String.get s i) :: !chars
done ;
let sorted = List.sort compare !chars in
String.concat "" sorted
;;
let count_groups words =
let counts = Hashtbl.create 16 in
List.iter
(fun w ->
let k = canonical w in
match Hashtbl.find_opt counts k with
| None -> Hashtbl.add counts k 1
| Some n -> Hashtbl.replace counts k (n + 1))
words ;
Hashtbl.length counts
;;
count_groups ["eat"; "tea"; "tan"; "ate"; "nat"; "bat"]

17
lib/ocaml/baseline/atm.ml Normal file
View File

@@ -0,0 +1,17 @@
type account = { mutable balance : int }
exception Insufficient
let withdraw acct amt =
if amt > acct.balance then raise Insufficient
else acct.balance <- acct.balance - amt
let deposit acct amt = acct.balance <- acct.balance + amt
;;
let a = { balance = 100 } in
deposit a 50;
withdraw a 30;
try (withdraw a 200; -1)
with Insufficient -> a.balance

18
lib/ocaml/baseline/bag.ml Normal file
View File

@@ -0,0 +1,18 @@
let count_words text =
let words = String.split_on_char ' ' text in
let counts = Hashtbl.create 8 in
List.iter (fun w ->
let n = match Hashtbl.find_opt counts w with
| Some n -> n + 1
| None -> 1
in
Hashtbl.replace counts w n
) words;
counts
let max_count counts =
Hashtbl.fold (fun _ v acc -> if v > acc then v else acc) counts 0
;;
max_count (count_words "the quick brown fox jumps over the lazy dog the fox")

View File

@@ -0,0 +1,25 @@
let is_balanced s =
let stack = Stack.create () in
let n = String.length s in
let ok = ref true in
let i = ref 0 in
while !i < n && !ok do
let c = s.[!i] in
(if c = '(' || c = '[' || c = '{' then Stack.push c stack
else if c = ')' then
(if Stack.is_empty stack || Stack.pop stack <> '(' then ok := false)
else if c = ']' then
(if Stack.is_empty stack || Stack.pop stack <> '[' then ok := false)
else if c = '}' then
(if Stack.is_empty stack || Stack.pop stack <> '{' then ok := false));
i := !i + 1
done;
!ok && Stack.is_empty stack
;;
(if is_balanced "({[abc]d}e)" then 1 else 0) +
(if is_balanced "(a]" then 1 else 0) +
(if is_balanced "{[}]" then 1 else 0) +
(if is_balanced "(())" then 1 else 0) +
(if is_balanced "" then 1 else 0)

View File

@@ -0,0 +1,19 @@
let to_base_n n base =
let digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in
if n = 0 then "0"
else begin
let m = ref (abs n) in
let acc = ref "" in
while !m > 0 do
acc := String.make 1 digits.[!m mod base] ^ !acc;
m := !m / base
done;
if n < 0 then "-" ^ !acc else !acc
end
;;
String.length (to_base_n 255 16) +
String.length (to_base_n 1024 2) +
String.length (to_base_n 100 10) +
String.length (to_base_n 0 16)

View File

@@ -0,0 +1,42 @@
let interpret prog =
let mem = Array.make 256 0 in
let ptr = ref 0 in
let pc = ref 0 in
let n = String.length prog in
let acc = ref 0 in
while !pc < n do
let c = prog.[!pc] in
(if c = '>' then ptr := !ptr + 1
else if c = '<' then ptr := !ptr - 1
else if c = '+' then mem.(!ptr) <- mem.(!ptr) + 1
else if c = '-' then mem.(!ptr) <- mem.(!ptr) - 1
else if c = '.' then acc := !acc + mem.(!ptr)
else if c = '[' then begin
if mem.(!ptr) = 0 then begin
let depth = ref 1 in
while !depth > 0 do
pc := !pc + 1;
let c = prog.[!pc] in
if c = '[' then depth := !depth + 1
else if c = ']' then depth := !depth - 1
done
end
end
else if c = ']' then begin
if mem.(!ptr) <> 0 then begin
let depth = ref 1 in
while !depth > 0 do
pc := !pc - 1;
let c = prog.[!pc] in
if c = ']' then depth := !depth + 1
else if c = '[' then depth := !depth - 1
done
end
end);
pc := !pc + 1
done;
!acc
;;
interpret "+++[.-]"

43
lib/ocaml/baseline/bfs.ml Normal file
View File

@@ -0,0 +1,43 @@
(* Baseline: graph BFS using Queue + Hashtbl visited set.
Returns the count of reachable nodes. *)
(* Adjacency as an assoc list of (node, neighbors). *)
let graph =
[ ("A", ["B"; "C"])
; ("B", ["D"])
; ("C", ["D"; "E"])
; ("D", ["F"])
; ("E", ["F"])
; ("F", [])
]
;;
let neighbors n =
match List.assoc_opt n graph with
| None -> []
| Some ns -> ns
;;
let bfs start =
let visited = Hashtbl.create 16 in
let q = Queue.create () in
Queue.push start q ;
Hashtbl.add visited start true ;
let rec loop () =
if Queue.is_empty q then ()
else
let v = Queue.pop q in
List.iter
(fun n ->
if not (Hashtbl.mem visited n) then begin
Hashtbl.add visited n true ;
Queue.push n q
end)
(neighbors v) ;
loop ()
in
loop () ;
Hashtbl.length visited
;;
bfs "A"

View File

@@ -0,0 +1,24 @@
let bigint_add a b =
let rec aux a b carry =
match (a, b) with
| ([], []) -> if carry = 0 then [] else [carry]
| (x :: xs, []) ->
let s = x + carry in
(s mod 10) :: aux xs [] (s / 10)
| ([], y :: ys) ->
let s = y + carry in
(s mod 10) :: aux [] ys (s / 10)
| (x :: xs, y :: ys) ->
let s = x + y + carry in
(s mod 10) :: aux xs ys (s / 10)
in
aux a b 0
;;
let r1 = bigint_add [9;9;9] [1] in
let r2 = bigint_add [5;6;7] [8;9;1] in
let r3 = bigint_add [9;9;9;9;9;9;9;9] [1] in
List.fold_left (+) 0 r1
+ List.fold_left (+) 0 r2
+ List.length r3

View File

@@ -0,0 +1,47 @@
let parent i = (i - 1) / 2
let lchild i = 2 * i + 1
let rchild i = 2 * i + 2
let swap a i j =
let t = a.(i) in
a.(i) <- a.(j);
a.(j) <- t
let rec sift_up a i =
if i > 0 && a.(parent i) > a.(i) then begin
swap a i (parent i);
sift_up a (parent i)
end
let rec sift_down a n i =
let l = lchild i and r = rchild i in
let smallest = ref i in
if l < n && a.(l) < a.(!smallest) then smallest := l;
if r < n && a.(r) < a.(!smallest) then smallest := r;
if !smallest <> i then begin
swap a i !smallest;
sift_down a n !smallest
end
let push a size x =
a.(!size) <- x;
size := !size + 1;
sift_up a (!size - 1)
let pop a size =
let m = a.(0) in
size := !size - 1;
a.(0) <- a.(!size);
sift_down a !size 0;
m
;;
let a = Array.make 20 0 in
let s = ref 0 in
List.iter (fun x -> push a s x) [9; 4; 7; 1; 8; 3; 5; 2; 6];
let total = ref 0 in
for _ = 1 to 9 do
total := !total * 10 + pop a s
done;
!total

View File

@@ -0,0 +1,13 @@
let bisect f lo hi =
let lo = ref lo and hi = ref hi in
for _ = 1 to 50 do
let mid = (!lo +. !hi) /. 2.0 in
if f mid = 0.0 || f !lo *. f mid < 0.0 then hi := mid
else lo := mid
done;
!lo
;;
let r = bisect (fun x -> x *. x -. 2.0) 1.0 2.0 in
int_of_float (r *. 100.0)

View File

@@ -0,0 +1,12 @@
let popcount n =
let count = ref 0 in
let m = ref n in
while !m > 0 do
if !m land 1 = 1 then count := !count + 1;
m := !m lsr 1
done;
!count
;;
popcount 1023 + popcount 5 + popcount 1024 + popcount 0xff

View File

@@ -0,0 +1,24 @@
let bowling_score frames =
let arr = Array.of_list frames in
let n = Array.length arr in
let total = ref 0 in
let i = ref 0 in
let frame = ref 1 in
while !frame <= 10 && !i < n do
if arr.(!i) = 10 then begin
total := !total + 10 + arr.(!i + 1) + arr.(!i + 2);
i := !i + 1
end else if !i + 1 < n && arr.(!i) + arr.(!i + 1) = 10 then begin
total := !total + 10 + arr.(!i + 2);
i := !i + 2
end else begin
total := !total + arr.(!i) + arr.(!i + 1);
i := !i + 2
end;
frame := !frame + 1
done;
!total
;;
bowling_score [10; 7; 3; 9; 0; 10; 0; 8; 8; 2; 0; 6; 10; 10; 10; 8; 1]

View File

@@ -0,0 +1,20 @@
let interpret prog =
let mem = Array.make 256 0 in
let ptr = ref 0 in
let pc = ref 0 in
let n = String.length prog in
let acc = ref 0 in
while !pc < n do
let c = prog.[!pc] in
(if c = '>' then ptr := !ptr + 1
else if c = '<' then ptr := !ptr - 1
else if c = '+' then mem.(!ptr) <- mem.(!ptr) + 1
else if c = '-' then mem.(!ptr) <- mem.(!ptr) - 1
else if c = '.' then acc := !acc + mem.(!ptr));
pc := !pc + 1
done;
!acc
;;
interpret "+++++.+++++.+++++.+++++.+++++."

View File

@@ -0,0 +1,16 @@
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
;;
let a = Array.of_list [1;3;5;7;9;11;13;15;17;19;21] in
bsearch a 13 + bsearch a 5 + bsearch a 100

View File

@@ -0,0 +1,25 @@
(* Baseline: binary search tree with insert + in-order traversal *)
type 'a tree =
| Leaf
| Node of 'a * 'a tree * 'a tree
;;
let rec insert x t =
match t with
| Leaf -> Node (x, Leaf, Leaf)
| Node (v, l, r) ->
if x < v then Node (v, insert x l, r)
else if x > v then Node (v, l, insert x r)
else t
;;
let rec inorder t =
match t with
| Leaf -> []
| Node (v, l, r) -> List.append (inorder l) (v :: inorder r)
;;
let from_list xs = List.fold_left (fun t x -> insert x t) Leaf xs ;;
let t = from_list [5; 3; 8; 1; 4; 7; 9; 2] ;;
List.fold_left (fun a b -> a + b) 0 (inorder t)

View File

@@ -0,0 +1,14 @@
let shift_char c k =
let n = Char.code c in
if n >= 97 && n <= 122 then
Char.chr (((n - 97 + k) mod 26 + 26) mod 26 + 97)
else c
let encode s k =
String.init (String.length s) (fun i -> shift_char s.[i] k)
;;
(* ROT13 round-trip: encode (encode "hello" 13) 13 = "hello".
Sum the codes of two chars to give a deterministic integer check. *)
let r = encode (encode "hello" 13) 13 in
Char.code r.[0] + Char.code r.[4]

View File

@@ -0,0 +1,76 @@
(* Baseline: recursive-descent calculator for "+", "*", parens, ints. *)
type expr =
| Lit of int
| Add of expr * expr
| Mul of expr * expr
;;
let parse_input src =
let pos = ref 0 in
let peek () = if !pos < String.length src then String.get src !pos else "" in
let advance () = pos := !pos + 1 in
let skip_ws () =
while !pos < String.length src && peek () = " " do advance () done
in
let rec parse_atom () =
skip_ws () ;
if peek () = "(" then begin
advance () ;
let e = parse_expr () in
skip_ws () ;
advance () ; (* consume ')' *)
e
end
else
let start = !pos in
let rec digits () =
if !pos < String.length src then
let c = peek () in
if c >= "0" && c <= "9" then begin advance () ; digits () end
else ()
in
digits () ;
let n = Int.of_string (String.sub src start (!pos - start)) in
Lit n
and parse_term () =
skip_ws () ;
let lhs = ref (parse_atom ()) in
let rec loop () =
skip_ws () ;
if peek () = "*" then begin
advance () ;
lhs := Mul (!lhs, parse_atom ()) ;
loop ()
end
in
loop () ;
!lhs
and parse_expr () =
skip_ws () ;
let lhs = ref (parse_term ()) in
let rec loop () =
skip_ws () ;
if peek () = "+" then begin
advance () ;
lhs := Add (!lhs, parse_term ()) ;
loop ()
end
in
loop () ;
!lhs
in
parse_expr ()
;;
let rec eval e =
match e with
| Lit n -> n
| Add (a, b) -> eval a + eval b
| Mul (a, b) -> eval a * eval b
;;
(* (1 + 2) * 3 + 4 = 9 + 4 = 13 *)
eval (parse_input "(1 + 2) * 3 + 4")

View File

@@ -0,0 +1,13 @@
let catalan n =
let dp = Array.make (n + 1) 0 in
dp.(0) <- 1;
for i = 1 to n do
for j = 0 to i - 1 do
dp.(i) <- dp.(i) + dp.(j) * dp.(i - 1 - j)
done
done;
dp.(n)
;;
catalan 5

View File

@@ -0,0 +1,5 @@
(* Baseline: closures + curried application *)
let make_adder n = fun x -> n + x ;;
let add5 = make_adder 5 ;;
let add10 = make_adder 10 ;;
add5 100 + add10 200

View File

@@ -0,0 +1,15 @@
let coin_change coins target =
let dp = Array.make (target + 1) (target + 1) in
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;
if dp.(target) > target then -1
else dp.(target)
;;
coin_change [1; 5; 10; 25] 67

View File

@@ -0,0 +1,13 @@
let count_ways coins target =
let dp = Array.make (target + 1) 0 in
dp.(0) <- 1;
List.iter (fun c ->
for i = c to target do
dp.(i) <- dp.(i) + dp.(i - c)
done
) coins;
dp.(target)
;;
count_ways [1; 2; 5; 10; 25] 50

View File

@@ -0,0 +1,42 @@
let count_inv arr =
let n = Array.length arr in
let temp = Array.make n 0 in
let count = ref 0 in
let rec merge lo mid hi =
let i = ref lo and j = ref (mid + 1) and k = ref lo in
while !i <= mid && !j <= hi do
if arr.(!i) <= arr.(!j) then begin
temp.(!k) <- arr.(!i);
i := !i + 1
end else begin
temp.(!k) <- arr.(!j);
count := !count + (mid - !i + 1);
j := !j + 1
end;
k := !k + 1
done;
while !i <= mid do
temp.(!k) <- arr.(!i);
i := !i + 1; k := !k + 1
done;
while !j <= hi do
temp.(!k) <- arr.(!j);
j := !j + 1; k := !k + 1
done;
for x = lo to hi do
arr.(x) <- temp.(x)
done
and sort lo hi =
if lo < hi then begin
let mid = (lo + hi) / 2 in
sort lo mid;
sort (mid + 1) hi;
merge lo mid hi
end
in
sort 0 (n - 1);
!count
;;
count_inv [|8; 4; 2; 1; 3; 5; 7; 6|]

12
lib/ocaml/baseline/csv.ml Normal file
View File

@@ -0,0 +1,12 @@
let sum_second_col text =
let lines = String.split_on_char '\n' text in
List.fold_left (fun acc line ->
let fields = String.split_on_char ',' line in
if List.length fields >= 2 then
acc + int_of_string (List.nth fields 1)
else acc
) 0 lines
;;
sum_second_col "a,1,extra\nb,2,extra\nc,3,extra\nd,4,extra"

View File

@@ -0,0 +1,37 @@
let n = 5
let edges = [|
[(1, 4); (2, 1)];
[(3, 1)];
[(1, 2); (3, 5)];
[(4, 3)];
[]
|]
let dijkstra src =
let dist = Array.make n 1000000 in
dist.(src) <- 0;
let visited = Array.make n false in
for _ = 0 to n - 1 do
let u = ref (-1) in
let best = ref 1000000 in
for v = 0 to n - 1 do
if (not visited.(v)) && dist.(v) < !best then begin
best := dist.(v);
u := v
end
done;
if !u >= 0 then begin
visited.(!u) <- true;
List.iter (fun (v, w) ->
if dist.(!u) + w < dist.(v) then
dist.(v) <- dist.(!u) + w
) edges.(!u)
end
done;
dist
;;
let d = dijkstra 0 in
d.(4)

View File

@@ -0,0 +1,10 @@
let euler1 limit =
let sum = ref 0 in
for i = 1 to limit - 1 do
if i mod 3 = 0 || i mod 5 = 0 then sum := !sum + i
done;
!sum
;;
euler1 1000

View File

@@ -0,0 +1,22 @@
let sieve_sum n =
let s = Array.make (n + 1) true in
s.(0) <- false;
s.(1) <- false;
for i = 2 to n do
if s.(i) then begin
let j = ref (i * i) in
while !j <= n do
s.(!j) <- false;
j := !j + i
done
end
done;
let total = ref 0 in
for i = 2 to n do
if s.(i) then total := !total + i
done;
!total
;;
sieve_sum 100

View File

@@ -0,0 +1,25 @@
let collatz_len n =
let m = ref n in
let c = ref 0 in
while !m > 1 do
if !m mod 2 = 0 then m := !m / 2
else m := 3 * !m + 1;
c := !c + 1
done;
!c
let euler14 limit =
let best = ref 0 in
let best_n = ref 0 in
for n = 2 to limit do
let l = collatz_len n in
if l > !best then begin
best := l;
best_n := n
end
done;
!best_n
;;
euler14 100

View File

@@ -0,0 +1,14 @@
let euler16 n =
let p = ref 1 in
for _ = 1 to n do p := !p * 2 done;
let sum = ref 0 in
let m = ref !p in
while !m > 0 do
sum := !sum + !m mod 10;
m := !m / 10
done;
!sum
;;
euler16 15

View File

@@ -0,0 +1,15 @@
let euler2 limit =
let a = ref 1 in
let b = ref 2 in
let sum = ref 0 in
while !a <= limit do
if !a mod 2 = 0 then sum := !sum + !a;
let c = !a + !b in
a := !b;
b := c
done;
!sum
;;
euler2 4000000

View File

@@ -0,0 +1,25 @@
let div_sum n =
let s = ref 1 in
let i = ref 2 in
while !i * !i <= n do
if n mod !i = 0 then begin
s := !s + !i;
let q = n / !i in
if q <> !i then s := !s + q
end;
i := !i + 1
done;
if n = 1 then 0 else !s
let euler21 limit =
let total = ref 0 in
for a = 2 to limit do
let b = div_sum a in
if b <> a && b > a && b <= limit && div_sum b = a then
total := !total + a + b
done;
!total
;;
euler21 300

View File

@@ -0,0 +1,17 @@
let euler25 n =
let a = ref 1 in
let b = ref 1 in
let i = ref 2 in
let target = ref 1 in
for _ = 1 to n - 1 do target := !target * 10 done;
while !b < !target do
let c = !a + !b in
a := !b;
b := c;
i := !i + 1
done;
!i
;;
euler25 12

View File

@@ -0,0 +1,15 @@
let euler28 n =
let s = ref 1 in
let k = ref 1 in
for layer = 1 to (n - 1) / 2 do
let step = 2 * layer in
for _ = 1 to 4 do
k := !k + step;
s := !s + !k
done
done;
!s
;;
euler28 7

View File

@@ -0,0 +1,14 @@
let euler29 n =
let h = Hashtbl.create 64 in
for a = 2 to n do
for b = 2 to n do
let p = ref 1 in
for _ = 1 to b do p := !p * a done;
Hashtbl.replace h !p ()
done
done;
Hashtbl.length h
;;
euler29 5

View File

@@ -0,0 +1,15 @@
let largest_prime_factor n =
let m = ref n in
let factor = ref 2 in
let largest = ref 0 in
while !m > 1 do
if !m mod !factor = 0 then begin
largest := !factor;
m := !m / !factor
end else factor := !factor + 1
done;
!largest
;;
largest_prime_factor 13195

View File

@@ -0,0 +1,22 @@
let pow_digit_sum n p =
let m = ref n in
let s = ref 0 in
while !m > 0 do
let d = !m mod 10 in
let pd = ref 1 in
for _ = 1 to p do pd := !pd * d done;
s := !s + !pd;
m := !m / 10
done;
!s
let euler30 p limit =
let total = ref 0 in
for n = 2 to limit do
if pow_digit_sum n p = n then total := !total + n
done;
!total
;;
euler30 3 999

View File

@@ -0,0 +1,24 @@
let fact n =
let r = ref 1 in
for i = 2 to n do r := !r * i done;
!r
let digit_fact_sum n =
let m = ref n in
let s = ref 0 in
while !m > 0 do
s := !s + fact (!m mod 10);
m := !m / 10
done;
!s
let euler34 limit =
let total = ref 0 in
for n = 3 to limit do
if digit_fact_sum n = n then total := !total + n
done;
!total
;;
euler34 2000

View File

@@ -0,0 +1,41 @@
let pal_dec n =
let s = string_of_int n in
let len = String.length s in
let p = ref true in
for i = 0 to len / 2 - 1 do
if s.[i] <> s.[len - 1 - i] then p := false
done;
!p
let to_binary n =
if n = 0 then "0"
else
let buf = Buffer.create 32 in
let m = ref n in
let stack = ref [] in
while !m > 0 do
stack := (!m mod 2) :: !stack;
m := !m / 2
done;
List.iter (fun d -> Buffer.add_string buf (string_of_int d)) !stack;
Buffer.contents buf
let pal_bin n =
let s = to_binary n in
let len = String.length s in
let p = ref true in
for i = 0 to len / 2 - 1 do
if s.[i] <> s.[len - 1 - i] then p := false
done;
!p
let euler36 limit =
let sum = ref 0 in
for n = 1 to limit - 1 do
if pal_dec n && pal_bin n then sum := !sum + n
done;
!sum
;;
euler36 1000

View File

@@ -0,0 +1,22 @@
let euler40 () =
let buf = Buffer.create 4096 in
let len = ref 0 in
let i = ref 1 in
while !len < 1500 do
let s = string_of_int !i in
Buffer.add_string buf s;
len := !len + String.length s;
i := !i + 1
done;
let s = Buffer.contents buf in
let prod = ref 1 in
let positions = [1; 10; 100; 1000] in
List.iter (fun p ->
let c = s.[p - 1] in
prod := !prod * (Char.code c - Char.code '0')
) positions;
!prod
;;
euler40 ()

View File

@@ -0,0 +1,21 @@
let is_pal n =
let s = string_of_int n in
let len = String.length s in
let p = ref true in
for i = 0 to len / 2 - 1 do
if s.[i] <> s.[len - 1 - i] then p := false
done;
!p
let euler4 lo hi =
let m = ref 0 in
for a = lo to hi do
for b = a to hi do
let p = a * b in
if p > !m && is_pal p then m := p
done
done;
!m
;;
euler4 10 99

View File

@@ -0,0 +1,11 @@
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
let euler5 n =
let r = ref 1 in
for i = 2 to n do
r := lcm !r i
done;
!r
;;
euler5 20

View File

@@ -0,0 +1,12 @@
let euler6 n =
let sum = ref 0 in
let sum_sq = ref 0 in
for i = 1 to n do
sum := !sum + i;
sum_sq := !sum_sq + i * i
done;
!sum * !sum - !sum_sq
;;
euler6 100

View File

@@ -0,0 +1,22 @@
let nth_prime n =
let count = ref 0 in
let i = ref 1 in
let result = ref 0 in
while !count < n do
i := !i + 1;
let p = ref true in
let j = ref 2 in
while !j * !j <= !i && !p do
if !i mod !j = 0 then p := false;
j := !j + 1
done;
if !p then begin
count := !count + 1;
if !count = n then result := !i
end
done;
!result
;;
nth_prime 100

View File

@@ -0,0 +1,17 @@
let euler9 () =
let result = ref 0 in
for a = 1 to 333 do
let num = 500000 - 1000 * a in
let den = 1000 - a in
if num mod den = 0 then begin
let b = num / den in
if b > a then
let c = 1000 - a - b in
if c > b then result := a * b * c
end
done;
!result
;;
euler9 ()

View File

@@ -0,0 +1,17 @@
(* Baseline: exception declaration + raise + try-with *)
exception NegArg of int ;;
let safe_sqrt n =
if n < 0 then raise (NegArg n)
else
begin
let rec find_sqrt i =
if i * i > n then i - 1
else find_sqrt (i + 1)
in find_sqrt 0
end ;;
let result =
try
safe_sqrt 16
with
| NegArg _ -> 0 ;;
result

View File

@@ -0,0 +1,16 @@
exception Negative of int
let safe_sqrt n =
if n < 0 then raise (Negative n)
else
let g = ref 1 in
while !g * !g < n do g := !g + 1 done;
!g
let try_sqrt n =
try safe_sqrt n with
| Negative x -> -x
;;
try_sqrt 16 + try_sqrt 25 + try_sqrt (-7) + try_sqrt 100

View File

@@ -0,0 +1,158 @@
{
"abundant.ml": 21,
"ackermann.ml": 125,
"adler32.ml": 300286872,
"anagram_check.ml": 2,
"anagram_groups.ml": 3,
"anagrams.ml": 3,
"atm.ml": 120,
"bag.ml": 3,
"bowling.ml": 167,
"bf_full.ml": 6,
"bisect.ml": 141,
"bigint_add.ml": 28,
"binary_heap.ml": 123456789,
"bits.ml": 21,
"balance.ml": 3,
"base_n.ml": 17,
"bfs.ml": 6,
"btree.ml": 39,
"brainfuck.ml": 75,
"bsearch.ml": 7,
"caesar.ml": 215,
"calc.ml": 13,
"catalan.ml": 42,
"closures.ml": 315,
"coin_change.ml": 6,
"count_change.ml": 406,
"count_inversions.ml": 12,
"csv.ml": 10,
"dijkstra.ml": 7,
"exception_handle.ml": 4,
"exception_user.ml": 26,
"euler1.ml": 233168,
"euler16.ml": 26,
"euler10.ml": 1060,
"euler14.ml": 97,
"euler2.ml": 4613732,
"euler21_small.ml": 504,
"euler25.ml": 55,
"euler28.ml": 261,
"euler29_small.ml": 15,
"euler30_cube.ml": 1301,
"euler34_small.ml": 145,
"euler36.ml": 1772,
"euler40_small.ml": 15,
"euler3.ml": 29,
"euler4_small.ml": 9009,
"euler5.ml": 232792560,
"euler6.ml": 25164150,
"euler7.ml": 541,
"euler9.ml": 31875000,
"expr_eval.ml": 16,
"expr_simp.ml": 22,
"factorial.ml": 3628800,
"fib_doubling.ml": 102334155,
"fib_mod.ml": 391360,
"fraction.ml": 7,
"frequency.ml": 5,
"gcd_lcm.ml": 60,
"gray_code.ml": 136,
"grep_count.ml": 3,
"grid_paths.ml": 210,
"group_consec.ml": 53,
"hailstone.ml": 111,
"harshad.ml": 33,
"hamming.ml": 4,
"hanoi.ml": 1023,
"hist.ml": 75,
"huffman.ml": 224,
"int_sqrt.ml": 1027,
"is_prime.ml": 25,
"fizz_classifier.ml": 540,
"fizzbuzz.ml": 57,
"flatten_tree.ml": 28,
"floyd_warshall.ml": 9,
"lis.ml": 6,
"list_ops.ml": 30,
"luhn.ml": 2,
"mat_mul.ml": 621,
"max_path_tree.ml": 11,
"max_product3.ml": 300,
"max_run.ml": 5,
"mod_inverse.ml": 27,
"josephus.ml": 11,
"json_pretty.ml": 24,
"kadane.ml": 6,
"kmp.ml": 5,
"knapsack.ml": 36,
"lambda_calc.ml": 7,
"lcs.ml": 4,
"majority_vote.ml": 4,
"manacher.ml": 7,
"lev_iter.ml": 16,
"levenshtein.ml": 11,
"memo_fib.ml": 75025,
"mortgage.ml": 1073,
"mst_kruskal.ml": 11,
"merge_intervals.ml": 12,
"merge_sort.ml": 44,
"merge_two.ml": 441,
"module_use.ml": 3,
"monotonic.ml": 4,
"newton_sqrt.ml": 1414,
"number_words.ml": 106,
"mutable_record.ml": 10,
"option_match.ml": 5,
"palindrome.ml": 4,
"palindrome_sum.ml": 49500,
"paren_depth.ml": 7,
"partition.ml": 3025,
"partition_count.ml": 176,
"pancake_sort.ml": 910,
"pascal.ml": 252,
"peano.ml": 30,
"perfect.ml": 3,
"pi_leibniz.ml": 314,
"prefix_sum.ml": 66,
"pretty_table.ml": 64,
"poly_stack.ml": 5,
"pow_mod.ml": 738639,
"prime_factors.ml": 17,
"pythagorean.ml": 16,
"queens.ml": 2,
"quickselect.ml": 5,
"quicksort.ml": 44,
"roman.ml": 44,
"rolling_hash.ml": 6,
"reverse_int.ml": 54329,
"rpn.ml": 9,
"run_decode.ml": 21,
"run_length.ml": 11,
"safe_div.ml": 20,
"shuffle.ml": 55,
"simpson_int.ml": 10000,
"stable_unique.ml": 46,
"subseq_check.ml": 3,
"tail_factorial.ml": 479001600,
"tarjan_scc.ml": 4,
"subset_sum.ml": 8,
"tic_tac_toe.ml": 1,
"topo_sort.ml": 6,
"word_freq.ml": 8,
"xor_cipher.ml": 601,
"zerosafe.ml": 28,
"zigzag.ml": 55,
"zip_unzip.ml": 1000,
"sieve.ml": 15,
"sum_squares.ml": 385,
"tree_depth.ml": 4,
"triangle.ml": 11,
"trie.ml": 6,
"triangle_div.ml": 120,
"twosum.ml": 5,
"union_find.ml": 4,
"unique_set.ml": 9,
"validate.ml": 417,
"word_count.ml": 3
}

View File

@@ -0,0 +1,19 @@
(* Baseline: a tiny expression evaluator using ADTs + match *)
type expr =
| Lit of int
| Add of expr * expr
| Mul of expr * expr
| Neg of expr
;;
let rec eval e =
match e with
| Lit n -> n
| Add (a, b) -> eval a + eval b
| Mul (a, b) -> eval a * eval b
| Neg x -> 0 - eval x
;;
(* (1 + 2) * (3 + 4) - 5 = 21 - 5 = 16 *)
eval
(Add (Mul (Add (Lit 1, Lit 2), Add (Lit 3, Lit 4)), Neg (Lit 5)))

View File

@@ -0,0 +1,33 @@
type expr =
| Num of int
| Add of expr * expr
| Mul of expr * expr
let rec simp e =
match e with
| Num n -> Num n
| Add (a, b) ->
(match (simp a, simp b) with
| (Num 0, x) -> x
| (x, Num 0) -> x
| (Num n, Num m) -> Num (n + m)
| (a', b') -> Add (a', b'))
| Mul (a, b) ->
(match (simp a, simp b) with
| (Num 0, _) -> Num 0
| (_, Num 0) -> Num 0
| (Num 1, x) -> x
| (x, Num 1) -> x
| (Num n, Num m) -> Num (n * m)
| (a', b') -> Mul (a', b'))
let rec eval e =
match e with
| Num n -> n
| Add (a, b) -> eval a + eval b
| Mul (a, b) -> eval a * eval b
;;
let e = Add (Mul (Num 3, Num 5), Add (Num 0, Mul (Num 1, Num 7))) in
eval (simp e)

View File

@@ -0,0 +1,4 @@
(* Baseline: factorial via let-rec *)
let rec fact n =
if n = 0 then 1 else n * fact (n - 1) ;;
fact 10

View File

@@ -0,0 +1,14 @@
let rec fib_pair n =
if n = 0 then (0, 1)
else
let (a, b) = fib_pair (n / 2) in
let c = a * (2 * b - a) in
let d = a * a + b * b in
if n mod 2 = 0 then (c, d)
else (d, c + d)
let fib n = let (f, _) = fib_pair n in f
;;
fib 40

View File

@@ -0,0 +1,13 @@
let fib_mod n m =
let a = ref 0 in
let b = ref 1 in
for _ = 1 to n do
let c = (!a + !b) mod m in
a := !b;
b := c
done;
!a
;;
fib_mod 100 1000003

View File

@@ -0,0 +1,21 @@
let classify n =
let by3 = n mod 3 = 0 in
let by5 = n mod 5 = 0 in
if by3 && by5 then `FizzBuzz
else if by3 then `Fizz
else if by5 then `Buzz
else `Num n
let score x = match x with
| `FizzBuzz -> 100
| `Fizz -> 10
| `Buzz -> 5
| `Num n -> n
;;
let total = ref 0 in
for i = 1 to 30 do
total := !total + score (classify i)
done;
!total

View File

@@ -0,0 +1,17 @@
(* Baseline: fizzbuzz returning a list of strings *)
let fizzbuzz n =
let acc = ref [] in
for i = 1 to n do
let s =
if i mod 15 = 0 then "FizzBuzz"
else if i mod 3 = 0 then "Fizz"
else if i mod 5 = 0 then "Buzz"
else Int.to_string i
in
acc := s :: !acc
done ;
List.rev !acc
;;
(* Concatenated for a deterministic check value via String.length *)
String.length (String.concat "," (fizzbuzz 15))

View File

@@ -0,0 +1,17 @@
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)
;;
let t = Node [
Leaf 1;
Node [Leaf 2; Leaf 3];
Node [Node [Leaf 4]; Leaf 5; Leaf 6];
Leaf 7
]
in
List.fold_left (+) 0 (flatten t)

View File

@@ -0,0 +1,26 @@
let inf_int = 1000000
let floyd n graph =
let d = Array.init n (fun i ->
Array.init n (fun j -> graph.(i).(j))) in
for k = 0 to n - 1 do
for i = 0 to n - 1 do
for j = 0 to n - 1 do
if d.(i).(k) + d.(k).(j) < d.(i).(j) then
d.(i).(j) <- d.(i).(k) + d.(k).(j)
done
done
done;
d
;;
let n = 4 in
let g = Array.init n (fun _ -> Array.make n inf_int) in
for i = 0 to n - 1 do g.(i).(i) <- 0 done;
g.(0).(1) <- 5;
g.(0).(3) <- 10;
g.(1).(2) <- 3;
g.(2).(3) <- 1;
let d = floyd n g in
d.(0).(3)

View File

@@ -0,0 +1,20 @@
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 =
let g = gcd (abs n) (abs d) in
if d < 0 then { num = -n / g; den = -d / g }
else { num = n / g; den = d / g }
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)
;;
let r = add (make 1 2) (make 1 3) in
let s = mul (make 2 3) (make 3 4) in
let t = add r s in
t.num + t.den

View File

@@ -0,0 +1,18 @@
let count_chars s =
let t = Hashtbl.create 8 in
for i = 0 to String.length s - 1 do
let c = s.[i] in
let n = match Hashtbl.find_opt t c with
| Some v -> v + 1
| None -> 1
in
Hashtbl.replace t c n
done;
t
let max_count t =
Hashtbl.fold (fun _ v acc -> if v > acc then v else acc) t 0
;;
max_count (count_chars "abracadabra")

View File

@@ -0,0 +1,6 @@
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 + lcm 4 6 + lcm 12 18

View File

@@ -0,0 +1,12 @@
let gray n =
let m = 1 lsl n in
let result = Array.make m 0 in
for i = 0 to m - 1 do
result.(i) <- i lxor (i lsr 1)
done;
result
;;
let g = gray 4 in
Array.fold_left (+) 0 g + Array.length g

View File

@@ -0,0 +1,17 @@
let rec str_contains s sub i =
let nl = String.length s in
let sl = String.length sub in
if i + sl > nl then false
else if String.sub s i sl = sub then true
else str_contains s sub (i + 1)
let count_matching needle text =
let lines = String.split_on_char '\n' text in
List.fold_left (fun acc line ->
if str_contains line needle 0 then acc + 1
else acc
) 0 lines
;;
count_matching "fox" "the quick brown fox\nfox runs fast\nthe dog\nfoxes are clever"

View File

@@ -0,0 +1,17 @@
let count_paths m n =
let dp = Array.make ((m + 1) * (n + 1)) 0 in
dp.(0) <- 1;
for i = 0 to m do
for j = 0 to n do
if i > 0 || j > 0 then begin
let above = if i > 0 then dp.((i - 1) * (n + 1) + j) else 0 in
let left = if j > 0 then dp.(i * (n + 1) + j - 1) else 0 in
dp.(i * (n + 1) + j) <- above + left
end
done
done;
dp.(m * (n + 1) + n)
;;
count_paths 4 6

View File

@@ -0,0 +1,18 @@
let rec group xs =
match xs with
| [] -> []
| x :: rest ->
let rec collect cur acc tail =
match tail with
| [] -> (List.rev acc, [])
| y :: ys ->
if y = cur then collect cur (y :: acc) ys
else (List.rev acc, y :: ys)
in
let (g, tail) = collect x [x] rest in
g :: group tail
;;
let gs = group [1;1;2;2;2;3;1;1;4] in
List.length gs * 10 + List.length (List.nth gs 1)

View File

@@ -0,0 +1,13 @@
let collatz_length n =
let m = ref n in
let count = ref 0 in
while !m > 1 do
if !m mod 2 = 0 then m := !m / 2
else m := 3 * !m + 1;
count := !count + 1
done;
!count
;;
collatz_length 27

View File

@@ -0,0 +1,13 @@
let hamming s t =
if String.length s <> String.length t then -1
else begin
let d = ref 0 in
for i = 0 to String.length s - 1 do
if s.[i] <> t.[i] then d := !d + 1
done;
!d
end
;;
hamming "karolin" "kathrin" + hamming "1011101" "1001001" + hamming "abc" "abcd"

View File

@@ -0,0 +1,11 @@
let rec hanoi n from to_ via =
if n = 0 then 0
else
let a = hanoi (n - 1) from via to_ in
let b = 1 in
let c = hanoi (n - 1) via to_ from in
a + b + c
;;
hanoi 10 1 3 2

View File

@@ -0,0 +1,19 @@
let digit_sum n =
let m = ref n in
let s = ref 0 in
while !m > 0 do
s := !s + !m mod 10;
m := !m / 10
done;
!s
let count_harshad limit =
let c = ref 0 in
for n = 1 to limit do
if n mod (digit_sum n) = 0 then c := !c + 1
done;
!c
;;
count_harshad 100

View File

@@ -0,0 +1,21 @@
let hist xs =
let h = Hashtbl.create 8 in
List.iter (fun x ->
let n = match Hashtbl.find_opt h x with
| Some n -> n + 1
| None -> 1
in
Hashtbl.replace h x n
) xs;
h
let max_value h =
Hashtbl.fold (fun _ v acc -> if v > acc then v else acc) h 0
let total h =
Hashtbl.fold (fun _ v acc -> acc + v) h 0
;;
let h = hist [1;2;3;1;4;5;1;2;6;7;1;8;9;1;0] in
total h * max_value h

View File

@@ -0,0 +1,38 @@
type tree = Leaf of int * char | Node of int * tree * tree
let weight = function
| Leaf (w, _) -> w
| Node (w, _, _) -> w
let rec insert t lst =
match lst with
| [] -> [t]
| h :: rest ->
if weight t <= weight h then t :: lst
else h :: insert t rest
let rec build_tree lst =
match lst with
| [] -> failwith "empty"
| [t] -> t
| a :: b :: rest ->
let merged = Node (weight a + weight b, a, b) in
build_tree (insert merged rest)
let rec depth d t =
match t with
| Leaf (w, _) -> w * d
| Node (_, l, r) -> depth (d + 1) l + depth (d + 1) r
;;
let initial = [
Leaf (5, 'a');
Leaf (9, 'b');
Leaf (12, 'c');
Leaf (13, 'd');
Leaf (16, 'e');
Leaf (45, 'f')
] in
let t = build_tree initial in
depth 0 t

View File

@@ -0,0 +1,14 @@
let isqrt n =
if n < 2 then n
else
let x = ref n in
let y = ref ((!x + 1) / 2) in
while !y < !x do
x := !y;
y := (!x + n / !x) / 2
done;
!x
;;
isqrt 144 + isqrt 200 + isqrt 1000000 + isqrt 2

View File

@@ -0,0 +1,21 @@
let is_prime n =
if n < 2 then false
else
let p = ref true in
let i = ref 2 in
while !i * !i <= n && !p do
if n mod !i = 0 then p := false;
i := !i + 1
done;
!p
let count_primes n =
let c = ref 0 in
for i = 2 to n do
if is_prime i then c := !c + 1
done;
!c
;;
count_primes 100

View File

@@ -0,0 +1,7 @@
let rec josephus n k =
if n = 1 then 0
else (josephus (n - 1) k + k) mod n
;;
josephus 50 3 + 1

View File

@@ -0,0 +1,20 @@
type json =
| JNull
| JBool of bool
| JInt of int
| JStr of string
| JList of json list
let rec to_string j =
match j with
| JNull -> "null"
| JBool b -> if b then "true" else "false"
| JInt n -> string_of_int n
| JStr s -> "\"" ^ s ^ "\""
| JList xs ->
"[" ^ String.concat "," (List.map to_string xs) ^ "]"
;;
let j = JList [JInt 1; JBool true; JNull; JStr "hi"; JList [JInt 2; JInt 3]] in
String.length (to_string j)

View File

@@ -0,0 +1,12 @@
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
;;
max_subarray [-2; 1; -3; 4; -1; 2; 1; -5; 4]

37
lib/ocaml/baseline/kmp.ml Normal file
View File

@@ -0,0 +1,37 @@
let kmp_table pat =
let n = String.length pat in
let t = Array.make n 0 in
let k = ref 0 in
for i = 1 to n - 1 do
while !k > 0 && pat.[!k] <> pat.[i] do
k := t.(!k - 1)
done;
if pat.[!k] = pat.[i] then k := !k + 1;
t.(i) <- !k
done;
t
let kmp_search text pat =
let m = String.length text in
let n = String.length pat in
if n = 0 then 0
else begin
let t = kmp_table pat in
let count = ref 0 in
let k = ref 0 in
for i = 0 to m - 1 do
while !k > 0 && pat.[!k] <> text.[i] do
k := t.(!k - 1)
done;
if pat.[!k] = text.[i] then k := !k + 1;
if !k = n then begin
count := !count + 1;
k := t.(n - 1)
end
done;
!count
end
;;
kmp_search "abababcabababcababcc" "abab"

View File

@@ -0,0 +1,17 @@
let knapsack values weights cap =
let n = Array.length values in
let dp = Array.make (cap + 1) 0 in
for i = 0 to n - 1 do
let v = values.(i) and w = weights.(i) in
for c = cap downto w do
let take = dp.(c - w) + v in
if take > dp.(c) then dp.(c) <- take
done
done;
dp.(cap)
;;
let v = [|6; 10; 12; 15; 20|] in
let w = [|1; 2; 3; 4; 5|] in
knapsack v w 8

View File

@@ -0,0 +1,41 @@
(* Baseline: untyped lambda calculus with closures over a Hashtbl env *)
type term =
| Var of string
| Abs of string * term
| App of term * term
| Num of int
;;
type value =
| VNum of int
| VClos of string * term * (string * value) list
;;
let rec lookup name env =
match env with
| [] -> failwith "unbound"
| (n, v) :: t -> if n = name then v else lookup name t
;;
let rec eval env t =
match t with
| Num n -> VNum n
| Var x -> lookup x env
| Abs (x, body) -> VClos (x, body, env)
| App (f, a) ->
let fv = eval env f in
let av = eval env a in
(match fv with
| VClos (param, body, captured) ->
eval ((param, av) :: captured) body
| _ -> failwith "not a function")
;;
let unwrap v = match v with VNum n -> n | _ -> failwith "not a number" ;;
(* (\x. \y. x) 7 99 = 7 *)
let term =
App (App (Abs ("x", Abs ("y", Var "x")), Num 7), Num 99)
;;
unwrap (eval [] term)

23
lib/ocaml/baseline/lcs.ml Normal file
View File

@@ -0,0 +1,23 @@
let lcs s1 s2 =
let m = String.length s1 in
let n = String.length s2 in
let prev = Array.make (n + 1) 0 in
let curr = Array.make (n + 1) 0 in
for i = 1 to m do
for j = 1 to n do
if s1.[i - 1] = s2.[j - 1] then
curr.(j) <- prev.(j - 1) + 1
else if prev.(j) >= curr.(j - 1) then
curr.(j) <- prev.(j)
else
curr.(j) <- curr.(j - 1)
done;
for j = 0 to n do
prev.(j) <- curr.(j)
done
done;
prev.(n)
;;
lcs "ABCBDAB" "BDCAB"

View File

@@ -0,0 +1,30 @@
let lev_iter s1 s2 =
let m = String.length s1 in
let n = String.length s2 in
let prev = Array.make (n + 1) 0 in
let curr = Array.make (n + 1) 0 in
for j = 0 to n do prev.(j) <- j done;
for i = 1 to m do
curr.(0) <- i;
for j = 1 to n do
if s1.[i - 1] = s2.[j - 1] then
curr.(j) <- prev.(j - 1)
else begin
let a = prev.(j) in
let b = curr.(j - 1) in
let c = prev.(j - 1) in
let m1 = if a < b then a else b in
curr.(j) <- (if m1 < c then m1 else c) + 1
end
done;
for j = 0 to n do prev.(j) <- curr.(j) done
done;
prev.(n)
;;
lev_iter "kitten" "sitting"
+ lev_iter "saturday" "sunday"
+ lev_iter "abc" "abc"
+ lev_iter "" "abcde"
+ lev_iter "intention" "execution"

View File

@@ -0,0 +1,18 @@
let rec lev s1 s2 i j =
if i = 0 then j
else if j = 0 then i
else if s1.[i - 1] = s2.[j - 1] then
lev s1 s2 (i - 1) (j - 1)
else
1 + min (lev s1 s2 (i - 1) j)
(min (lev s1 s2 i (j - 1)) (lev s1 s2 (i - 1) (j - 1)))
let dist s1 s2 = lev s1 s2 (String.length s1) (String.length s2)
;;
dist "abc" "abx"
+ dist "ab" "ba"
+ dist "abc" "axyc"
+ dist "" "abcd"
+ dist "ab" ""

18
lib/ocaml/baseline/lis.ml Normal file
View File

@@ -0,0 +1,18 @@
let lis arr =
let n = Array.length arr in
let dp = Array.make n 1 in
for i = 1 to n - 1 do
for j = 0 to i - 1 do
if arr.(j) < arr.(i) && dp.(j) + 1 > dp.(i) then
dp.(i) <- dp.(j) + 1
done
done;
let best = ref 0 in
for i = 0 to n - 1 do
if dp.(i) > !best then best := dp.(i)
done;
!best
;;
lis [|10; 22; 9; 33; 21; 50; 41; 60; 80|]

View File

@@ -0,0 +1,5 @@
(* Baseline: List functions exercise *)
let xs = [1; 2; 3; 4; 5] ;;
let doubled = List.map (fun x -> x * 2) xs ;;
let total = List.fold_left (fun a b -> a + b) 0 doubled ;;
total

View File

@@ -0,0 +1,20 @@
let luhn s =
let n = String.length s in
let total = ref 0 in
for i = 0 to n - 1 do
let d = Char.code s.[n - 1 - i] - Char.code '0' in
let v = if i mod 2 = 1 then
let dd = d * 2 in
if dd > 9 then dd - 9 else dd
else d
in
total := !total + v
done;
!total mod 10 = 0
;;
(if luhn "79927398713" then 1 else 0)
+ (if luhn "79927398710" then 1 else 0)
+ (if luhn "4532015112830366" then 1 else 0)
+ (if luhn "1234567890123456" then 1 else 0)

View File

@@ -0,0 +1,15 @@
let majority xs =
let cand = ref 0 in
let count = ref 0 in
List.iter (fun x ->
if !count = 0 then begin
cand := x;
count := 1
end else if x = !cand then count := !count + 1
else count := !count - 1
) xs;
!cand
;;
majority [3; 3; 4; 2; 4; 4; 2; 4; 4]

Some files were not shown because too many files have changed in this diff Show More