Compare commits

298 Commits

Author SHA1 Message Date
0231bb46a6 ocaml: phase 5.1 trapping_rain.ml baseline (LeetCode trapped water = 6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Classic trapped-rain-water two-pass DP:

  left_max[i]  = max(heights[0..i])    (forward sweep)
  right_max[i] = max(heights[i..n-1])  (downto sweep)
  water        = sum over i of (min(left_max[i], right_max[i])
                                  - heights[i])

For [0; 1; 0; 2; 1; 0; 1; 3; 2; 1; 2; 1]: water = 6.

Tests dual sweep (forward + downto), array of running maxes,
inline-if rhs of <- for running-max update (uses iter-236 fix
for <- accepting if/match RHS).

203 baseline programs total.
2026-05-11 05:54:39 +00:00
fed07059a3 ocaml: phase 5.1 gas_station.ml baseline (circular tour start = 3)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Classic O(n) greedy gas-station algorithm:

  walk once, tracking
    total = sum of (gas[i] - cost[i])  -- if negative, no answer
    curr  = running tank since start   -- on negative, advance
                                          start past i+1 and reset

  if total < 0 then -1 else start

For gas = [1;2;3;4;5], cost = [3;4;5;1;2], unique start = 3.

Tests `total` + `curr` parallel accumulators, reset-on-failure
pattern.

202 baseline programs total.
2026-05-11 05:44:38 +00:00
c8327823ee ocaml: phase 5.1 min_jumps.ml baseline (greedy BFS-like min jumps = 4)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Greedy BFS-frontier style — track the farthest reach within the
current jump's reachable range, and bump the jump counter when i
runs into the current frontier:

  while !i < n - 1 do
    farthest := max(farthest, i + arr.(i));
    if !i = !cur_end then begin
      jumps := !jumps + 1;
      cur_end := !farthest
    end;
    i := !i + 1
  done

For [2; 3; 1; 1; 2; 4; 2; 0; 1; 1] (n = 10), the optimal jump
sequence 0 -> 1 -> 4 -> 5 -> 9 uses 4 jumps.

Tests greedy-with-frontier pattern, three parallel refs
(jumps, cur_end, farthest), mixed for-style index loop using ref.

201 baseline programs total.
2026-05-11 05:34:46 +00:00
fad81e0b0c ocaml: phase 5.1 combinations.ml baseline (C(9, 4) = 126)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Pascal-recursion combination enumerator:

  let rec choose k xs =
    if k = 0 then [[]]
    else match xs with
      | [] -> []
      | h :: rest ->
        List.map (fun c -> h :: c) (choose (k - 1) rest)
        @ choose k rest

  C(9, 4) = |choose 4 [1; ...; 9]| = 126

Tests pure-functional enumeration with List.map + closure over h,
@ append, [] | h :: rest pattern match on shrinking input.

200 baseline programs total -- milestone.
2026-05-11 05:25:03 +00:00
3ccce58e0a ocaml: phase 5.1 unique_paths_obs.ml baseline (4x4 grid w/ obstacles = 3)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Standard 2D unique-paths DP with obstacles gating each cell:

  dp[i][j] = if grid[i][j] = 1 then 0
             else dp[i-1][j] + dp[i][j-1]

Grid (1s are obstacles):
  . . . .
  . # . .
  . . . #
  # . . .

dp:
  1 1 1 1
  1 0 1 2
  1 1 2 0
  0 1 3 3

Returns dp[3][3] = 3.

Complements grid_paths.ml (no-obstacles version) — same DP shape
but obstacles zero out cells and reshape the path count.

199 baseline programs total.
2026-05-11 05:14:47 +00:00
8ab2f80615 ocaml: phase 5.1 daily_temperatures.ml baseline (sum of waits = 10)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Monotonic decreasing stack — for each day i, pop entries from
the stack whose temperature is strictly less than today's; their
answer is (i - popped_index).

  temps  = [73; 74; 75; 71; 69; 72; 76; 73]
  answer = [ 1;  1;  4;  2;  1;  1;  0;  0]
  sum    = 10

Complementary to next_greater.ml (iter 256) — same monotonic-stack
skeleton but stores the distance to the next greater element
rather than its value.

Tests `match !stack with | top :: rest when …` pattern with
guard inside a while-cont-flag loop.

198 baseline programs total.
2026-05-11 05:04:37 +00:00
230f803abb ocaml: phase 5.1 count_bits.ml baseline (sum popcount 0..100 = 319)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
DP recurrence for popcount that avoids host bitwise operations:

  result[i] = result[i / 2] + (i mod 2)

Drops the low bit (i / 2 stands in for i lsr 1) and adds it back
if it was 1 (i mod 2 stands in for i land 1).

  sum over 0..100 of popcount(i) = 319

Tests pure-arithmetic popcount, accumulating ref + DP array,
classic look-back to half-index pattern.

197 baseline programs total.
2026-05-11 04:54:36 +00:00
b240408a4c ocaml: phase 5.1 bs_rotated.ml baseline (rotated array search, encoded -66)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Binary search in a rotated sorted array. Standard sorted-half
test at each step:

  if arr.(lo) <= arr.(mid) then
    left half [lo, mid] is sorted -> check whether target is in it
  else
    right half [mid, hi] is sorted -> check whether target is in it

For [4; 5; 6; 7; 0; 1; 2]:
  search 0 -> index 4
  search 7 -> index 3
  search 3 -> -1 (absent)

Encoded fingerprint: 4 + 3*10 + (-1)*100 = -66.

First baseline returning a negative top-level value; the runner
uses literal grep -qF so leading minus parses fine.

196 baseline programs total.
2026-05-11 04:44:37 +00:00
67ece98ba1 ocaml: phase 5.1 task_scheduler.ml baseline ("AAABBC" cooldown 2 -> 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Task-scheduler closed-form min total intervals:

  m = max letter frequency
  k = number of letters tied at frequency m
  answer = max((m - 1) * (n + 1) + k, total_tasks)

For "AAABBC" with cooldown n = 2:
  freq A = 3, freq B = 2, freq C = 1 -> m = 3, k = 1
  formula = (3 - 1) * (2 + 1) + 1 = 7
  total tasks = 6
  answer = 7

Witness schedule: A, B, C, A, B, idle, A.

Tests String.iter with side-effecting count update via
Char.code arithmetic, fixed-size 26-bucket histogram.

195 baseline programs total.
2026-05-11 04:34:40 +00:00
33be068c01 ocaml: phase 5.1 min_subarr_target.ml baseline (min subarray sum >= 7 = 2)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Classic two-pointer / sliding window: expand right, then shrink
left while the window still satisfies the >= constraint, recording
the smallest valid length.

  for r = 0 to n - 1 do
    sum := !sum + arr.(r);
    while !sum >= target do
      ... record (r - !l + 1) if smaller ...
      sum := !sum - arr.(!l);
      l := !l + 1
    done
  done

For [2; 3; 1; 2; 4; 3], target 7 -> window [4, 3] of length 2.

Sentinel n+1 marks "not found"; final guard reduces to 0.

Tests for + inner while shrinking loop, ref-tracked sum updated
on both expansion and contraction.

194 baseline programs total.
2026-05-11 04:24:29 +00:00
bf468e5ec3 ocaml: phase 5.1 min_meeting_rooms.ml baseline (8 meetings, min 4 rooms)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Sweep-line algorithm via separately-sorted starts / ends arrays:

  while i < n do
    if starts[i] < ends[j] then begin busy++; rooms = max; i++ end
    else begin busy--; j++ end
  done

  intervals: (0,30) (5,10) (15,20) (10,25) (5,12)
             (20,35) (0,5)  (8,18)

At time 8, meetings (0,30), (5,10), (5,12), (8,18) are all active
simultaneously -> answer = 4.

Tests local helper bound via let (`let bubble a = ...`) for
in-place sort, dual-pointer sweep on parallel ordered event streams.

193 baseline programs total.
2026-05-11 04:14:33 +00:00
90ba37ecc8 ocaml: phase 5.1 activity_select.ml baseline (greedy non-overlap = 4)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Activity selection by earliest end time. Manual bubble sort over
(start, end) tuple array, then sweep accepting whenever the next
start >= last_end.

  intervals: (1,4) (3,5) (0,6) (5,7) (3,8) (5,9) (6,10) (8,11)
             (8,12) (2,13) (12,14)

  selection: (1,4) -> (5,7) -> (8,11) -> (12,14)  = 4 activities

Tests double-loop bubble sort on tuple array with let-pattern
destructure for swap-key extraction, in-place swap of tuple cells.

192 baseline programs total.
2026-05-11 04:04:42 +00:00
3f00e62577 ocaml: phase 5.1 count_paths_dag.ml baseline (source-to-sink paths = 3)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Count source-to-sink paths in a DAG via Kahn's topological sort
plus accumulation:

  paths[source] = 1
  for u in topological order:
    for v in adj[u]: paths[v] += paths[u]

Same 6-node DAG as topo_sort.ml:
  0 -> {1, 2}    1 -> {3}    2 -> {3, 4}    3 -> {5}    4 -> {5}

The three witnesses 0 -> 5:
  0 -> 1 -> 3 -> 5
  0 -> 2 -> 3 -> 5
  0 -> 2 -> 4 -> 5

Tests Queue-driven Kahn order + List.rev to recover topological
order, module-level mutable arrays (in_deg, paths), accumulation
in topological traversal.

191 baseline programs total.
2026-05-11 03:54:52 +00:00
97a29c6bac ocaml: phase 5.1 stock_two.ml baseline (best of 2 transactions = 6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Two-pass partition DP for max profit with at most 2 transactions:

  left[i]  = max single-trans profit in prices[0..i]
              (forward scan tracking running min)
  right[i] = max single-trans profit in prices[i..n-1]
              (backward scan tracking running max)
  answer   = max over i of (left[i] + right[i])

For [3; 3; 5; 0; 0; 3; 1; 4]:
  optimal partition i = 2:
    left[2]  = sell@5 after buy@3            = 2
    right[2] = sell@4 after buy@0 in [2..7]  = 4
                                       total = 6

Tests parallel forward + backward passes on parallel DP arrays,
mixed ref + array state, for downto + for ascending scans on
the same data.

190 baseline programs total.
2026-05-11 03:44:40 +00:00
73efd229be ocaml: phase 5.1 house_robber.ml baseline (max non-adjacent sum = 22)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Classic House Robber linear DP:

  dp[i] = max(dp[i-2] + houses[i], dp[i-1])

For [2; 7; 9; 3; 1; 5; 8; 6]:

  dp = [2, 7, 11, 11, 12, 16, 20, 22]
  max sum = 22

Optimal pick is {indices 0, 2, 5, 7}: 2 + 9 + 5 + 6 = 22 (all
non-adjacent).

Tests linear DP with early-return edge cases for n=0 and n=1,
inline-if rhs for max-of-two, dual look-back into dp[i-2] and
dp[i-1].

189 baseline programs total.
2026-05-11 03:34:48 +00:00
6d89da9380 ocaml: phase 5.1 interval_overlap.ml baseline (7 intervals, 6 overlapping pairs)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
For each (i, j) pair with i < j, test whether intervals (s1, e1)
and (s2, e2) overlap via the standard `s1 <= e2 && s2 <= e1`.

  intervals: (1,4) (2,5) (7,9) (3,6) (8,10) (11,12) (0,2)

  overlapping pairs:
    (1,4) & (2,5)        (1,4) & (3,6)        (1,4) & (0,2)
    (2,5) & (3,6)        (2,5) & (0,2)
    (7,9) & (8,10)
                                                     = 6

Tests double for-loop with both-side tuple destructure via
`let (s1, e1) = arr.(i) in`, conjunctive comparison, list-to-
array conversion.

188 baseline programs total.
2026-05-11 03:24:45 +00:00
d3340107e6 ocaml: phase 5.1 count_palindromes.ml baseline ("aabaa" -> 9 palindromes)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Expand-around-center linear-time palindrome counting:

  for c = 0 to 2*n - 2 do
    let l = ref (c / 2) in
    let r = ref ((c + 1) / 2) in
    while !l >= 0 && !r < n && s.[!l] = s.[!r] do
      count := !count + 1; l := !l - 1; r := !r + 1
    done
  done

The 2n-1 centers cover both odd (c even -> l = r) and even
(c odd -> l = r - 1) palindromes.

For "aabaa":
  5 singletons + 2 "aa" + 1 "aba" + 1 "aabaa" = 9

Complements lps_dp.ml (longest subsequence) and manacher.ml
(longest substring); this one *counts* all palindromic substrings.

187 baseline programs total.
2026-05-11 03:14:23 +00:00
aaa6020037 ocaml: phase 5.1 count_subarrays_k.ml baseline (sum-k subarrays = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Count contiguous subarrays summing to a target k using a prefix
sum table:

  prefix[i+1] = prefix[i] + arr[i]
  count = |{ (i, j) : 0 <= i < j <= n,  prefix[j] - prefix[i] = k }|

For arr = [1; 1; 1; 2; -1; 3; 1; -2; 4] and k = 3, the seven
witnesses are:

  [1, 1, 1]            (0..2)
  [1, 1, 2, -1]        (1..4)
  [1, 2]               (2..3)
  [2, -1, 3, 1, -2]    (3..7)
  [-1, 3, 1]           (4..6)
  [3]                  (5..5)
  [1, -2, 4]           (6..8)

Negative-valued arrays exercise mixed-sign integer arithmetic.

186 baseline programs total.
2026-05-11 03:04:14 +00:00
8ef24847d3 ocaml: phase 5.1 floyd_cycle.ml baseline (tortoise-hare, mu=0 lam=8 -> 8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Floyd's cycle detection on a numeric function f(x) = (2x + 5) mod 17.
Three phases:

  Phase 1: advance slow/fast until collision inside the cycle
            (fast double-steps, slow single-steps)
  Phase 2: restart slow from x0; advance both by 1 until they
            meet — count is mu (length of tail before cycle)
  Phase 3: advance fast around cycle once until it meets slow
            — count is lam (cycle length)

For x0 = 1, the orbit visits 1, 7, 2, 9, 6, 0, 5, 15 then returns
to 1 — pure cycle of length 8, mu = 0, lam = 8. Encoded as
mu*100 + lam = 8.

Tests three sequential while loops sharing ref state,
double-step `fast := f (f !fast)`, meeting-condition flag.

185 baseline programs total.
2026-05-11 02:54:50 +00:00
b3ee88e9bb ocaml: phase 5.1 kth_two.ml baseline (8th smallest of two sorted = 8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Two-pointer merge advancing the smaller-head pointer k times,
without materializing the merged array:

  while !count < k do
    let pick_a =
      if !i = m then false        (* a exhausted, take from b *)
      else if !j = n then true    (* b exhausted, take from a *)
      else a.(!i) <= b.(!j)
    in
    if pick_a then ... else ...;
    count := !count + 1
  done

For a = [1;3;5;7;9;11;13], b = [2;4;6;8;10;12]:
  merged order: 1,2,3,4,5,6,7,8,9,10,11,12,13
  8th element = 8.

Tests nested if/else if/else flowing into a bool, dual-ref
two-pointer loop, separate count counter for k-th constraint.

184 baseline programs total.
2026-05-11 02:44:40 +00:00
2c7a1bfc47 ocaml: phase 5.1 permutations_gen.ml baseline (24 perms, 12 with a<b ends)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Recursive permutation generator via fold-pick-recurse:

  let rec permutations xs = match xs with
    | [] -> [[]]
    | _ ->
      List.fold_left (fun acc x ->
        let rest = List.filter (fun y -> y <> x) xs in
        let subs = permutations rest in
        acc @ List.map (fun p -> x :: p) subs
      ) [] xs

For permutations of [1; 2; 3; 4] (24 total), count those whose
first element is less than the last:

  match p with
  | [a; _; _; b] when a < b -> count := !count + 1
  | _ -> ()

By symmetry, exactly half satisfy a < b = 12.

Tests List.filter, recursive fold with append, fixed-length
list pattern [a; _; _; b] with multiple wildcards + when guard.

183 baseline programs total.
2026-05-11 02:34:58 +00:00
047ea62d43 ocaml: phase 5.1 regex_simple.ml baseline (./* matcher, 7/28 match)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Recursive regex matcher with Leetcode-style semantics:
  .   matches any single character
  <c>* matches zero or more of <c>

  let rec is_match s i p j =
    if j = String.length p then i = String.length s
    else
      let first = i < String.length s
                  && (p.[j] = '.' || p.[j] = s.[i])
      in
      if j + 1 < String.length p && p.[j+1] = '*' then
           is_match s i p (j + 2)                   (* skip * group *)
        || (first && is_match s (i + 1) p j)        (* consume one  *)
      else
        first && is_match s (i + 1) p (j + 1)

Patterns vs texts:
  .a.b    | aabb axb "" abcd abc aaabbbc x   -> 1 match
  a.*b    | aabb axb "" abcd abc aaabbbc x   -> 2 matches
  x*      | aabb axb "" abcd abc aaabbbc x   -> 2 matches
  a*b*c   | aabb axb "" abcd abc aaabbbc x   -> 2 matches
                                             total = 7

Complements wildcard_match.ml which uses LIKE-style * / ?.

182 baseline programs total.
2026-05-11 02:25:04 +00:00
2726ed9b8a ocaml: phase 5.1 palindrome_part.ml baseline (min cuts "aabba" = 1)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Two-phase palindrome-partition DP for the minimum-cuts variant:

  Phase 1: is_pal[i][j] palindrome table via length-major fill
           (single chars, then pairs, then expand inward).

  Phase 2: cuts[i] = 0 if s[0..i] is itself a palindrome,
                  = min over j of (cuts[j-1] + 1)
                    where s[j..i] is a palindrome.

  min_cut "aabba" = 1   ("a" | "abba")

Tests two sequential 2D DPs sharing the same is_pal matrix,
inline begin/end branches inside the length-major fill, mixed
bool and int 2D arrays.

181 baseline programs total.
2026-05-11 02:14:44 +00:00
6d7df11224 ocaml: phase 5.1 island_count.ml baseline (6x7 grid, 5 components)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Count 4-connected components of 1-cells via DFS flood from every
unvisited 1-cell.

Grid (1s shown as #):
  # # . . . # #
  # . . . # # .
  . . # # . . .
  . # # # . # #
  # . . . . # .
  # # . # # # .

Components:
  {(0,0),(0,1),(1,0)}
  {(0,5),(0,6),(1,4),(1,5)}
  {(2,2),(2,3),(3,1),(3,2),(3,3)}
  {(3,5),(3,6),(4,5),(5,3),(5,4),(5,5)}
  {(4,0),(5,0),(5,1)}
                              -> 5 islands

Complementary to flood_fill.ml (largest component); this counts
total components.

Tests recursive function returning () at early-exit branches,
ordered double-for entry pass triggering one fill per island root.

180 baseline programs total.
2026-05-11 02:04:08 +00:00
8a80bd3923 ocaml: phase 5.1 dp_word_break.ml baseline (4/5 strings segmentable)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Classic word-break DP — for each position i, check whether any
dictionary word ends at i with a prior reachable position:

  dp[i] = exists w in dict with wl <= i and
          dp[i - wl] && s.sub (i - wl) wl = w

Dictionary: apple, pen, pine, pineapple, cats, cat, and, sand, dog
Inputs:
  applepenapple        yes  (apple pen apple)
  pineapplepenapple    yes  (pineapple pen apple)
  catsanddog           yes  (cats and dog)
  catsandog            no   (no segmentation reaches the end)
  applesand            yes  (apple sand)

Tests bool-typed Array, String.sub primitive, nested List.iter
over the dict inside for-loop over end positions, closure capture
of the outer dp.

179 baseline programs total.
2026-05-11 01:53:21 +00:00
609205b551 ocaml: phase 5.1 histogram_area.ml baseline (largest rectangle = 10)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Linear-time stack algorithm for largest rectangle in histogram:

  for i = 0 to n do
    let h = if i = n then 0 else heights.(i) in
    while top-of-stack's height > h do
      pop the top, compute its max-width rectangle:
        width = (no-stack ? i : i - prev_top - 1)
        area  = height * width
      update best
    done;
    if i < n then push i
  done

Sentinel pass at i=n with h=0 flushes the remaining stack.

For [2; 1; 5; 6; 2; 3], bars at indices 2 (h=5) and 3 (h=6) form
a width-2 rectangle of height 5 = 10.

Tests guarded patterns with `when` inside while-cont-flag, nested
`match !stack with | [] -> i | t :: _ -> i - t - 1` for width
computation.

178 baseline programs total.
2026-05-11 01:42:26 +00:00
f9371e7d22 ocaml: phase 5.1 lps_dp.ml baseline (LPS "BBABCBCAB" = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Standard O(n^2) length-major DP for longest palindromic
subsequence:

  dp[i][j] = dp[i+1][j-1] + 2                if s[i] = s[j]
           = max(dp[i+1][j], dp[i][j-1])     otherwise

  lps "BBABCBCAB" = 7   (witness "BABCBAB" etc.)

Complementary to manacher.ml (longest palindromic *substring*,
also length 7 on that input by coincidence) — this is the
subsequence variant which doesn't require contiguity.

Tests length-major fill order, inline if for the length-2 base
case, double-nested for with derived j = i + len - 1.

177 baseline programs total.
2026-05-11 01:32:41 +00:00
7f310a4da7 ocaml: phase 5.1 bs_bounds.ml baseline (lower/upper bound, fingerprint 3211)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
C++-style lower_bound / upper_bound on a sorted array:

  lower_bound — first index >= x  (loop uses arr.(mid) < x)
  upper_bound — first index >  x  (loop uses arr.(mid) <= x)

  a = [|1; 2; 2; 3; 3; 3; 5; 7; 9|]

  upper(x) - lower(x) gives the count of x in a:
    cnt3 = 3   cnt2 = 2   cnt5 = 1   cnt9 = 1   cnt4 = 0

  fingerprint = 3*1000 + 2*100 + 1*10 + 1 + 0 = 3211

Tests parallel while loops with bisection on ref, mixed strict
and non-strict comparison branches, count-via-subtraction idiom.

176 baseline programs total.
2026-05-11 01:22:31 +00:00
6780acd0af ocaml: phase 5.1 distinct_subseq.ml baseline ("rabbit" in "rabbbit" = 3)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Classic distinct-subsequences 2D DP:

  dp[i][j] = dp[i-1][j] + (s[i-1] = t[j-1] ? dp[i-1][j-1] : 0)
  dp[i][0] = 1   (empty t is a subseq of any prefix of s)

  count_subseq "rabbbit" "rabbit" = 3

The three witnesses correspond to which 'b' in "rabbbit" is
dropped (positions 2, 3, or 4 zero-indexed of the run of bs).

Complements subseq_check.ml (just tests presence); this one counts
distinct embeddings.

Tests 2D DP with Array.init n (fun _ -> Array.make m 0), base row
initialization, mixed string + array indexing.

175 baseline programs total.
2026-05-11 01:12:33 +00:00
b771ea306c ocaml: phase 5.1 bracket_match.ml baseline (5/9 balanced strings)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Stack-based multi-bracket parenthesis matching for ( [ { ) ] }.
Non-bracket chars are skipped (treated as content).

Tests:
  ()           yes
  [{()}]       yes
  ({[}])       no  (mismatched closer)
  ""           yes
  ((           no  (unclosed)
  ()[](){}     yes
  (a(b)c)      yes  (a/b/c skipped)
  (()          no
  ])           no
                 5 balanced

Body uses begin/end-wrapped match inside while:

  else if c = ')' || c = ']' || c = '}' then begin
    match !stack with
    | [] -> ok := false
    | top :: rest ->
      let pair =
        (c = ')' && top = '(') ||
        (c = ']' && top = '[') ||
        (c = '}' && top = '{')
      in
      if pair then stack := rest else ok := false
  end

Tests side-effecting match arms inside while body, ref-of-list as
stack, multi-char pairing dispatch.

174 baseline programs total.
2026-05-11 01:02:59 +00:00
6c77dec495 ocaml: phase 5.1 wildcard_match.ml baseline (* / ? matcher, 6/18 match)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Recursive wildcard matcher:

  let rec is_match s i p j =
    if j = String.length p then i = String.length s
    else if p.[j] = '*' then
         is_match s i p (j + 1)             (* * matches empty   *)
      || (i < String.length s && is_match s (i + 1) p j)  (* * eats char *)
    else
      i < String.length s
      && (p.[j] = '?' || p.[j] = s.[i])
      && is_match s (i + 1) p (j + 1)

Patterns vs texts:
  a*b      | aaab abc abxyz xy xyz axby   -> 1 match
  ?b*c     | aaab abc abxyz xy xyz axby   -> 1 match
  *x*y*    | aaab abc abxyz xy xyz axby   -> 4 matches
                                  total   = 6

Tests deeply nested short-circuit && / ||, char equality on
pattern bytes, doubly-nested List.iter cross product.

173 baseline programs total.
2026-05-11 00:52:42 +00:00
0a3f02d636 ocaml: phase 5.1 powerset_target.ml baseline (subsets of {1..10} summing to 15 = 20)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Recursive powerset construction by doubling:

  let rec gen xs = match xs with
    | [] -> [[]]
    | h :: rest ->
      let sub = gen rest in
      sub @ List.map (fun s -> h :: s) sub

Enumerates all 2^10 = 1024 subsets, filters by sum:

  count = |{ S ⊆ {1..10} | Σ S = 15 }| = 20

Examples: {1,2,3,4,5}, {2,3,4,6}, {1,4,10}, {7,8}, {6,9}, ...

Tests recursive subset construction via List.map + closures,
pattern matching with h :: rest, List.fold_left (+) 0 sum reduce,
exhaustive O(2^n * n) traversal.

172 baseline programs total.
2026-05-11 00:42:08 +00:00
800dca67ca ocaml: parser accepts top-level tuple patterns in match cases
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Real OCaml accepts `match e1, e2 with | p1, p2 -> …` without
surrounding parens. parse-pattern previously stopped at the cons
layer (`p :: rest`) and treated a trailing `,` as a separator
the outer caller couldn't handle, surfacing as
"expected op -> got op ,".

Fix: `parse-pattern` now collects comma-separated patterns into a
:ptuple after parse-pattern-cons, before the optional `as` alias.
The scrutinee side already built tuples via parse-tuple, so both
sides are now symmetric.

lru_cache.ml (iter 258) reverts its workaround back to the natural
form:

  let rec take n lst = match n, lst with
    | 0, _ -> []
    | _, [] -> []
    | _, h :: r -> h :: take (n - 1) r

607/607 regressions clean.
2026-05-11 00:31:08 +00:00
fd1f94f292 ocaml: phase 5.1 lru_cache.ml baseline (cap=3 LRU, fingerprint 499)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Functional LRU cache via association-list ordered most-recent-first.
Get / put both:
  - find or remove the existing entry
  - cons the fresh (k, v) to the front
  - on put, trim the tail when over capacity

Sequence:
  put 1 100; put 2 200; put 3 300
  a = get 1  -> 100  (moves 1 to front)
  put 4 400          (evicts 2)
  b = get 2  -> -1   (no longer cached)
  c = get 3  -> 300
  d = get 1  -> 100
  a + b + c + d = 499

Tests `match … with (k', v) :: rest when k' = k -> …` tuple-cons
patterns with `when` guards, `function` keyword for arg-less
match, recursive find/remove/take over the same list.

Parser limit found: `match n, lst with` ad-hoc tuple-scrutinee is
not yet supported (got "expected op -> got op ,"); workaround
uses outer `if` plus inner match.

171 baseline programs total.
2026-05-11 00:20:30 +00:00
1d1c35a438 ocaml: phase 5.1 convex_hull.ml baseline (Andrew monotone chain, 5 vertices)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Andrew's monotone chain hull over 8 integer points:

  pts = [(0,0); (1,1); (2,0); (2,2); (0,2); (1,0); (3,3); (5,1)]

Sort lex, build lower hull L->R then upper R->L, popping while
the cross product is non-positive (collinear included on hull).

Hull traverse: (0,0) -> (2,0) -> (5,1) -> (3,3) -> (0,2) = 5
((2,0) lies on the lower edge from (0,0) to (5,1)).

Tests List.sort with 2-tuple comparator using nested pair
destructure, repeated `let (x, y) = arr.(i) in` array tuple
destructure across both passes, while + cont-flag pattern.

170 baseline programs total.
2026-05-11 00:09:06 +00:00
ca34cede88 ocaml: phase 5.1 next_greater.ml baseline (monotonic stack, sum 153)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Right-to-left monotonic stack for next-greater-element:

  for i = n - 1 downto 0 do
    while (match !stack with [] -> false | h :: _ -> h <= arr.(i)) do
      stack := List.tl !stack
    done;
    (match !stack with
     | [] -> res.(i) <- -1
     | h :: _ -> res.(i) <- h);
    stack := arr.(i) :: !stack
  done

For [4; 5; 2; 25; 7; 8; 1; 30; 12]:
  results: [5; 25; 25; 30; 8; 30; 30; -1; -1]
  sum of non-negative = 5+25+25+30+8+30+30 = 153

Tests stack as ref list with match-driven peek, match-as-bool in
while-guard, inline parenthesized match driving <-.

169 baseline programs total.
2026-05-10 23:58:50 +00:00
cb626fc402 ocaml: phase 5.1 fenwick_tree.ml baseline (BIT over 8 elements, fingerprint 228)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Fenwick / Binary Indexed Tree for prefix sums. The classic
`i & -i` low-bit trick needs negative-aware AND, but our `land`
evaluator (iter 127, bitwise via floor/mod arithmetic) only handles
non-negative operands. Workaround: a portable lowbit helper that
finds the largest power of 2 dividing i:

  let lowbit i =
    let r = ref 1 in
    while !r * 2 <= i && i mod (!r * 2) = 0 do
      r := !r * 2
    done;
    !r

After building from [1;3;5;7;9;11;13;15]:
  total       = prefix_sum 8 = 64
  update 1 by +100
  after       = prefix_sum 8 = 164
  total + after = 228

Tests recursive update / prefix_sum chains via helper-extracted
lowbit; documents a non-obvious limit of the bitwise-emulation
layer.

168 baseline programs total.
2026-05-10 23:48:46 +00:00
175a77fba5 ocaml: phase 5.1 segment_tree.ml baseline (range-sum tree, fingerprint 4232)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Power-of-two-indexed segment tree over [1;3;5;7;9;11;13;15]:
  build sums (root holds total = 64)
  query returns range sum in O(log n)
  update propagates a point delta back up the path

Sequence:
  r1 = query [2,5] = 5 + 7 + 9 + 11 = 32
  update idx 3 += 10  (so a[3] becomes 17)
  r2 = query [2,5] = 5 + 17 + 9 + 11 = 42
  encoded fingerprint = r1 + r2*100 = 32 + 4200 = 4232

Tests three mutually independent recursive functions with array
index arithmetic on 2*node / 2*node+1, half-bisection on mid =
(l+r)/2, bottom-up combine pattern.

167 baseline programs total.
2026-05-10 23:38:40 +00:00
3fe3b7b66f ocaml: phase 5.1 magic_square.ml baseline (5x5 Siamese, diag sum = 65)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Siamese construction for odd-order magic squares:

  - place 1 at (0, n/2)
  - for k = 2..n^2, move up-right with (x-1+n) mod n wrap
  - if the target cell is taken, drop down one row instead

  for n=5, magic constant = n*(n^2+1)/2 = 5*26/2 = 65

Returns the main-diagonal sum (65 by construction).

Tests 2D array via Array.init + Array.make, mod arithmetic with
the (x-1+n) mod n idiom for negative-safe wrap, nested begin/end
branches inside for-loop body.

166 baseline programs total.
2026-05-10 23:28:29 +00:00
689438d12e ocaml: phase 5.1 matrix_power.ml baseline (F(30) = 832040 via 2x2 matrix pow)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Fibonacci via repeated-squaring matrix exponentiation:

  [[1, 1], [1, 0]] ^ n = [[F(n+1), F(n)], [F(n), F(n-1)]]

Recursive O(log n) power:

  let rec mpow m n =
    if n = 0 then identity
    else if n mod 2 = 0 then let h = mpow m (n / 2) in mul h h
    else mul m (mpow m (n - 1))

Returns the .b cell after raising to the 30th power -> 832040 = F(30).

Tests record literal construction inside recursive function returns,
record field access (x.a etc), and pure integer arithmetic in the
matrix multiply.

165 baseline programs total.
2026-05-10 23:18:26 +00:00
d1a4616ac4 ocaml: phase 5.1 bipartite.ml baseline (7-node bipartite, 4 in color 0)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
BFS-based 2-coloring of an undirected graph:

  edges (undirected):
    0-1  0-3  1-2  1-4  2-5  3-4  3-6  5-6

  Partition: {0, 2, 4, 6} vs {1, 3, 5} (no odd cycles)

Returns count of color-0 vertices (4) on success, -1 on odd-cycle
detection.

Tests Queue-based BFS with a source-loop wrapper for disconnected
graphs, `1 - color.(u)` toggle, color-equality conflict check
on already-colored neighbors.

164 baseline programs total.
2026-05-10 23:08:16 +00:00
32f6c4ee0c ocaml: phase 5.1 egg_drop.ml baseline (2 eggs, 36 floors -> 8 trials)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Classic egg-drop puzzle DP:

  dp[e][f] = 1 + min over k in [1, f] of
              max(dp[e-1][k-1], dp[e][f-k])

For 2 eggs over 36 floors, the optimal worst-case is 8 trials
(closed form: triangular number bound).

Tests 2D DP with triple-nested for-loops, max-of-two via inline
if, large sentinel constant (100000000), mixed shifted indexing
(e-1) and (f-k) where both shift independently.

163 baseline programs total.
2026-05-10 22:58:13 +00:00
62712accdd ocaml: phase 5.1 polygon_area.ml baseline (pentagon 2x area = 32)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Shoelace formula on a pentagon with integer vertices:

  pts = [(0,0); (4,0); (4,3); (2,5); (0,3)]

  2 * area = | Σ (x_i * y_{i+1} - x_{i+1} * y_i) |
           = | 0*0 - 4*0 + 4*3 - 4*0 + 4*5 - 2*3 + 2*3 - 0*5
                + 0*0 - 0*3 |
           = 32

Returns the doubled form (32) to stay integral.

Tests:
  - let (x1, y1) = arr.(i) in   -- tuple destructure from array
  - arr.((i + 1) mod n)         -- modular wrap-around index
  - if a < 0 then - a else a    -- prefix - negation

162 baseline programs total.
2026-05-10 22:47:22 +00:00
c69a7694c8 ocaml: phase 5.1 min_cost_path.ml baseline (4x4 grid DP, optimal cost 12)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Standard 2D DP for min-cost path with right/down moves only:

  dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + cost[i][j]

  cost:                  dp:
    1 3 1 2                1  4  5  7
    1 5 1 3                2  7  6  9
    4 2 1 4                6  8  7 11
    1 6 2 3                7 13  9 12

Optimal cost from (0,0) to (3,3) = 12.

Tests nested 2D arrays via Array.init + Array.make, double-nested
for-loops with branched edges (first row, first column, general),
mixed .(i-1).(j) read + .(i).(j)<- write on the same DP array.

161 baseline programs total.
2026-05-10 22:37:44 +00:00
5384ff6c42 ocaml: phase 5.1 topo_dfs.ml baseline (DFS topo sort, fingerprint 24135)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
DFS topological sort — recurse on out-edges first, prepend after:

  let rec dfs v =
    if not visited.(v) then begin
      visited.(v) <- true;
      List.iter dfs adj.(v);
      order := v :: !order
    end

Same 6-node DAG as iter 230's Kahn's-algorithm baseline:
  0 -> {1, 2}
  1 -> {3}
  2 -> {3, 4}
  3 -> {5}
  4 -> {5}
  5

DFS order: [0; 2; 4; 1; 3; 5]
Horner fold: 0->0->2->24->241->2413->24135.

Complementary to topo_sort.ml (Kahn's BFS); tests recursive DFS
with no explicit stack + List.fold_left as a horner reduction.

160 baseline programs total.
2026-05-10 22:27:18 +00:00
bcb7db2ea4 ocaml: phase 5.1 radix_sort.ml baseline (LSD radix sort, sentinel 802002)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
LSD radix sort over base 10 digits. Per pass:
  - 10 bucket-refs created via Array.init 10 (fun _ -> ref []) (each
    closure call yields a distinct list cell)
  - scan array, append each value to its digit's bucket
  - flatten buckets back to the array in order

Input  [170;45;75;90;802;24;2;66]
Output [2;24;45;66;75;90;170;802]

Sentinel: a.(0) + a.(7)*1000 = 2 + 802*1000 = 802002.

Tests array-of-refs with !buckets.(d) deref, list-mode bucket
sort within in-place array sort, unused for-loop var (`for _ =
1 to maxd`).

159 baseline programs total.
2026-05-10 22:17:40 +00:00
5eed0dd5f5 ocaml: phase 5.1 coin_min.ml baseline (67 cents in US coins = 6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Minimum-coin DP with -1 sentinel for unreachable values:

  let coin_min coins amount =
    let dp = Array.make (amount + 1) (-1) in
    dp.(0) <- 0;
    for i = 1 to amount do
      List.iter (fun c ->
        if c <= i && dp.(i - c) >= 0 then begin
          let cand = dp.(i - c) + 1 in
          if dp.(i) < 0 || cand < dp.(i) then dp.(i) <- cand
        end
      ) coins
    done;
    dp.(amount)

  coin_min [1; 5; 10; 25] 67 = 6   (* 25+25+10+5+1+1 *)

Tests `if c <= i && dp.(i-c) >= 0 then` short-circuit guard;
relies on iter-242 fix so dp.(i-c) is not evaluated when c > i.

158 baseline programs total.
2026-05-10 22:07:17 +00:00
3ea8967571 ocaml: phase 5.1 flood_fill.ml baseline (largest grid component = 7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Recursive 4-way flood fill from every unvisited 1-cell:

  let rec flood visited r c =
    if r < 0 || r >= h || c < 0 || c >= w then 0
    else if visited.(r).(c) || grid.(r).(c) = 0 then 0
    else begin
      visited.(r).(c) <- true;
      1 + flood visited (r - 1) c
        + flood visited (r + 1) c
        + flood visited r (c - 1)
        + flood visited r (c + 1)
    end

Grid (1s shown as #, 0s as .):
  # # . # #
  # . . . #
  . . # . .
  # # # # .
  . . . # #

Largest component: {(2,2),(3,0),(3,1),(3,2),(3,3),(4,3),(4,4)} = 7.

Bounds check r >= 0 must short-circuit before visited/grid reads;
relies on the && / || fix from iter 242.

157 baseline programs total.
2026-05-10 21:57:42 +00:00
e057d9f18f ocaml: phase 5.1 next_permutation.ml baseline (5! - 1 = 119 successors)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Standard in-place next-permutation (Narayana's algorithm):

  let next_perm a =
    let n = Array.length a in
    let i = ref (n - 2) in
    while !i >= 0 && a.(!i) >= a.(!i + 1) do i := !i - 1 done;
    if !i < 0 then false
    else begin
      let j = ref (n - 1) in
      while a.(!j) <= a.(!i) do j := !j - 1 done;
      swap a.(!i) a.(!j);
      reverse a (!i + 1) (n - 1);
      true
    end

Starting from [1;2;3;4;5], next_perm returns true 119 times then
false (when reverse-sorted). 5! - 1 = 119.

Tests guarded `while … && a.(!i) … do` loops that rely on the
iter-242 short-circuit fix.

156 baseline programs total.
2026-05-10 21:47:52 +00:00
4761d41a0d ocaml: && / || short-circuit fix + bfs_grid.ml baseline (5x5 grid, dist 8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Before: `:op` handler always evaluated both operands before dispatching
to ocaml-eval-op. For pure binops that's fine, but `&&` / `||` MUST
short-circuit:

  if nr >= 0 && grid.(nr).(nc) = 0 then ...

When nr = -1, real OCaml never evaluates `grid.(-1)`. Our evaluator
did, and crashed with "nth: list/string and number".

Fix: special-case `&&` and `||` in :op dispatch, mirroring the same
pattern already used for `:=` and `<-`. Evaluate lhs, branch on it,
and only evaluate rhs when needed.

Latent since baseline 1 — earlier programs never triggered it because
the rhs was unconditionally safe.

bfs_grid.ml: shortest path through a 5x5 grid with walls. Standard
BFS using Queue.{push,pop,is_empty} + Array.init for the 2D distance
matrix. Path 0,0 -> ... -> 4,4 has length 8. 155 baseline programs
total.
2026-05-10 21:37:41 +00:00
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
448 changed files with 19937 additions and 29237 deletions

View File

@@ -528,183 +528,6 @@ let () =
| [Rational (_, d)] -> Integer d
| [Integer _] -> Integer 1
| _ -> raise (Eval_error "denominator: expected rational or integer"));
(* printf-spec: apply one Tcl/printf format spec to one arg.
spec is like "%5.2f", "%-10s", "%x", "%c", "%d". Always starts with %
and ends with the conversion char. Supports d i u x X o c s f e g.
Coerces arg to the right type per conversion. *)
register "printf-spec" (fun args ->
let spec_str, arg = match args with
| [String s; v] -> (s, v)
| _ -> raise (Eval_error "printf-spec: (spec arg)")
in
let n = String.length spec_str in
if n < 2 || spec_str.[0] <> '%' then
raise (Eval_error ("printf-spec: invalid spec " ^ spec_str));
let type_char = spec_str.[n - 1] in
let to_int v = match v with
| Integer i -> i
| Number f -> int_of_float f
| String s ->
let s = String.trim s in
(try int_of_string s
with _ ->
try int_of_float (float_of_string s)
with _ -> 0)
| Bool true -> 1 | Bool false -> 0
| _ -> 0
in
let to_float v = match v with
| Number f -> f
| Integer i -> float_of_int i
| String s ->
let s = String.trim s in
(try float_of_string s with _ -> 0.0)
| _ -> 0.0
in
let to_string v = match v with
| String s -> s
| Integer i -> string_of_int i
| Number f -> Sx_types.format_number f
| Bool true -> "1" | Bool false -> "0"
| Nil -> ""
| _ -> Sx_types.inspect v
in
try
match type_char with
| 'd' | 'i' ->
let fmt = Scanf.format_from_string spec_str "%d" in
String (Printf.sprintf fmt (to_int arg))
| 'u' ->
let fmt = Scanf.format_from_string spec_str "%u" in
String (Printf.sprintf fmt (to_int arg))
| 'x' ->
let fmt = Scanf.format_from_string spec_str "%x" in
String (Printf.sprintf fmt (to_int arg))
| 'X' ->
let fmt = Scanf.format_from_string spec_str "%X" in
String (Printf.sprintf fmt (to_int arg))
| 'o' ->
let fmt = Scanf.format_from_string spec_str "%o" in
String (Printf.sprintf fmt (to_int arg))
| 'c' ->
let n_val = to_int arg in
let body = String.sub spec_str 0 (n - 1) in
let fmt = Scanf.format_from_string (body ^ "s") "%s" in
String (Printf.sprintf fmt (String.make 1 (Char.chr (n_val land 0xff))))
| 's' ->
let fmt = Scanf.format_from_string spec_str "%s" in
String (Printf.sprintf fmt (to_string arg))
| 'f' ->
let fmt = Scanf.format_from_string spec_str "%f" in
String (Printf.sprintf fmt (to_float arg))
| 'e' ->
let fmt = Scanf.format_from_string spec_str "%e" in
String (Printf.sprintf fmt (to_float arg))
| 'E' ->
let fmt = Scanf.format_from_string spec_str "%E" in
String (Printf.sprintf fmt (to_float arg))
| 'g' ->
let fmt = Scanf.format_from_string spec_str "%g" in
String (Printf.sprintf fmt (to_float arg))
| 'G' ->
let fmt = Scanf.format_from_string spec_str "%G" in
String (Printf.sprintf fmt (to_float arg))
| _ -> raise (Eval_error ("printf-spec: unsupported conversion " ^ String.make 1 type_char))
with
| Eval_error _ as e -> raise e
| _ -> raise (Eval_error ("printf-spec: invalid format " ^ spec_str)));
(* scan-spec: apply one Tcl/scanf format spec to a string.
Returns (consumed-count . parsed-value), or nil on failure. *)
register "scan-spec" (fun args ->
let spec_str, str = match args with
| [String s; String input] -> (s, input)
| _ -> raise (Eval_error "scan-spec: (spec input)")
in
let n = String.length spec_str in
if n < 2 || spec_str.[0] <> '%' then
raise (Eval_error ("scan-spec: invalid spec " ^ spec_str));
let type_char = spec_str.[n - 1] in
let len = String.length str in
(* skip leading whitespace for non-%c/%s conversions *)
let i = ref 0 in
if type_char <> 'c' then
while !i < len && (str.[!i] = ' ' || str.[!i] = '\t' || str.[!i] = '\n') do incr i done;
let start = !i in
try
match type_char with
| 'd' | 'i' ->
let j = ref !i in
if !j < len && (str.[!j] = '-' || str.[!j] = '+') then incr j;
while !j < len && str.[!j] >= '0' && str.[!j] <= '9' do incr j done;
if !j > start && (str.[start] >= '0' && str.[start] <= '9'
|| (!j > start + 1 && (str.[start] = '-' || str.[start] = '+'))) then
let n_val = int_of_string (String.sub str start (!j - start)) in
let d = Hashtbl.create 2 in
Hashtbl.replace d "value" (Integer n_val);
Hashtbl.replace d "consumed" (Integer !j);
Dict d
else Nil
| 'x' | 'X' ->
let j = ref !i in
while !j < len &&
((str.[!j] >= '0' && str.[!j] <= '9') ||
(str.[!j] >= 'a' && str.[!j] <= 'f') ||
(str.[!j] >= 'A' && str.[!j] <= 'F')) do incr j done;
if !j > start then
let n_val = int_of_string ("0x" ^ String.sub str start (!j - start)) in
let d = Hashtbl.create 2 in
Hashtbl.replace d "value" (Integer n_val);
Hashtbl.replace d "consumed" (Integer !j);
Dict d
else Nil
| 'o' ->
let j = ref !i in
while !j < len && str.[!j] >= '0' && str.[!j] <= '7' do incr j done;
if !j > start then
let n_val = int_of_string ("0o" ^ String.sub str start (!j - start)) in
let d = Hashtbl.create 2 in
Hashtbl.replace d "value" (Integer n_val);
Hashtbl.replace d "consumed" (Integer !j);
Dict d
else Nil
| 'f' | 'e' | 'g' ->
let j = ref !i in
if !j < len && (str.[!j] = '-' || str.[!j] = '+') then incr j;
while !j < len && ((str.[!j] >= '0' && str.[!j] <= '9') || str.[!j] = '.') do incr j done;
if !j < len && (str.[!j] = 'e' || str.[!j] = 'E') then begin
incr j;
if !j < len && (str.[!j] = '-' || str.[!j] = '+') then incr j;
while !j < len && str.[!j] >= '0' && str.[!j] <= '9' do incr j done
end;
if !j > start then
let f_val = float_of_string (String.sub str start (!j - start)) in
let d = Hashtbl.create 2 in
Hashtbl.replace d "value" (Number f_val);
Hashtbl.replace d "consumed" (Integer !j);
Dict d
else Nil
| 's' ->
let j = ref !i in
while !j < len && str.[!j] <> ' ' && str.[!j] <> '\t' && str.[!j] <> '\n' do incr j done;
if !j > start then
let d = Hashtbl.create 2 in
Hashtbl.replace d "value" (String (String.sub str start (!j - start)));
Hashtbl.replace d "consumed" (Integer !j);
Dict d
else Nil
| 'c' ->
if !i < len then
let d = Hashtbl.create 2 in
Hashtbl.replace d "value" (Integer (Char.code str.[!i]));
Hashtbl.replace d "consumed" (Integer (!i + 1));
Dict d
else Nil
| _ -> raise (Eval_error ("scan-spec: unsupported conversion " ^ String.make 1 type_char))
with
| Eval_error _ as e -> raise e
| _ -> Nil);
register "parse-int" (fun args ->
let parse_leading_int s =
let len = String.length s in
@@ -3576,204 +3399,6 @@ let () =
Nil
| _ -> raise (Eval_error "channel-set-blocking!: (channel bool)"));
(* === Exec === run an external process; capture stdout *)
register "exec-process" (fun args ->
let items = match args with
| [List xs] | [ListRef { contents = xs }] -> xs
| _ -> raise (Eval_error "exec-process: (cmd-list)")
in
let argv = Array.of_list (List.map (function
| String s -> s
| v -> Sx_types.inspect v
) items) in
if Array.length argv = 0 then raise (Eval_error "exec: empty command");
let (out_r, out_w) = Unix.pipe () in
let (err_r, err_w) = Unix.pipe () in
let pid =
try Unix.create_process argv.(0) argv Unix.stdin out_w err_w
with Unix.Unix_error (e, _, _) ->
Unix.close out_r; Unix.close out_w;
Unix.close err_r; Unix.close err_w;
raise (Eval_error ("exec: " ^ Unix.error_message e))
in
Unix.close out_w;
Unix.close err_w;
let buf = Buffer.create 256 in
let errbuf = Buffer.create 64 in
let chunk = Bytes.create 4096 in
let read_all fd target =
try
let stop = ref false in
while not !stop do
let n = Unix.read fd chunk 0 (Bytes.length chunk) in
if n = 0 then stop := true
else Buffer.add_subbytes target chunk 0 n
done
with _ -> ()
in
read_all out_r buf;
read_all err_r errbuf;
Unix.close out_r;
Unix.close err_r;
let (_, status) = Unix.waitpid [] pid in
let exit_code = match status with
| Unix.WEXITED n -> n
| Unix.WSIGNALED _ | Unix.WSTOPPED _ -> 1
in
let s = Buffer.contents buf in
let trimmed =
if String.length s > 0 && s.[String.length s - 1] = '\n'
then String.sub s 0 (String.length s - 1) else s
in
if exit_code <> 0 then
raise (Eval_error ("exec: child exited " ^ string_of_int exit_code
^ (if Buffer.length errbuf > 0
then ": " ^ Buffer.contents errbuf
else "")))
else String trimmed);
(* exec-pipeline: takes a list of words like Tcl `exec` would receive.
Recognizes `|` as a stage separator and `> file`, `>> file`, `< file`,
`2>@1` (stderr→stdout), `2> file`. Returns trimmed stdout of the last
stage; raises Eval_error if the last stage exits non-zero. *)
register "exec-pipeline" (fun args ->
let items = match args with
| [List xs] | [ListRef { contents = xs }] -> xs
| _ -> raise (Eval_error "exec-pipeline: (word-list)")
in
let words = List.map (function
| String s -> s
| v -> Sx_types.inspect v
) items in
if words = [] then raise (Eval_error "exec: empty command");
let split_stages ws =
let rec loop acc cur = function
| [] -> List.rev (List.rev cur :: acc)
| "|" :: rest -> loop (List.rev cur :: acc) [] rest
| w :: rest -> loop acc (w :: cur) rest
in
loop [] [] ws
in
let extract_redirs ws =
let in_path = ref None in
let out_path = ref None in
let out_append = ref false in
let err_path = ref None in
let merge_err = ref false in
let cleaned = ref [] in
let rec loop = function
| [] -> ()
| "<" :: p :: rest -> in_path := Some p; loop rest
| ">" :: p :: rest -> out_path := Some p; out_append := false; loop rest
| ">>" :: p :: rest -> out_path := Some p; out_append := true; loop rest
| "2>@1" :: rest -> merge_err := true; loop rest
| "2>" :: p :: rest -> err_path := Some p; loop rest
| w :: rest -> cleaned := w :: !cleaned; loop rest
in
loop ws;
(List.rev !cleaned, !in_path, !out_path, !out_append, !err_path, !merge_err)
in
let stages = List.map extract_redirs (split_stages words) in
if stages = [] then raise (Eval_error "exec: no stages");
let n = List.length stages in
let pipes = Array.init (max 0 (n - 1)) (fun _ -> Unix.pipe ()) in
let (final_r, final_w) = Unix.pipe () in
let (errstash_r, errstash_w) = Unix.pipe () in
let pids = ref [] in
let close_safe fd = try Unix.close fd with _ -> () in
let open_in_redir = function
| None -> Unix.stdin
| Some path ->
(try Unix.openfile path [Unix.O_RDONLY] 0o644
with Unix.Unix_error (e, _, _) ->
raise (Eval_error ("exec: open <" ^ path ^ ": " ^ Unix.error_message e)))
in
let open_out_redir path append =
let flags = Unix.O_WRONLY :: Unix.O_CREAT :: (if append then [Unix.O_APPEND] else [Unix.O_TRUNC]) in
try Unix.openfile path flags 0o644
with Unix.Unix_error (e, _, _) ->
raise (Eval_error ("exec: open >" ^ path ^ ": " ^ Unix.error_message e))
in
let stages_arr = Array.of_list stages in
(try
Array.iteri (fun i (cleaned, ip, op, app, ep, merge) ->
if cleaned = [] then raise (Eval_error "exec: empty stage in pipeline");
let argv = Array.of_list cleaned in
let stdin_fd =
if i = 0 then open_in_redir ip
else fst pipes.(i - 1)
in
let stdout_fd =
if i = n - 1 then
(match op with
| None -> final_w
| Some path -> open_out_redir path app)
else snd pipes.(i)
in
let stderr_fd =
if merge then stdout_fd
else (match ep with
| None -> if i = n - 1 then errstash_w else Unix.stderr
| Some path -> open_out_redir path false)
in
let pid =
try Unix.create_process argv.(0) argv stdin_fd stdout_fd stderr_fd
with Unix.Unix_error (e, _, _) ->
raise (Eval_error ("exec: " ^ argv.(0) ^ ": " ^ Unix.error_message e))
in
pids := pid :: !pids;
if i > 0 then close_safe (fst pipes.(i - 1));
if i < n - 1 then close_safe (snd pipes.(i));
if i = 0 && ip <> None then close_safe stdin_fd;
if i = n - 1 && op <> None then close_safe stdout_fd;
if not merge && ep <> None then close_safe stderr_fd
) stages_arr
with e ->
close_safe final_r; close_safe final_w;
close_safe errstash_r; close_safe errstash_w;
Array.iter (fun (a,b) -> close_safe a; close_safe b) pipes;
raise e);
close_safe final_w;
close_safe errstash_w;
let buf = Buffer.create 256 in
let errbuf = Buffer.create 64 in
let chunk = Bytes.create 4096 in
let read_all fd target =
try
let stop = ref false in
while not !stop do
let r = Unix.read fd chunk 0 (Bytes.length chunk) in
if r = 0 then stop := true
else Buffer.add_subbytes target chunk 0 r
done
with _ -> ()
in
read_all final_r buf;
read_all errstash_r errbuf;
close_safe final_r;
close_safe errstash_r;
let exit_codes = List.rev_map (fun pid ->
let (_, st) = Unix.waitpid [] pid in
match st with
| Unix.WEXITED c -> c
| _ -> 1
) !pids in
let final_code = match List.rev exit_codes with
| [] -> 0
| last :: _ -> last
in
let s = Buffer.contents buf in
let trimmed =
if String.length s > 0 && s.[String.length s - 1] = '\n'
then String.sub s 0 (String.length s - 1) else s
in
if final_code <> 0 then
raise (Eval_error ("exec: pipeline last stage exited " ^ string_of_int final_code
^ (if Buffer.length errbuf > 0
then ": " ^ Buffer.contents errbuf
else "")))
else String trimmed);
(* === Sockets === wrapping Unix.socket/connect/bind/listen/accept *)
let resolve_inet_addr host =
if host = "" || host = "0.0.0.0" then Unix.inet_addr_any

View File

@@ -270,15 +270,6 @@
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
((= tt :name)
(cond
((and (< (+ i 1) (len tokens)) (= (tok-type (nth tokens (+ i 1))) :assign))
(let
((rhs-tokens (slice tokens (+ i 2) (len tokens))))
(let
((rhs-expr (parse-apl-expr rhs-tokens)))
(collect-segments-loop
tokens
(len tokens)
(append acc {:kind "val" :node (list :assign-expr tv rhs-expr)})))))
((some (fn (q) (= q tv)) apl-quad-fn-names)
(let
((op-result (collect-ops tokens (+ i 1))))
@@ -344,22 +335,10 @@
((= tt :glyph)
(cond
((or (= tv "") (= tv "⍵"))
(if
(and
(< (+ i 1) (len tokens))
(= (tok-type (nth tokens (+ i 1))) :assign))
(let
((rhs-tokens (slice tokens (+ i 2) (len tokens))))
(let
((rhs-expr (parse-apl-expr rhs-tokens)))
(collect-segments-loop
tokens
(len tokens)
(append acc {:kind "val" :node (list :assign-expr tv rhs-expr)}))))
(collect-segments-loop
tokens
(+ i 1)
(append acc {:kind "val" :node (list :name tv)}))))
(append acc {:kind "val" :node (list :name tv)})))
((= tv "∇")
(collect-segments-loop
tokens
@@ -414,13 +393,7 @@
ni
(append acc {:kind "fn" :node fn-node})))))))
((apl-parse-op-glyph? tv)
(if
(or (= tv "/") (= tv "⌿") (= tv "\\") (= tv "⍀"))
(collect-segments-loop
tokens
(+ i 1)
(append acc {:kind "fn" :node (list :fn-glyph tv)}))
(collect-segments-loop tokens (+ i 1) acc)))
(collect-segments-loop tokens (+ i 1) acc))
(true (collect-segments-loop tokens (+ i 1) acc))))
(true (collect-segments-loop tokens (+ i 1) acc))))))))

View File

@@ -808,25 +808,6 @@
((picked (map (fn (i) (nth arr-ravel i)) kept)))
(make-array (list (len picked)) picked))))))
(define
apl-compress-first
(fn
(mask arr)
(let
((mask-ravel (get mask :ravel))
(shape (get arr :shape))
(ravel (get arr :ravel)))
(if
(< (len shape) 2)
(apl-compress mask arr)
(let
((rows (first shape)) (cols (last shape)))
(let
((kept-rows (filter (fn (i) (not (= 0 (nth mask-ravel i)))) (range 0 rows))))
(let
((new-ravel (reduce (fn (acc r) (append acc (map (fn (j) (nth ravel (+ (* r cols) j))) (range 0 cols)))) (list) kept-rows)))
(make-array (cons (len kept-rows) (rest shape)) new-ravel))))))))
(define
apl-primes
(fn
@@ -1004,28 +985,6 @@
(some (fn (c) (= c 0)) codes)
(some (fn (c) (= c (nth e 1))) codes)))))
(define apl-rng-state 12345)
(define apl-rng-seed! (fn (s) (set! apl-rng-state s)))
(define
apl-rng-next!
(fn
()
(begin
(set!
apl-rng-state
(mod (+ (* apl-rng-state 1103515245) 12345) 2147483648))
apl-rng-state)))
(define
apl-roll
(fn
(arr)
(let
((n (if (scalar? arr) (first (get arr :ravel)) (first (get arr :ravel)))))
(apl-scalar (+ apl-io (mod (apl-rng-next!) n))))))
(define
apl-cartesian
(fn

View File

@@ -312,146 +312,3 @@
"train: mean of 10 has shape ()"
(mksh (apl-run "(+/÷≢) 10"))
(list))
(apl-test
"compress: 1 0 1 0 1 / 10 20 30 40 50"
(mkrv (apl-run "1 0 1 0 1 / 10 20 30 40 50"))
(list 10 30 50))
(apl-test
"compress: empty mask → empty"
(mkrv (apl-run "0 0 0 / 1 2 3"))
(list))
(apl-test
"primes via classic idiom (multi-stmt)"
(mkrv (apl-run "P ← 30 ⋄ (2 = +⌿ 0 = P ∘.| P) / P"))
(list 2 3 5 7 11 13 17 19 23 29))
(apl-test
"primes via classic idiom (n=20)"
(mkrv (apl-run "P ← 20 ⋄ (2 = +⌿ 0 = P ∘.| P) / P"))
(list 2 3 5 7 11 13 17 19))
(apl-test
"compress: filter even values"
(mkrv (apl-run "(0 = 2 | 1 2 3 4 5 6) / 1 2 3 4 5 6"))
(list 2 4 6))
(apl-test "inline-assign: x ← 5" (mkrv (apl-run "x ← 5")) (list 5))
(apl-test
"inline-assign: (2×x) + x←10 → 30"
(mkrv (apl-run "(2 × x) + x ← 10"))
(list 30))
(apl-test
"inline-assign primes one-liner: (2=+⌿0=a∘.|a)/a←30"
(mkrv (apl-run "(2 = +⌿ 0 = a ∘.| a) / a ← 30"))
(list 2 3 5 7 11 13 17 19 23 29))
(apl-test
"inline-assign: x is reusable — x + x ← 7 → 14"
(mkrv (apl-run "x + x ← 7"))
(list 14))
(apl-test
"inline-assign in dfn: f ← {x + x ← ⍵} ⋄ f 8 → 16"
(mkrv (apl-run "f ← {x + x ← ⍵} ⋄ f 8"))
(list 16))
(begin (apl-rng-seed! 42) nil)
(apl-test
"?10 with seed 42 → 8 (deterministic)"
(mkrv (apl-run "?10"))
(list 8))
(apl-test "?10 next call → 5" (mkrv (apl-run "?10")) (list 5))
(apl-test
"?100 stays in range"
(let ((v (first (mkrv (apl-run "?100"))))) (and (>= v 1) (<= v 100)))
true)
(begin (apl-rng-seed! 42) nil)
(apl-test
"?10 with re-seed 42 → 8 (reproducible)"
(mkrv (apl-run "?10"))
(list 8))
(apl-test
"apl-run-file: load primes.apl returns dfn AST"
(first (apl-run-file "lib/apl/tests/programs/primes.apl"))
:dfn)
(apl-test
"apl-run-file: life.apl parses without error"
(first (apl-run-file "lib/apl/tests/programs/life.apl"))
:dfn)
(apl-test
"apl-run-file: quicksort.apl parses without error"
(first (apl-run-file "lib/apl/tests/programs/quicksort.apl"))
:dfn)
(apl-test
"apl-run-file: source-then-call returns primes count"
(mksh
(apl-run
(str (file-read "lib/apl/tests/programs/primes.apl") " ⋄ primes 30")))
(list 10))
(apl-test
"primes one-liner with ⍵-rebind: primes 30"
(mkrv
(apl-run "primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵} ⋄ primes 30"))
(list 2 3 5 7 11 13 17 19 23 29))
(apl-test
"primes one-liner: primes 50"
(mkrv
(apl-run "primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵} ⋄ primes 50"))
(list 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47))
(apl-test
"primes.apl loaded + called via apl-run-file"
(mkrv
(apl-run
(str (file-read "lib/apl/tests/programs/primes.apl") " ⋄ primes 20")))
(list 2 3 5 7 11 13 17 19))
(apl-test
"primes.apl loaded — count of primes ≤ 100"
(first
(mksh
(apl-run
(str
(file-read "lib/apl/tests/programs/primes.apl")
" ⋄ primes 100"))))
25)
(apl-test
"⍉ monadic transpose 2x3 → 3x2"
(mkrv (apl-run "⍉ (2 3) 6"))
(list 1 4 2 5 3 6))
(apl-test
"⍉ transpose shape (3 2)"
(mksh (apl-run "⍉ (2 3) 6"))
(list 3 2))
(apl-test "⊢ monadic identity" (mkrv (apl-run "⊢ 1 2 3")) (list 1 2 3))
(apl-test
"5 ⊣ 1 2 3 → 5 (left)"
(mkrv (apl-run "5 ⊣ 1 2 3"))
(list 5))
(apl-test
"5 ⊢ 1 2 3 → 1 2 3 (right)"
(mkrv (apl-run "5 ⊢ 1 2 3"))
(list 1 2 3))
(apl-test "⍕ 42 → \"42\" (alias for ⎕FMT)" (apl-run "⍕ 42") "42")

View File

@@ -252,6 +252,8 @@
(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 4 has 24" (len (apl-permutations 4)) 24)

View File

@@ -39,11 +39,6 @@
((= g "⊖") apl-reverse-first)
((= g "⍋") apl-grade-up)
((= g "⍒") apl-grade-down)
((= g "?") apl-roll)
((= g "⍉") apl-transpose)
((= g "⊢") (fn (a) a))
((= g "⊣") (fn (a) a))
((= g "⍕") apl-quad-fmt)
((= g "⎕FMT") apl-quad-fmt)
((= g "⎕←") apl-quad-print)
(else (error "no monadic fn for glyph")))))
@@ -85,11 +80,6 @@
((= g "∊") apl-member)
((= g "") apl-index-of)
((= g "~") apl-without)
((= g "/") apl-compress)
((= g "⌿") apl-compress-first)
((= g "⍉") apl-transpose-dyadic)
((= g "⊢") (fn (a b) b))
((= g "⊣") (fn (a b) a))
(else (error "no dyadic fn for glyph")))))
(define
@@ -129,14 +119,8 @@
(let
((nm (nth node 1)))
(cond
((= nm "")
(let
((v (get env "")))
(if (= v nil) (get env "alpha") v)))
((= nm "⍵")
(let
((v (get env "⍵")))
(if (= v nil) (get env "omega") v)))
((= nm "") (get env "alpha"))
((= nm "⍵") (get env "omega"))
((= nm "⎕IO") (apl-quad-io))
((= nm "⎕ML") (apl-quad-ml))
((= nm "⎕FR") (apl-quad-fr))
@@ -148,11 +132,7 @@
(if
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
(apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
(let
((arg-val (apl-eval-ast arg env)))
(let
((new-env (if (and (list? arg) (> (len arg) 0) (= (first arg) :assign-expr)) (assoc env (nth arg 1) arg-val) env)))
((apl-resolve-monadic fn-node new-env) arg-val))))))
((apl-resolve-monadic fn-node env) (apl-eval-ast arg env)))))
((= tag :dyad)
(let
((fn-node (nth node 1))
@@ -164,13 +144,9 @@
(get env "nabla")
(apl-eval-ast lhs env)
(apl-eval-ast rhs env))
(let
((rhs-val (apl-eval-ast rhs env)))
(let
((new-env (if (and (list? rhs) (> (len rhs) 0) (= (first rhs) :assign-expr)) (assoc env (nth rhs 1) rhs-val) env)))
((apl-resolve-dyadic fn-node new-env)
(apl-eval-ast lhs new-env)
rhs-val))))))
((apl-resolve-dyadic fn-node env)
(apl-eval-ast lhs env)
(apl-eval-ast rhs env)))))
((= tag :program) (apl-eval-stmts (rest node) env))
((= tag :dfn) node)
((= tag :bracket)
@@ -183,8 +159,6 @@
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
axis-exprs)))
(apl-bracket-multi axes arr))))
((= tag :assign-expr) (apl-eval-ast (nth node 2) env))
((= tag :assign) (apl-eval-ast (nth node 2) env))
(else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
(define
@@ -564,5 +538,3 @@
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
(define apl-run-file (fn (path) (apl-run (file-read path))))

View File

@@ -76,7 +76,7 @@ cat > "$TMPFILE" << 'EPOCHS'
(eval "(list er-fib-test-pass er-fib-test-count)")
EPOCHS
timeout 600 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1
timeout 120 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1
# Parse "(N M)" from the line after each "(ok-len <epoch> ...)" marker.
parse_pair() {

View File

@@ -1,16 +1,16 @@
{
"language": "erlang",
"total_pass": 530,
"total": 530,
"total_pass": 0,
"total": 0,
"suites": [
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
{"name":"parse","pass":52,"total":52,"status":"ok"},
{"name":"eval","pass":346,"total":346,"status":"ok"},
{"name":"runtime","pass":39,"total":39,"status":"ok"},
{"name":"ring","pass":4,"total":4,"status":"ok"},
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
{"name":"bank","pass":8,"total":8,"status":"ok"},
{"name":"echo","pass":7,"total":7,"status":"ok"},
{"name":"fib","pass":8,"total":8,"status":"ok"}
{"name":"tokenize","pass":0,"total":0,"status":"ok"},
{"name":"parse","pass":0,"total":0,"status":"ok"},
{"name":"eval","pass":0,"total":0,"status":"ok"},
{"name":"runtime","pass":0,"total":0,"status":"ok"},
{"name":"ring","pass":0,"total":0,"status":"ok"},
{"name":"ping-pong","pass":0,"total":0,"status":"ok"},
{"name":"bank","pass":0,"total":0,"status":"ok"},
{"name":"echo","pass":0,"total":0,"status":"ok"},
{"name":"fib","pass":0,"total":0,"status":"ok"}
]
}

View File

@@ -1,18 +1,18 @@
# Erlang-on-SX Scoreboard
**Total: 530 / 530 tests passing**
**Total: 0 / 0 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | tokenize | 62 | 62 |
| ✅ | parse | 52 | 52 |
| ✅ | eval | 346 | 346 |
| ✅ | runtime | 39 | 39 |
| ✅ | ring | 4 | 4 |
| ✅ | ping-pong | 4 | 4 |
| ✅ | bank | 8 | 8 |
| ✅ | echo | 7 | 7 |
| ✅ | fib | 8 | 8 |
| ✅ | tokenize | 0 | 0 |
| ✅ | parse | 0 | 0 |
| ✅ | eval | 0 | 0 |
| ✅ | runtime | 0 | 0 |
| ✅ | ring | 0 | 0 |
| ✅ | ping-pong | 0 | 0 |
| ✅ | bank | 0 | 0 |
| ✅ | echo | 0 | 0 |
| ✅ | fib | 0 | 0 |
Generated by `lib/erlang/conformance.sh`.

View File

@@ -14,8 +14,6 @@ PRELOADS=(
lib/haskell/runtime.sx
lib/haskell/match.sx
lib/haskell/eval.sx
lib/haskell/map.sx
lib/haskell/set.sx
lib/haskell/testlib.sx
)
@@ -38,24 +36,6 @@ SUITES=(
"matrix:lib/haskell/tests/program-matrix.sx"
"wordcount:lib/haskell/tests/program-wordcount.sx"
"powers:lib/haskell/tests/program-powers.sx"
"caesar:lib/haskell/tests/program-caesar.sx"
"runlength-str:lib/haskell/tests/program-runlength-str.sx"
"showadt:lib/haskell/tests/program-showadt.sx"
"showio:lib/haskell/tests/program-showio.sx"
"partial:lib/haskell/tests/program-partial.sx"
"statistics:lib/haskell/tests/program-statistics.sx"
"newton:lib/haskell/tests/program-newton.sx"
"wordfreq:lib/haskell/tests/program-wordfreq.sx"
"mapgraph:lib/haskell/tests/program-mapgraph.sx"
"uniquewords:lib/haskell/tests/program-uniquewords.sx"
"setops:lib/haskell/tests/program-setops.sx"
"shapes:lib/haskell/tests/program-shapes.sx"
"person:lib/haskell/tests/program-person.sx"
"config:lib/haskell/tests/program-config.sx"
"counter:lib/haskell/tests/program-counter.sx"
"accumulate:lib/haskell/tests/program-accumulate.sx"
"safediv:lib/haskell/tests/program-safediv.sx"
"trycatch:lib/haskell/tests/program-trycatch.sx"
)
emit_scoreboard_json() {

View File

@@ -131,280 +131,119 @@
(let
((tag (first node)))
(cond
;; Transformations
((= tag "where")
(list
:let (map hk-desugar (nth node 2))
:let
(map hk-desugar (nth node 2))
(hk-desugar (nth node 1))))
((= tag "guarded") (hk-guards-to-if (nth node 1)))
((= tag "list-comp")
(hk-lc-desugar (hk-desugar (nth node 1)) (nth node 2)))
(hk-lc-desugar
(hk-desugar (nth node 1))
(nth node 2)))
;; Expression nodes
((= tag "app")
(list
:app (hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "p-rec")
(let
((cname (nth node 1))
(field-pats (nth node 2))
(field-order (hk-record-field-names cname)))
(cond
((nil? field-order)
(raise (str "p-rec: no record info for " cname)))
(:else
(list
:p-con
cname
(map
(fn
(fname)
(let
((p (hk-find-rec-pair field-pats fname)))
(cond
((nil? p) (list :p-wild))
(:else (hk-desugar (nth p 1))))))
field-order))))))
((= tag "rec-update")
(list
:rec-update
(hk-desugar (nth node 1))
(map
(fn (p) (list (first p) (hk-desugar (nth p 1))))
(nth node 2))))
((= tag "rec-create")
(let
((cname (nth node 1))
(field-pairs (nth node 2))
(field-order (hk-record-field-names cname)))
(cond
((nil? field-order)
(raise (str "rec-create: no record info for " cname)))
(:else
(let
((acc (list :con cname)))
(begin
(for-each
(fn
(fname)
(let
((pair
(hk-find-rec-pair field-pairs fname)))
(cond
((nil? pair)
(raise
(str
"rec-create: missing field "
fname
" for "
cname)))
(:else
(set!
acc
(list
:app
acc
(hk-desugar (nth pair 1))))))))
field-order)
acc))))))
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "op")
(list
:op (nth node 1)
:op
(nth node 1)
(hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
((= tag "if")
(list
:if (hk-desugar (nth node 1))
:if
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "tuple") (list :tuple (map hk-desugar (nth node 1))))
((= tag "list") (list :list (map hk-desugar (nth node 1))))
((= tag "tuple")
(list :tuple (map hk-desugar (nth node 1))))
((= tag "list")
(list :list (map hk-desugar (nth node 1))))
((= tag "range")
(list
:range (hk-desugar (nth node 1))
:range
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "range-step")
(list
:range-step (hk-desugar (nth node 1))
:range-step
(hk-desugar (nth node 1))
(hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "lambda")
(list :lambda (nth node 1) (hk-desugar (nth node 2))))
(list
:lambda
(nth node 1)
(hk-desugar (nth node 2))))
((= tag "let")
(list
:let (map hk-desugar (nth node 1))
:let
(map hk-desugar (nth node 1))
(hk-desugar (nth node 2))))
((= tag "case")
(list
:case (hk-desugar (nth node 1))
:case
(hk-desugar (nth node 1))
(map hk-desugar (nth node 2))))
((= tag "alt")
(list :alt (hk-desugar (nth node 1)) (hk-desugar (nth node 2))))
(list :alt (nth node 1) (hk-desugar (nth node 2))))
((= tag "do") (hk-desugar-do (nth node 1)))
((= tag "sect-left")
(list :sect-left (nth node 1) (hk-desugar (nth node 2))))
(list
:sect-left
(nth node 1)
(hk-desugar (nth node 2))))
((= tag "sect-right")
(list :sect-right (nth node 1) (hk-desugar (nth node 2))))
(list
:sect-right
(nth node 1)
(hk-desugar (nth node 2))))
;; Top-level
((= tag "program")
(list :program (map hk-desugar (hk-expand-records (nth node 1)))))
(list :program (map hk-desugar (nth node 1))))
((= tag "module")
(list
:module (nth node 1)
:module
(nth node 1)
(nth node 2)
(nth node 3)
(map hk-desugar (hk-expand-records (nth node 4)))))
(map hk-desugar (nth node 4))))
;; Decls carrying a body
((= tag "fun-clause")
(list
:fun-clause (nth node 1)
(map hk-desugar (nth node 2))
(hk-desugar (nth node 3))))
((= tag "instance-decl")
(list
:instance-decl (nth node 1)
:fun-clause
(nth node 1)
(nth node 2)
(map hk-desugar (nth node 3))))
(hk-desugar (nth node 3))))
((= tag "pat-bind")
(list :pat-bind (nth node 1) (hk-desugar (nth node 2))))
(list
:pat-bind
(nth node 1)
(hk-desugar (nth node 2))))
((= tag "bind")
(list :bind (nth node 1) (hk-desugar (nth node 2))))
(list
:bind
(nth node 1)
(hk-desugar (nth node 2))))
;; Everything else: leaf literals, vars, cons, patterns,
;; types, imports, type-sigs, data / newtype / fixity, …
(:else node)))))))
;; Convenience — tokenize + layout + parse + desugar.
(define hk-record-fields (dict))
(define
hk-core
(fn (src) (hk-desugar (hk-parse-top src))))
(define
hk-register-record-fields!
(fn (cname fields) (dict-set! hk-record-fields cname fields)))
(define
hk-record-field-names
(fn
(cname)
(if (has-key? hk-record-fields cname) (get hk-record-fields cname) nil)))
(define
hk-record-field-index
(fn
(cname fname)
(let
((fields (hk-record-field-names cname)))
(cond
((nil? fields) -1)
(:else
(let
((i 0) (idx -1))
(begin
(for-each
(fn
(f)
(begin (when (= f fname) (set! idx i)) (set! i (+ i 1))))
fields)
idx)))))))
(define
hk-find-rec-pair
(fn
(pairs name)
(cond
((empty? pairs) nil)
((= (first (first pairs)) name) (first pairs))
(:else (hk-find-rec-pair (rest pairs) name)))))
(define
hk-record-accessors
(fn
(cname rec-fields)
(let
((n (len rec-fields)) (i 0) (out (list)))
(define
hk-ra-loop
(fn
()
(when
(< i n)
(let
((field (nth rec-fields i)))
(let
((fname (first field)) (j 0) (pats (list)))
(define
hk-pat-loop
(fn
()
(when
(< j n)
(begin
(append!
pats
(if
(= j i)
(list "p-var" "__rec_field")
(list "p-wild")))
(set! j (+ j 1))
(hk-pat-loop)))))
(hk-pat-loop)
(append!
out
(list
"fun-clause"
fname
(list (list "p-con" cname pats))
(list "var" "__rec_field")))
(set! i (+ i 1))
(hk-ra-loop))))))
(hk-ra-loop)
out)))
(define
hk-expand-records
(fn
(decls)
(let
((out (list)))
(for-each
(fn
(d)
(cond
((and (list? d) (= (first d) "data"))
(let
((dname (nth d 1))
(tvars (nth d 2))
(cons-list (nth d 3))
(deriving (if (> (len d) 4) (nth d 4) (list)))
(new-cons (list))
(accessors (list)))
(begin
(for-each
(fn
(c)
(cond
((= (first c) "con-rec")
(let
((cname (nth c 1)) (rec-fields (nth c 2)))
(begin
(hk-register-record-fields!
cname
(map (fn (f) (first f)) rec-fields))
(append!
new-cons
(list
"con-def"
cname
(map (fn (f) (nth f 1)) rec-fields)))
(for-each
(fn (a) (append! accessors a))
(hk-record-accessors cname rec-fields)))))
(:else (append! new-cons c))))
cons-list)
(append!
out
(if
(empty? deriving)
(list "data" dname tvars new-cons)
(list "data" dname tvars new-cons deriving)))
(for-each (fn (a) (append! out a)) accessors))))
(:else (append! out d))))
decls)
out)))
(define hk-core (fn (src) (hk-desugar (hk-parse-top src))))
(define hk-core-expr (fn (src) (hk-desugar (hk-parse src))))
hk-core-expr
(fn (src) (hk-desugar (hk-parse src))))

File diff suppressed because one or more lines are too long

View File

@@ -1,520 +0,0 @@
;; map.sx — Phase 11 Data.Map: weight-balanced BST in pure SX.
;;
;; Algorithm: Adams's weight-balanced tree (the same family as Haskell's
;; Data.Map). Each node tracks its size; rotations maintain the invariant
;;
;; size(small-side) * delta >= size(large-side) (delta = 3)
;;
;; with single or double rotations chosen by the gamma ratio (gamma = 2).
;; The size field is an Int and is included so `size`, `lookup`, etc. are
;; O(log n) on both extremes of the tree.
;;
;; Representation:
;; Empty → ("Map-Empty")
;; Node → ("Map-Node" key val left right size)
;;
;; All operations are pure SX — no mutation of nodes once constructed.
;; The user-facing Haskell layer (Phase 11 next iteration) wraps these
;; for `import Data.Map as Map`.
;; ── Constructors ────────────────────────────────────────────
(define hk-map-empty (list "Map-Empty"))
(define
hk-map-node
(fn
(k v l r)
(list "Map-Node" k v l r (+ 1 (+ (hk-map-size l) (hk-map-size r))))))
;; ── Predicates and accessors ────────────────────────────────
(define hk-map-empty? (fn (m) (and (list? m) (= (first m) "Map-Empty"))))
(define hk-map-node? (fn (m) (and (list? m) (= (first m) "Map-Node"))))
(define
hk-map-size
(fn (m) (cond ((hk-map-empty? m) 0) (:else (nth m 5)))))
(define hk-map-key (fn (m) (nth m 1)))
(define hk-map-val (fn (m) (nth m 2)))
(define hk-map-left (fn (m) (nth m 3)))
(define hk-map-right (fn (m) (nth m 4)))
;; ── Weight-balanced rotations ───────────────────────────────
;; delta and gamma per Adams 1992 / Haskell Data.Map.
(define hk-map-delta 3)
(define hk-map-gamma 2)
(define
hk-map-single-l
(fn
(k v l r)
(let
((rk (hk-map-key r))
(rv (hk-map-val r))
(rl (hk-map-left r))
(rr (hk-map-right r)))
(hk-map-node rk rv (hk-map-node k v l rl) rr))))
(define
hk-map-single-r
(fn
(k v l r)
(let
((lk (hk-map-key l))
(lv (hk-map-val l))
(ll (hk-map-left l))
(lr (hk-map-right l)))
(hk-map-node lk lv ll (hk-map-node k v lr r)))))
(define
hk-map-double-l
(fn
(k v l r)
(let
((rk (hk-map-key r))
(rv (hk-map-val r))
(rl (hk-map-left r))
(rr (hk-map-right r))
(rlk (hk-map-key (hk-map-left r)))
(rlv (hk-map-val (hk-map-left r)))
(rll (hk-map-left (hk-map-left r)))
(rlr (hk-map-right (hk-map-left r))))
(hk-map-node
rlk
rlv
(hk-map-node k v l rll)
(hk-map-node rk rv rlr rr)))))
(define
hk-map-double-r
(fn
(k v l r)
(let
((lk (hk-map-key l))
(lv (hk-map-val l))
(ll (hk-map-left l))
(lr (hk-map-right l))
(lrk (hk-map-key (hk-map-right l)))
(lrv (hk-map-val (hk-map-right l)))
(lrl (hk-map-left (hk-map-right l)))
(lrr (hk-map-right (hk-map-right l))))
(hk-map-node
lrk
lrv
(hk-map-node lk lv ll lrl)
(hk-map-node k v lrr r)))))
;; ── Balanced node constructor ──────────────────────────────
;; Use this in place of hk-map-node when one side may have grown
;; or shrunk by one and we need to restore the weight invariant.
(define
hk-map-balance
(fn
(k v l r)
(let
((sl (hk-map-size l)) (sr (hk-map-size r)))
(cond
((<= (+ sl sr) 1) (hk-map-node k v l r))
((> sr (* hk-map-delta sl))
(let
((rl (hk-map-left r)) (rr (hk-map-right r)))
(cond
((< (hk-map-size rl) (* hk-map-gamma (hk-map-size rr)))
(hk-map-single-l k v l r))
(:else (hk-map-double-l k v l r)))))
((> sl (* hk-map-delta sr))
(let
((ll (hk-map-left l)) (lr (hk-map-right l)))
(cond
((< (hk-map-size lr) (* hk-map-gamma (hk-map-size ll)))
(hk-map-single-r k v l r))
(:else (hk-map-double-r k v l r)))))
(:else (hk-map-node k v l r))))))
(define
hk-map-singleton
(fn (k v) (hk-map-node k v hk-map-empty hk-map-empty)))
(define
hk-map-insert
(fn
(k v m)
(cond
((hk-map-empty? m) (hk-map-singleton k v))
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-insert k v (hk-map-left m))
(hk-map-right m)))
((> k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-left m)
(hk-map-insert k v (hk-map-right m))))
(:else (hk-map-node k v (hk-map-left m) (hk-map-right m)))))))))
(define
hk-map-lookup
(fn
(k m)
(cond
((hk-map-empty? m) (list "Nothing"))
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk) (hk-map-lookup k (hk-map-left m)))
((> k mk) (hk-map-lookup k (hk-map-right m)))
(:else (list "Just" (hk-map-val m)))))))))
(define
hk-map-member
(fn
(k m)
(cond
((hk-map-empty? m) false)
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk) (hk-map-member k (hk-map-left m)))
((> k mk) (hk-map-member k (hk-map-right m)))
(:else true)))))))
(define hk-map-null hk-map-empty?)
(define
hk-map-find-min
(fn
(m)
(cond
((hk-map-empty? (hk-map-left m))
(list (hk-map-key m) (hk-map-val m)))
(:else (hk-map-find-min (hk-map-left m))))))
(define
hk-map-delete-min
(fn
(m)
(cond
((hk-map-empty? (hk-map-left m)) (hk-map-right m))
(:else
(hk-map-balance
(hk-map-key m)
(hk-map-val m)
(hk-map-delete-min (hk-map-left m))
(hk-map-right m))))))
(define
hk-map-find-max
(fn
(m)
(cond
((hk-map-empty? (hk-map-right m))
(list (hk-map-key m) (hk-map-val m)))
(:else (hk-map-find-max (hk-map-right m))))))
(define
hk-map-delete-max
(fn
(m)
(cond
((hk-map-empty? (hk-map-right m)) (hk-map-left m))
(:else
(hk-map-balance
(hk-map-key m)
(hk-map-val m)
(hk-map-left m)
(hk-map-delete-max (hk-map-right m)))))))
(define
hk-map-glue
(fn
(l r)
(cond
((hk-map-empty? l) r)
((hk-map-empty? r) l)
((> (hk-map-size l) (hk-map-size r))
(let
((mp (hk-map-find-max l)))
(hk-map-balance (first mp) (nth mp 1) (hk-map-delete-max l) r)))
(:else
(let
((mp (hk-map-find-min r)))
(hk-map-balance (first mp) (nth mp 1) l (hk-map-delete-min r)))))))
(define
hk-map-delete
(fn
(k m)
(cond
((hk-map-empty? m) m)
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-delete k (hk-map-left m))
(hk-map-right m)))
((> k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-left m)
(hk-map-delete k (hk-map-right m))))
(:else (hk-map-glue (hk-map-left m) (hk-map-right m)))))))))
(define
hk-map-from-list
(fn
(pairs)
(reduce
(fn (acc p) (hk-map-insert (first p) (nth p 1) acc))
hk-map-empty
pairs)))
(define
hk-map-to-asc-list
(fn
(m)
(cond
((hk-map-empty? m) (list))
(:else
(append
(hk-map-to-asc-list (hk-map-left m))
(cons
(list (hk-map-key m) (hk-map-val m))
(hk-map-to-asc-list (hk-map-right m))))))))
(define hk-map-to-list hk-map-to-asc-list)
(define
hk-map-keys
(fn
(m)
(cond
((hk-map-empty? m) (list))
(:else
(append
(hk-map-keys (hk-map-left m))
(cons (hk-map-key m) (hk-map-keys (hk-map-right m))))))))
(define
hk-map-elems
(fn
(m)
(cond
((hk-map-empty? m) (list))
(:else
(append
(hk-map-elems (hk-map-left m))
(cons (hk-map-val m) (hk-map-elems (hk-map-right m))))))))
(define
hk-map-union-with
(fn
(f m1 m2)
(reduce
(fn
(acc p)
(let
((k (first p)) (v (nth p 1)))
(let
((look (hk-map-lookup k acc)))
(cond
((= (first look) "Just")
(hk-map-insert k (f (nth look 1) v) acc))
(:else (hk-map-insert k v acc))))))
m1
(hk-map-to-asc-list m2))))
(define
hk-map-intersection-with
(fn
(f m1 m2)
(reduce
(fn
(acc p)
(let
((k (first p)) (v1 (nth p 1)))
(let
((look (hk-map-lookup k m2)))
(cond
((= (first look) "Just")
(hk-map-insert k (f v1 (nth look 1)) acc))
(:else acc)))))
hk-map-empty
(hk-map-to-asc-list m1))))
(define
hk-map-difference
(fn
(m1 m2)
(reduce
(fn
(acc p)
(let
((k (first p)) (v (nth p 1)))
(cond ((hk-map-member k m2) acc) (:else (hk-map-insert k v acc)))))
hk-map-empty
(hk-map-to-asc-list m1))))
(define
hk-map-foldl-with-key
(fn
(f acc m)
(cond
((hk-map-empty? m) acc)
(:else
(let
((acc1 (hk-map-foldl-with-key f acc (hk-map-left m))))
(let
((acc2 (f acc1 (hk-map-key m) (hk-map-val m))))
(hk-map-foldl-with-key f acc2 (hk-map-right m))))))))
(define
hk-map-foldr-with-key
(fn
(f acc m)
(cond
((hk-map-empty? m) acc)
(:else
(let
((acc1 (hk-map-foldr-with-key f acc (hk-map-right m))))
(let
((acc2 (f (hk-map-key m) (hk-map-val m) acc1)))
(hk-map-foldr-with-key f acc2 (hk-map-left m))))))))
(define
hk-map-map-with-key
(fn
(f m)
(cond
((hk-map-empty? m) m)
(:else
(list
"Map-Node"
(hk-map-key m)
(f (hk-map-key m) (hk-map-val m))
(hk-map-map-with-key f (hk-map-left m))
(hk-map-map-with-key f (hk-map-right m))
(hk-map-size m))))))
(define
hk-map-filter-with-key
(fn
(p m)
(hk-map-foldr-with-key
(fn (k v acc) (cond ((p k v) (hk-map-insert k v acc)) (:else acc)))
hk-map-empty
m)))
(define
hk-map-adjust
(fn
(f k m)
(cond
((hk-map-empty? m) m)
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk)
(hk-map-node
mk
(hk-map-val m)
(hk-map-adjust f k (hk-map-left m))
(hk-map-right m)))
((> k mk)
(hk-map-node
mk
(hk-map-val m)
(hk-map-left m)
(hk-map-adjust f k (hk-map-right m))))
(:else
(hk-map-node
mk
(f (hk-map-val m))
(hk-map-left m)
(hk-map-right m)))))))))
(define
hk-map-insert-with
(fn
(f k v m)
(cond
((hk-map-empty? m) (hk-map-singleton k v))
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-insert-with f k v (hk-map-left m))
(hk-map-right m)))
((> k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-left m)
(hk-map-insert-with f k v (hk-map-right m))))
(:else
(hk-map-node
mk
(f v (hk-map-val m))
(hk-map-left m)
(hk-map-right m)))))))))
(define
hk-map-insert-with-key
(fn
(f k v m)
(cond
((hk-map-empty? m) (hk-map-singleton k v))
(:else
(let
((mk (hk-map-key m)))
(cond
((< k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-insert-with-key f k v (hk-map-left m))
(hk-map-right m)))
((> k mk)
(hk-map-balance
mk
(hk-map-val m)
(hk-map-left m)
(hk-map-insert-with-key f k v (hk-map-right m))))
(:else
(hk-map-node
mk
(f k v (hk-map-val m))
(hk-map-left m)
(hk-map-right m)))))))))
(define
hk-map-alter
(fn
(f k m)
(let
((look (hk-map-lookup k m)))
(let
((res (f look)))
(cond
((= (first res) "Nothing") (hk-map-delete k m))
(:else (hk-map-insert k (nth res 1) m)))))))

View File

@@ -87,41 +87,45 @@
((nil? res) nil)
(:else (assoc res (nth pat 1) val)))))
(:else
(let
((fv (hk-force val)))
(let ((fv (hk-force val)))
(cond
((= tag "p-int")
(if (and (number? fv) (= fv (nth pat 1))) env nil))
(if
(and (number? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-float")
(if (and (number? fv) (= fv (nth pat 1))) env nil))
(if
(and (number? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-string")
(if (and (string? fv) (= fv (nth pat 1))) env nil))
(if
(and (string? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-char")
(if (and (string? fv) (= fv (nth pat 1))) env nil))
(if
(and (string? fv) (= fv (nth pat 1)))
env
nil))
((= tag "p-con")
(let
((pat-name (nth pat 1)) (pat-args (nth pat 2)))
(cond
((and (= pat-name ":") (hk-str? fv) (not (hk-str-null? fv)))
(let
((str-head (hk-str-head fv))
(str-tail (hk-str-tail fv)))
(let
((head-pat (nth pat-args 0))
(tail-pat (nth pat-args 1)))
(let
((res (hk-match head-pat str-head env)))
(cond
((nil? res) nil)
(:else (hk-match tail-pat str-tail res)))))))
((not (hk-is-con-val? fv)) nil)
((not (= (hk-val-con-name fv) pat-name)) nil)
(:else
(let
((val-args (hk-val-con-args fv)))
(cond
((not (= (len val-args) (len pat-args))) nil)
(:else (hk-match-all pat-args val-args env))))))))
((not (= (len pat-args) (len val-args)))
nil)
(:else
(hk-match-all
pat-args
val-args
env))))))))
((= tag "p-tuple")
(let
((items (nth pat 1)))
@@ -130,8 +134,13 @@
((not (= (hk-val-con-name fv) "Tuple")) nil)
((not (= (len (hk-val-con-args fv)) (len items)))
nil)
(:else (hk-match-all items (hk-val-con-args fv) env)))))
((= tag "p-list") (hk-match-list-pat (nth pat 1) fv env))
(:else
(hk-match-all
items
(hk-val-con-args fv)
env)))))
((= tag "p-list")
(hk-match-list-pat (nth pat 1) fv env))
(:else nil))))))))))
(define
@@ -152,26 +161,17 @@
hk-match-list-pat
(fn
(items val env)
(let
((fv (hk-force val)))
(let ((fv (hk-force val)))
(cond
((empty? items)
(if
(or
(and (hk-is-con-val? fv) (= (hk-val-con-name fv) "[]"))
(and (hk-str? fv) (hk-str-null? fv)))
(and
(hk-is-con-val? fv)
(= (hk-val-con-name fv) "[]"))
env
nil))
(:else
(cond
((and (hk-str? fv) (not (hk-str-null? fv)))
(let
((h (hk-str-head fv)) (t (hk-str-tail fv)))
(let
((res (hk-match (first items) h env)))
(cond
((nil? res) nil)
(:else (hk-match-list-pat (rest items) t res))))))
((not (hk-is-con-val? fv)) nil)
((not (= (hk-val-con-name fv) ":")) nil)
(:else
@@ -183,7 +183,11 @@
((res (hk-match (first items) h env)))
(cond
((nil? res) nil)
(:else (hk-match-list-pat (rest items) t res)))))))))))))
(:else
(hk-match-list-pat
(rest items)
t
res)))))))))))))
;; ── Convenience: parse a pattern from source for tests ─────
;; (Uses the parser's case-alt entry — `case _ of pat -> 0` —

View File

@@ -208,19 +208,9 @@
((= (get t "type") "char")
(do (hk-advance!) (list :char (get t "value"))))
((= (get t "type") "varid")
(do
(hk-advance!)
(cond
((hk-match? "lbrace" nil)
(hk-parse-rec-update (list :var (get t "value"))))
(:else (list :var (get t "value"))))))
(do (hk-advance!) (list :var (get t "value"))))
((= (get t "type") "conid")
(do
(hk-advance!)
(cond
((hk-match? "lbrace" nil)
(hk-parse-rec-create (get t "value")))
(:else (list :con (get t "value"))))))
(do (hk-advance!) (list :con (get t "value"))))
((= (get t "type") "qvarid")
(do (hk-advance!) (list :var (get t "value"))))
((= (get t "type") "qconid")
@@ -466,90 +456,6 @@
(do
(hk-expect! "rbracket" nil)
(list :list (list first-e))))))))))
(define
hk-parse-rec-create
(fn
(cname)
(begin
(hk-expect! "lbrace" nil)
(let
((fields (list)))
(define
hk-rc-loop
(fn
()
(when
(hk-match? "varid" nil)
(let
((fname (get (hk-advance!) "value")))
(begin
(hk-expect! "reservedop" "=")
(let
((fexpr (hk-parse-expr-inner)))
(begin
(append! fields (list fname fexpr))
(when
(hk-match? "comma" nil)
(begin (hk-advance!) (hk-rc-loop))))))))))
(hk-rc-loop)
(hk-expect! "rbrace" nil)
(list :rec-create cname fields)))))
(define
hk-parse-rec-update
(fn
(rec-expr)
(begin
(hk-expect! "lbrace" nil)
(let
((fields (list)))
(define
hk-ru-loop
(fn
()
(when
(hk-match? "varid" nil)
(let
((fname (get (hk-advance!) "value")))
(begin
(hk-expect! "reservedop" "=")
(let
((fexpr (hk-parse-expr-inner)))
(begin
(append! fields (list fname fexpr))
(when
(hk-match? "comma" nil)
(begin (hk-advance!) (hk-ru-loop))))))))))
(hk-ru-loop)
(hk-expect! "rbrace" nil)
(list :rec-update rec-expr fields)))))
(define
hk-parse-rec-pat
(fn
(cname)
(begin
(hk-expect! "lbrace" nil)
(let
((field-pats (list)))
(define
hk-rp-loop
(fn
()
(when
(hk-match? "varid" nil)
(let
((fname (get (hk-advance!) "value")))
(begin
(hk-expect! "reservedop" "=")
(let
((fpat (hk-parse-pat)))
(begin
(append! field-pats (list fname fpat))
(when
(hk-match? "comma" nil)
(begin (hk-advance!) (hk-rp-loop))))))))))
(hk-rp-loop)
(hk-expect! "rbrace" nil)
(list :p-rec cname field-pats)))))
(define
hk-parse-fexp
(fn
@@ -790,12 +696,7 @@
(:else
(do (hk-advance!) (list :p-var (get t "value")))))))
((= (get t "type") "conid")
(do
(hk-advance!)
(cond
((hk-match? "lbrace" nil)
(hk-parse-rec-pat (get t "value")))
(:else (list :p-con (get t "value") (list))))))
(do (hk-advance!) (list :p-con (get t "value") (list))))
((= (get t "type") "qconid")
(do (hk-advance!) (list :p-con (get t "value") (list))))
((= (get t "type") "lparen") (hk-parse-paren-pat))
@@ -861,24 +762,16 @@
(cond
((and (not (nil? t)) (or (= (get t "type") "conid") (= (get t "type") "qconid")))
(let
((name (get (hk-advance!) "value")))
(cond
((hk-match? "lbrace" nil)
(hk-parse-rec-pat name))
(:else
(let
((args (list)))
((name (get (hk-advance!) "value")) (args (list)))
(define
hk-pca-loop
(fn
()
(when
(hk-apat-start? (hk-peek))
(do
(append! args (hk-parse-apat))
(hk-pca-loop)))))
(do (append! args (hk-parse-apat)) (hk-pca-loop)))))
(hk-pca-loop)
(list :p-con name args))))))
(list :p-con name args)))
(:else (hk-parse-apat))))))
(define
hk-parse-pat
@@ -1319,47 +1212,16 @@
(not (hk-match? "conid" nil))
(hk-err "expected constructor name"))
(let
((name (get (hk-advance!) "value")))
(cond
((hk-match? "lbrace" nil)
(begin
(hk-advance!)
(let
((rec-fields (list)))
(define
hk-rec-loop
(fn
()
(when
(hk-match? "varid" nil)
(let
((fname (get (hk-advance!) "value")))
(begin
(hk-expect! "reservedop" "::")
(let
((ftype (hk-parse-type)))
(begin
(append! rec-fields (list fname ftype))
(when
(hk-match? "comma" nil)
(begin (hk-advance!) (hk-rec-loop))))))))))
(hk-rec-loop)
(hk-expect! "rbrace" nil)
(list :con-rec name rec-fields))))
(:else
(let
((fields (list)))
((name (get (hk-advance!) "value")) (fields (list)))
(define
hk-cd-loop
(fn
()
(when
(hk-atype-start? (hk-peek))
(begin
(append! fields (hk-parse-atype))
(hk-cd-loop)))))
(do (append! fields (hk-parse-atype)) (hk-cd-loop)))))
(hk-cd-loop)
(list :con-def name fields)))))))
(list :con-def name fields))))
(define
hk-parse-tvars
(fn

View File

@@ -12,7 +12,12 @@
(define
hk-register-con!
(fn (cname arity type-name) (dict-set! hk-constructors cname {:arity arity :type type-name})))
(fn
(cname arity type-name)
(dict-set!
hk-constructors
cname
{:arity arity :type type-name})))
(define hk-is-con? (fn (name) (has-key? hk-constructors name)))
@@ -43,15 +48,26 @@
(fn
(data-node)
(let
((type-name (nth data-node 1)) (cons-list (nth data-node 3)))
((type-name (nth data-node 1))
(cons-list (nth data-node 3)))
(for-each
(fn (cd) (hk-register-con! (nth cd 1) (len (nth cd 2)) type-name))
(fn
(cd)
(hk-register-con!
(nth cd 1)
(len (nth cd 2))
type-name))
cons-list))))
;; (:newtype NAME TVARS CNAME FIELD)
(define
hk-register-newtype!
(fn (nt-node) (hk-register-con! (nth nt-node 3) 1 (nth nt-node 1))))
(fn
(nt-node)
(hk-register-con!
(nth nt-node 3)
1
(nth nt-node 1))))
;; Walk a decls list, registering every `data` / `newtype` decl.
(define
@@ -62,9 +78,15 @@
(fn
(d)
(cond
((and (list? d) (not (empty? d)) (= (first d) "data"))
((and
(list? d)
(not (empty? d))
(= (first d) "data"))
(hk-register-data! d))
((and (list? d) (not (empty? d)) (= (first d) "newtype"))
((and
(list? d)
(not (empty? d))
(= (first d) "newtype"))
(hk-register-newtype! d))
(:else nil)))
decls)))
@@ -77,12 +99,16 @@
((nil? ast) nil)
((not (list? ast)) nil)
((empty? ast) nil)
((= (first ast) "program") (hk-register-decls! (nth ast 1)))
((= (first ast) "module") (hk-register-decls! (nth ast 4)))
((= (first ast) "program")
(hk-register-decls! (nth ast 1)))
((= (first ast) "module")
(hk-register-decls! (nth ast 4)))
(:else nil))))
;; Convenience: source → AST → desugar → register.
(define hk-load-source! (fn (src) (hk-register-program! (hk-core src))))
(define
hk-load-source!
(fn (src) (hk-register-program! (hk-core src))))
;; ── Built-in constructors pre-registered ─────────────────────
;; Bool — used implicitly by `if`, comparison operators.
@@ -102,49 +128,3 @@
(hk-register-con! "LT" 0 "Ordering")
(hk-register-con! "EQ" 0 "Ordering")
(hk-register-con! "GT" 0 "Ordering")
(hk-register-con! "SomeException" 1 "SomeException")
(define
hk-str?
(fn (v) (or (string? v) (and (dict? v) (has-key? v "hk-str")))))
(define
hk-str-head
(fn
(v)
(if
(string? v)
(char-code (char-at v 0))
(char-code (char-at (get v "hk-str") (get v "hk-off"))))))
(define
hk-str-tail
(fn
(v)
(let
((buf (if (string? v) v (get v "hk-str")))
(off (if (string? v) 1 (+ (get v "hk-off") 1))))
(if (>= off (string-length buf)) (list "[]") {:hk-off off :hk-str buf}))))
(define
hk-str-null?
(fn
(v)
(if
(string? v)
(= (string-length v) 0)
(>= (get v "hk-off") (string-length (get v "hk-str"))))))
(define
hk-str-to-native
(fn
(v)
(if
(string? v)
v
(let
((buf (get v "hk-str")) (off (get v "hk-off")))
(reduce
(fn (acc i) (str acc (char-at buf i)))
""
(range off (string-length buf)))))))

View File

@@ -1,6 +1,6 @@
{
"date": "2026-05-08",
"total_pass": 285,
"date": "2026-05-06",
"total_pass": 156,
"total_fail": 0,
"programs": {
"fib": {"pass": 2, "fail": 0},
@@ -9,7 +9,7 @@
"nqueens": {"pass": 2, "fail": 0},
"calculator": {"pass": 5, "fail": 0},
"collatz": {"pass": 11, "fail": 0},
"palindrome": {"pass": 12, "fail": 0},
"palindrome": {"pass": 8, "fail": 0},
"maybe": {"pass": 12, "fail": 0},
"fizzbuzz": {"pass": 12, "fail": 0},
"anagram": {"pass": 9, "fail": 0},
@@ -19,25 +19,7 @@
"primes": {"pass": 12, "fail": 0},
"zipwith": {"pass": 9, "fail": 0},
"matrix": {"pass": 8, "fail": 0},
"wordcount": {"pass": 10, "fail": 0},
"powers": {"pass": 14, "fail": 0},
"caesar": {"pass": 8, "fail": 0},
"runlength-str": {"pass": 9, "fail": 0},
"showadt": {"pass": 5, "fail": 0},
"showio": {"pass": 5, "fail": 0},
"partial": {"pass": 7, "fail": 0},
"statistics": {"pass": 5, "fail": 0},
"newton": {"pass": 5, "fail": 0},
"wordfreq": {"pass": 7, "fail": 0},
"mapgraph": {"pass": 6, "fail": 0},
"uniquewords": {"pass": 4, "fail": 0},
"setops": {"pass": 8, "fail": 0},
"shapes": {"pass": 5, "fail": 0},
"person": {"pass": 7, "fail": 0},
"config": {"pass": 10, "fail": 0},
"counter": {"pass": 7, "fail": 0},
"accumulate": {"pass": 8, "fail": 0},
"safediv": {"pass": 8, "fail": 0},
"trycatch": {"pass": 8, "fail": 0}
"wordcount": {"pass": 7, "fail": 0},
"powers": {"pass": 14, "fail": 0}
}
}

View File

@@ -1,6 +1,6 @@
# Haskell-on-SX Scoreboard
Updated 2026-05-08 · Phase 6 (prelude extras + 18 programs)
Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
| Program | Tests | Status |
|---------|-------|--------|
@@ -10,7 +10,7 @@ Updated 2026-05-08 · Phase 6 (prelude extras + 18 programs)
| nqueens.hs | 2/2 | ✓ |
| calculator.hs | 5/5 | ✓ |
| collatz.hs | 11/11 | ✓ |
| palindrome.hs | 12/12 | ✓ |
| palindrome.hs | 8/8 | ✓ |
| maybe.hs | 12/12 | ✓ |
| fizzbuzz.hs | 12/12 | ✓ |
| anagram.hs | 9/9 | ✓ |
@@ -20,24 +20,6 @@ Updated 2026-05-08 · Phase 6 (prelude extras + 18 programs)
| primes.hs | 12/12 | ✓ |
| zipwith.hs | 9/9 | ✓ |
| matrix.hs | 8/8 | ✓ |
| wordcount.hs | 10/10 | ✓ |
| wordcount.hs | 7/7 | ✓ |
| powers.hs | 14/14 | ✓ |
| caesar.hs | 8/8 | ✓ |
| runlength-str.hs | 9/9 | ✓ |
| showadt.hs | 5/5 | ✓ |
| showio.hs | 5/5 | ✓ |
| partial.hs | 7/7 | ✓ |
| statistics.hs | 5/5 | ✓ |
| newton.hs | 5/5 | ✓ |
| wordfreq.hs | 7/7 | ✓ |
| mapgraph.hs | 6/6 | ✓ |
| uniquewords.hs | 4/4 | ✓ |
| setops.hs | 8/8 | ✓ |
| shapes.hs | 5/5 | ✓ |
| person.hs | 7/7 | ✓ |
| config.hs | 10/10 | ✓ |
| counter.hs | 7/7 | ✓ |
| accumulate.hs | 8/8 | ✓ |
| safediv.hs | 8/8 | ✓ |
| trycatch.hs | 8/8 | ✓ |
| **Total** | **285/285** | **36/36 programs** |
| **Total** | **156/156** | **18/18 programs** |

View File

@@ -1,62 +0,0 @@
;; set.sx — Phase 12 Data.Set: wraps Data.Map with unit values.
;;
;; A Set is a Map from key to (). All set operations delegate to the map
;; ops, ignoring the value side. Storage representation matches Data.Map:
;;
;; Empty → ("Map-Empty")
;; Node → ("Map-Node" key () left right size)
;;
;; Tradeoff: trivial maintenance burden, slight overhead per node from
;; the unused value slot. Faster path forward than re-implementing the
;; weight-balanced BST.
;;
;; Functions live in this file; the Haskell-level `import Data.Set` /
;; `import qualified Data.Set as Set` wiring (next Phase 12 box) binds
;; them under the chosen alias.
(define hk-set-unit (list "Tuple"))
(define hk-set-empty hk-map-empty)
(define hk-set-singleton (fn (k) (hk-map-singleton k hk-set-unit)))
(define hk-set-insert (fn (k s) (hk-map-insert k hk-set-unit s)))
(define hk-set-delete hk-map-delete)
(define hk-set-member hk-map-member)
(define hk-set-size hk-map-size)
(define hk-set-null hk-map-null)
(define hk-set-to-asc-list hk-map-keys)
(define hk-set-to-list hk-map-keys)
(define
hk-set-from-list
(fn (xs) (reduce (fn (acc k) (hk-set-insert k acc)) hk-set-empty xs)))
(define
hk-set-union
(fn (a b) (hk-map-union-with (fn (x y) hk-set-unit) a b)))
(define
hk-set-intersection
(fn (a b) (hk-map-intersection-with (fn (x y) hk-set-unit) a b)))
(define hk-set-difference hk-map-difference)
(define
hk-set-is-subset-of
(fn (a b) (= (hk-map-size (hk-map-difference a b)) 0)))
(define
hk-set-filter
(fn (p s) (hk-map-filter-with-key (fn (k v) (p k)) s)))
(define hk-set-map (fn (f s) (hk-set-from-list (map f (hk-map-keys s)))))
(define
hk-set-foldr
(fn (f z s) (hk-map-foldr-with-key (fn (k v acc) (f k acc)) z s)))
(define
hk-set-foldl
(fn (f z s) (hk-map-foldl-with-key (fn (acc k v) (f acc k)) z s)))

View File

@@ -55,8 +55,6 @@ for FILE in "${FILES[@]}"; do
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/map.sx")
(load "lib/haskell/set.sx")
$INFER_LOAD
(load "lib/haskell/testlib.sx")
(epoch 2)
@@ -100,8 +98,6 @@ EPOCHS
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/map.sx")
(load "lib/haskell/set.sx")
$INFER_LOAD
(load "lib/haskell/testlib.sx")
(epoch 2)

View File

@@ -56,21 +56,3 @@
(append!
hk-test-fails
{:actual actual :expected expected :name name})))))
(define
hk-test-error
(fn
(name thunk expected-substring)
(let
((caught (guard (e (true (if (string? e) e (str e)))) (begin (thunk) nil))))
(cond
((nil? caught)
(do
(set! hk-test-fail (+ hk-test-fail 1))
(append! hk-test-fails {:actual "no error raised" :expected (str "error containing: " expected-substring) :name name})))
((>= (index-of caught expected-substring) 0)
(set! hk-test-pass (+ hk-test-pass 1)))
(:else
(do
(set! hk-test-fail (+ hk-test-fail 1))
(append! hk-test-fails {:actual caught :expected (str "error containing: " expected-substring) :name name})))))))

View File

@@ -1,86 +0,0 @@
;; class-defaults.sx — Phase 13: class default method implementations.
;; ── Eq default: myNeq derived from myEq via `not (myEq x y)` ──
(define
hk-myeq-source
"class MyEq a where\n myEq :: a -> a -> Bool\n myNeq :: a -> a -> Bool\n myNeq x y = not (myEq x y)\ninstance MyEq Int where\n myEq x y = x == y\n")
(hk-test
"Eq default: myNeq 3 5 = True (no explicit myNeq in instance)"
(hk-deep-force (hk-run (str hk-myeq-source "main = myNeq 3 5\n")))
(list "True"))
(hk-test
"Eq default: myNeq 3 3 = False"
(hk-deep-force (hk-run (str hk-myeq-source "main = myNeq 3 3\n")))
(list "False"))
(hk-test
"Eq default: myEq still works in same instance"
(hk-deep-force (hk-run (str hk-myeq-source "main = myEq 7 7\n")))
(list "True"))
;; ── Override path: instance can still provide the method explicitly. ──
(hk-test
"Default override: instance-provided beats class default"
(hk-deep-force
(hk-run
"class Hi a where\n greet :: a -> String\n greet x = \"default\"\ninstance Hi Bool where\n greet x = \"override\"\nmain = greet True"))
"override")
(hk-test
"Default fallback: empty instance picks default"
(hk-deep-force
(hk-run
"class Hi a where\n greet :: a -> String\n greet x = \"default\"\ninstance Hi Bool where\nmain = greet True"))
"default")
(define
hk-myord-source
"class MyOrd a where\n myCmp :: a -> a -> Bool\n myMax :: a -> a -> a\n myMin :: a -> a -> a\n myMax a b = if myCmp a b then a else b\n myMin a b = if myCmp a b then b else a\ninstance MyOrd Int where\n myCmp x y = x >= y\n")
(hk-test
"Ord default: myMax 3 5 = 5"
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 3 5\n")))
5)
(hk-test
"Ord default: myMax 8 2 = 8"
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 8 2\n")))
8)
(hk-test
"Ord default: myMin 3 5 = 3"
(hk-deep-force (hk-run (str hk-myord-source "main = myMin 3 5\n")))
3)
(hk-test
"Ord default: myMin 8 2 = 2"
(hk-deep-force (hk-run (str hk-myord-source "main = myMin 8 2\n")))
2)
(hk-test
"Ord default: myMax of equals returns first"
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 4 4\n")))
4)
(define
hk-mynum-source
"class MyNum a where\n mySub :: a -> a -> a\n myLt :: a -> a -> Bool\n myNegate :: a -> a\n myAbs :: a -> a\n myNegate x = mySub (mySub x x) x\n myAbs x = if myLt x (mySub x x) then myNegate x else x\ninstance MyNum Int where\n mySub x y = x - y\n myLt x y = x < y\n")
(hk-test
"Num default: myNegate 5 = -5"
(hk-deep-force (hk-run (str hk-mynum-source "main = myNegate 5\n")))
-5)
(hk-test
"Num default: myAbs (myNegate 7) = 7"
(hk-deep-force (hk-run (str hk-mynum-source "main = myAbs (myNegate 7)\n")))
7)
(hk-test
"Num default: myAbs 9 = 9"
(hk-deep-force (hk-run (str hk-mynum-source "main = myAbs 9\n")))
9)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -12,14 +12,14 @@
"deriving Show: constructor with arg"
(hk-deep-force
(hk-run "data Wrapper = Wrap Int deriving (Show)\nmain = show (Wrap 42)"))
"Wrap 42")
"(Wrap 42)")
(hk-test
"deriving Show: nested constructors"
(hk-deep-force
(hk-run
"data Tree = Leaf | Node Int Tree Tree deriving (Show)\nmain = show (Node 1 Leaf Leaf)"))
"Node 1 Leaf Leaf")
"(Node 1 Leaf Leaf)")
(hk-test
"deriving Show: second constructor"
@@ -30,31 +30,6 @@
;; ─── Eq ──────────────────────────────────────────────────────────────────────
(hk-test
"deriving Show: nested ADT wraps inner constructor in parens"
(hk-deep-force
(hk-run
"data Tree = Leaf | Node Int Tree Tree deriving (Show)\nmain = show (Node 1 Leaf (Node 2 Leaf Leaf))"))
"Node 1 Leaf (Node 2 Leaf Leaf)")
(hk-test
"deriving Show: Maybe Maybe wraps inner Just"
(hk-deep-force (hk-run "main = show (Just (Just 3))"))
"Just (Just 3)")
(hk-test
"deriving Show: negative argument wrapped in parens"
(hk-deep-force (hk-run "main = show (Just (negate 3))"))
"Just (-3)")
(hk-test
"deriving Show: list element does not need parens"
(hk-deep-force
(hk-run "data Box = Box [Int] deriving (Show)\nmain = show (Box [1,2,3])"))
"Box [1,2,3]")
;; ─── combined Eq + Show ───────────────────────────────────────────────────────
(hk-test
"deriving Eq: same constructor"
(hk-deep-force
@@ -83,12 +58,14 @@
"data Color = Red | Green | Blue deriving (Eq)\nmain = show (Red /= Blue)"))
"True")
;; ─── combined Eq + Show ───────────────────────────────────────────────────────
(hk-test
"deriving Eq Show: combined"
"deriving Eq Show: combined in parens"
(hk-deep-force
(hk-run
"data Shape = Circle Int | Square Int deriving (Eq, Show)\nmain = show (Circle 5)"))
"Circle 5")
"(Circle 5)")
(hk-test
"deriving Eq Show: eq on constructor with arg"

View File

@@ -1,99 +0,0 @@
;; errors.sx — Phase 9 error / undefined / partial-fn coverage via hk-test-error.
;; ── error builtin ────────────────────────────────────────────
(define
hk-as-list
(fn
(xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(hk-test-error
"error: raises with literal message"
(fn () (hk-deep-force (hk-run "main = error \"boom\"")))
"hk-error: boom")
(hk-test-error
"error: raises with computed message"
(fn () (hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)")))
"hk-error: oops: 42")
;; ── undefined ────────────────────────────────────────────────
(hk-test-error
"error: nested in if branch (only fires when forced)"
(fn
()
(hk-deep-force (hk-run "main = if 1 == 1 then error \"taken\" else 0")))
"taken")
(hk-test-error
"undefined: raises Prelude.undefined"
(fn () (hk-deep-force (hk-run "main = undefined")))
"Prelude.undefined")
;; The non-strict path: undefined doesn't fire when not forced.
(hk-test-error
"undefined: forced via arithmetic"
(fn () (hk-deep-force (hk-run "main = undefined + 1")))
"Prelude.undefined")
;; ── partial functions ───────────────────────────────────────
(hk-test
"undefined: lazy, not forced when discarded"
(hk-deep-force (hk-run "main = let _ = undefined in 5"))
5)
(hk-test-error
"head []: raises Prelude.head: empty list"
(fn () (hk-deep-force (hk-run "main = head []")))
"Prelude.head: empty list")
(hk-test-error
"tail []: raises Prelude.tail: empty list"
(fn () (hk-deep-force (hk-run "main = tail []")))
"Prelude.tail: empty list")
;; head and tail still work on non-empty lists.
(hk-test-error
"fromJust Nothing: raises Maybe.fromJust: Nothing"
(fn () (hk-deep-force (hk-run "main = fromJust Nothing")))
"Maybe.fromJust: Nothing")
(hk-test
"head [42]: still works"
(hk-deep-force (hk-run "main = head [42]"))
42)
;; ── error in IO context ─────────────────────────────────────
(hk-test
"tail [1,2,3]: still works"
(hk-as-list (hk-deep-force (hk-run "main = tail [1,2,3]")))
(list 2 3))
(hk-test
"hk-run-io: error in main lands in io-lines"
(let
((lines (hk-run-io "main = error \"caught here\"")))
(>= (index-of (str lines) "caught here") 0))
true)
;; ── hk-test-error helper itself ─────────────────────────────
(hk-test
"hk-run-io: putStrLn before error preserves earlier output"
(let
((lines (hk-run-io "main = do { putStrLn \"first\"; error \"died\"; putStrLn \"never\" }")))
(and
(>= (index-of (str lines) "first") 0)
(>= (index-of (str lines) "died") 0)))
true)
;; hk-as-list helper for converting a forced Haskell cons into an SX list.
(hk-test-error
"hk-test-error: matches partial substring inside wrapped exception"
(fn () (hk-deep-force (hk-run "main = error \"unique-marker-xyz\"")))
"unique-marker-xyz")
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -231,82 +231,16 @@
1)
;; ── Laziness: app args evaluate only when forced ──
(hk-test
"error builtin: raises with hk-error prefix"
(guard
(e (true (>= (index-of e "hk-error: boom") 0)))
(begin (hk-deep-force (hk-run "main = error \"boom\"")) false))
true)
(hk-test
"error builtin: raises with computed message"
(guard
(e (true (>= (index-of e "hk-error: oops: 42") 0)))
(begin
(hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)"))
false))
true)
(hk-test
"undefined: raises hk-error with Prelude.undefined message"
(guard
(e (true (>= (index-of e "hk-error: Prelude.undefined") 0)))
(begin (hk-deep-force (hk-run "main = undefined")) false))
true)
(hk-test
"undefined: lazy — only fires when forced"
(hk-deep-force (hk-run "main = if True then 42 else undefined"))
42)
(hk-test
"head []: raises Prelude.head: empty list"
(guard
(e (true (>= (index-of e "Prelude.head: empty list") 0)))
(begin (hk-deep-force (hk-run "main = head []")) false))
true)
(hk-test
"tail []: raises Prelude.tail: empty list"
(guard
(e (true (>= (index-of e "Prelude.tail: empty list") 0)))
(begin (hk-deep-force (hk-run "main = tail []")) false))
true)
;; ── not / id built-ins ──
(hk-test
"fromJust Nothing: raises Maybe.fromJust: Nothing"
(guard
(e (true (>= (index-of e "Maybe.fromJust: Nothing") 0)))
(begin (hk-deep-force (hk-run "main = fromJust Nothing")) false))
true)
(hk-test
"fromJust (Just 5) = 5"
(hk-deep-force (hk-run "main = fromJust (Just 5)"))
5)
(hk-test
"head [42] = 42 (still works for non-empty)"
(hk-deep-force (hk-run "main = head [42]"))
42)
(hk-test-error
"hk-test-error helper: catches matching error"
(fn () (hk-deep-force (hk-run "main = error \"boom\"")))
"hk-error: boom")
(hk-test-error
"hk-test-error helper: catches head [] error"
(fn () (hk-deep-force (hk-run "main = head []")))
"Prelude.head: empty list")
(hk-test
"second arg never forced"
(hk-eval-expr-source "(\\x y -> x) 1 (error \"never\")")
(hk-eval-expr-source
"(\\x y -> x) 1 (error \"never\")")
1)
(hk-test
"first arg never forced"
(hk-eval-expr-source "(\\x y -> y) (error \"never\") 99")
(hk-eval-expr-source
"(\\x y -> y) (error \"never\") 99")
99)
(hk-test
@@ -317,7 +251,9 @@
(hk-test
"lazy: const drops its second argument"
(hk-prog-val "const x y = x\nresult = const 5 (error \"boom\")" "result")
(hk-prog-val
"const x y = x\nresult = const 5 (error \"boom\")"
"result")
5)
(hk-test
@@ -334,10 +270,9 @@
"result")
(list "True"))
;; ── not / id built-ins ──
(hk-test "not True" (hk-eval-expr-source "not True") (list "False"))
(hk-test "not False" (hk-eval-expr-source "not False") (list "True"))
(hk-test "id" (hk-eval-expr-source "id 42") 42)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,105 +0,0 @@
;; Phase 16 — Exception handling unit tests.
(hk-test
"catch — success path returns the action result"
(hk-deep-force
(hk-run
"main = catch (return 42) (\\(SomeException m) -> return 0)"))
(list "IO" 42))
(hk-test
"catch — error caught, handler receives message"
(hk-deep-force
(hk-run
"main = catch (error \"boom\") (\\(SomeException m) -> return m)"))
(list "IO" "boom"))
(hk-test
"try — success returns Right v"
(hk-deep-force
(hk-run "main = try (return 42)"))
(list "IO" (list "Right" 42)))
(hk-test
"try — error returns Left (SomeException msg)"
(hk-deep-force
(hk-run "main = try (error \"oops\")"))
(list "IO" (list "Left" (list "SomeException" "oops"))))
(hk-test
"handle — flip catch — caught error message"
(hk-deep-force
(hk-run
"main = handle (\\(SomeException m) -> return m) (error \"hot\")"))
(list "IO" "hot"))
(hk-test
"throwIO + catch — handler sees the SomeException"
(hk-deep-force
(hk-run
"main = catch (throwIO (SomeException \"bang\")) (\\(SomeException m) -> return m)"))
(list "IO" "bang"))
(hk-test
"throwIO + try — Left side"
(hk-deep-force
(hk-run
"main = try (throwIO (SomeException \"x\"))"))
(list "IO" (list "Left" (list "SomeException" "x"))))
(hk-test
"evaluate — pure value returns IO v"
(hk-deep-force
(hk-run "main = evaluate (1 + 2 + 3)"))
(list "IO" 6))
(hk-test
"evaluate — error surfaces as catchable exception"
(hk-deep-force
(hk-run
"main = catch (evaluate (error \"deep\")) (\\(SomeException m) -> return m)"))
(list "IO" "deep"))
(hk-test
"nested catch — inner handler runs first"
(hk-deep-force
(hk-run
"main = catch (catch (error \"inner\") (\\(SomeException m) -> error (m ++ \"-rethrown\"))) (\\(SomeException m) -> return m)"))
(list "IO" "inner-rethrown"))
(hk-test
"catch chain — handler can succeed inside IO"
(hk-deep-force
(hk-run
"main = do { x <- catch (error \"e1\") (\\(SomeException m) -> return 100); return (x + 1) }"))
(list "IO" 101))
(hk-test
"try then bind on Right"
(hk-deep-force
(hk-run
"branch (Right v) = return (v * 2)
branch (Left _) = return 0
main = do { r <- try (return 21); branch r }"))
(list "IO" 42))
(hk-test
"try then bind on Left"
(hk-deep-force
(hk-run
"branch (Right _) = return \"ok\"
branch (Left (SomeException m)) = return m
main = do { r <- try (error \"failed\"); branch r }"))
(list "IO" "failed"))
(hk-test
"catch — handler can use closed-over IORef"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef
main = do
r <- IORef.newIORef 0
catch (error \"x\") (\\(SomeException m) -> IORef.writeIORef r 7)
v <- IORef.readIORef r
return v"))
(list "IO" 7))

View File

@@ -1,31 +0,0 @@
;; instance-where.sx — Phase 13: where-clauses inside instance bodies.
(hk-test
"instance method body with where-helper (Bool)"
(hk-deep-force
(hk-run
"class Greet a where\n greet :: a -> String\ninstance Greet Bool where\n greet x = mkMsg x\n where mkMsg True = \"yes\"\n mkMsg False = \"no\"\nmain = greet True"))
"yes")
(hk-test
"instance method body with where-helper (False branch)"
(hk-deep-force
(hk-run
"class Greet a where\n greet :: a -> String\ninstance Greet Bool where\n greet x = mkMsg x\n where mkMsg True = \"yes\"\n mkMsg False = \"no\"\nmain = greet False"))
"no")
(hk-test
"instance method body with where-binding referenced multiple times"
(hk-deep-force
(hk-run
"class Twice a where\n twice :: a -> Int\ninstance Twice Int where\n twice x = h + h\n where h = x + 1\nmain = twice 5"))
12)
(hk-test
"instance method body with multi-binding where"
(hk-deep-force
(hk-run
"class Calc a where\n calc :: a -> Int\ninstance Calc Int where\n calc x = a + b\n where a = x * 2\n b = x + 1\nmain = calc 3"))
10)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -64,11 +64,12 @@
(hk-test
"readFile error on missing file"
(guard
(e (true (>= (index-of e "file not found") 0)))
(begin
(set! hk-vfs (dict))
(let
((lines (hk-run-io "main = readFile \"no.txt\" >>= putStrLn")))
(>= (index-of (str lines) "file not found") 0)))
(hk-run-io "main = readFile \"no.txt\" >>= putStrLn")
false))
true)
(hk-test

View File

@@ -1,94 +0,0 @@
;; Phase 15 — IORef unit tests.
(hk-test
"newIORef + readIORef returns initial value"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 42; v <- IORef.readIORef r; return v }"))
(list "IO" 42))
(hk-test
"writeIORef updates the cell"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 0; IORef.writeIORef r 99; v <- IORef.readIORef r; return v }"))
(list "IO" 99))
(hk-test
"writeIORef returns IO ()"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 0; IORef.writeIORef r 1 }"))
(list "IO" (list "Tuple")))
(hk-test
"modifyIORef applies a function"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 5; IORef.modifyIORef r (\\x -> x * 2); v <- IORef.readIORef r; return v }"))
(list "IO" 10))
(hk-test
"modifyIORef' (strict) applies a function"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 7; IORef.modifyIORef' r (\\x -> x + 3); v <- IORef.readIORef r; return v }"))
(list "IO" 10))
(hk-test
"two reads return the same value"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 11; a <- IORef.readIORef r; b <- IORef.readIORef r; return (a + b) }"))
(list "IO" 22))
(hk-test
"shared ref across do-steps: write then read"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 1; IORef.writeIORef r 2; IORef.writeIORef r 3; v <- IORef.readIORef r; return v }"))
(list "IO" 3))
(hk-test
"two refs are independent"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r1 <- IORef.newIORef 1; r2 <- IORef.newIORef 2; IORef.writeIORef r1 10; a <- IORef.readIORef r1; b <- IORef.readIORef r2; return (a + b) }"))
(list "IO" 12))
(hk-test
"string-valued IORef"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef \"hi\"; IORef.writeIORef r \"bye\"; v <- IORef.readIORef r; return v }"))
(list "IO" "bye"))
(hk-test
"list-valued IORef + cons"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef [1,2,3]; IORef.modifyIORef r (\\xs -> 0 : xs); v <- IORef.readIORef r; return v }"))
(list
"IO"
(list ":" 0 (list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))))
(hk-test
"counter loop: increment N times"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nloop r 0 = return ()\nloop r n = do { IORef.modifyIORef r (\\x -> x + 1); loop r (n - 1) }\nmain = do { r <- IORef.newIORef 0; loop r 10; v <- IORef.readIORef r; return v }"))
(list "IO" 10))
(hk-test
"modifyIORef' inside a loop"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\ngo r 0 = return ()\ngo r n = do { IORef.modifyIORef' r (\\x -> x + n); go r (n - 1) }\nmain = do { r <- IORef.newIORef 0; go r 5; v <- IORef.readIORef r; return v }"))
(list "IO" 15))
(hk-test
"newIORef inside a function passed via parameter"
(hk-deep-force
(hk-run
"import qualified Data.IORef as IORef\nbump r = IORef.modifyIORef r (\\x -> x + 100)\nmain = do { r <- IORef.newIORef 1; bump r; v <- IORef.readIORef r; return v }"))
(list "IO" 101))

View File

@@ -1,196 +0,0 @@
;; map.sx — Phase 11 Data.Map unit tests.
;;
;; Tests both the SX-level `hk-map-*` helpers and the Haskell-level
;; `Map.*` aliases bound by the import handler.
(define
hk-as-list
(fn
(xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
;; ── SX-level (direct hk-map-*) ───────────────────────────────
(hk-test
"hk-map-empty: size 0, null true"
(list (hk-map-size hk-map-empty) (hk-map-null hk-map-empty))
(list 0 true))
(hk-test
"hk-map-singleton: lookup hit"
(let
((m (hk-map-singleton 5 "five")))
(list (hk-map-size m) (hk-map-lookup 5 m)))
(list 1 (list "Just" "five")))
(hk-test
"hk-map-insert: lookup hit on inserted"
(let ((m (hk-map-insert 1 "a" hk-map-empty))) (hk-map-lookup 1 m))
(list "Just" "a"))
(hk-test
"hk-map-lookup: miss returns Nothing"
(hk-map-lookup 99 (hk-map-singleton 1 "a"))
(list "Nothing"))
(hk-test
"hk-map-insert: overwrites existing key"
(let
((m (hk-map-insert 1 "second" (hk-map-insert 1 "first" hk-map-empty))))
(hk-map-lookup 1 m))
(list "Just" "second"))
(hk-test
"hk-map-delete: removes key"
(let
((m (hk-map-insert 2 "b" (hk-map-insert 1 "a" hk-map-empty))))
(let
((m2 (hk-map-delete 1 m)))
(list (hk-map-size m2) (hk-map-lookup 1 m2) (hk-map-lookup 2 m2))))
(list 1 (list "Nothing") (list "Just" "b")))
(hk-test
"hk-map-delete: missing key is no-op"
(let ((m (hk-map-singleton 1 "a"))) (hk-map-size (hk-map-delete 99 m)))
1)
(hk-test
"hk-map-member: true on existing"
(hk-map-member 1 (hk-map-singleton 1 "a"))
true)
(hk-test
"hk-map-member: false on missing"
(hk-map-member 99 (hk-map-singleton 1 "a"))
false)
(hk-test
"hk-map-from-list: builds map; keys sorted"
(hk-map-keys
(hk-map-from-list
(list (list 3 "c") (list 1 "a") (list 5 "e") (list 2 "b"))))
(list 1 2 3 5))
(hk-test
"hk-map-from-list: duplicates — last wins"
(hk-map-lookup
1
(hk-map-from-list (list (list 1 "first") (list 1 "second"))))
(list "Just" "second"))
(hk-test
"hk-map-to-asc-list: ordered traversal"
(hk-map-to-asc-list
(hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b"))))
(list (list 1 "a") (list 2 "b") (list 3 "c")))
(hk-test
"hk-map-elems: in key order"
(hk-map-elems
(hk-map-from-list (list (list 3 30) (list 1 10) (list 2 20))))
(list 10 20 30))
(hk-test
"hk-map-union-with: combines duplicates"
(hk-map-to-asc-list
(hk-map-union-with
(fn (a b) (str a "+" b))
(hk-map-from-list (list (list 1 "a") (list 2 "b")))
(hk-map-from-list (list (list 2 "B") (list 3 "c")))))
(list (list 1 "a") (list 2 "b+B") (list 3 "c")))
(hk-test
"hk-map-intersection-with: keeps shared keys"
(hk-map-to-asc-list
(hk-map-intersection-with
+
(hk-map-from-list (list (list 1 10) (list 2 20)))
(hk-map-from-list (list (list 2 200) (list 3 30)))))
(list (list 2 220)))
(hk-test
"hk-map-difference: drops m2 keys"
(hk-map-keys
(hk-map-difference
(hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c")))
(hk-map-from-list (list (list 2 "x")))))
(list 1 3))
(hk-test
"hk-map-foldl-with-key: in-order accumulate"
(hk-map-foldl-with-key
(fn (acc k v) (str acc k v))
""
(hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b"))))
"1a2b3c")
(hk-test
"hk-map-map-with-key: transforms values"
(hk-map-to-asc-list
(hk-map-map-with-key
(fn (k v) (* k v))
(hk-map-from-list (list (list 2 10) (list 3 100)))))
(list (list 2 20) (list 3 300)))
(hk-test
"hk-map-filter-with-key: keeps matches"
(hk-map-keys
(hk-map-filter-with-key
(fn (k v) (> k 1))
(hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c")))))
(list 2 3))
(hk-test
"hk-map-adjust: applies f to existing"
(hk-map-lookup
1
(hk-map-adjust (fn (v) (* v 10)) 1 (hk-map-singleton 1 5)))
(list "Just" 50))
(hk-test
"hk-map-insert-with: combines on existing"
(hk-map-lookup 1 (hk-map-insert-with + 1 5 (hk-map-singleton 1 10)))
(list "Just" 15))
(hk-test
"hk-map-alter: Nothing → delete"
(hk-map-size
(hk-map-alter
(fn (mv) (list "Nothing"))
1
(hk-map-from-list (list (list 1 "a") (list 2 "b")))))
1)
;; ── Haskell-level (Map.*) via import wiring ─────────────────
(hk-test
"Map.size after Map.insert chain"
(hk-deep-force
(hk-run
"import qualified Data.Map as Map\nmain = Map.size (Map.insert 2 \"b\" (Map.insert 1 \"a\" Map.empty))"))
2)
(hk-test
"Map.lookup hit"
(hk-deep-force
(hk-run
"import qualified Data.Map as Map\nmain = Map.lookup 1 (Map.insert 1 \"a\" Map.empty)"))
(list "Just" "a"))
(hk-test
"Map.lookup miss"
(hk-deep-force
(hk-run
"import qualified Data.Map as Map\nmain = Map.lookup 99 (Map.insert 1 \"a\" Map.empty)"))
(list "Nothing"))
(hk-test
"Map.member true"
(hk-deep-force
(hk-run
"import qualified Data.Map as Map\nmain = Map.member 5 (Map.insert 5 \"x\" Map.empty)"))
(list "True"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,180 +0,0 @@
;; numerics.sx — Phase 10 numeric tower verification.
;;
;; Practical integer-precision limit in Haskell-on-SX:
;; • Raw SX `(* a b)` stays exact up to ±2^62 (≈ 4.6e18, OCaml int63).
;; • BUT the Haskell tokenizer/parser parses an integer literal as a float
;; once it exceeds 2^53 (≈ 9.007e15). Once any operand is a float, the
;; binop result is a float (and decimal-precision is lost past 2^53).
;; • Therefore: programs that stay below ~9e15 are exact; larger literals
;; or accumulated products silently become floats. `factorial 18` is the
;; last factorial that stays exact (6.4e15); `factorial 19` already floats.
;;
;; In Haskell terms, `Int` and `Integer` both currently map to SX number, so
;; we don't yet support arbitrary-precision Integer. Documented; unbounded
;; Integer is out of scope for Phase 10 — see Phase 11+ if it becomes needed.
(define
hk-as-list
(fn
(xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(hk-test
"factorial 10 = 3628800 (small, exact)"
(hk-deep-force
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 10"))
3628800)
(hk-test
"factorial 15 = 1307674368000 (mid-range, exact)"
(hk-deep-force
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 15"))
1307674368000)
(hk-test
"factorial 18 = 6402373705728000 (last exact factorial)"
(hk-deep-force
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 18"))
6402373705728000)
(hk-test
"1000000 * 1000000 = 10^12 (exact)"
(hk-deep-force (hk-run "main = 1000000 * 1000000"))
1000000000000)
(hk-test
"1000000000 * 1000000000 = 10^18 (exact, at boundary)"
(hk-deep-force (hk-run "main = 1000000000 * 1000000000"))
1e+18)
(hk-test
"2^62 boundary: pow accumulates exactly"
(hk-deep-force
(hk-run "pow b 0 = 1\npow b n = b * pow b (n - 1)\nmain = pow 2 62"))
4.6116860184273879e+18)
(hk-test
"show factorial 12 = 479001600 (whole, fits in 32-bit)"
(hk-deep-force
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = show (fact 12)"))
"479001600")
(hk-test
"negate large positive — preserves magnitude"
(hk-deep-force (hk-run "main = negate 1000000000000000000"))
-1e+18)
(hk-test
"abs negative large — preserves magnitude"
(hk-deep-force (hk-run "main = abs (negate 1000000000000000000)"))
1e+18)
(hk-test
"div on large ints"
(hk-deep-force (hk-run "main = div 1000000000000000000 1000000000"))
1000000000)
(hk-test
"fromIntegral 42 = 42 (identity in our runtime)"
(hk-deep-force (hk-run "main = fromIntegral 42"))
42)
(hk-test
"fromIntegral preserves negative"
(hk-deep-force (hk-run "main = fromIntegral (negate 7)"))
-7)
(hk-test
"fromIntegral round-trips through arithmetic"
(hk-deep-force (hk-run "main = fromIntegral 5 + fromIntegral 3"))
8)
(hk-test
"fromIntegral in a program (mixing with map)"
(hk-as-list (hk-deep-force (hk-run "main = map fromIntegral [1,2,3]")))
(list 1 2 3))
(hk-test
"toInteger 100 = 100 (identity)"
(hk-deep-force (hk-run "main = toInteger 100"))
100)
(hk-test
"fromInteger 7 = 7 (identity)"
(hk-deep-force (hk-run "main = fromInteger 7"))
7)
(hk-test
"toInteger / fromInteger round-trip"
(hk-deep-force (hk-run "main = fromInteger (toInteger 42)"))
42)
(hk-test
"toInteger preserves negative"
(hk-deep-force (hk-run "main = toInteger (negate 13)"))
-13)
(hk-test
"show 3.14 = 3.14"
(hk-deep-force (hk-run "main = show 3.14"))
"3.14")
(hk-test
"show 1.0e10 — whole-valued float renders as decimal (int/float ambiguity)"
(hk-deep-force (hk-run "main = show 1.0e10"))
"10000000000")
(hk-test
"show 0.001 uses scientific form (sub-0.1)"
(hk-deep-force (hk-run "main = show 0.001"))
"1.0e-3")
(hk-test
"show negative float"
(hk-deep-force (hk-run "main = show (negate 3.14)"))
"-3.14")
(hk-test "sqrt 16 = 4" (hk-deep-force (hk-run "main = sqrt 16")) 4)
(hk-test "floor 3.7 = 3" (hk-deep-force (hk-run "main = floor 3.7")) 3)
(hk-test "ceiling 3.2 = 4" (hk-deep-force (hk-run "main = ceiling 3.2")) 4)
(hk-test
"ceiling on whole = self"
(hk-deep-force (hk-run "main = ceiling 4"))
4)
(hk-test "round 2.6 = 3" (hk-deep-force (hk-run "main = round 2.6")) 3)
(hk-test
"truncate -3.7 = -3"
(hk-deep-force (hk-run "main = truncate (negate 3.7)"))
-3)
(hk-test "recip 4.0 = 0.25" (hk-deep-force (hk-run "main = recip 4.0")) 0.25)
(hk-test "1.0 / 4.0 = 0.25" (hk-deep-force (hk-run "main = 1.0 / 4.0")) 0.25)
(hk-test
"fromRational 0.5 = 0.5 (identity)"
(hk-deep-force (hk-run "main = fromRational 0.5"))
0.5)
(hk-test "pi ≈ 3.14159" (hk-deep-force (hk-run "main = pi")) 3.14159)
(hk-test "exp 0 = 1" (hk-deep-force (hk-run "main = exp 0")) 1)
(hk-test "sin 0 = 0" (hk-deep-force (hk-run "main = sin 0")) 0)
(hk-test "cos 0 = 1" (hk-deep-force (hk-run "main = cos 0")) 1)
(hk-test "2 ** 10 = 1024" (hk-deep-force (hk-run "main = 2 ** 10")) 1024)
(hk-test "log (exp 5) ≈ 5" (hk-deep-force (hk-run "main = log (exp 5)")) 5)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,81 +0,0 @@
;; accumulate.hs — accumulate results into an IORef [Int] (Phase 15 conformance).
(define
hk-accumulate-source
"import qualified Data.IORef as IORef\n\npush :: IORef [Int] -> Int -> IO ()\npush r x = IORef.modifyIORef r (\\xs -> x : xs)\n\npushAll :: IORef [Int] -> [Int] -> IO ()\npushAll r [] = return ()\npushAll r (x:xs) = do\n push r x\n pushAll r xs\n\nreadReversed :: IORef [Int] -> IO [Int]\nreadReversed r = do\n xs <- IORef.readIORef r\n return (reverse xs)\n\ndoubleEach :: IORef [Int] -> [Int] -> IO ()\ndoubleEach r [] = return ()\ndoubleEach r (x:xs) = do\n push r (x * 2)\n doubleEach r xs\n\nsumIntoRef :: IORef Int -> [Int] -> IO ()\nsumIntoRef r [] = return ()\nsumIntoRef r (x:xs) = do\n IORef.modifyIORef r (\\acc -> acc + x)\n sumIntoRef r xs\n\n")
(hk-test
"accumulate.hs — push three then read length"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef []; push r 1; push r 2; push r 3; xs <- IORef.readIORef r; return (length xs) }")))
(list "IO" 3))
(hk-test
"accumulate.hs — pushAll preserves reverse order"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef []; pushAll r [1,2,3,4]; xs <- IORef.readIORef r; return xs }")))
(list
"IO"
(list ":" 4 (list ":" 3 (list ":" 2 (list ":" 1 (list "[]")))))))
(hk-test
"accumulate.hs — readReversed gives original order"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef []; pushAll r [10,20,30]; readReversed r }")))
(list "IO" (list ":" 10 (list ":" 20 (list ":" 30 (list "[]"))))))
(hk-test
"accumulate.hs — doubleEach maps then accumulates"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef []; doubleEach r [1,2,3]; readReversed r }")))
(list "IO" (list ":" 2 (list ":" 4 (list ":" 6 (list "[]"))))))
(hk-test
"accumulate.hs — sum into Int IORef"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef 0; sumIntoRef r [1,2,3,4,5]; v <- IORef.readIORef r; return v }")))
(list "IO" 15))
(hk-test
"accumulate.hs — empty list leaves ref untouched"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef [99]; pushAll r []; xs <- IORef.readIORef r; return xs }")))
(list "IO" (list ":" 99 (list "[]"))))
(hk-test
"accumulate.hs — pushAll then sumIntoRef on the same input"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"main = do { r <- IORef.newIORef 0; sumIntoRef r [10,20,30,40]; v <- IORef.readIORef r; return v }")))
(list "IO" 100))
(hk-test
"accumulate.hs — accumulate results from a recursive helper"
(hk-deep-force
(hk-run
(str
hk-accumulate-source
"squaresUpTo r 0 = return ()\nsquaresUpTo r n = do { push r (n * n); squaresUpTo r (n - 1) }\nmain = do { r <- IORef.newIORef []; squaresUpTo r 4; readReversed r }")))
(list
"IO"
(list ":" 16 (list ":" 9 (list ":" 4 (list ":" 1 (list "[]")))))))

View File

@@ -1,80 +0,0 @@
;; caesar.hs — Caesar cipher.
;; Source: https://rosettacode.org/wiki/Caesar_cipher#Haskell (adapted).
;;
;; Exercises chr, ord, isUpper, isLower, mod, string pattern matching
;; (x:xs) over a String (which is now a [Char] string view), and map
;; from the Phase 7 string=[Char] foundation.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-as-list
(fn
(xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-caesar-source
"shift n c = if isUpper c\n then chr (mod ((ord c) - 65 + n) 26 + 65)\n else if isLower c\n then chr (mod ((ord c) - 97 + n) 26 + 97)\n else chr c\n\ncaesarRec n [] = []\ncaesarRec n (x:xs) = shift n x : caesarRec n xs\n\ncaesarMap n s = map (shift n) s\n")
(hk-test
"caesar.hs — caesarRec 3 \"ABC\" = DEF"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarRec 3 \"ABC\"\n") "r"))
(list "D" "E" "F"))
(hk-test
"caesar.hs — caesarRec 13 \"Hello\" = Uryyb"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarRec 13 \"Hello\"\n") "r"))
(list "U" "r" "y" "y" "b"))
(hk-test
"caesar.hs — caesarRec 1 \"AZ\" wraps to BA"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarRec 1 \"AZ\"\n") "r"))
(list "B" "A"))
(hk-test
"caesar.hs — caesarRec 0 \"World\" identity"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarRec 0 \"World\"\n") "r"))
(list "W" "o" "r" "l" "d"))
(hk-test
"caesar.hs — caesarRec preserves punctuation"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarRec 3 \"Hi!\"\n") "r"))
(list "K" "l" "!"))
(hk-test
"caesar.hs — caesarMap 3 \"abc\" via map"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarMap 3 \"abc\"\n") "r"))
(list "d" "e" "f"))
(hk-test
"caesar.hs — caesarMap 13 round-trips with caesarMap 13"
(hk-as-list
(hk-prog-val
(str
hk-caesar-source
"r = caesarMap 13 (foldr (\\c acc -> c : acc) [] (caesarMap 13 \"Hello\"))\n")
"r"))
(list "H" "e" "l" "l" "o"))
(hk-test
"caesar.hs — caesarRec 25 \"AB\" = ZA"
(hk-as-list
(hk-prog-val (str hk-caesar-source "r = caesarRec 25 \"AB\"\n") "r"))
(list "Z" "A"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,63 +0,0 @@
;; config.hs — multi-field config record; partial update; defaultConfig
;; constant.
;;
;; Exercises Phase 14: 4-field record, defaultConfig as a CAF, partial
;; updates that change one or two fields, accessors over derived configs.
(define
hk-config-source
"data Config = Config { host :: String, port :: Int, retries :: Int, debug :: Bool } deriving (Show)\n\ndefaultConfig = Config { host = \"localhost\", port = 8080, retries = 3, debug = False }\n\ndevConfig = defaultConfig { debug = True }\nremoteConfig = defaultConfig { host = \"api.example.com\", port = 443 }\n")
(hk-test
"config.hs — defaultConfig host"
(hk-deep-force (hk-run (str hk-config-source "main = host defaultConfig")))
"localhost")
(hk-test
"config.hs — defaultConfig port"
(hk-deep-force (hk-run (str hk-config-source "main = port defaultConfig")))
8080)
(hk-test
"config.hs — defaultConfig retries"
(hk-deep-force
(hk-run (str hk-config-source "main = retries defaultConfig")))
3)
(hk-test
"config.hs — devConfig flips debug"
(hk-deep-force (hk-run (str hk-config-source "main = debug devConfig")))
(list "True"))
(hk-test
"config.hs — devConfig preserves host"
(hk-deep-force (hk-run (str hk-config-source "main = host devConfig")))
"localhost")
(hk-test
"config.hs — devConfig preserves port"
(hk-deep-force (hk-run (str hk-config-source "main = port devConfig")))
8080)
(hk-test
"config.hs — remoteConfig new host"
(hk-deep-force (hk-run (str hk-config-source "main = host remoteConfig")))
"api.example.com")
(hk-test
"config.hs — remoteConfig new port"
(hk-deep-force (hk-run (str hk-config-source "main = port remoteConfig")))
443)
(hk-test
"config.hs — remoteConfig preserves retries"
(hk-deep-force
(hk-run (str hk-config-source "main = retries remoteConfig")))
3)
(hk-test
"config.hs — remoteConfig preserves debug"
(hk-deep-force (hk-run (str hk-config-source "main = debug remoteConfig")))
(list "False"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,66 +0,0 @@
;; counter.hs — IORef-backed mutable counter (Phase 15 conformance).
(define
hk-counter-source
"import qualified Data.IORef as IORef\n\ncount :: IORef Int -> Int -> IO ()\ncount r 0 = return ()\ncount r n = do\n IORef.modifyIORef r (\\x -> x + 1)\n count r (n - 1)\n\ncountBy :: IORef Int -> Int -> Int -> IO ()\ncountBy r step 0 = return ()\ncountBy r step n = do\n IORef.modifyIORef r (\\x -> x + step)\n countBy r step (n - 1)\n\nnewCounter :: Int -> IO (IORef Int)\nnewCounter v = IORef.newIORef v\n\nbumpAndRead :: IORef Int -> IO Int\nbumpAndRead r = do\n IORef.modifyIORef r (\\x -> x + 1)\n IORef.readIORef r\n\n")
(hk-test
"counter.hs — start at 0, count 5 ⇒ 5"
(hk-deep-force
(hk-run
(str
hk-counter-source
"main = do { r <- newCounter 0; count r 5; v <- IORef.readIORef r; return v }")))
(list "IO" 5))
(hk-test
"counter.hs — start at 100, count 10 ⇒ 110"
(hk-deep-force
(hk-run
(str
hk-counter-source
"main = do { r <- newCounter 100; count r 10; v <- IORef.readIORef r; return v }")))
(list "IO" 110))
(hk-test
"counter.hs — countBy step 5, n 4 ⇒ 20"
(hk-deep-force
(hk-run
(str
hk-counter-source
"main = do { r <- newCounter 0; countBy r 5 4; v <- IORef.readIORef r; return v }")))
(list "IO" 20))
(hk-test
"counter.hs — bumpAndRead returns updated value"
(hk-deep-force
(hk-run
(str hk-counter-source "main = do { r <- newCounter 41; bumpAndRead r }")))
(list "IO" 42))
(hk-test
"counter.hs — count then countBy compose"
(hk-deep-force
(hk-run
(str
hk-counter-source
"main = do { r <- newCounter 0; count r 3; countBy r 10 2; v <- IORef.readIORef r; return v }")))
(list "IO" 23))
(hk-test
"counter.hs — two independent counters"
(hk-deep-force
(hk-run
(str
hk-counter-source
"main = do { a <- newCounter 0; b <- newCounter 0; count a 7; countBy b 100 2; va <- IORef.readIORef a; vb <- IORef.readIORef b; return (va + vb) }")))
(list "IO" 207))
(hk-test
"counter.hs — modifyIORef' (strict) variant"
(hk-deep-force
(hk-run
(str
hk-counter-source
"tick r 0 = return ()\ntick r n = do { IORef.modifyIORef' r (\\x -> x + 1); tick r (n - 1) }\nmain = do { r <- newCounter 0; tick r 50; v <- IORef.readIORef r; return v }")))
(list "IO" 50))

View File

@@ -1,46 +0,0 @@
;; mapgraph.hs — adjacency-list using Data.Map (BFS-style traversal).
;;
;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`,
;; `Map.insert`, `Map.lookup`, `Map.findWithDefault`. Adjacency lists are
;; stored as `Map Int [Int]`; `neighbors` does a default-empty lookup.
(define
hk-mapgraph-source
"import qualified Data.Map as Map\n\nemptyG = Map.empty\n\naddEdge u v g = Map.insertWith add u [v] g\n where add new old = new ++ old\n\nbuild = addEdge 1 2 (addEdge 1 3 (addEdge 2 4 (addEdge 3 4 (addEdge 4 5 emptyG))))\n\nneighbors n g = Map.findWithDefault [] n g\n")
(hk-test
"mapgraph.hs — neighbors of 1"
(hk-deep-force
(hk-run (str hk-mapgraph-source "main = neighbors 1 build\n")))
(list ":" 2 (list ":" 3 (list "[]"))))
(hk-test
"mapgraph.hs — neighbors of 4"
(hk-deep-force
(hk-run (str hk-mapgraph-source "main = neighbors 4 build\n")))
(list ":" 5 (list "[]")))
(hk-test
"mapgraph.hs — neighbors of 5 (leaf, no entry) defaults to []"
(hk-deep-force
(hk-run (str hk-mapgraph-source "main = neighbors 5 build\n")))
(list "[]"))
(hk-test
"mapgraph.hs — neighbors of 99 (absent) defaults to []"
(hk-deep-force
(hk-run (str hk-mapgraph-source "main = neighbors 99 build\n")))
(list "[]"))
(hk-test
"mapgraph.hs — Map.member 1"
(hk-deep-force
(hk-run (str hk-mapgraph-source "main = Map.member 1 build\n")))
(list "True"))
(hk-test
"mapgraph.hs — Map.size = 4 source nodes"
(hk-deep-force (hk-run (str hk-mapgraph-source "main = Map.size build\n")))
4)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,49 +0,0 @@
;; newton.hs — Newton's method for square root.
;; Source: classic numerical analysis exercise.
;;
;; Exercises Phase 10: `Float`, `abs`, `/`, iteration via `until`.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-newton-source
"improve x guess = (guess + x / guess) / 2\n\ngoodEnough x guess = abs (guess * guess - x) < 0.0001\n\nnewtonSqrt x = newtonHelp x 1.0\n\nnewtonHelp x guess = if goodEnough x guess\n then guess\n else newtonHelp x (improve x guess)\n")
(hk-test
"newton.hs — newtonSqrt 4 ≈ 2"
(hk-prog-val
(str hk-newton-source "r = abs (newtonSqrt 4.0 - 2.0) < 0.001\n")
"r")
(list "True"))
(hk-test
"newton.hs — newtonSqrt 9 ≈ 3"
(hk-prog-val
(str hk-newton-source "r = abs (newtonSqrt 9.0 - 3.0) < 0.001\n")
"r")
(list "True"))
(hk-test
"newton.hs — newtonSqrt 2 ≈ 1.41421"
(hk-prog-val
(str hk-newton-source "r = abs (newtonSqrt 2.0 - 1.41421) < 0.001\n")
"r")
(list "True"))
(hk-test
"newton.hs — improve converges (one step)"
(hk-prog-val (str hk-newton-source "r = improve 4.0 1.0\n") "r")
2.5)
(hk-test
"newton.hs — newtonSqrt 100 ≈ 10"
(hk-prog-val
(str hk-newton-source "r = abs (newtonSqrt 100.0 - 10.0) < 0.001\n")
"r")
(list "True"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,58 +0,0 @@
;; partial.hs — exercises Phase 9 partial functions caught at the top level.
;;
;; Each program calls a partial function on bad input; hk-run-io catches the
;; raise and appends the error message to io-lines so tests can inspect.
(hk-test
"partial.hs — main = print (head [])"
(let
((lines (hk-run-io "main = print (head [])")))
(>= (index-of (str lines) "Prelude.head: empty list") 0))
true)
(hk-test
"partial.hs — main = print (tail [])"
(let
((lines (hk-run-io "main = print (tail [])")))
(>= (index-of (str lines) "Prelude.tail: empty list") 0))
true)
(hk-test
"partial.hs — main = print (fromJust Nothing)"
(let
((lines (hk-run-io "main = print (fromJust Nothing)")))
(>= (index-of (str lines) "Maybe.fromJust: Nothing") 0))
true)
(hk-test
"partial.hs — putStrLn before error preserves prior output"
(let
((lines (hk-run-io "main = do { putStrLn \"step 1\"; putStrLn (show (head [])); putStrLn \"never\" }")))
(and
(>= (index-of (str lines) "step 1") 0)
(>= (index-of (str lines) "Prelude.head: empty list") 0)
(= (index-of (str lines) "never") -1)))
true)
(hk-test
"partial.hs — undefined as IO action"
(let
((lines (hk-run-io "main = print undefined")))
(>= (index-of (str lines) "Prelude.undefined") 0))
true)
(hk-test
"partial.hs — catches error from a user-thrown error"
(let
((lines (hk-run-io "main = error \"boom from main\"")))
(>= (index-of (str lines) "boom from main") 0))
true)
;; Negative case: when no error is raised, io-lines doesn't contain
;; "Prelude" prefixes from our error path.
(hk-test
"partial.hs — happy path: head [42] succeeds, no error in output"
(hk-run-io "main = print (head [42])")
(list "42"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,51 +0,0 @@
;; person.hs — record type with accessors, update, deriving Show.
;;
;; Exercises Phase 14: data with record syntax, accessor functions,
;; record creation, record update, deriving Show on a record.
(define
hk-person-source
"data Person = Person { name :: String, age :: Int } deriving (Show)\n\nalice = Person { name = \"alice\", age = 30 }\nbob = Person { name = \"bob\", age = 25 }\n\nbirthday p = p { age = age p + 1 }\n")
(hk-test
"person.hs — alice's name"
(hk-deep-force (hk-run (str hk-person-source "main = name alice")))
"alice")
(hk-test
"person.hs — alice's age"
(hk-deep-force (hk-run (str hk-person-source "main = age alice")))
30)
(hk-test
"person.hs — birthday adds one year"
(hk-deep-force
(hk-run (str hk-person-source "main = age (birthday alice)")))
31)
(hk-test
"person.hs — birthday preserves name"
(hk-deep-force
(hk-run (str hk-person-source "main = name (birthday alice)")))
"alice")
(hk-test
"person.hs — show alice"
(hk-deep-force (hk-run (str hk-person-source "main = show alice")))
"Person \"alice\" 30")
(hk-test
"person.hs — bob has different name"
(hk-deep-force (hk-run (str hk-person-source "main = name bob")))
"bob")
(hk-test
"person.hs — pattern match in function"
(hk-deep-force
(hk-run
(str
hk-person-source
"greet (Person { name = n }) = \"Hi, \" ++ n\nmain = greet alice")))
"Hi, alice")
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,83 +0,0 @@
;; runlength-str.hs — run-length encoding on a String.
;; Source: https://rosettacode.org/wiki/Run-length_encoding#Haskell (adapted).
;;
;; Exercises String pattern matching `(x:xs)`, `span` over a string view,
;; tuple construction `(Int, Char)`, character equality, and tuple-in-cons
;; patterns `((n, c) : rest)` — all enabled by Phase 7 string=[Char].
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-as-list
(fn
(xs)
(cond
((and (list? xs) (= (first xs) "[]")) (list))
((and (list? xs) (= (first xs) ":"))
(cons (nth xs 1) (hk-as-list (nth xs 2))))
(:else xs))))
(define
hk-rle-source
"encodeRL [] = []\nencodeRL (x:xs) = let (same, rest) = span eqX xs\n eqX y = y == x\n in (1 + length same, x) : encodeRL rest\n\nreplicateRL 0 _ = []\nreplicateRL n c = c : replicateRL (n - 1) c\n\ndecodeRL [] = []\ndecodeRL ((n, c) : rest) = replicateRL n c ++ decodeRL rest\n")
(hk-test
"rle.hs — encodeRL [] = []"
(hk-as-list (hk-prog-val (str hk-rle-source "r = encodeRL \"\"\n") "r"))
(list))
(hk-test
"rle.hs — length (encodeRL \"aabbbcc\") = 3"
(hk-prog-val (str hk-rle-source "r = length (encodeRL \"aabbbcc\")\n") "r")
3)
(hk-test
"rle.hs — map fst (encodeRL \"aabbbcc\") = [2,3,2]"
(hk-as-list
(hk-prog-val (str hk-rle-source "r = map fst (encodeRL \"aabbbcc\")\n") "r"))
(list 2 3 2))
(hk-test
"rle.hs — map snd (encodeRL \"aabbbcc\") = [97,98,99]"
(hk-as-list
(hk-prog-val (str hk-rle-source "r = map snd (encodeRL \"aabbbcc\")\n") "r"))
(list 97 98 99))
(hk-test
"rle.hs — counts of encodeRL \"aabbbccddddee\" = [2,3,2,4,2]"
(hk-as-list
(hk-prog-val
(str hk-rle-source "r = map fst (encodeRL \"aabbbccddddee\")\n")
"r"))
(list 2 3 2 4 2))
(hk-test
"rle.hs — chars of encodeRL \"aabbbccddddee\" = [97,98,99,100,101]"
(hk-as-list
(hk-prog-val
(str hk-rle-source "r = map snd (encodeRL \"aabbbccddddee\")\n")
"r"))
(list 97 98 99 100 101))
(hk-test
"rle.hs — singleton encodeRL \"x\""
(hk-as-list
(hk-prog-val (str hk-rle-source "r = map fst (encodeRL \"x\")\n") "r"))
(list 1))
(hk-test
"rle.hs — decodeRL round-trip preserves \"aabbbcc\""
(hk-as-list
(hk-prog-val (str hk-rle-source "r = decodeRL (encodeRL \"aabbbcc\")\n") "r"))
(list 97 97 98 98 98 99 99))
(hk-test
"rle.hs — replicateRL 4 65 = [65,65,65,65]"
(hk-as-list (hk-prog-val (str hk-rle-source "r = replicateRL 4 65\n") "r"))
(list 65 65 65 65))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,80 +0,0 @@
;; safediv.hs — safe division using catch (Phase 16 conformance).
(define
hk-safediv-source
"safeDiv :: Int -> Int -> IO Int
safeDiv _ 0 = throwIO (SomeException \"division by zero\")
safeDiv x y = return (x `div` y)
guarded :: Int -> Int -> IO Int
guarded x y = catch (safeDiv x y) (\\(SomeException _) -> return 0)
reason :: Int -> Int -> IO String
reason x y = catch (safeDiv x y `seq` return \"ok\")
(\\(SomeException m) -> return m)
bothBranches :: Int -> Int -> IO Int
bothBranches x y = do
v <- catch (safeDiv x y) (\\(SomeException _) -> return (-1))
return (v + 100)
")
(hk-test
"safediv.hs — divide by non-zero"
(hk-deep-force
(hk-run
(str hk-safediv-source "main = guarded 10 2")))
(list "IO" 5))
(hk-test
"safediv.hs — divide by zero returns 0"
(hk-deep-force
(hk-run
(str hk-safediv-source "main = guarded 10 0")))
(list "IO" 0))
(hk-test
"safediv.hs — divide by zero — reason captured"
(hk-deep-force
(hk-run
(str hk-safediv-source "main = catch (safeDiv 1 0) (\\(SomeException m) -> return 0) >> reason 1 0")))
(list "IO" "division by zero"))
(hk-test
"safediv.hs — bothBranches success path"
(hk-deep-force
(hk-run
(str hk-safediv-source "main = bothBranches 8 2")))
(list "IO" 104))
(hk-test
"safediv.hs — bothBranches failure path"
(hk-deep-force
(hk-run
(str hk-safediv-source "main = bothBranches 8 0")))
(list "IO" 99))
(hk-test
"safediv.hs — chained safeDiv with catch"
(hk-deep-force
(hk-run
(str hk-safediv-source
"main = do { a <- guarded 20 4; b <- guarded 7 0; return (a + b) }")))
(list "IO" 5))
(hk-test
"safediv.hs — try then bind through Either"
(hk-deep-force
(hk-run
(str hk-safediv-source
"main = do { r <- try (safeDiv 1 0); case r of { Right v -> return v; Left (SomeException m) -> return 999 } }")))
(list "IO" 999))
(hk-test
"safediv.hs — handle (flip catch)"
(hk-deep-force
(hk-run
(str hk-safediv-source
"main = handle (\\(SomeException _) -> return 0) (safeDiv 5 0)")))
(list "IO" 0))

View File

@@ -1,61 +0,0 @@
;; setops.hs — set union/intersection/difference on integer sets.
;;
;; Exercises Phase 12: `import qualified Data.Set as Set`, all three
;; combining operations + isSubsetOf.
(define
hk-setops-source
"import qualified Data.Set as Set\n\ns1 = Set.insert 1 (Set.insert 2 (Set.insert 3 Set.empty))\ns2 = Set.insert 3 (Set.insert 4 (Set.insert 5 Set.empty))\ns3 = Set.insert 1 (Set.insert 2 Set.empty)\n")
(hk-test
"setops.hs — union size = 5"
(hk-deep-force
(hk-run (str hk-setops-source "main = Set.size (Set.union s1 s2)\n")))
5)
(hk-test
"setops.hs — intersection size = 1"
(hk-deep-force
(hk-run
(str hk-setops-source "main = Set.size (Set.intersection s1 s2)\n")))
1)
(hk-test
"setops.hs — intersection contains 3"
(hk-deep-force
(hk-run
(str hk-setops-source "main = Set.member 3 (Set.intersection s1 s2)\n")))
(list "True"))
(hk-test
"setops.hs — difference s1 s2 size = 2"
(hk-deep-force
(hk-run (str hk-setops-source "main = Set.size (Set.difference s1 s2)\n")))
2)
(hk-test
"setops.hs — difference doesn't contain shared key"
(hk-deep-force
(hk-run
(str hk-setops-source "main = Set.member 3 (Set.difference s1 s2)\n")))
(list "False"))
(hk-test
"setops.hs — s3 is subset of s1"
(hk-deep-force
(hk-run (str hk-setops-source "main = Set.isSubsetOf s3 s1\n")))
(list "True"))
(hk-test
"setops.hs — s1 not subset of s3"
(hk-deep-force
(hk-run (str hk-setops-source "main = Set.isSubsetOf s1 s3\n")))
(list "False"))
(hk-test
"setops.hs — empty set is subset of anything"
(hk-deep-force
(hk-run (str hk-setops-source "main = Set.isSubsetOf Set.empty s1\n")))
(list "True"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,40 +0,0 @@
;; shapes.hs — class Area with a default perimeter, two instances
;; using where-local helpers.
;;
;; Exercises Phase 13: class default method (perimeter), instance
;; methods that use `where`-bindings.
(define
hk-shapes-source
"class Shape a where\n area :: a -> Int\n perimeter :: a -> Int\n perimeter x = quadrilateral x\n where quadrilateral y = 2 * (sideA y + sideB y)\n sideA z = 1\n sideB z = 1\n\ndata Square = Square Int\ndata Rect = Rect Int Int\n\ninstance Shape Square where\n area (Square s) = s * s\n perimeter (Square s) = 4 * s\n\ninstance Shape Rect where\n area (Rect w h) = w * h\n perimeter (Rect w h) = peri\n where peri = 2 * (w + h)\n")
(hk-test
"shapes.hs — area of Square 5 = 25"
(hk-deep-force (hk-run (str hk-shapes-source "main = area (Square 5)\n")))
25)
(hk-test
"shapes.hs — perimeter of Square 5 = 20"
(hk-deep-force
(hk-run (str hk-shapes-source "main = perimeter (Square 5)\n")))
20)
(hk-test
"shapes.hs — area of Rect 3 4 = 12"
(hk-deep-force (hk-run (str hk-shapes-source "main = area (Rect 3 4)\n")))
12)
(hk-test
"shapes.hs — perimeter of Rect 3 4 = 14 (via where-bound)"
(hk-deep-force
(hk-run (str hk-shapes-source "main = perimeter (Rect 3 4)\n")))
14)
(hk-test
"shapes.hs — Square sums area + perimeter"
(hk-deep-force
(hk-run
(str hk-shapes-source "main = area (Square 4) + perimeter (Square 4)\n")))
32)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,45 +0,0 @@
;; showadt.hs — `deriving (Show)` on a multi-constructor recursive ADT.
;; Source: classic exposition example, e.g. Real World Haskell ch.6.
;;
;; Exercises Phase 8: `deriving (Show)` on an ADT whose constructors recurse
;; into themselves; precedence-based paren wrapping for nested arguments;
;; `print` from the prelude (which is `putStrLn (show x)`).
(define
hk-showadt-source
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\n\nmain = do\n print (Lit 3)\n print (Add (Lit 1) (Lit 2))\n print (Mul (Lit 3) (Add (Lit 4) (Lit 5)))\n")
(hk-test
"showadt.hs — main prints three lines"
(hk-run-io hk-showadt-source)
(list "Lit 3" "Add (Lit 1) (Lit 2)" "Mul (Lit 3) (Add (Lit 4) (Lit 5))"))
(hk-test
"showadt.hs — show Lit 3"
(hk-deep-force
(hk-run
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Lit 3)"))
"Lit 3")
(hk-test
"showadt.hs — show Add wraps both args"
(hk-deep-force
(hk-run
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Add (Lit 1) (Lit 2))"))
"Add (Lit 1) (Lit 2)")
(hk-test
"showadt.hs — fully nested Mul of Adds"
(hk-deep-force
(hk-run
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Mul (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4)))"))
"Mul (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4))")
(hk-test
"showadt.hs — Lit with negative literal wraps int in parens"
(hk-deep-force
(hk-run
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Lit (negate 7))"))
"Lit (-7)")
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,36 +0,0 @@
;; showio.hs — `print` on various types inside a `do` block.
;;
;; Exercises Phase 8 `print x = putStrLn (show x)` and the IO monad's
;; statement sequencing. Each `print` produces one io-line.
(define
hk-showio-source
"main = do\n print 42\n print True\n print False\n print [1,2,3]\n print (1, 2)\n print (Just 5)\n print Nothing\n print \"hello\"\n")
(hk-test
"showio.hs — main produces 8 lines, all show-formatted"
(hk-run-io hk-showio-source)
(list "42" "True" "False" "[1,2,3]" "(1,2)" "Just 5" "Nothing" "\"hello\""))
(hk-test
"showio.hs — print Int alone"
(hk-run-io "main = print 42")
(list "42"))
(hk-test
"showio.hs — print list of Maybe"
(hk-run-io "main = print [Just 1, Nothing, Just 3]")
(list "[Just 1,Nothing,Just 3]"))
(hk-test
"showio.hs — print nested tuple"
(hk-run-io "main = print ((1, 2), (3, 4))")
(list "((1,2),(3,4))"))
(hk-test
"showio.hs — print derived ADT inside do"
(hk-run-io
"data Color = Red | Green | Blue deriving (Show)\nmain = do { print Red; print Green; print Blue }")
(list "Red" "Green" "Blue"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,45 +0,0 @@
;; statistics.hs — mean, variance, std-dev on a [Double].
;; Source: classic textbook example.
;;
;; Exercises Phase 10: `fromIntegral`, `/`, `sqrt`, list ops on `[Double]`.
(define
hk-prog-val
(fn
(src name)
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
(define
hk-stats-source
"mean xs = sum xs / fromIntegral (length xs)\n\nvariance xs = let m = mean xs\n sqDiff x = (x - m) * (x - m)\n in sum (map sqDiff xs) / fromIntegral (length xs)\n\nstdDev xs = sqrt (variance xs)\n")
(hk-test
"statistics.hs — mean [1,2,3,4,5] = 3"
(hk-prog-val (str hk-stats-source "r = mean [1.0,2.0,3.0,4.0,5.0]\n") "r")
3)
(hk-test
"statistics.hs — mean [10,20,30] = 20"
(hk-prog-val (str hk-stats-source "r = mean [10.0,20.0,30.0]\n") "r")
20)
(hk-test
"statistics.hs — variance [2,4,4,4,5,5,7,9] = 4"
(hk-prog-val
(str hk-stats-source "r = variance [2.0,4.0,4.0,4.0,5.0,5.0,7.0,9.0]\n")
"r")
4)
(hk-test
"statistics.hs — stdDev [2,4,4,4,5,5,7,9] = 2"
(hk-prog-val
(str hk-stats-source "r = stdDev [2.0,4.0,4.0,4.0,5.0,5.0,7.0,9.0]\n")
"r")
2)
(hk-test
"statistics.hs — variance of constant list = 0"
(hk-prog-val (str hk-stats-source "r = variance [5.0,5.0,5.0,5.0]\n") "r")
0)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,95 +0,0 @@
;; trycatch.hs — try pattern: branch on Left/Right (Phase 16 conformance).
(define
hk-trycatch-source
"parseInt :: String -> IO Int
parseInt \"zero\" = return 0
parseInt \"one\" = return 1
parseInt \"two\" = return 2
parseInt s = throwIO (SomeException (\"unknown: \" ++ s))
describe :: Either SomeException Int -> String
describe (Right v) = \"got \" ++ show v
describe (Left (SomeException m)) = \"err: \" ++ m
trial :: String -> IO String
trial s = do
r <- try (parseInt s)
return (describe r)
run3 :: String -> String -> String -> IO [String]
run3 a b c = do
ra <- trial a
rb <- trial b
rc <- trial c
return [ra, rb, rc]
")
(hk-test
"trycatch.hs — Right branch"
(hk-deep-force
(hk-run
(str hk-trycatch-source "main = trial \"one\"")))
(list "IO" "got 1"))
(hk-test
"trycatch.hs — Left branch with message"
(hk-deep-force
(hk-run
(str hk-trycatch-source "main = trial \"banana\"")))
(list "IO" "err: unknown: banana"))
(hk-test
"trycatch.hs — chain over three inputs, all good"
(hk-deep-force
(hk-run
(str hk-trycatch-source "main = run3 \"zero\" \"one\" \"two\"")))
(list "IO"
(list ":" "got 0"
(list ":" "got 1"
(list ":" "got 2"
(list "[]"))))))
(hk-test
"trycatch.hs — chain over three inputs, mixed"
(hk-deep-force
(hk-run
(str hk-trycatch-source "main = run3 \"zero\" \"qux\" \"two\"")))
(list "IO"
(list ":" "got 0"
(list ":" "err: unknown: qux"
(list ":" "got 2"
(list "[]"))))))
(hk-test
"trycatch.hs — Left from throwIO carries message"
(hk-deep-force
(hk-run
(str hk-trycatch-source
"main = do { r <- try (throwIO (SomeException \"explicit\")); return (describe r) }")))
(list "IO" "err: explicit"))
(hk-test
"trycatch.hs — Right preserves the int"
(hk-deep-force
(hk-run
(str hk-trycatch-source
"main = do { r <- try (return 42); return (describe r) }")))
(list "IO" "got 42"))
(hk-test
"trycatch.hs — pattern-bind on Right inside do"
(hk-deep-force
(hk-run
(str hk-trycatch-source
"main = do { Right v <- try (parseInt \"two\"); return (v + 100) }")))
(list "IO" 102))
(hk-test
"trycatch.hs — handle alias on parseInt failure"
(hk-deep-force
(hk-run
(str hk-trycatch-source
"main = handle (\\(SomeException m) -> return (\"caught: \" ++ m)) (parseInt \"nope\" >>= (\\v -> return (show v)))")))
(list "IO" "caught: unknown: nope"))

View File

@@ -1,35 +0,0 @@
;; uniquewords.hs — count unique words using Data.Set.
;;
;; Exercises Phase 12: `import qualified Data.Set as Set`, `Set.empty`,
;; `Set.insert`, `Set.size`, `foldl`.
(define
hk-uniquewords-source
"import qualified Data.Set as Set\n\naddWord s w = Set.insert w s\n\nuniqueWords ws = foldl addWord Set.empty ws\n\nresult = uniqueWords [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n")
(hk-test
"uniquewords.hs — unique count = 3"
(hk-deep-force
(hk-run (str hk-uniquewords-source "main = Set.size result\n")))
3)
(hk-test
"uniquewords.hs — \"the\" present"
(hk-deep-force
(hk-run (str hk-uniquewords-source "main = Set.member \"the\" result\n")))
(list "True"))
(hk-test
"uniquewords.hs — \"missing\" absent"
(hk-deep-force
(hk-run (str hk-uniquewords-source "main = Set.member \"missing\" result\n")))
(list "False"))
(hk-test
"uniquewords.hs — empty list yields empty set"
(hk-deep-force
(hk-run
"import qualified Data.Set as Set\nmain = Set.size (foldl (\\s w -> Set.insert w s) Set.empty [])"))
0)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,54 +0,0 @@
;; wordfreq.hs — word-frequency histogram using Data.Map.
;; Source: Rosetta Code "Word frequency" (Haskell entry, simplified).
;;
;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`,
;; `Map.insertWith`, `Map.lookup`, `Map.findWithDefault`, `foldl`.
(define
hk-wordfreq-source
"import qualified Data.Map as Map\n\ncountWord m w = Map.insertWith (+) w 1 m\n\nwordFreq xs = foldl countWord Map.empty xs\n\nresult = wordFreq [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n")
(hk-test
"wordfreq.hs — \"the\" counted 3 times"
(hk-deep-force
(hk-run (str hk-wordfreq-source "main = Map.lookup \"the\" result\n")))
(list "Just" 3))
(hk-test
"wordfreq.hs — \"cat\" counted 2 times"
(hk-deep-force
(hk-run (str hk-wordfreq-source "main = Map.lookup \"cat\" result\n")))
(list "Just" 2))
(hk-test
"wordfreq.hs — \"dog\" counted 1 time"
(hk-deep-force
(hk-run (str hk-wordfreq-source "main = Map.lookup \"dog\" result\n")))
(list "Just" 1))
(hk-test
"wordfreq.hs — \"missing\" not present"
(hk-deep-force
(hk-run (str hk-wordfreq-source "main = Map.lookup \"missing\" result\n")))
(list "Nothing"))
(hk-test
"wordfreq.hs — Map.size = 3 unique words"
(hk-deep-force (hk-run (str hk-wordfreq-source "main = Map.size result\n")))
3)
(hk-test
"wordfreq.hs — findWithDefault for missing returns 0"
(hk-deep-force
(hk-run
(str hk-wordfreq-source "main = Map.findWithDefault 0 \"absent\" result\n")))
0)
(hk-test
"wordfreq.hs — findWithDefault for present returns count"
(hk-deep-force
(hk-run
(str hk-wordfreq-source "main = Map.findWithDefault 0 \"the\" result\n")))
3)
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,127 +0,0 @@
;; records.sx — Phase 14 record syntax tests.
(define
hk-person-source
"data Person = Person { name :: String, age :: Int }\n")
(define hk-pt-source "data Pt = Pt { x :: Int, y :: Int }\n")
;; ── Creation ────────────────────────────────────────────────
(hk-test
"creation: Person { name = \"a\", age = 1 } via accessor name"
(hk-deep-force
(hk-run
(str
hk-person-source
"main = name (Person { name = \"alice\", age = 30 })")))
"alice")
(hk-test
"creation: source order doesn't matter (age first)"
(hk-deep-force
(hk-run
(str hk-person-source "main = name (Person { age = 99, name = \"bob\" })")))
"bob")
(hk-test
"creation: age accessor returns the right field"
(hk-deep-force
(hk-run
(str hk-person-source "main = age (Person { age = 99, name = \"bob\" })")))
99)
;; ── Accessors ──────────────────────────────────────────────
(hk-test
"accessor: x of Pt"
(hk-deep-force
(hk-run (str hk-pt-source "main = x (Pt { x = 7, y = 99 })")))
7)
(hk-test
"accessor: y of Pt"
(hk-deep-force
(hk-run (str hk-pt-source "main = y (Pt { x = 7, y = 99 })")))
99)
;; ── Update — single field ──────────────────────────────────
(hk-test
"update one field: age changes"
(hk-deep-force
(hk-run
(str
hk-person-source
"alice = Person { name = \"alice\", age = 30 }\nmain = age (alice { age = 31 })")))
31)
(hk-test
"update one field: name preserved"
(hk-deep-force
(hk-run
(str
hk-person-source
"alice = Person { name = \"alice\", age = 30 }\nmain = name (alice { age = 31 })")))
"alice")
;; ── Update — two fields ────────────────────────────────────
(hk-test
"update two fields: both changed"
(hk-deep-force
(hk-run
(str
hk-person-source
"alice = Person { name = \"alice\", age = 30 }\nbob = alice { name = \"bob\", age = 50 }\nmain = age bob")))
50)
(hk-test
"update two fields: name takes new value"
(hk-deep-force
(hk-run
(str
hk-person-source
"alice = Person { name = \"alice\", age = 30 }\nbob = alice { name = \"bob\", age = 50 }\nmain = name bob")))
"bob")
;; ── Record patterns ────────────────────────────────────────
(hk-test
"case-alt record pattern: Pt { x = a }"
(hk-deep-force
(hk-run
(str
hk-pt-source
"getX p = case p of Pt { x = a } -> a\nmain = getX (Pt { x = 7, y = 99 })")))
7)
(hk-test
"case-alt record pattern: multi-field bind"
(hk-deep-force
(hk-run
(str
hk-pt-source
"sumPt p = case p of Pt { x = a, y = b } -> a + b\nmain = sumPt (Pt { x = 3, y = 4 })")))
7)
(hk-test
"fun-LHS record pattern"
(hk-deep-force
(hk-run
(str
hk-person-source
"getName (Person { name = n }) = n\nmain = getName (Person { name = \"alice\", age = 30 })")))
"alice")
;; ── deriving Show on a record ───────────────────────────────
(hk-test
"deriving Show on a record produces positional output"
(hk-deep-force
(hk-run
"data Person = Person { name :: String, age :: Int } deriving (Show)\nmain = show (Person { name = \"alice\", age = 30 })"))
"Person \"alice\" 30")
(hk-test
"deriving Show on Pt"
(hk-deep-force
(hk-run
"data Pt = Pt { x :: Int, y :: Int } deriving (Show)\nmain = show (Pt { x = 3, y = 4 })"))
"Pt 3 4")
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,119 +0,0 @@
;; set.sx — Phase 12 Data.Set unit tests.
;; ── SX-level (direct hk-set-*) ───────────────────────────────
(hk-test
"hk-set-empty: size 0 + null"
(list (hk-set-size hk-set-empty) (hk-set-null hk-set-empty))
(list 0 true))
(hk-test
"hk-set-singleton: member yes"
(let
((s (hk-set-singleton 5)))
(list (hk-set-size s) (hk-set-member 5 s) (hk-set-member 99 s)))
(list 1 true false))
(hk-test
"hk-set-insert: idempotent"
(let
((s (hk-set-insert 1 (hk-set-insert 1 hk-set-empty))))
(hk-set-size s))
1)
(hk-test
"hk-set-from-list: dedupes"
(hk-set-to-asc-list (hk-set-from-list (list 3 1 4 1 5 9 2 6)))
(list 1 2 3 4 5 6 9))
(hk-test
"hk-set-delete: removes"
(let
((s (hk-set-from-list (list 1 2 3))))
(hk-set-to-asc-list (hk-set-delete 2 s)))
(list 1 3))
(hk-test
"hk-set-union"
(hk-set-to-asc-list
(hk-set-union
(hk-set-from-list (list 1 2 3))
(hk-set-from-list (list 3 4 5))))
(list 1 2 3 4 5))
(hk-test
"hk-set-intersection"
(hk-set-to-asc-list
(hk-set-intersection
(hk-set-from-list (list 1 2 3 4))
(hk-set-from-list (list 3 4 5 6))))
(list 3 4))
(hk-test
"hk-set-difference"
(hk-set-to-asc-list
(hk-set-difference
(hk-set-from-list (list 1 2 3 4))
(hk-set-from-list (list 3 4 5))))
(list 1 2))
(hk-test
"hk-set-is-subset-of: yes"
(hk-set-is-subset-of
(hk-set-from-list (list 2 3))
(hk-set-from-list (list 1 2 3 4)))
true)
(hk-test
"hk-set-is-subset-of: no"
(hk-set-is-subset-of
(hk-set-from-list (list 5 6))
(hk-set-from-list (list 1 2 3 4)))
false)
(hk-test
"hk-set-filter"
(hk-set-to-asc-list
(hk-set-filter (fn (k) (> k 2)) (hk-set-from-list (list 1 2 3 4 5))))
(list 3 4 5))
(hk-test
"hk-set-map"
(hk-set-to-asc-list
(hk-set-map (fn (k) (* k 10)) (hk-set-from-list (list 1 2 3))))
(list 10 20 30))
(hk-test
"hk-set-foldr: sum"
(hk-set-foldr + 0 (hk-set-from-list (list 1 2 3 4 5)))
15)
;; ── Haskell-level (Set.* via import wiring) ──────────────────
(hk-test
"Set.size after Set.insert chain"
(hk-deep-force
(hk-run
"import qualified Data.Set as Set\nmain = Set.size (Set.insert 3 (Set.insert 1 (Set.insert 2 Set.empty)))"))
3)
(hk-test
"Set.member true"
(hk-deep-force
(hk-run
"import qualified Data.Set as Set\nmain = Set.member 5 (Set.insert 5 Set.empty)"))
(list "True"))
(hk-test
"Set.union via Haskell"
(hk-deep-force
(hk-run
"import Data.Set\nmain = Set.size (Set.union (Set.insert 1 Set.empty) (Set.insert 2 Set.empty))"))
2)
(hk-test
"Set.isSubsetOf via Haskell"
(hk-deep-force
(hk-run
"import qualified Data.Set as S\nmain = S.isSubsetOf (S.insert 1 S.empty) (S.insert 2 (S.insert 1 S.empty))"))
(list "True"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -1,140 +0,0 @@
;; show.sx — tests for the Show / Read class plumbing.
;;
;; Covers Phase 8:
;; - showsPrec / showParen / shows / showString stubs
;; - Read class stubs (reads / readsPrec / read)
;; - direct show coverage (Int, Bool, String, list, tuple, Maybe, ADT, ...)
;; ── ShowS / showsPrec / showParen stubs ──────────────────────
(hk-test
"shows: prepends show output"
(hk-deep-force (hk-run "main = shows 5 \"abc\""))
"5abc")
(hk-test
"shows: works on True"
(hk-deep-force (hk-run "main = shows True \"x\""))
"Truex")
(hk-test
"showString: prepends literal"
(hk-deep-force (hk-run "main = showString \"hello\" \" world\""))
"hello world")
(hk-test
"showParen True: wraps inner output in parens"
(hk-deep-force (hk-run "main = showParen True (showString \"inside\") \"\""))
"(inside)")
(hk-test
"showParen False: passes through unchanged"
(hk-deep-force (hk-run "main = showParen False (showString \"inside\") \"\""))
"inside")
(hk-test
"showsPrec: prepends show output regardless of prec"
(hk-deep-force (hk-run "main = showsPrec 11 42 \"end\""))
"42end")
(hk-test
"showParen + manual composition: build (Just 3)"
(hk-deep-force
(hk-run
"buildJust3 s = showString \"Just \" (shows 3 s)\nmain = showParen True buildJust3 \"\""))
"(Just 3)")
;; ── Read stubs ───────────────────────────────────────────────
(hk-test
"reads: stub returns empty list (null-check)"
(hk-deep-force (hk-run "main = show (null (reads \"42\"))"))
"True")
(hk-test
"readsPrec: stub returns empty list"
(hk-deep-force (hk-run "main = show (null (readsPrec 0 \"True\"))"))
"True")
(hk-test
"reads: type-checks in expression context (length)"
(hk-deep-force (hk-run "main = show (length (reads \"abc\"))"))
"0")
;; ── Direct `show` audit coverage ─────────────────────────────
(hk-test "show Int" (hk-deep-force (hk-run "main = show 42")) "42")
(hk-test
"show negative Int"
(hk-deep-force (hk-run "main = show (negate 5)"))
"-5")
(hk-test "show Bool True" (hk-deep-force (hk-run "main = show True")) "True")
(hk-test
"show Bool False"
(hk-deep-force (hk-run "main = show False"))
"False")
(hk-test
"show String quotes the value"
(hk-deep-force (hk-run "main = show \"hello\""))
"\"hello\"")
(hk-test
"show list of Int"
(hk-deep-force (hk-run "main = show [1,2,3]"))
"[1,2,3]")
(hk-test
"show empty list"
(hk-deep-force (hk-run "main = show (drop 5 [1,2,3])"))
"[]")
(hk-test
"show pair tuple"
(hk-deep-force (hk-run "main = show (1, True)"))
"(1,True)")
(hk-test
"show triple tuple"
(hk-deep-force (hk-run "main = show (1, 2, 3)"))
"(1,2,3)")
(hk-test
"show Maybe Nothing"
(hk-deep-force (hk-run "main = show Nothing"))
"Nothing")
(hk-test
"show Maybe Just"
(hk-deep-force (hk-run "main = show (Just 3)"))
"Just 3")
(hk-test
"show nested Just wraps inner in parens"
(hk-deep-force (hk-run "main = show (Just (Just 3))"))
"Just (Just 3)")
(hk-test
"show Just (negate 3) wraps negative in parens"
(hk-deep-force (hk-run "main = show (Just (negate 3))"))
"Just (-3)")
(hk-test
"show custom nullary ADT"
(hk-deep-force
(hk-run "data Day = Mon | Tue | Wed deriving (Show)\nmain = show Tue"))
"Tue")
(hk-test
"show custom multi-constructor ADT"
(hk-deep-force
(hk-run
"data Shape = Pt | Sq Int | Rect Int Int deriving (Show)\nmain = show (Rect 3 4)"))
"Rect 3 4")
(hk-test
"show list of Maybe wraps each element"
(hk-deep-force (hk-run "main = show [Just 1, Nothing, Just 2]"))
"[Just 1,Nothing,Just 2]")
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}

View File

@@ -37,11 +37,11 @@
(hk-ts "show neg" "negate 7" "-7")
(hk-ts "show bool T" "True" "True")
(hk-ts "show bool F" "False" "False")
(hk-ts "show list" "[1,2,3]" "[1,2,3]")
(hk-ts "show Just" "Just 5" "Just 5")
(hk-ts "show list" "[1,2,3]" "[1, 2, 3]")
(hk-ts "show Just" "Just 5" "(Just 5)")
(hk-ts "show Nothing" "Nothing" "Nothing")
(hk-ts "show LT" "LT" "LT")
(hk-ts "show tuple" "(1, True)" "(1,True)")
(hk-ts "show tuple" "(1, True)" "(1, True)")
;; ── Num extras ───────────────────────────────────────────────
(hk-test "signum pos" (hk-deep-force (hk-run "main = signum 5")) 1)
@@ -59,13 +59,13 @@
(hk-test
"foldr cons"
(hk-deep-force (hk-run "main = show (foldr (:) [] [1,2,3])"))
"[1,2,3]")
"[1, 2, 3]")
;; ── List ops ─────────────────────────────────────────────────
(hk-test
"reverse"
(hk-deep-force (hk-run "main = show (reverse [1,2,3])"))
"[3,2,1]")
"[3, 2, 1]")
(hk-test "null []" (hk-deep-force (hk-run "main = null []")) (list "True"))
(hk-test
"null xs"
@@ -82,7 +82,7 @@
(hk-test
"zip"
(hk-deep-force (hk-run "main = show (zip [1,2] [3,4])"))
"[(1,3),(2,4)]")
"[(1, 3), (2, 4)]")
(hk-test "sum" (hk-deep-force (hk-run "main = sum [1,2,3,4,5]")) 15)
(hk-test "product" (hk-deep-force (hk-run "main = product [1,2,3,4]")) 24)
(hk-test "maximum" (hk-deep-force (hk-run "main = maximum [3,1,9,2]")) 9)
@@ -112,7 +112,7 @@
(hk-test
"fmap list"
(hk-deep-force (hk-run "main = show (fmap (+1) [1,2,3])"))
"[2,3,4]")
"[2, 3, 4]")
;; ── Monad / Applicative ──────────────────────────────────────
(hk-test "return" (hk-deep-force (hk-run "main = return 7")) (list "IO" 7))
@@ -134,7 +134,7 @@
(hk-test
"lookup hit"
(hk-deep-force (hk-run "main = show (lookup 2 [(1,10),(2,20)])"))
"Just 20")
"(Just 20)")
(hk-test
"lookup miss"
(hk-deep-force (hk-run "main = show (lookup 9 [(1,10)])"))

View File

@@ -1,139 +0,0 @@
;; String / Char tests — Phase 7 items 1-4.
;;
;; Covers:
;; hk-str? / hk-str-head / hk-str-tail / hk-str-null? (runtime helpers)
;; chr / ord / toUpper / toLower (builtins in eval)
;; cons-pattern on strings via match.sx (":"-intercept)
;; empty-list pattern on strings via match.sx ("[]"-intercept)
;; ── hk-str? predicate ────────────────────────────────────────────────────
(hk-test "hk-str? native string" (hk-str? "hello") true)
(hk-test "hk-str? empty string" (hk-str? "") true)
(hk-test "hk-str? view dict" (hk-str? {:hk-off 1 :hk-str "hi"}) true)
(hk-test "hk-str? rejects number" (hk-str? 42) false)
;; ── hk-str-null? predicate ───────────────────────────────────────────────
(hk-test "hk-str-null? empty string" (hk-str-null? "") true)
(hk-test "hk-str-null? non-empty" (hk-str-null? "a") false)
(hk-test "hk-str-null? exhausted view" (hk-str-null? {:hk-off 2 :hk-str "hi"}) true)
(hk-test "hk-str-null? live view" (hk-str-null? {:hk-off 1 :hk-str "hi"}) false)
;; ── hk-str-head ──────────────────────────────────────────────────────────
(hk-test "hk-str-head native string" (hk-str-head "hello") 104)
(hk-test "hk-str-head view at offset" (hk-str-head {:hk-off 1 :hk-str "hello"}) 101)
;; ── hk-str-tail ──────────────────────────────────────────────────────────
(hk-test "hk-str-tail of single char is nil" (hk-str-tail "h") (list "[]"))
(hk-test
"hk-str-tail of two-char string is live view"
(hk-str-null? (hk-str-tail "hi"))
false)
(hk-test
"hk-str-tail head of tail of hi is i"
(hk-str-head (hk-str-tail "hi"))
105)
;; ── chr / ord ────────────────────────────────────────────────────────────
(hk-test "chr 65 = A" (hk-eval-expr-source "chr 65") "A")
(hk-test "chr 104 = h" (hk-eval-expr-source "chr 104") "h")
(hk-test "ord char literal 'A' = 65" (hk-eval-expr-source "ord 'A'") 65)
(hk-test "ord char literal 'a' = 97" (hk-eval-expr-source "ord 'a'") 97)
(hk-test
"ord of head string = char code"
(hk-eval-expr-source "ord (head \"hello\")")
104)
;; ── toUpper / toLower ────────────────────────────────────────────────────
(hk-test "toUpper 97 = 65 (a->A)" (hk-eval-expr-source "toUpper 97") 65)
(hk-test
"toUpper 65 = 65 (already upper)"
(hk-eval-expr-source "toUpper 65")
65)
(hk-test
"toUpper 48 = 48 (digit unchanged)"
(hk-eval-expr-source "toUpper 48")
48)
(hk-test "toLower 65 = 97 (A->a)" (hk-eval-expr-source "toLower 65") 97)
(hk-test
"toLower 97 = 97 (already lower)"
(hk-eval-expr-source "toLower 97")
97)
(hk-test
"toLower 48 = 48 (digit unchanged)"
(hk-eval-expr-source "toLower 48")
48)
;; ── Pattern matching on strings ──────────────────────────────────────────
(hk-test
"cons pattern: head of hello = 104"
(hk-eval-expr-source "case \"hello\" of { (x:_) -> x }")
104)
(hk-test
"cons pattern: tail is traversable"
(hk-eval-expr-source "case \"hi\" of { (_:xs) -> case xs of { (y:_) -> y } }")
105)
(hk-test
"empty list pattern matches empty string"
(hk-eval-expr-source "case \"\" of { [] -> True; _ -> False }")
(list "True"))
(hk-test
"empty list pattern fails on non-empty"
(hk-eval-expr-source "case \"a\" of { [] -> True; _ -> False }")
(list "False"))
(hk-test
"cons pattern fails on empty string"
(hk-eval-expr-source "case \"\" of { (_:_) -> True; _ -> False }")
(list "False"))
;; ── Haskell programs using string traversal ──────────────────────────────
(hk-test
"null prelude on empty string"
(hk-eval-expr-source "null \"\"")
(list "True"))
(hk-test
"null prelude on non-empty string"
(hk-eval-expr-source "null \"abc\"")
(list "False"))
(hk-test
"length of string via cons recursion"
(hk-eval-expr-source "let { f [] = 0; f (_:xs) = 1 + f xs } in f \"hello\"")
5)
(hk-test
"map ord over string gives char codes"
(hk-deep-force (hk-eval-expr-source "map ord \"abc\""))
(list ":" 97 (list ":" 98 (list ":" 99 (list "[]")))))
(hk-test
"map toUpper over char codes then chr"
(hk-eval-expr-source "chr (toUpper (ord (head \"abc\")))")
"A")
(hk-test
"head then ord using prelude head"
(hk-eval-expr-source "ord (head \"hello\")")
104)

View File

@@ -226,28 +226,6 @@
value)
(list (quote set!) (hs-to-sx target) value)))))))
(true (list (quote set!) (hs-to-sx target) value)))))))
;; Throttle/debounce extraction state — module-level so they don't get
;; redefined on every emit-on call (which was causing JIT churn). Set
;; via _strip-throttle-debounce at the start of each emit-on, used in
;; the handler-build step inside scan-on.
(define _throttle-ms nil)
(define _debounce-ms nil)
(define
_strip-throttle-debounce
(fn
(lst)
(cond
((<= (len lst) 1) lst)
((= (first lst) :throttle)
(do
(set! _throttle-ms (nth lst 1))
(_strip-throttle-debounce (rest (rest lst)))))
((= (first lst) :debounce)
(do
(set! _debounce-ms (nth lst 1))
(_strip-throttle-debounce (rest (rest lst)))))
(true
(cons (first lst) (_strip-throttle-debounce (rest lst)))))))
(define
emit-on
(fn
@@ -256,8 +234,6 @@
((parts (rest ast)))
(let
((event-name (first parts)))
(set! _throttle-ms nil)
(set! _debounce-ms nil)
(define
scan-on
(fn
@@ -290,13 +266,6 @@
((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote let) (list (list (quote __hs-exc) nil) (list (quote __hs-reraise) false)) (list (quote do) (list (quote guard) (list var (list true (list (quote let) (list (list var (list (quote host-hs-normalize-exc) var))) (list (quote guard) (list (quote __inner-exc) (list true (list (quote do) (list (quote set!) (quote __hs-exc) (quote __inner-exc)) (list (quote set!) (quote __hs-reraise) true)))) catch-body)))) compiled-body) (hs-to-sx finally-info) (list (quote when) (quote __hs-reraise) (list (quote raise) (quote __hs-exc))))) (list (quote let) (list (list (quote __hs-exc) nil) (list (quote __hs-reraise) false)) (list (quote do) (list (quote guard) (list var (list true (list (quote let) (list (list var (list (quote host-hs-normalize-exc) var))) (list (quote guard) (list (quote __inner-exc) (list true (list (quote do) (list (quote set!) (quote __hs-exc) (quote __inner-exc)) (list (quote set!) (quote __hs-reraise) true)))) catch-body)))) compiled-body) (list (quote when) (quote __hs-reraise) (list (quote raise) (quote __hs-exc))))))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body))))
(let
((handler (let ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) (let ((base-handler (list (quote fn) (list (quote event)) (if (uses-the-result? wrapped-body) (list (quote let) (list (list (quote the-result) nil)) wrapped-body) wrapped-body)))) (if count-filter-info (let ((mn (get count-filter-info "min")) (mx (get count-filter-info "max"))) (list (quote let) (list (list (quote __hs-count) 0)) (list (quote fn) (list (quote event)) (list (quote begin) (list (quote set!) (quote __hs-count) (list (quote +) (quote __hs-count) 1)) (list (quote when) (if (= mx -1) (list (quote >=) (quote __hs-count) mn) (list (quote and) (list (quote >=) (quote __hs-count) mn) (list (quote <=) (quote __hs-count) mx))) (nth base-handler 2)))))) base-handler)))))
(let
((handler (cond
(_throttle-ms
(list (quote hs-throttle!) handler (hs-to-sx _throttle-ms)))
(_debounce-ms
(list (quote hs-debounce!) handler (hs-to-sx _debounce-ms)))
(true handler))))
(let
((on-call (if every? (list (quote hs-on-every) target event-name handler) (list (quote hs-on) target event-name handler))))
(cond
@@ -356,7 +325,7 @@
(first pair)
handler))
or-sources)))
on-call))))))))))))))
on-call)))))))))))))
((= (first items) :from)
(scan-on
(rest (rest items))
@@ -500,7 +469,7 @@
count-filter-info
elsewhere?
or-sources)))))
(scan-on (_strip-throttle-debounce (rest parts)) nil nil false nil nil nil nil nil false nil)))))
(scan-on (rest parts) nil nil false nil nil nil nil nil false nil)))))
(define
emit-send
(fn
@@ -2521,15 +2490,6 @@
(quote fn)
(list (quote it))
(hs-to-sx body))))
((and (list? expr) (= (first expr) (quote attr)))
(list
(quote hs-attr-watch!)
(hs-to-sx (nth expr 2))
(nth expr 1)
(list
(quote fn)
(list (quote it))
(hs-to-sx body))))
(true nil))))
((= head (quote init))
(list

View File

@@ -1358,17 +1358,7 @@
cls
(first extra-classes)
tgt))
((and
(= (tp-type) "keyword") (= (tp-val) "for")
;; Only consume 'for' as a duration clause if the next
;; token is NOT '<ident> in ...' — that pattern is a
;; for-in loop, not a toggle duration.
(not
(and
(> (len tokens) (+ p 2))
(= (get (nth tokens (+ p 1)) "type") "ident")
(= (get (nth tokens (+ p 2)) "value") "in")))
(do (adv!) true))
((match-kw "for")
(let
((dur (parse-expr)))
(list (quote toggle-class-for) cls tgt dur)))
@@ -3100,17 +3090,7 @@
(= (tp-val) "queue"))
(do (adv!) (adv!)))
(let
((every? (match-kw "every"))
(throttle-ms nil)
(debounce-ms nil))
;; 'throttled at <duration>' / 'debounced at <duration>'
;; — parsed as handler modifiers, captured as :throttle / :debounce parts.
(when (and (= (tp-type) "ident") (= (tp-val) "throttled"))
(adv!)
(when (match-kw "at") (set! throttle-ms (parse-expr))))
(when (and (= (tp-type) "ident") (= (tp-val) "debounced"))
(adv!)
(when (match-kw "at") (set! debounce-ms (parse-expr))))
((every? (match-kw "every")))
(let
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
(let
@@ -3125,10 +3105,6 @@
(match-kw "end")
(let
((parts (list (quote on) event-name)))
(let
((parts (if throttle-ms (append parts (list :throttle throttle-ms)) parts)))
(let
((parts (if debounce-ms (append parts (list :debounce debounce-ms)) parts)))
(let
((parts (if every? (append parts (list :every true)) parts)))
(let
@@ -3151,7 +3127,7 @@
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
(let
((parts (append parts (list (if (> (len event-vars) 0) (cons (quote do) (append (map (fn (nm) (list (quote ref) nm)) event-vars) (if (and (list? body) (= (first body) (quote do))) (rest body) (list body)))) body)))))
parts))))))))))))))))))))))))))))
parts))))))))))))))))))))))))))
(define
parse-init-feat
(fn
@@ -3201,7 +3177,6 @@
(or
(= (tp-type) "hat")
(= (tp-type) "local")
(= (tp-type) "attr")
(and (= (tp-type) "keyword") (= (tp-val) "dom")))
(let
((expr (parse-expr)))

View File

@@ -12,29 +12,6 @@
;; Register an event listener. Returns unlisten function.
;; (hs-on target event-name handler) → unlisten-fn
(begin
(define _hs-config-log-all false)
(define _hs-log-captured (list))
(define
hs-set-log-all!
(fn (flag) (set! _hs-config-log-all (if flag true false))))
(define hs-get-log-captured (fn () _hs-log-captured))
(define
hs-clear-log-captured!
(fn () (begin (set! _hs-log-captured (list)) nil)))
(define
hs-log-event!
(fn
(msg)
(when
_hs-config-log-all
(begin
(set! _hs-log-captured (append _hs-log-captured (list msg)))
(host-call (host-global "console") "log" msg)
nil)))))
;; Run an initializer function immediately.
;; (hs-init thunk) — called at element boot time
(define
hs-each
(fn
@@ -45,52 +22,17 @@
;; (hs-init thunk) — called at element boot time
(define meta (host-new "Object"))
;; Run an initializer function immediately.
;; (hs-init thunk) — called at element boot time
(define
hs-on-every
(fn (target event-name handler) (dom-listen target event-name handler)))
;; ── Async / timing ──────────────────────────────────────────────
;; Wait for a duration in milliseconds.
;; In hyperscript, wait is async-transparent — execution pauses.
;; Here we use perform/IO suspension for true pause semantics.
(define
hs-on-every
(fn (target event-name handler) (dom-listen target event-name handler)))
;; Throttle: drops events that arrive within the window. First event fires
;; immediately; subsequent events within `ms` of the previous fire are dropped.
;; Returns a wrapped handler suitable for hs-on / hs-on-every.
(define
hs-throttle!
(fn
(handler ms)
(let
((__hs-last-fire 0))
(fn
(event)
(let
((__hs-now (host-call (host-global "Date") "now")))
(when
(>= (- __hs-now __hs-last-fire) ms)
(set! __hs-last-fire __hs-now)
(handler event)))))))
;; Debounce: waits until `ms` has elapsed since the last event before firing.
;; In our synchronous test mock no time passes, so the timer fires immediately
;; via setTimeout(_, 0); the wrapped handler still gets called once per burst.
(define
hs-debounce!
(fn
(handler ms)
(let
((__hs-timer nil))
(fn
(event)
(when __hs-timer (host-call (host-global "window") "clearTimeout" __hs-timer))
(set! __hs-timer
(host-call (host-global "window") "setTimeout"
(host-new-function (list "ev") "return arguments[0](arguments[1]);")
ms handler event))))))
;; Wait for a DOM event on a target.
;; (hs-wait-for target event-name) — suspends until event fires
(define
_hs-on-caller
(let
@@ -103,7 +45,8 @@
(host-set! _ctx "meta" _m)
_ctx)))
;; Wait for CSS transitions/animations to settle on an element.
;; Wait for a DOM event on a target.
;; (hs-wait-for target event-name) — suspends until event fires
(define
hs-on
(fn
@@ -123,14 +66,14 @@
(append prev (list unlisten)))
unlisten))))))
;; ── Class manipulation ──────────────────────────────────────────
;; Toggle a single class on an element.
;; Wait for CSS transitions/animations to settle on an element.
(define
hs-on-every
(fn (target event-name handler) (dom-listen target event-name handler)))
;; Toggle between two classes — exactly one is active at a time.
;; ── Class manipulation ──────────────────────────────────────────
;; Toggle a single class on an element.
(define
hs-on-intersection-attach!
(fn
@@ -146,8 +89,7 @@
(host-call observer "observe" target)
observer)))))
;; Take a class from siblings — add to target, remove from others.
;; (hs-take! target cls) — like radio button class behavior
;; Toggle between two classes — exactly one is active at a time.
(define
hs-on-mutation-attach!
(fn
@@ -168,18 +110,19 @@
(host-call observer "observe" target opts)
observer))))))
;; Take a class from siblings — add to target, remove from others.
;; (hs-take! target cls) — like radio button class behavior
(define hs-init (fn (thunk) (thunk)))
;; ── DOM insertion ───────────────────────────────────────────────
;; Put content at a position relative to a target.
;; pos: "into" | "before" | "after"
(define hs-init (fn (thunk) (thunk)))
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
;; ── Navigation / traversal ──────────────────────────────────────
;; Navigate to a URL.
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
;; Find next sibling matching a selector (or any sibling).
(begin
(define
hs-wait-for
@@ -192,7 +135,7 @@
(target event-name timeout-ms)
(perform (list (quote io-wait-event) target event-name timeout-ms)))))
;; Find previous sibling matching a selector.
;; Find next sibling matching a selector (or any sibling).
(define
hs-settle
(fn
@@ -200,7 +143,7 @@
(hs-null-raise! target)
(when (not (nil? target)) (perform (list (quote io-settle) target)))))
;; First element matching selector within a scope.
;; Find previous sibling matching a selector.
(define
hs-toggle-class!
(fn
@@ -210,7 +153,7 @@
(not (nil? target))
(host-call (host-get target "classList") "toggle" cls))))
;; Last element matching selector.
;; First element matching selector within a scope.
(define
hs-toggle-var-cycle!
(fn
@@ -232,7 +175,7 @@
var-name
(if (= idx -1) (first values) (nth values (mod (+ idx 1) n))))))))
;; First/last within a specific scope.
;; Last element matching selector.
(define
hs-toggle-between!
(fn
@@ -245,6 +188,7 @@
(do (dom-remove-class target cls1) (dom-add-class target cls2))
(do (dom-remove-class target cls2) (dom-add-class target cls1))))))
;; First/last within a specific scope.
(define
hs-toggle-style!
(fn
@@ -268,9 +212,6 @@
(dom-set-style target prop "hidden")
(dom-set-style target prop "")))))))
;; ── Iteration ───────────────────────────────────────────────────
;; Repeat a thunk N times.
(define
hs-toggle-style-between!
(fn
@@ -282,7 +223,9 @@
(dom-set-style target prop val2)
(dom-set-style target prop val1)))))
;; Repeat forever (until break — relies on exception/continuation).
;; ── Iteration ───────────────────────────────────────────────────
;; Repeat a thunk N times.
(define
hs-toggle-style-cycle!
(fn
@@ -303,10 +246,7 @@
(true (find-next (rest remaining))))))
(dom-set-style target prop (find-next vals)))))
;; ── Fetch ───────────────────────────────────────────────────────
;; Fetch a URL, parse response according to format.
;; (hs-fetch url format) — format is "json" | "text" | "html"
;; Repeat forever (until break — relies on exception/continuation).
(define
hs-take!
(fn
@@ -329,7 +269,8 @@
(when with-cls (dom-remove-class target with-cls))))
(let
((attr-val (if (> (len extra) 0) (first extra) nil))
(with-val (if (> (len extra) 1) (nth extra 1) nil)))
(with-val
(if (> (len extra) 1) (nth extra 1) nil)))
(do
(for-each
(fn
@@ -346,10 +287,10 @@
(dom-set-attr target name attr-val)
(dom-set-attr target name ""))))))))
;; ── Type coercion ───────────────────────────────────────────────
;; ── Fetch ───────────────────────────────────────────────────────
;; Coerce a value to a type by name.
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
;; Fetch a URL, parse response according to format.
;; (hs-fetch url format) — format is "json" | "text" | "html"
(begin
(define
hs-element?
@@ -506,10 +447,10 @@
(dom-insert-adjacent-html target "beforeend" value)
(hs-boot-subtree! target)))))))))))
;; ── Object creation ─────────────────────────────────────────────
;; ── Type coercion ───────────────────────────────────────────────
;; Make a new object of a given type.
;; (hs-make type-name) — creates empty object/collection
;; Coerce a value to a type by name.
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
(define
hs-add-to!
(fn
@@ -523,11 +464,10 @@
((hs-is-set? target) (do (host-call target "add" value) target))
(true (do (host-call target "push" value) target)))))
;; ── Behavior installation ───────────────────────────────────────
;; ── Object creation ─────────────────────────────────────────────
;; Install a behavior on an element.
;; A behavior is a function that takes (me ...params) and sets up features.
;; (hs-install behavior-fn me ...args)
;; Make a new object of a given type.
;; (hs-make type-name) — creates empty object/collection
(define
hs-remove-from!
(fn
@@ -537,10 +477,11 @@
((hs-is-set? target) (do (host-call target "delete" value) target))
(true (host-call target "splice" (host-call target "indexOf" value) 1)))))
;; ── Measurement ─────────────────────────────────────────────────
;; ── Behavior installation ───────────────────────────────────────
;; Measure an element's bounding rect, store as local variables.
;; Returns a dict with x, y, width, height, top, left, right, bottom.
;; Install a behavior on an element.
;; A behavior is a function that takes (me ...params) and sets up features.
;; (hs-install behavior-fn me ...args)
(define
hs-splice-at!
(fn
@@ -553,7 +494,10 @@
((i (if (< idx 0) (+ n idx) idx)))
(cond
((or (< i 0) (>= i n)) target)
(true (concat (slice target 0 i) (slice target (+ i 1) n))))))
(true
(concat
(slice target 0 i)
(slice target (+ i 1) n))))))
(do
(when
target
@@ -564,10 +508,10 @@
(host-call target "splice" i 1))))
target))))
;; Return the current text selection as a string. In the browser this is
;; `window.getSelection().toString()`. In the mock test runner, a test
;; setup stashes the desired selection text at `window.__test_selection`
;; and the fallback path returns that so tests can assert on the result.
;; ── Measurement ─────────────────────────────────────────────────
;; Measure an element's bounding rect, store as local variables.
;; Returns a dict with x, y, width, height, top, left, right, bottom.
(define
hs-index
(fn
@@ -579,11 +523,10 @@
((string? obj) (nth obj key))
(true (host-get obj key)))))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
;; Return the current text selection as a string. In the browser this is
;; `window.getSelection().toString()`. In the mock test runner, a test
;; setup stashes the desired selection text at `window.__test_selection`
;; and the fallback path returns that so tests can assert on the result.
(define
hs-put-at!
(fn
@@ -605,6 +548,11 @@
((= pos "start") (host-call target "unshift" value)))
target)))))))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-dict-without
(fn
@@ -641,11 +589,6 @@
((w (host-global "window")))
(if w (host-call w "prompt" msg) nil))))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-answer
(fn
@@ -654,6 +597,11 @@
((w (host-global "window")))
(if w (if (host-call w "confirm" msg) yes-val no-val) no-val))))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-answer-alert
(fn
@@ -714,10 +662,6 @@
(if (nil? sel) "" (host-call sel "toString" (list))))
stash)))))
(define
hs-reset!
(fn
@@ -764,6 +708,10 @@
(when default-val (dom-set-prop target "value" default-val)))))
(true nil)))))))
(define
hs-next
(fn
@@ -782,8 +730,7 @@
((dom-matches? el sel) el)
(true (find-next (dom-next-sibling el))))))
(find-next sibling)))))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
(define
hs-previous
(fn
@@ -802,9 +749,10 @@
((dom-matches? el sel) el)
(true (find-prev (dom-get-prop el "previousElementSibling"))))))
(find-prev sibling)))))
;; DOM query stub — sandbox returns empty list
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
(define _hs-last-query-sel nil)
;; Method dispatch — obj.method(args)
;; DOM query stub — sandbox returns empty list
(define
hs-null-raise!
(fn
@@ -815,9 +763,7 @@
((msg (str "'" (or (host-get (host-global "window") "_hs_last_query_sel") "target") "' is null")))
(host-set! (host-global "window") "_hs_null_error" msg)
(guard (_null-e (true nil)) (raise msg))))))
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
;; Method dispatch — obj.method(args)
(define
hs-empty-raise!
(fn
@@ -831,7 +777,9 @@
((msg (str "'" (or (host-get (host-global "window") "_hs_last_query_sel") "target") "' is null")))
(host-set! (host-global "window") "_hs_null_error" msg)
(guard (_null-e (true nil)) (raise msg))))))
;; Property-based is — check obj.key truthiness
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
(define
hs-query-all-checked
(fn
@@ -839,14 +787,14 @@
(let
((result (hs-query-all sel)))
(do (hs-empty-raise! result) result))))
;; Array slicing (inclusive both ends)
;; Property-based is — check obj.key truthiness
(define
hs-dispatch!
(fn
(target event detail)
(hs-null-raise! target)
(when (not (nil? target)) (dom-dispatch target event detail))))
;; Collection: sorted by
;; Array slicing (inclusive both ends)
(define
hs-query-all
(fn
@@ -854,7 +802,7 @@
(do
(host-set! (host-global "window") "_hs_last_query_sel" sel)
(dom-query-all (dom-document) sel))))
;; Collection: sorted by descending
;; Collection: sorted by
(define
hs-query-all-in
(fn
@@ -863,17 +811,17 @@
(nil? target)
(hs-query-all sel)
(host-call target "querySelectorAll" sel))))
;; Collection: split by
;; Collection: sorted by descending
(define
hs-list-set
(fn
(lst idx val)
(append (take lst idx) (cons val (drop lst (+ idx 1))))))
;; Collection: joined by
;; Collection: split by
(define
hs-to-number
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
;; Collection: joined by
(define
hs-query-first
(fn
@@ -1003,7 +951,7 @@
((= (str ex) "hs-continue") (do-loop (rest remaining)))
(true (raise ex))))))))
(do-loop items))))
;; Collection: joined by
(begin
(define
hs-append
@@ -1044,7 +992,7 @@
(host-get value "outerHTML")
(str value))))
(true nil)))))
;; Collection: joined by
(define
hs-sender
(fn
@@ -1136,7 +1084,6 @@
(hs-host-to-sx (perform (list "io-parse-json" raw))))
((= fmt "number")
(hs-to-number (perform (list "io-parse-text" raw))))
((= fmt "html") (perform (list "io-parse-html" raw)))
(true (perform (list "io-parse-text" raw)))))))))
(define hs-fetch (fn (url format) (hs-fetch-impl url format false)))
@@ -1676,10 +1623,14 @@
((ch (substring sel i (+ i 1))))
(cond
((= ch ".")
(do (flush!) (set! mode "class") (walk (+ i 1))))
(do
(flush!)
(set! mode "class")
(walk (+ i 1))))
((= ch "#")
(do (flush!) (set! mode "id") (walk (+ i 1))))
(true (do (set! cur (str cur ch)) (walk (+ i 1)))))))))
(true
(do (set! cur (str cur ch)) (walk (+ i 1)))))))))
(walk 0)
(flush!)
{:tag tag :classes classes :id id}))))
@@ -1773,11 +1724,11 @@
(value type-name)
(if (nil? value) false (hs-type-check value type-name))))
(define
hs-strict-eq
(fn (a b) (and (= (type-of a) (type-of b)) (= a b))))
(define
hs-id=
(fn
@@ -1809,20 +1760,6 @@
((nil? suffix) false)
(true (ends-with? (str s) (str suffix))))))
(define
hs-attr-watch!
(fn
(target attr-name handler)
(let
((mo-class (host-get (host-global "window") "MutationObserver")))
(when
mo-class
(let
((cb (fn (records observer) (for-each (fn (rec) (when (= (host-get rec "attributeName") attr-name) (handler (host-call target "getAttribute" attr-name)))) records))))
(let
((mo (host-new "MutationObserver" cb)))
(host-call mo "observe" target {:attributeFilter (list attr-name) :attributes true})))))))
(define
hs-scoped-set!
(fn
@@ -1868,7 +1805,10 @@
((and (dict? a) (dict? b))
(let
((pos (host-call a "compareDocumentPosition" b)))
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
(if
(number? pos)
(not (= 0 (mod (/ pos 4) 2)))
false)))
(true (< (str a) (str b))))))
(define
@@ -1989,7 +1929,10 @@
((and (dict? a) (dict? b))
(let
((pos (host-call a "compareDocumentPosition" b)))
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
(if
(number? pos)
(not (= 0 (mod (/ pos 4) 2)))
false)))
(true (< (str a) (str b))))))
(define
@@ -2042,7 +1985,9 @@
(define
hs-morph-char
(fn (s p) (if (or (< p 0) (>= p (string-length s))) nil (nth s p))))
(fn
(s p)
(if (or (< p 0) (>= p (string-length s))) nil (nth s p))))
(define
hs-morph-index-from
@@ -2070,7 +2015,10 @@
(q)
(let
((c (hs-morph-char s q)))
(if (and c (< (index-of stop c) 0)) (loop (+ q 1)) q))))
(if
(and c (< (index-of stop c) 0))
(loop (+ q 1))
q))))
(let ((e (loop p))) (list (substring s p e) e))))
(define
@@ -2112,7 +2060,9 @@
(append
acc
(list
(list name (substring s (+ p4 1) close)))))))
(list
name
(substring s (+ p4 1) close)))))))
((= c2 "'")
(let
((close (hs-morph-index-from s "'" (+ p4 1))))
@@ -2122,7 +2072,9 @@
(append
acc
(list
(list name (substring s (+ p4 1) close)))))))
(list
name
(substring s (+ p4 1) close)))))))
(true
(let
((r2 (hs-morph-read-until s p4 " \t\n/>")))
@@ -2206,7 +2158,9 @@
(for-each
(fn
(c)
(when (> (string-length c) 0) (dom-add-class el c)))
(when
(> (string-length c) 0)
(dom-add-class el c)))
(split v " ")))
((and keep-id (= n "id")) nil)
(true (dom-set-attr el n v)))))
@@ -2307,7 +2261,8 @@
((parts (split resolved ":")))
(let
((prop (first parts))
(val (if (> (len parts) 1) (nth parts 1) nil)))
(val
(if (> (len parts) 1) (nth parts 1) nil)))
(cond
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
(let
@@ -2347,7 +2302,8 @@
((parts (split resolved ":")))
(let
((prop (first parts))
(val (if (> (len parts) 1) (nth parts 1) nil)))
(val
(if (> (len parts) 1) (nth parts 1) nil)))
(cond
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
(let
@@ -2452,10 +2408,14 @@
(if
(= depth 1)
j
(find-close (+ j 1) (- depth 1)))
(find-close
(+ j 1)
(- depth 1)))
(if
(= (nth raw j) "{")
(find-close (+ j 1) (+ depth 1))
(find-close
(+ j 1)
(+ depth 1))
(find-close (+ j 1) depth))))))
(let
((close (find-close start 1)))
@@ -2566,7 +2526,10 @@
(if
(= (len lst) 0)
-1
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
(if
(= (first lst) item)
i
(idx-loop (rest lst) (+ i 1))))))
(idx-loop obj 0)))
(true
(let
@@ -2658,7 +2621,8 @@
(cond
((= end "hs-pick-end") n)
((= end "hs-pick-start") 0)
((and (number? end) (< end 0)) (max 0 (+ n end)))
((and (number? end) (< end 0))
(max 0 (+ n end)))
(true end))))
(cond
((string? col) (slice col s e))
@@ -2838,8 +2802,6 @@
hs-sorted-by-desc
(fn (col key-fn) (reverse (hs-sorted-by col key-fn))))
;; ── SourceInfo API ────────────────────────────────────────────────
(define
hs-dom-has-var?
(fn
@@ -2859,6 +2821,8 @@
((store (host-get el "__hs_vars")))
(if (nil? store) nil (host-get store name)))))
;; ── SourceInfo API ────────────────────────────────────────────────
(define
hs-dom-set-var-raw!
(fn
@@ -2949,12 +2913,7 @@
(define
hs-null-error!
(fn
(selector)
(let
((msg (str "'" selector "' is null")))
(host-set! (host-global "window") "_hs_null_error" msg)
(guard (_null-e (true nil)) (raise msg)))))
(fn (selector) (raise (str "'" selector "' is null"))))
(define
hs-named-target
@@ -2974,7 +2933,9 @@
((results (hs-query-all selector)))
(if
(and
(or (nil? results) (and (list? results) (= (len results) 0)))
(or
(nil? results)
(and (list? results) (= (len results) 0)))
(string? selector)
(> (len selector) 0)
(= (substring selector 0 1) "#"))

View File

@@ -856,229 +856,3 @@
(scan-template!)
(t-emit! "eof" nil)
tokens)))
;; ── Stream wrapper for upstream-style stateful tokenizer API ───────────────
;;
;; Upstream _hyperscript exposes a Tokens object with cursor + follow-set
;; semantics on _hyperscript.internals.tokenizer. Our hs-tokenize returns a
;; flat list; the stream wrapper adds the stateful operations.
;;
;; Type names map ours → upstream's (e.g. "ident" → "IDENTIFIER").
(define
hs-stream-type-map
(fn
(t)
(cond
((= t "ident") "IDENTIFIER")
((= t "number") "NUMBER")
((= t "string") "STRING")
((= t "class") "CLASS_REF")
((= t "id") "ID_REF")
((= t "attr") "ATTRIBUTE_REF")
((= t "style") "STYLE_REF")
((= t "whitespace") "WHITESPACE")
((= t "op") "OPERATOR")
((= t "eof") "EOF")
(true (upcase t)))))
;; Create a stream from a source string.
;; Returns a dict — mutable via dict-set!.
(define
hs-stream
(fn
(src)
{:tokens (hs-tokenize src) :pos 0 :follows (list) :last-match nil :last-ws nil}))
;; Skip whitespace tokens, advancing pos to the next non-WS token.
;; Captures the last skipped whitespace value into :last-ws.
(define
hs-stream-skip-ws!
(fn
(s)
(let
((tokens (get s :tokens)))
(define
loop
(fn
()
(let
((p (get s :pos)))
(when
(and (< p (len tokens))
(= (get (nth tokens p) :type) "whitespace"))
(do
(dict-set! s :last-ws (get (nth tokens p) :value))
(dict-set! s :pos (+ p 1))
(loop))))))
(loop))))
;; Current token (after skipping whitespace).
(define
hs-stream-current
(fn
(s)
(do
(hs-stream-skip-ws! s)
(let
((tokens (get s :tokens)) (p (get s :pos)))
(if (< p (len tokens)) (nth tokens p) nil)))))
;; Returns the current token if its value matches; advances and updates
;; :last-match. Returns nil otherwise (no advance).
;; Honors the follow set: tokens whose value is in :follows do NOT match.
(define
hs-stream-match
(fn
(s value)
(let
((cur (hs-stream-current s)))
(cond
((nil? cur) nil)
((some (fn (f) (= f value)) (get s :follows)) nil)
((= (get cur :value) value)
(do
(dict-set! s :pos (+ (get s :pos) 1))
(dict-set! s :last-match cur)
cur))
(true nil)))))
;; Match by upstream-style type name. Accepts any number of allowed types.
(define
hs-stream-match-type
(fn
(s &rest types)
(let
((cur (hs-stream-current s)))
(cond
((nil? cur) nil)
((some (fn (t) (= (hs-stream-type-map (get cur :type)) t)) types)
(do
(dict-set! s :pos (+ (get s :pos) 1))
(dict-set! s :last-match cur)
cur))
(true nil)))))
;; Match if value is one of the given names.
(define
hs-stream-match-any
(fn
(s &rest names)
(let
((cur (hs-stream-current s)))
(cond
((nil? cur) nil)
((some (fn (n) (= (get cur :value) n)) names)
(do
(dict-set! s :pos (+ (get s :pos) 1))
(dict-set! s :last-match cur)
cur))
(true nil)))))
;; Match an op token whose value is in the list.
(define
hs-stream-match-any-op
(fn
(s &rest ops)
(let
((cur (hs-stream-current s)))
(cond
((nil? cur) nil)
((and (= (get cur :type) "op")
(some (fn (o) (= (get cur :value) o)) ops))
(do
(dict-set! s :pos (+ (get s :pos) 1))
(dict-set! s :last-match cur)
cur))
(true nil)))))
;; Peek N non-WS tokens ahead. Returns the token if its value matches; nil otherwise.
(define
hs-stream-peek
(fn
(s value offset)
(let
((tokens (get s :tokens)))
(define
skip-n-non-ws
(fn
(p remaining)
(cond
((>= p (len tokens)) -1)
((= (get (nth tokens p) :type) "whitespace")
(skip-n-non-ws (+ p 1) remaining))
((= remaining 0) p)
(true (skip-n-non-ws (+ p 1) (- remaining 1))))))
(let
((p (skip-n-non-ws (get s :pos) offset)))
(if (and (>= p 0) (< p (len tokens))
(= (get (nth tokens p) :value) value))
(nth tokens p)
nil)))))
;; Consume tokens until one whose value matches the marker. Returns
;; the consumed list (excluding the marker). Marker becomes current.
(define
hs-stream-consume-until
(fn
(s marker)
(let
((tokens (get s :tokens)) (out (list)))
(define
loop
(fn
(acc)
(let
((p (get s :pos)))
(cond
((>= p (len tokens)) acc)
((= (get (nth tokens p) :value) marker) acc)
(true
(do
(dict-set! s :pos (+ p 1))
(loop (append acc (list (nth tokens p))))))))))
(loop out))))
;; Consume until the next whitespace token; returns the consumed list.
(define
hs-stream-consume-until-ws
(fn
(s)
(let
((tokens (get s :tokens)))
(define
loop
(fn
(acc)
(let
((p (get s :pos)))
(cond
((>= p (len tokens)) acc)
((= (get (nth tokens p) :type) "whitespace") acc)
(true
(do
(dict-set! s :pos (+ p 1))
(loop (append acc (list (nth tokens p))))))))))
(loop (list)))))
;; Follow-set management.
(define hs-stream-push-follow! (fn (s v) (dict-set! s :follows (cons v (get s :follows)))))
(define
hs-stream-pop-follow!
(fn (s) (let ((f (get s :follows))) (when (> (len f) 0) (dict-set! s :follows (rest f))))))
(define
hs-stream-push-follows!
(fn (s vs) (for-each (fn (v) (hs-stream-push-follow! s v)) vs)))
(define
hs-stream-pop-follows!
(fn (s n) (when (> n 0) (do (hs-stream-pop-follow! s) (hs-stream-pop-follows! s (- n 1))))))
(define
hs-stream-clear-follows!
(fn (s) (let ((saved (get s :follows))) (do (dict-set! s :follows (list)) saved))))
(define
hs-stream-restore-follows!
(fn (s saved) (dict-set! s :follows saved)))
;; Last-consumed token / whitespace.
(define hs-stream-last-match (fn (s) (get s :last-match)))
(define hs-stream-last-ws (fn (s) (get s :last-ws)))

View File

@@ -1,234 +0,0 @@
;; lib/kernel/eval.sx — Kernel evaluator.
;;
;; The evaluator is `lookup-and-combine`: there are no hardcoded special
;; forms. Even $if / $define! / $lambda are ordinary operatives bound in
;; the standard environment (Phase 4). This file builds the dispatch
;; machinery and the operative/applicative tagged-value protocol.
;;
;; Tagged values
;; -------------
;; {:knl-tag :env :bindings DICT :parent PARENT-OR-NIL}
;; A first-class Kernel environment. Bindings is a mutable SX dict
;; keyed by symbol name; parent walks up the lookup chain.
;;
;; {:knl-tag :operative :impl FN}
;; Primitive operative. FN receives (args dyn-env) — args are the
;; UN-evaluated argument expressions, dyn-env is the calling env.
;;
;; {:knl-tag :operative :params P :env-param EP :body B :static-env SE}
;; User-defined operative (built by $vau). Same tag; dispatch in
;; kernel-call-operative forks on which keys are present.
;;
;; {:knl-tag :applicative :underlying OP}
;; An applicative wraps an operative. Calls evaluate args first,
;; then forward to the underlying operative.
;;
;; The env-param of a user operative may be the sentinel :knl-ignore,
;; in which case the dynamic env is not bound.
;;
;; Public API
;; (kernel-eval EXPR ENV) — primary entry
;; (kernel-combine COMBINER ARGS DYN-ENV)
;; (kernel-call-operative OP ARGS DYN-ENV)
;; (kernel-bind-params! ENV PARAMS ARGS)
;; (kernel-make-env) / (kernel-extend-env P)
;; (kernel-env-bind! E N V) / (kernel-env-lookup E N)
;; (kernel-env-has? E N) / (kernel-env? V)
;; (kernel-make-primitive-operative IMPL)
;; (kernel-make-primitive-applicative IMPL)
;; (kernel-make-user-operative PARAMS EPARAM BODY STATIC-ENV)
;; (kernel-wrap OP) / (kernel-unwrap APP)
;; (kernel-operative? V) / (kernel-applicative? V) / (kernel-combiner? V)
;;
;; Consumes: lib/kernel/parser.sx (kernel-string?, kernel-string-value)
;; ── Environments — first-class, pure-SX (binding dict + parent) ──
(define kernel-env? (fn (v) (and (dict? v) (= (get v :knl-tag) :env))))
(define kernel-make-env (fn () {:parent nil :knl-tag :env :bindings {}}))
(define kernel-extend-env (fn (parent) {:parent parent :knl-tag :env :bindings {}}))
(define
kernel-env-bind!
(fn (env name val) (dict-set! (get env :bindings) name val) val))
(define
kernel-env-has?
(fn
(env name)
(cond
((nil? env) false)
((not (kernel-env? env)) false)
((dict-has? (get env :bindings) name) true)
(:else (kernel-env-has? (get env :parent) name)))))
(define
kernel-env-lookup
(fn
(env name)
(cond
((nil? env) (error (str "kernel-eval: unbound symbol: " name)))
((not (kernel-env? env))
(error (str "kernel-eval: corrupt env: " env)))
((dict-has? (get env :bindings) name) (get (get env :bindings) name))
(:else (kernel-env-lookup (get env :parent) name)))))
;; ── Tagged-value constructors and predicates ─────────────────────
(define kernel-make-primitive-operative (fn (impl) {:impl impl :knl-tag :operative}))
(define
kernel-make-user-operative
(fn (params eparam body static-env) {:knl-tag :operative :static-env static-env :params params :body body :env-param eparam}))
(define
kernel-operative?
(fn (v) (and (dict? v) (= (get v :knl-tag) :operative))))
(define
kernel-applicative?
(fn (v) (and (dict? v) (= (get v :knl-tag) :applicative))))
(define
kernel-combiner?
(fn (v) (or (kernel-operative? v) (kernel-applicative? v))))
(define
kernel-wrap
(fn
(op)
(cond
((kernel-operative? op) {:knl-tag :applicative :underlying op})
(:else (error "kernel-wrap: argument must be an operative")))))
(define
kernel-unwrap
(fn
(app)
(cond
((kernel-applicative? app) (get app :underlying))
(:else (error "kernel-unwrap: argument must be an applicative")))))
(define
kernel-make-primitive-applicative
(fn
(impl)
(kernel-wrap
(kernel-make-primitive-operative (fn (args dyn-env) (impl args))))))
;; As above, but IMPL receives (args dyn-env). Used by combinators that
;; re-enter the evaluator (map, filter, reduce, apply, eval, ...).
(define kernel-make-primitive-applicative-with-env
(fn (impl)
(kernel-wrap
(kernel-make-primitive-operative
(fn (args dyn-env) (impl args dyn-env))))))
;; ── The evaluator ────────────────────────────────────────────────
(define
kernel-eval
(fn
(expr env)
(cond
((number? expr) expr)
((boolean? expr) expr)
((nil? expr) expr)
((kernel-string? expr) (kernel-string-value expr))
((string? expr) (kernel-env-lookup env expr))
((list? expr)
(cond
((= (length expr) 0) expr)
(:else
(let
((combiner (kernel-eval (first expr) env))
(args (rest expr)))
(kernel-combine combiner args env)))))
(:else (error (str "kernel-eval: unknown form: " expr))))))
(define
kernel-combine
(fn
(combiner args dyn-env)
(cond
((kernel-operative? combiner)
(kernel-call-operative combiner args dyn-env))
((kernel-applicative? combiner)
(kernel-combine
(get combiner :underlying)
(kernel-eval-args args dyn-env)
dyn-env))
(:else (error (str "kernel-eval: not a combiner: " combiner))))))
;; Operatives may be primitive (:impl is a host fn) or user-defined
;; (carry :params / :env-param / :body / :static-env). The dispatch
;; fork is here so kernel-combine stays small.
(define
kernel-call-operative
(fn
(op args dyn-env)
(cond
((dict-has? op :impl) ((get op :impl) args dyn-env))
((dict-has? op :body)
(let
((local (kernel-extend-env (get op :static-env))))
(kernel-bind-params! local (get op :params) args)
(let
((eparam (get op :env-param)))
(when
(not (= eparam :knl-ignore))
(kernel-env-bind! local eparam dyn-env)))
;; :body is a list of forms — evaluate in sequence, return last.
(knl-eval-body (get op :body) local)))
(:else (error "kernel-call-operative: malformed operative")))))
(define knl-eval-body
(fn (forms env)
(cond
((= (length forms) 1) (kernel-eval (first forms) env))
(:else
(begin
(kernel-eval (first forms) env)
(knl-eval-body (rest forms) env))))))
;; Phase 3 supports a flat parameter list only — destructuring later.
(define
kernel-bind-params!
(fn
(env params args)
(cond
((or (nil? params) (= (length params) 0))
(cond
((or (nil? args) (= (length args) 0)) nil)
(:else (error "kernel-call: too many arguments"))))
((or (nil? args) (= (length args) 0))
(error "kernel-call: too few arguments"))
(:else
(begin
(kernel-env-bind! env (first params) (first args))
(kernel-bind-params! env (rest params) (rest args)))))))
(define
kernel-eval-args
(fn
(args env)
(cond
((or (nil? args) (= (length args) 0)) (list))
(:else
(cons
(kernel-eval (first args) env)
(kernel-eval-args (rest args) env))))))
(define
kernel-eval-program
(fn
(forms env)
(cond
((or (nil? forms) (= (length forms) 0)) nil)
((= (length forms) 1) (kernel-eval (first forms) env))
(:else
(begin
(kernel-eval (first forms) env)
(kernel-eval-program (rest forms) env))))))

View File

@@ -1,253 +0,0 @@
;; lib/kernel/parser.sx — Kernel s-expression reader.
;;
;; Reads R-1RK lexical syntax: numbers, strings, symbols, booleans (#t/#f),
;; the empty list (), nested lists, and ; line comments. Reader macros
;; (' ` , ,@) deferred to Phase 6 per the plan.
;;
;; Public AST shape:
;; number → SX number
;; #t / #f → SX true / false
;; () → SX empty list (Kernel's nil — the empty list)
;; "..." → {:knl-string "..."} wrapped to distinguish from symbols
;; foo → "foo" bare SX string is a Kernel symbol
;; (a b c) → SX list of forms
;;
;; Public API:
;; (kernel-parse SRC) — first form; errors on extra trailing input
;; (kernel-parse-all SRC) — all top-level forms, as SX list
;; (kernel-string? V) — recognise wrapped string literal
;; (kernel-string-value V) — extract the underlying string
;;
;; Consumes: lib/guest/lex.sx (lex-digit?, lex-whitespace?)
(define kernel-string-make (fn (s) {:knl-string s}))
(define
kernel-string?
(fn (v) (and (dict? v) (string? (get v :knl-string)))))
(define kernel-string-value (fn (v) (get v :knl-string)))
;; Atom delimiters: characters that end a symbol or numeric token.
(define
knl-delim?
(fn
(c)
(or
(nil? c)
(lex-whitespace? c)
(= c "(")
(= c ")")
(= c "\"")
(= c ";")
(= c "'")
(= c "`")
(= c ","))))
;; Numeric grammar: [+-]? (digit+ ('.' digit+)? | '.' digit+) ([eE][+-]?digit+)?
(define
knl-numeric?
(fn
(s)
(let
((n (string-length s)))
(cond
((= n 0) false)
(:else
(let
((c0 (substring s 0 1)))
(let
((start (if (or (= c0 "+") (= c0 "-")) 1 0)))
(knl-num-body? s start n))))))))
(define
knl-num-body?
(fn
(s start n)
(cond
((>= start n) false)
((= (substring s start (+ start 1)) ".")
(knl-num-need-digits? s (+ start 1) n false))
((lex-digit? (substring s start (+ start 1)))
(knl-num-int-tail? s (+ start 1) n))
(:else false))))
(define
knl-num-int-tail?
(fn
(s i n)
(cond
((>= i n) true)
((lex-digit? (substring s i (+ i 1)))
(knl-num-int-tail? s (+ i 1) n))
((= (substring s i (+ i 1)) ".")
(knl-num-need-digits? s (+ i 1) n true))
((or (= (substring s i (+ i 1)) "e") (= (substring s i (+ i 1)) "E"))
(knl-num-exp-sign? s (+ i 1) n))
(:else false))))
(define
knl-num-need-digits?
(fn
(s i n had-int)
(cond
((>= i n) had-int)
((lex-digit? (substring s i (+ i 1)))
(knl-num-frac-tail? s (+ i 1) n))
(:else false))))
(define
knl-num-frac-tail?
(fn
(s i n)
(cond
((>= i n) true)
((lex-digit? (substring s i (+ i 1)))
(knl-num-frac-tail? s (+ i 1) n))
((or (= (substring s i (+ i 1)) "e") (= (substring s i (+ i 1)) "E"))
(knl-num-exp-sign? s (+ i 1) n))
(:else false))))
(define
knl-num-exp-sign?
(fn
(s i n)
(cond
((>= i n) false)
((or (= (substring s i (+ i 1)) "+") (= (substring s i (+ i 1)) "-"))
(knl-num-exp-digits? s (+ i 1) n false))
(:else (knl-num-exp-digits? s i n false)))))
(define
knl-num-exp-digits?
(fn
(s i n had)
(cond
((>= i n) had)
((lex-digit? (substring s i (+ i 1)))
(knl-num-exp-digits? s (+ i 1) n true))
(:else false))))
;; Reader: a closure over (src, pos). Exposes :read-form and :read-all.
(define
knl-make-reader
(fn
(src)
(let
((pos 0) (n (string-length src)))
(define
at
(fn () (if (< pos n) (substring src pos (+ pos 1)) nil)))
(define adv (fn () (set! pos (+ pos 1))))
(define
skip-line
(fn () (when (and (at) (not (= (at) "\n"))) (adv) (skip-line))))
(define
skip-ws
(fn
()
(cond
((nil? (at)) nil)
((lex-whitespace? (at)) (do (adv) (skip-ws)))
((= (at) ";") (do (adv) (skip-line) (skip-ws)))
(:else nil))))
(define
read-string-body
(fn
(acc)
(cond
((nil? (at)) (error "kernel-parse: unterminated string"))
((= (at) "\"") (do (adv) acc))
((= (at) "\\")
(do
(adv)
(let
((c (at)))
(when (nil? c) (error "kernel-parse: trailing backslash"))
(adv)
(read-string-body
(str
acc
(cond
((= c "n") "\n")
((= c "t") "\t")
((= c "r") "\r")
((= c "\"") "\"")
((= c "\\") "\\")
(:else c)))))))
(:else
(let ((c (at))) (adv) (read-string-body (str acc c)))))))
(define
read-atom-body
(fn
(acc)
(cond
((knl-delim? (at)) acc)
(:else (let ((c (at))) (adv) (read-atom-body (str acc c)))))))
(define
classify-atom
(fn
(s)
(cond
((= s "#t") true)
((= s "#f") false)
((knl-numeric? s) (string->number s))
(:else s))))
(define
read-form
(fn
()
(skip-ws)
(cond
((nil? (at)) :knl-eof)
((= (at) ")") (error "kernel-parse: unexpected ')'"))
((= (at) "(") (do (adv) (read-list (list))))
((= (at) "\"")
(do (adv) (kernel-string-make (read-string-body ""))))
((= (at) "'")
(do (adv) (list "$quote" (read-form))))
((= (at) "`")
(do (adv) (list "$quasiquote" (read-form))))
((= (at) ",")
(do (adv)
(cond
((= (at) "@")
(do (adv) (list "$unquote-splicing" (read-form))))
(:else (list "$unquote" (read-form))))))
(:else (classify-atom (read-atom-body ""))))))
(define
read-list
(fn
(acc)
(skip-ws)
(cond
((nil? (at)) (error "kernel-parse: unterminated list"))
((= (at) ")") (do (adv) acc))
(:else (read-list (append acc (list (read-form))))))))
(define
read-all
(fn
(acc)
(skip-ws)
(if (nil? (at)) acc (read-all (append acc (list (read-form)))))))
{:read-form read-form :read-all read-all})))
(define
kernel-parse-all
(fn (src) ((get (knl-make-reader src) :read-all) (list))))
(define
kernel-parse
(fn
(src)
(let
((r (knl-make-reader src)))
(let
((form ((get r :read-form))))
(cond
((= form :knl-eof) (error "kernel-parse: empty input"))
(:else
(let
((next ((get r :read-form))))
(if
(= next :knl-eof)
form
(error "kernel-parse: trailing input after first form")))))))))

View File

@@ -1,911 +0,0 @@
;; lib/kernel/runtime.sx — the operativeapplicative substrate and the
;; standard Kernel environment.
;;
;; Phase 3 supplied four user-visible combiners ($vau, $lambda, wrap,
;; unwrap). Phase 4 fills out the rest of the R-1RK core: $if, $define!,
;; $sequence, eval, make-environment, get-current-environment, plus
;; arithmetic, equality, list/pair, and boolean primitives — enough to
;; write factorial.
;;
;; The standard env is built by EXTENDING the base env, not replacing
;; it. So `kernel-standard-env` includes everything from `kernel-base-env`.
;;
;; Public API
;; (kernel-base-env) — Phase 3 combiners
;; (kernel-standard-env) — Phase 4 standard environment
(define
knl-eparam-sentinel
(fn
(sym)
(cond
((= sym "_") :knl-ignore)
((= sym "#ignore") :knl-ignore)
(:else sym))))
(define
knl-formals-ok?
(fn
(formals)
(cond
((not (list? formals)) false)
((= (length formals) 0) true)
((string? (first formals)) (knl-formals-ok? (rest formals)))
(:else false))))
;; ── $vau ─────────────────────────────────────────────────────────
(define
kernel-vau-impl
(fn
(args dyn-env)
(cond
((< (length args) 3)
(error "$vau: expects (formals env-param body...)"))
(:else
(let
((formals (first args))
(eparam-raw (nth args 1))
(body-forms (rest (rest args))))
(cond
((not (knl-formals-ok? formals))
(error "$vau: formals must be a list of symbols"))
((not (string? eparam-raw))
(error "$vau: env-param must be a symbol"))
(:else
(kernel-make-user-operative
formals
(knl-eparam-sentinel eparam-raw)
body-forms
dyn-env))))))))
(define
kernel-vau-operative
(kernel-make-primitive-operative kernel-vau-impl))
;; ── $lambda ──────────────────────────────────────────────────────
(define
kernel-lambda-impl
(fn
(args dyn-env)
(cond
((< (length args) 2)
(error "$lambda: expects (formals body...)"))
(:else
(let
((formals (first args)) (body-forms (rest args)))
(cond
((not (knl-formals-ok? formals))
(error "$lambda: formals must be a list of symbols"))
(:else
(kernel-wrap
(kernel-make-user-operative
formals
:knl-ignore
body-forms
dyn-env)))))))))
(define
kernel-lambda-operative
(kernel-make-primitive-operative kernel-lambda-impl))
;; ── wrap / unwrap / predicates ───────────────────────────────────
(define
kernel-wrap-applicative
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 1))
(error "wrap: expects exactly 1 argument"))
(:else (kernel-wrap (first args)))))))
(define
kernel-unwrap-applicative
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 1))
(error "unwrap: expects exactly 1 argument"))
(:else (kernel-unwrap (first args)))))))
(define
kernel-operative?-applicative
(kernel-make-primitive-applicative
(fn (args) (kernel-operative? (first args)))))
(define
kernel-applicative?-applicative
(kernel-make-primitive-applicative
(fn (args) (kernel-applicative? (first args)))))
(define
kernel-base-env
(fn
()
(let
((env (kernel-make-env)))
(kernel-env-bind! env "$vau" kernel-vau-operative)
(kernel-env-bind! env "$lambda" kernel-lambda-operative)
(kernel-env-bind! env "wrap" kernel-wrap-applicative)
(kernel-env-bind! env "unwrap" kernel-unwrap-applicative)
(kernel-env-bind! env "operative?" kernel-operative?-applicative)
(kernel-env-bind! env "applicative?" kernel-applicative?-applicative)
env)))
;; ── $if / $define! / $sequence ───────────────────────────────────
(define
kernel-if-operative
(kernel-make-primitive-operative
(fn
(args dyn-env)
(cond
((not (= (length args) 3))
(error "$if: expects (condition then-expr else-expr)"))
(:else
(let
((c (kernel-eval (first args) dyn-env)))
(if
c
(kernel-eval (nth args 1) dyn-env)
(kernel-eval (nth args 2) dyn-env))))))))
(define
kernel-define!-operative
(kernel-make-primitive-operative
(fn
(args dyn-env)
(cond
((not (= (length args) 2))
(error "$define!: expects (name expr)"))
((not (string? (first args)))
(error "$define!: name must be a symbol"))
(:else
(let
((v (kernel-eval (nth args 1) dyn-env)))
(kernel-env-bind! dyn-env (first args) v)
v))))))
(define
kernel-sequence-operative
(kernel-make-primitive-operative
(fn
(args dyn-env)
(cond
((or (nil? args) (= (length args) 0)) nil)
((= (length args) 1) (kernel-eval (first args) dyn-env))
(:else
(begin
(kernel-eval (first args) dyn-env)
((get kernel-sequence-operative :impl) (rest args) dyn-env)))))))
;; ── eval / make-environment / get-current-environment ───────────
(define
kernel-quote-operative
(kernel-make-primitive-operative
(fn
(args dyn-env)
(cond
((not (= (length args) 1)) (error "$quote: expects 1 argument"))
(:else (first args))))))
;; Quasiquote: walks the template, evaluating `$unquote` forms in the
;; dynamic env and splicing `$unquote-splicing` list results.
(define knl-quasi-walk
(fn (form dyn-env)
(cond
((not (list? form)) form)
((= (length form) 0) form)
((and (string? (first form)) (= (first form) "$unquote"))
(cond
((not (= (length form) 2))
(error "$unquote: expects exactly 1 argument"))
(:else (kernel-eval (nth form 1) dyn-env))))
(:else (knl-quasi-walk-list form dyn-env)))))
(define knl-quasi-walk-list
(fn (forms dyn-env)
(cond
((or (nil? forms) (= (length forms) 0)) (list))
(:else
(let ((head (first forms)))
(cond
((and (list? head)
(= (length head) 2)
(string? (first head))
(= (first head) "$unquote-splicing"))
(let ((spliced (kernel-eval (nth head 1) dyn-env)))
(cond
((not (list? spliced))
(error "$unquote-splicing: value must be a list"))
(:else
(knl-list-concat
spliced
(knl-quasi-walk-list (rest forms) dyn-env))))))
(:else
(cons (knl-quasi-walk head dyn-env)
(knl-quasi-walk-list (rest forms) dyn-env)))))))))
(define knl-list-concat
(fn (xs ys)
(cond
((or (nil? xs) (= (length xs) 0)) ys)
(:else (cons (first xs) (knl-list-concat (rest xs) ys))))))
;; $cond — multi-clause branch.
;; ($cond (TEST1 EXPR1 ...) (TEST2 EXPR2 ...) ...)
;; Evaluates each TEST in order; first truthy one runs its EXPRs (in
;; sequence) and returns the last; if no TEST is truthy, returns nil.
;; A clause with TEST = `else` always matches (sugar for $if's default).
(define knl-cond-impl
(fn (clauses dyn-env)
(cond
((or (nil? clauses) (= (length clauses) 0)) nil)
(:else
(let ((clause (first clauses)))
(cond
((not (list? clause))
(error "$cond: each clause must be a list"))
((= (length clause) 0)
(error "$cond: empty clause"))
((and (string? (first clause)) (= (first clause) "else"))
(knl-cond-eval-body (rest clause) dyn-env))
(:else
(let ((test-val (kernel-eval (first clause) dyn-env)))
(cond
(test-val (knl-cond-eval-body (rest clause) dyn-env))
(:else (knl-cond-impl (rest clauses) dyn-env)))))))))))
(define knl-cond-eval-body
(fn (body dyn-env)
(cond
((or (nil? body) (= (length body) 0)) nil)
((= (length body) 1) (kernel-eval (first body) dyn-env))
(:else
(begin
(kernel-eval (first body) dyn-env)
(knl-cond-eval-body (rest body) dyn-env))))))
(define kernel-cond-operative
(kernel-make-primitive-operative
(fn (args dyn-env) (knl-cond-impl args dyn-env))))
;; $when COND BODY... — evaluate body iff COND is truthy; else nil.
(define kernel-when-operative
(kernel-make-primitive-operative
(fn (args dyn-env)
(cond
((< (length args) 1)
(error "$when: expects (cond body...)"))
(:else
(let ((c (kernel-eval (first args) dyn-env)))
(cond
(c (knl-cond-eval-body (rest args) dyn-env))
(:else nil))))))))
;; $and? — short-circuit AND. Operative (not applicative) so untaken
;; clauses are NOT evaluated. Empty $and? returns true (the identity).
(define knl-and?-impl
(fn (args dyn-env)
(cond
((or (nil? args) (= (length args) 0)) true)
((= (length args) 1) (kernel-eval (first args) dyn-env))
(:else
(let ((v (kernel-eval (first args) dyn-env)))
(cond
(v (knl-and?-impl (rest args) dyn-env))
(:else v)))))))
(define kernel-and?-operative
(kernel-make-primitive-operative knl-and?-impl))
;; $or? — short-circuit OR. Operative; untaken clauses NOT evaluated.
;; Empty $or? returns false (the identity).
(define knl-or?-impl
(fn (args dyn-env)
(cond
((or (nil? args) (= (length args) 0)) false)
((= (length args) 1) (kernel-eval (first args) dyn-env))
(:else
(let ((v (kernel-eval (first args) dyn-env)))
(cond
(v v)
(:else (knl-or?-impl (rest args) dyn-env))))))))
(define kernel-or?-operative
(kernel-make-primitive-operative knl-or?-impl))
;; $unless COND BODY... — evaluate body iff COND is falsy; else nil.
(define kernel-unless-operative
(kernel-make-primitive-operative
(fn (args dyn-env)
(cond
((< (length args) 1)
(error "$unless: expects (cond body...)"))
(:else
(let ((c (kernel-eval (first args) dyn-env)))
(cond
(c nil)
(:else (knl-cond-eval-body (rest args) dyn-env)))))))))
(define kernel-quasiquote-operative
(kernel-make-primitive-operative
(fn (args dyn-env)
(cond
((not (= (length args) 1))
(error "$quasiquote: expects exactly 1 argument"))
(:else (knl-quasi-walk (first args) dyn-env))))))
(define
kernel-eval-applicative
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 2))
(error "eval: expects (expr env)"))
((not (kernel-env? (nth args 1)))
(error "eval: second arg must be a kernel env"))
(:else (kernel-eval (first args) (nth args 1)))))))
(define
kernel-make-environment-applicative
(kernel-make-primitive-applicative
(fn
(args)
(cond
((= (length args) 0) (kernel-make-env))
((= (length args) 1)
(cond
((not (kernel-env? (first args)))
(error "make-environment: parent must be a kernel env"))
(:else (kernel-extend-env (first args)))))
(:else (error "make-environment: 0 or 1 argument"))))))
;; ── arithmetic and comparison (binary; trivial to extend later) ─
(define
kernel-get-current-env-operative
(kernel-make-primitive-operative
(fn
(args dyn-env)
(cond
((not (= (length args) 0))
(error "get-current-environment: expects 0 arguments"))
(:else dyn-env)))))
(define
knl-bin-app
(fn
(name f)
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 2))
(error (str name ": expects 2 arguments")))
(:else (f (first args) (nth args 1))))))))
;; Variadic left-fold helper. ZERO-RES is the identity (`(+)` → 0);
;; ONE-FN handles single-arg case (`(- x)` negates; `(+ x)` returns x).
(define knl-fold-step
(fn (f acc rest-args)
(cond
((or (nil? rest-args) (= (length rest-args) 0)) acc)
(:else
(knl-fold-step f (f acc (first rest-args)) (rest rest-args))))))
(define knl-fold-app
(fn (name f zero-res one-fn)
(kernel-make-primitive-applicative
(fn (args)
(cond
((= (length args) 0) zero-res)
((= (length args) 1) (one-fn (first args)))
(:else (knl-fold-step f (first args) (rest args))))))))
;; Variadic n-ary chained comparison: `(< 1 2 3)` ≡ `(< 1 2)` AND `(< 2 3)`.
(define knl-chain-step
(fn (cmp prev rest-args)
(cond
((or (nil? rest-args) (= (length rest-args) 0)) true)
(:else
(let ((next (first rest-args)))
(cond
((cmp prev next)
(knl-chain-step cmp next (rest rest-args)))
(:else false)))))))
(define knl-chain-cmp
(fn (name cmp)
(kernel-make-primitive-applicative
(fn (args)
(cond
((< (length args) 2)
(error (str name ": expects at least 2 arguments")))
(:else (knl-chain-step cmp (first args) (rest args))))))))
;; ── list / pair primitives ──────────────────────────────────────
(define
knl-unary-app
(fn
(name f)
(kernel-make-primitive-applicative
(fn
(args)
(cond
((not (= (length args) 1))
(error (str name ": expects 1 argument")))
(:else (f (first args))))))))
(define kernel-cons-applicative (knl-bin-app "cons" (fn (a b) (cons a b))))
(define
kernel-car-applicative
(knl-unary-app
"car"
(fn
(xs)
(cond
((or (nil? xs) (and (list? xs) (= (length xs) 0)))
(error "car: empty list"))
(:else (first xs))))))
(define
kernel-cdr-applicative
(knl-unary-app
"cdr"
(fn
(xs)
(cond
((or (nil? xs) (and (list? xs) (= (length xs) 0)))
(error "cdr: empty list"))
(:else (rest xs))))))
(define
kernel-list-applicative
(kernel-make-primitive-applicative (fn (args) args)))
(define
kernel-length-applicative
(knl-unary-app "length" (fn (xs) (length xs))))
(define
kernel-null?-applicative
(knl-unary-app
"null?"
(fn (v) (or (nil? v) (and (list? v) (= (length v) 0))))))
;; ── boolean / equality ──────────────────────────────────────────
(define
kernel-pair?-applicative
(knl-unary-app
"pair?"
(fn (v) (and (list? v) (> (length v) 0)))))
(define knl-append-step
(fn (xs ys)
(cond
((or (nil? xs) (= (length xs) 0)) ys)
(:else (cons (first xs) (knl-append-step (rest xs) ys))))))
(define knl-all-lists?
(fn (xs)
(cond
((or (nil? xs) (= (length xs) 0)) true)
((list? (first xs)) (knl-all-lists? (rest xs)))
(:else false))))
(define knl-append-all
(fn (lists)
(cond
((or (nil? lists) (= (length lists) 0)) (list))
((= (length lists) 1) (first lists))
(:else
(knl-append-step (first lists)
(knl-append-all (rest lists)))))))
(define kernel-append-applicative
(kernel-make-primitive-applicative
(fn (args)
(cond
((knl-all-lists? args) (knl-append-all args))
(:else (error "append: all arguments must be lists"))))))
(define knl-reverse-step
(fn (xs acc)
(cond
((or (nil? xs) (= (length xs) 0)) acc)
(:else (knl-reverse-step (rest xs) (cons (first xs) acc))))))
(define kernel-reverse-applicative
(knl-unary-app "reverse"
(fn (xs)
(cond
((not (list? xs)) (error "reverse: argument must be a list"))
(:else (knl-reverse-step xs (list)))))))
(define kernel-not-applicative (knl-unary-app "not" (fn (v) (not v))))
;; Type predicates (Kernel-visible). Note `string?` covers BOTH symbols
;; and string-literals in our representation (symbols are bare SX
;; strings); a `kernel-string?` applicative distinguishes the two if
;; needed.
(define kernel-number?-applicative
(knl-unary-app "number?" (fn (v) (number? v))))
(define kernel-string?-applicative
(knl-unary-app "string?" (fn (v) (string? v))))
(define kernel-list?-applicative
(knl-unary-app "list?" (fn (v) (list? v))))
(define kernel-boolean?-applicative
(knl-unary-app "boolean?" (fn (v) (boolean? v))))
(define kernel-symbol?-applicative
(knl-unary-app "symbol?" (fn (v) (string? v))))
(define kernel-eq?-applicative (knl-bin-app "eq?" (fn (a b) (= a b))))
;; ── the standard environment ────────────────────────────────────
(define
kernel-equal?-applicative
(knl-bin-app "equal?" (fn (a b) (= a b))))
;; ── List combinators: map / filter / reduce ─────────────────────
;; These re-enter the evaluator on each element, so they use the
;; with-env applicative constructor.
;; When the combiner is an applicative, we MUST unwrap before calling
;; — otherwise kernel-combine will re-evaluate the already-evaluated
;; element values (and crash if an element is itself a list).
(define knl-apply-op
(fn (combiner)
(cond
((kernel-applicative? combiner) (kernel-unwrap combiner))
(:else combiner))))
(define knl-map-step
(fn (fn-val xs dyn-env)
(let ((op (knl-apply-op fn-val)))
(knl-map-walk op xs dyn-env))))
(define knl-map-walk
(fn (op xs dyn-env)
(cond
((or (nil? xs) (= (length xs) 0)) (list))
(:else
(cons (kernel-combine op (list (first xs)) dyn-env)
(knl-map-walk op (rest xs) dyn-env))))))
(define kernel-map-applicative
(kernel-make-primitive-applicative-with-env
(fn (args dyn-env)
(cond
((not (= (length args) 2))
(error "map: expects (fn list)"))
((not (kernel-combiner? (first args)))
(error "map: first arg must be a combiner"))
((not (list? (nth args 1)))
(error "map: second arg must be a list"))
(:else (knl-map-step (first args) (nth args 1) dyn-env))))))
(define knl-filter-step
(fn (pred xs dyn-env)
(knl-filter-walk (knl-apply-op pred) xs dyn-env)))
(define knl-filter-walk
(fn (op xs dyn-env)
(cond
((or (nil? xs) (= (length xs) 0)) (list))
(:else
(let ((keep? (kernel-combine op (list (first xs)) dyn-env)))
(cond
(keep?
(cons (first xs) (knl-filter-walk op (rest xs) dyn-env)))
(:else (knl-filter-walk op (rest xs) dyn-env))))))))
(define kernel-filter-applicative
(kernel-make-primitive-applicative-with-env
(fn (args dyn-env)
(cond
((not (= (length args) 2))
(error "filter: expects (pred list)"))
((not (kernel-combiner? (first args)))
(error "filter: first arg must be a combiner"))
((not (list? (nth args 1)))
(error "filter: second arg must be a list"))
(:else (knl-filter-step (first args) (nth args 1) dyn-env))))))
(define knl-reduce-step
(fn (fn-val xs acc dyn-env)
(knl-reduce-walk (knl-apply-op fn-val) xs acc dyn-env)))
(define knl-reduce-walk
(fn (op xs acc dyn-env)
(cond
((or (nil? xs) (= (length xs) 0)) acc)
(:else
(knl-reduce-walk
op
(rest xs)
(kernel-combine op (list acc (first xs)) dyn-env)
dyn-env)))))
;; (apply COMBINER ARGS-LIST) — call COMBINER with the elements of
;; ARGS-LIST as arguments. The Kernel canonical use: turn a constructed
;; list of values into a function call. We skip the applicative's
;; auto-eval step (via unwrap) because ARGS-LIST is already values, not
;; expressions; for a bare operative, we pass through directly.
(define kernel-apply-applicative
(kernel-make-primitive-applicative-with-env
(fn (args dyn-env)
(cond
((not (= (length args) 2))
(error "apply: expects (combiner args-list)"))
((not (kernel-combiner? (first args)))
(error "apply: first arg must be a combiner"))
((not (list? (nth args 1)))
(error "apply: second arg must be a list"))
(:else
(let ((op (cond
((kernel-applicative? (first args))
(kernel-unwrap (first args)))
(:else (first args)))))
(kernel-combine op (nth args 1) dyn-env)))))))
(define kernel-reduce-applicative
(kernel-make-primitive-applicative-with-env
(fn (args dyn-env)
(cond
((not (= (length args) 3))
(error "reduce: expects (fn init list)"))
((not (kernel-combiner? (first args)))
(error "reduce: first arg must be a combiner"))
((not (list? (nth args 2)))
(error "reduce: third arg must be a list"))
(:else
(knl-reduce-step (first args) (nth args 2)
(nth args 1) dyn-env))))))
;; ── Encapsulations: Kernel's opaque-type idiom ──────────────────
;;
;; (make-encapsulation-type) → (encapsulator predicate decapsulator)
;;
;; Each call returns three applicatives over a fresh family identity.
;; - (encapsulator V) → an opaque wrapper around V.
;; - (predicate V) → true iff V was wrapped by THIS family.
;; - (decapsulator W) → the inner value; errors on wrong family.
;;
;; Family identity is a fresh empty dict; SX compares dicts by reference,
;; so two `(make-encapsulation-type)` calls return distinct families.
;;
;; Pattern usage (Phase 5 lacks destructuring, so accessors are explicit):
;; ($define! triple (make-encapsulation-type))
;; ($define! wrap-promise (car triple))
;; ($define! promise? (car (cdr triple)))
;; ($define! unwrap-promise (car (cdr (cdr triple))))
(define kernel-make-encap-type-impl
(fn (args)
(cond
((not (= (length args) 0))
(error "make-encapsulation-type: expects 0 arguments"))
(:else
(let ((family {}))
(let ((encap
(kernel-make-primitive-applicative
(fn (vargs)
(cond
((not (= (length vargs) 1))
(error "encapsulator: expects 1 argument"))
(:else
{:knl-tag :encap
:family family
:value (first vargs)})))))
(pred
(kernel-make-primitive-applicative
(fn (vargs)
(cond
((not (= (length vargs) 1))
(error "predicate: expects 1 argument"))
(:else
(let ((v (first vargs)))
(and (dict? v)
(= (get v :knl-tag) :encap)
(= (get v :family) family))))))))
(decap
(kernel-make-primitive-applicative
(fn (vargs)
(cond
((not (= (length vargs) 1))
(error "decapsulator: expects 1 argument"))
(:else
(let ((v (first vargs)))
(cond
((not (and (dict? v)
(= (get v :knl-tag) :encap)))
(error "decapsulator: not an encapsulation"))
((not (= (get v :family) family))
(error "decapsulator: wrong family"))
(:else (get v :value))))))))))
(list encap pred decap)))))))
(define kernel-make-encap-type-applicative
(kernel-make-primitive-applicative kernel-make-encap-type-impl))
;; ── Hygiene: $let, $define-in!, make-environment ────────────────
;;
;; Kernel-on-SX is hygienic *by default* because user-defined operatives
;; (Phase 3) bind their formals + any $define! in a CHILD env extending
;; the operative's static-env, never the dyn-env. The caller's env is
;; only mutated when code explicitly says so (e.g. `(eval expr env-arg)`).
;;
;; Phase 6 adds two helpers that make the property easy to lean on:
;;
;; ($let ((NAME EXPR) ...) BODY)
;; Evaluates each EXPR in the calling env, binds NAME in a fresh
;; child env, evaluates BODY in that child env. NAMES don't leak.
;;
;; ($define-in! ENV NAME EXPR)
;; Binds NAME=value-of-EXPR in the *specified* env, not the dyn-env.
;; Useful for operatives that need to mutate a sandbox env without
;; touching their caller's env.
;;
;; Shutt's full scope-set / frame-stamp hygiene (lifted symbols carrying
;; provenance markers so introduced bindings can shadow without
;; capturing) is research-grade and not implemented here. Notes for
;; `lib/guest/reflective/hygiene.sx` candidate API below the std env.
(define knl-bind-let-vals!
(fn (local bindings dyn-env)
(cond
((or (nil? bindings) (= (length bindings) 0)) nil)
(:else
(let ((b (first bindings)))
(cond
((not (and (list? b) (= (length b) 2)))
(error "$let: each binding must be (name expr)"))
((not (string? (first b)))
(error "$let: binding name must be a symbol"))
(:else
(begin
(kernel-env-bind! local
(first b)
(kernel-eval (nth b 1) dyn-env))
(knl-bind-let-vals! local (rest bindings) dyn-env)))))))))
(define kernel-let-operative
(kernel-make-primitive-operative
(fn (args dyn-env)
(cond
((< (length args) 2)
(error "$let: expects (bindings body...)"))
((not (list? (first args)))
(error "$let: bindings must be a list"))
(:else
(let ((local (kernel-extend-env dyn-env)))
(knl-bind-let-vals! local (first args) dyn-env)
(knl-eval-body (rest args) local)))))))
;; $let* — sequential let. Each binding sees prior names in scope.
;; Implemented by nesting envs one per binding; the body runs in the
;; innermost env, so later bindings shadow earlier ones if names repeat.
(define knl-let*-step
(fn (bindings env body-forms)
(cond
((or (nil? bindings) (= (length bindings) 0))
(knl-eval-body body-forms env))
(:else
(let ((b (first bindings)))
(cond
((not (and (list? b) (= (length b) 2)))
(error "$let*: each binding must be (name expr)"))
((not (string? (first b)))
(error "$let*: binding name must be a symbol"))
(:else
(let ((child (kernel-extend-env env)))
(kernel-env-bind! child
(first b)
(kernel-eval (nth b 1) env))
(knl-let*-step (rest bindings) child body-forms)))))))))
(define kernel-let*-operative
(kernel-make-primitive-operative
(fn (args dyn-env)
(cond
((< (length args) 2)
(error "$let*: expects (bindings body...)"))
((not (list? (first args)))
(error "$let*: bindings must be a list"))
(:else
(knl-let*-step (first args) dyn-env (rest args)))))))
(define kernel-define-in!-operative
(kernel-make-primitive-operative
(fn (args dyn-env)
(cond
((not (= (length args) 3))
(error "$define-in!: expects (env-expr name expr)"))
((not (string? (nth args 1)))
(error "$define-in!: name must be a symbol"))
(:else
(let ((target (kernel-eval (first args) dyn-env)))
(cond
((not (kernel-env? target))
(error "$define-in!: first arg must evaluate to an env"))
(:else
(let ((v (kernel-eval (nth args 2) dyn-env)))
(kernel-env-bind! target (nth args 1) v)
v)))))))))
(define
kernel-standard-env
(fn
()
(let
((env (kernel-base-env)))
(kernel-env-bind! env "$if" kernel-if-operative)
(kernel-env-bind! env "$define!" kernel-define!-operative)
(kernel-env-bind! env "$sequence" kernel-sequence-operative)
(kernel-env-bind! env "$quote" kernel-quote-operative)
(kernel-env-bind! env "$quasiquote" kernel-quasiquote-operative)
(kernel-env-bind! env "$cond" kernel-cond-operative)
(kernel-env-bind! env "$when" kernel-when-operative)
(kernel-env-bind! env "$unless" kernel-unless-operative)
(kernel-env-bind! env "$and?" kernel-and?-operative)
(kernel-env-bind! env "$or?" kernel-or?-operative)
(kernel-env-bind! env "eval" kernel-eval-applicative)
(kernel-env-bind!
env
"make-environment"
kernel-make-environment-applicative)
(kernel-env-bind!
env
"get-current-environment"
kernel-get-current-env-operative)
(kernel-env-bind! env "+"
(knl-fold-app "+" (fn (a b) (+ a b)) 0 (fn (x) x)))
(kernel-env-bind! env "-"
(knl-fold-app "-" (fn (a b) (- a b)) 0 (fn (x) (- 0 x))))
(kernel-env-bind! env "*"
(knl-fold-app "*" (fn (a b) (* a b)) 1 (fn (x) x)))
(kernel-env-bind! env "/"
(knl-fold-app "/" (fn (a b) (/ a b)) 1 (fn (x) (/ 1 x))))
(kernel-env-bind! env "<" (knl-chain-cmp "<" (fn (a b) (< a b))))
(kernel-env-bind! env ">" (knl-chain-cmp ">" (fn (a b) (> a b))))
(kernel-env-bind! env "<=?" (knl-chain-cmp "<=?" (fn (a b) (<= a b))))
(kernel-env-bind! env ">=?" (knl-chain-cmp ">=?" (fn (a b) (>= a b))))
(kernel-env-bind! env "=?" kernel-eq?-applicative)
(kernel-env-bind! env "equal?" kernel-equal?-applicative)
(kernel-env-bind! env "eq?" kernel-eq?-applicative)
(kernel-env-bind! env "cons" kernel-cons-applicative)
(kernel-env-bind! env "car" kernel-car-applicative)
(kernel-env-bind! env "cdr" kernel-cdr-applicative)
(kernel-env-bind! env "list" kernel-list-applicative)
(kernel-env-bind! env "length" kernel-length-applicative)
(kernel-env-bind! env "null?" kernel-null?-applicative)
(kernel-env-bind! env "pair?" kernel-pair?-applicative)
(kernel-env-bind! env "map" kernel-map-applicative)
(kernel-env-bind! env "filter" kernel-filter-applicative)
(kernel-env-bind! env "reduce" kernel-reduce-applicative)
(kernel-env-bind! env "apply" kernel-apply-applicative)
(kernel-env-bind! env "append" kernel-append-applicative)
(kernel-env-bind! env "reverse" kernel-reverse-applicative)
(kernel-env-bind! env "number?" kernel-number?-applicative)
(kernel-env-bind! env "string?" kernel-string?-applicative)
(kernel-env-bind! env "list?" kernel-list?-applicative)
(kernel-env-bind! env "boolean?" kernel-boolean?-applicative)
(kernel-env-bind! env "symbol?" kernel-symbol?-applicative)
(kernel-env-bind! env "not" kernel-not-applicative)
(kernel-env-bind! env "make-encapsulation-type"
kernel-make-encap-type-applicative)
(kernel-env-bind! env "$let" kernel-let-operative)
(kernel-env-bind! env "$let*" kernel-let*-operative)
(kernel-env-bind! env "$define-in!" kernel-define-in!-operative)
env)))

View File

@@ -1,183 +0,0 @@
;; lib/kernel/tests/encap.sx — exercises make-encapsulation-type.
;;
;; The Phase 5 Kernel idiom: build opaque types whose constructor,
;; predicate, and accessor are all standard Kernel applicatives. The
;; identity is per-call, so two `(make-encapsulation-type)` calls
;; produce non-interchangeable families.
(define ken-test-pass 0)
(define ken-test-fail 0)
(define ken-test-fails (list))
(define
ken-test
(fn
(name actual expected)
(if
(= actual expected)
(set! ken-test-pass (+ ken-test-pass 1))
(begin
(set! ken-test-fail (+ ken-test-fail 1))
(append! ken-test-fails {:name name :actual actual :expected expected})))))
(define ken-eval-in (fn (src env) (kernel-eval (kernel-parse src) env)))
;; A helper that builds a standard env with `encap`/`pred?`/`decap`
;; bound from a single call to make-encapsulation-type.
(define
ken-make-encap-env
(fn
()
(let
((env (kernel-standard-env)))
(ken-eval-in "($define! triple (make-encapsulation-type))" env)
(ken-eval-in "($define! encap (car triple))" env)
(ken-eval-in "($define! pred? (car (cdr triple)))" env)
(ken-eval-in "($define! decap (car (cdr (cdr triple))))" env)
env)))
;; ── construction ────────────────────────────────────────────────
(ken-test
"make: returns 3-element list"
(ken-eval-in "(length (make-encapsulation-type))" (kernel-standard-env))
3)
(ken-test
"make: first is applicative"
(kernel-applicative?
(ken-eval-in "(car (make-encapsulation-type))" (kernel-standard-env)))
true)
(ken-test
"make: second is applicative"
(kernel-applicative?
(ken-eval-in
"(car (cdr (make-encapsulation-type)))"
(kernel-standard-env)))
true)
(ken-test
"make: third is applicative"
(kernel-applicative?
(ken-eval-in
"(car (cdr (cdr (make-encapsulation-type))))"
(kernel-standard-env)))
true)
;; ── round-trip ──────────────────────────────────────────────────
(ken-test
"round-trip: number"
(ken-eval-in "(decap (encap 42))" (ken-make-encap-env))
42)
(ken-test
"round-trip: string"
(ken-eval-in "(decap (encap ($quote hello)))" (ken-make-encap-env))
"hello")
(ken-test
"round-trip: list"
(ken-eval-in "(decap (encap (list 1 2 3)))" (ken-make-encap-env))
(list 1 2 3))
;; ── predicate ───────────────────────────────────────────────────
(ken-test
"pred?: wrapped value"
(ken-eval-in "(pred? (encap 1))" (ken-make-encap-env))
true)
(ken-test
"pred?: raw value"
(ken-eval-in "(pred? 1)" (ken-make-encap-env))
false)
(ken-test
"pred?: raw string"
(ken-eval-in "(pred? ($quote foo))" (ken-make-encap-env))
false)
(ken-test
"pred?: raw list"
(ken-eval-in "(pred? (list))" (ken-make-encap-env))
false)
;; ── opacity: different families are not interchangeable ─────────
(ken-test
"opacity: foreign value rejected by predicate"
(let
((env (kernel-standard-env)))
(ken-eval-in "($define! tA (make-encapsulation-type))" env)
(ken-eval-in "($define! tB (make-encapsulation-type))" env)
(ken-eval-in "($define! encA (car tA))" env)
(ken-eval-in "($define! predB (car (cdr tB)))" env)
(ken-eval-in "(predB (encA 42))" env))
false)
(ken-test
"opacity: decap rejects foreign value"
(let
((env (kernel-standard-env)))
(ken-eval-in "($define! tA (make-encapsulation-type))" env)
(ken-eval-in "($define! tB (make-encapsulation-type))" env)
(ken-eval-in "($define! encA (car tA))" env)
(ken-eval-in "($define! decapB (car (cdr (cdr tB))))" env)
(guard (e (true :raised)) (ken-eval-in "(decapB (encA 42))" env)))
:raised)
(ken-test
"opacity: decap rejects raw value"
(guard
(e (true :raised))
(ken-eval-in "(decap 42)" (ken-make-encap-env)))
:raised)
;; ── promise: classic Kernel encapsulation use case ──────────────
;; A "promise" wraps a thunk to compute on demand and memoises the
;; first result. Built entirely with the standard encap idiom.
(ken-test
"promise: force returns thunk result"
(let
((env (kernel-standard-env)))
(ken-eval-in
"($sequence\n ($define! ptriple (make-encapsulation-type))\n ($define! make-promise (car ptriple))\n ($define! promise? (car (cdr ptriple)))\n ($define! decode-promise (car (cdr (cdr ptriple))))\n ($define! force ($lambda (p) ((decode-promise p))))\n ($define! delay ($lambda (thunk) (make-promise thunk)))\n (force (delay ($lambda () (+ 19 23)))))"
env))
42)
(ken-test
"promise: promise? recognises its own type"
(let
((env (kernel-standard-env)))
(ken-eval-in
"($sequence\n ($define! ptriple (make-encapsulation-type))\n ($define! make-promise (car ptriple))\n ($define! promise? (car (cdr ptriple)))\n (promise? (make-promise ($lambda () 42))))"
env))
true)
(ken-test
"promise: promise? false on plain value"
(let
((env (kernel-standard-env)))
(ken-eval-in
"($sequence\n ($define! ptriple (make-encapsulation-type))\n ($define! promise? (car (cdr ptriple)))\n (promise? 99))"
env))
false)
;; ── independent families don't leak ─────────────────────────────
(ken-test
"two families: distinct identity"
(let
((env (kernel-standard-env)))
(ken-eval-in
"($sequence\n ($define! t1 (make-encapsulation-type))\n ($define! t2 (make-encapsulation-type))\n ($define! enc1 (car t1))\n ($define! pred2 (car (cdr t2)))\n (pred2 (enc1 ($quote stuff))))"
env))
false)
(ken-test
"same family: re-bound shares identity"
(let
((env (kernel-standard-env)))
(ken-eval-in
"($sequence\n ($define! t (make-encapsulation-type))\n ($define! e (car t))\n ($define! p (car (cdr t)))\n ($define! d (car (cdr (cdr t))))\n (list (p (e 7)) (d (e 7))))"
env))
(list true 7))
(define ken-tests-run! (fn () {:total (+ ken-test-pass ken-test-fail) :passed ken-test-pass :failed ken-test-fail :fails ken-test-fails}))

View File

@@ -1,270 +0,0 @@
;; lib/kernel/tests/eval.sx — exercises lib/kernel/eval.sx.
;;
;; Phase 2 covers literal evaluation, symbol lookup, and combiner
;; dispatch (operative vs applicative). Standard-environment operatives
;; ($if, $define!, $lambda, …) arrive in Phase 4, so tests build a
;; minimal env on the fly and verify the dispatch contract directly.
(define ke-test-pass 0)
(define ke-test-fail 0)
(define ke-test-fails (list))
(define
ke-test
(fn
(name actual expected)
(if
(= actual expected)
(set! ke-test-pass (+ ke-test-pass 1))
(begin
(set! ke-test-fail (+ ke-test-fail 1))
(append! ke-test-fails {:name name :actual actual :expected expected})))))
;; ── helpers ──────────────────────────────────────────────────────
(define ke-eval-src (fn (src env) (kernel-eval (kernel-parse src) env)))
(define
ke-make-test-env
(fn
()
(let
((env (kernel-make-env)))
(kernel-env-bind!
env
"+"
(kernel-make-primitive-applicative
(fn (args) (+ (first args) (nth args 1)))))
(kernel-env-bind!
env
"list"
(kernel-make-primitive-applicative (fn (args) args)))
(kernel-env-bind!
env
"$quote"
(kernel-make-primitive-operative (fn (args dyn-env) (first args))))
(kernel-env-bind!
env
"$if"
(kernel-make-primitive-operative
(fn
(args dyn-env)
(if
(kernel-eval (first args) dyn-env)
(kernel-eval (nth args 1) dyn-env)
(kernel-eval (nth args 2) dyn-env)))))
env)))
;; ── literal evaluation ───────────────────────────────────────────
(ke-test "lit: number" (ke-eval-src "42" (kernel-make-env)) 42)
(ke-test "lit: zero" (ke-eval-src "0" (kernel-make-env)) 0)
(ke-test "lit: float" (ke-eval-src "3.14" (kernel-make-env)) 3.14)
(ke-test "lit: true" (ke-eval-src "#t" (kernel-make-env)) true)
(ke-test "lit: false" (ke-eval-src "#f" (kernel-make-env)) false)
(ke-test "lit: string" (ke-eval-src "\"hello\"" (kernel-make-env)) "hello")
(ke-test "lit: empty list" (ke-eval-src "()" (kernel-make-env)) (list))
;; ── symbol lookup ────────────────────────────────────────────────
(ke-test
"sym: bound to number"
(let
((env (kernel-make-env)))
(kernel-env-bind! env "x" 100)
(ke-eval-src "x" env))
100)
(ke-test
"sym: bound to string"
(let
((env (kernel-make-env)))
(kernel-env-bind! env "name" "kernel")
(ke-eval-src "name" env))
"kernel")
(ke-test
"sym: parent-chain lookup"
(let
((p (kernel-make-env)))
(kernel-env-bind! p "outer" 1)
(let
((c (kernel-extend-env p)))
(kernel-env-bind! c "inner" 2)
(+ (ke-eval-src "outer" c) (ke-eval-src "inner" c))))
3)
(ke-test
"sym: child shadows parent"
(let
((p (kernel-make-env)))
(kernel-env-bind! p "x" 1)
(let
((c (kernel-extend-env p)))
(kernel-env-bind! c "x" 2)
(ke-eval-src "x" c)))
2)
(ke-test
"env-has?: present"
(let
((env (kernel-make-env)))
(kernel-env-bind! env "x" 1)
(kernel-env-has? env "x"))
true)
(ke-test
"env-has?: missing"
(kernel-env-has? (kernel-make-env) "nope")
false)
;; ── tagged-value predicates ─────────────────────────────────────
(ke-test
"tag: operative?"
(kernel-operative? (kernel-make-primitive-operative (fn (a e) nil)))
true)
(ke-test
"tag: applicative?"
(kernel-applicative? (kernel-make-primitive-applicative (fn (a) nil)))
true)
(ke-test
"tag: combiner? operative"
(kernel-combiner? (kernel-make-primitive-operative (fn (a e) nil)))
true)
(ke-test
"tag: combiner? applicative"
(kernel-combiner? (kernel-make-primitive-applicative (fn (a) nil)))
true)
(ke-test "tag: combiner? number" (kernel-combiner? 42) false)
(ke-test "tag: number is not operative" (kernel-operative? 42) false)
;; ── wrap / unwrap ────────────────────────────────────────────────
(ke-test
"wrap+unwrap roundtrip"
(let
((op (kernel-make-primitive-operative (fn (a e) :sentinel))))
(= (kernel-unwrap (kernel-wrap op)) op))
true)
(ke-test
"wrap produces applicative"
(kernel-applicative?
(kernel-wrap (kernel-make-primitive-operative (fn (a e) nil))))
true)
(ke-test
"unwrap of primitive-applicative is operative"
(kernel-operative?
(kernel-unwrap (kernel-make-primitive-applicative (fn (a) nil))))
true)
;; ── combiner dispatch — applicatives evaluate their args ─────────
(ke-test
"applicative: simple call"
(ke-eval-src "(+ 2 3)" (ke-make-test-env))
5)
(ke-test
"applicative: nested"
(ke-eval-src "(+ (+ 1 2) (+ 3 4))" (ke-make-test-env))
10)
(ke-test
"applicative: receives evaluated args"
(let
((env (ke-make-test-env)))
(kernel-env-bind! env "x" 10)
(kernel-env-bind! env "y" 20)
(ke-eval-src "(+ x y)" env))
30)
(ke-test
"applicative: list builds an SX list of values"
(let
((env (ke-make-test-env)))
(kernel-env-bind! env "a" 1)
(kernel-env-bind! env "b" 2)
(ke-eval-src "(list a b 99)" env))
(list 1 2 99))
;; ── combiner dispatch — operatives DO NOT evaluate their args ───
(ke-test
"operative: $quote returns symbol unevaluated"
(ke-eval-src "($quote foo)" (ke-make-test-env))
"foo")
(ke-test
"operative: $quote returns list unevaluated"
(ke-eval-src "($quote (+ 1 2))" (ke-make-test-env))
(list "+" 1 2))
(ke-test
"operative: $if true branch"
(ke-eval-src "($if #t 1 2)" (ke-make-test-env))
1)
(ke-test
"operative: $if false branch"
(ke-eval-src "($if #f 1 2)" (ke-make-test-env))
2)
(ke-test
"operative: $if doesn't eval untaken branch"
(ke-eval-src "($if #t 99 unbound)" (ke-make-test-env))
99)
(ke-test
"operative: $if takes dynamic env for branches"
(let
((env (ke-make-test-env)))
(kernel-env-bind! env "x" 7)
(ke-eval-src "($if #t x 0)" env))
7)
;; ── operative built ON-THE-FLY can inspect raw expressions ──────
(ke-test
"operative: sees raw symbol head"
(let
((env (kernel-make-env)))
(kernel-env-bind!
env
"head"
(kernel-make-primitive-operative (fn (args dyn-env) (first args))))
(ke-eval-src "(head (+ 1 2))" env))
(list "+" 1 2))
(ke-test
"operative: sees dynamic env"
(let
((env (kernel-make-env)))
(kernel-env-bind! env "x" 999)
(kernel-env-bind!
env
"$probe"
(kernel-make-primitive-operative
(fn (args dyn-env) (kernel-env-lookup dyn-env "x"))))
(ke-eval-src "($probe ignored)" env))
999)
;; ── error cases ──────────────────────────────────────────────────
(ke-test
"error: unbound symbol"
(guard
(e (true :raised))
(kernel-eval (kernel-parse "nope") (kernel-make-env)))
:raised)
(ke-test
"error: combine non-combiner"
(guard
(e (true :raised))
(let
((env (kernel-make-env)))
(kernel-env-bind! env "x" 42)
(kernel-eval (kernel-parse "(x 1)") env)))
:raised)
(define ke-tests-run! (fn () {:total (+ ke-test-pass ke-test-fail) :passed ke-test-pass :failed ke-test-fail :fails ke-test-fails}))

View File

@@ -1,220 +0,0 @@
;; lib/kernel/tests/hygiene.sx — exercises Phase 6 hygiene helpers.
;;
;; Kernel-on-SX is hygienic by default: $vau/$lambda close over their
;; static env, and bind their formals (plus any $define!s in the body)
;; in a CHILD env. The caller's env is only mutated when user code
;; explicitly threads the env-param through `eval` or `$define-in!`.
;;
;; These tests verify the property, plus the Phase 6 helpers ($let and
;; $define-in!). Shutt's full scope-set hygiene (lifted symbols with
;; provenance markers) is research-grade and is NOT implemented — see
;; the plan's reflective-API notes for the proposed approach.
(define kh-test-pass 0)
(define kh-test-fail 0)
(define kh-test-fails (list))
(define
kh-test
(fn
(name actual expected)
(if
(= actual expected)
(set! kh-test-pass (+ kh-test-pass 1))
(begin
(set! kh-test-fail (+ kh-test-fail 1))
(append! kh-test-fails {:name name :actual actual :expected expected})))))
(define kh-eval-in (fn (src env) (kernel-eval (kernel-parse src) env)))
;; ── Default hygiene: $define! inside operative body stays local ─
(kh-test
"hygiene: vau body $define! doesn't escape"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in
"($define! my-op ($vau () _ ($sequence ($define! x 999) x)))"
env)
(kh-eval-in "(my-op)" env)
(kh-eval-in "x" env))
1)
(kh-test
"hygiene: vau body $define! visible inside body"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in
"($define! my-op ($vau () _ ($sequence ($define! x 999) x)))"
env)
(kh-eval-in "(my-op)" env))
999)
(kh-test
"hygiene: lambda body $define! doesn't escape"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! y 50)" env)
(kh-eval-in "($define! f ($lambda () ($sequence ($define! y 7) y)))" env)
(kh-eval-in "(f)" env)
(kh-eval-in "y" env))
50)
(kh-test
"hygiene: caller's binding visible inside operative"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! caller-x 88)" env)
(kh-eval-in "($define! my-op ($vau () _ caller-x))" env)
(kh-eval-in "(my-op)" env))
88)
;; ── $let — proper hygienic scoping ──────────────────────────────
(kh-test
"let: returns body value"
(kh-eval-in "($let ((x 5)) (+ x 1))" (kernel-standard-env))
6)
(kh-test
"let: multiple bindings"
(kh-eval-in "($let ((x 3) (y 4)) (+ x y))" (kernel-standard-env))
7)
(kh-test
"let: bindings shadow outer"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in "($let ((x 99)) x)" env))
99)
(kh-test
"let: bindings don't leak after"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in "($let ((x 99)) x)" env)
(kh-eval-in "x" env))
1)
(kh-test
"let: parallel — RHS sees outer, not inner"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in "($let ((x 10) (y x)) y)" env))
1)
(kh-test
"let: nested"
(kh-eval-in "($let ((x 1)) ($let ((y 2)) (+ x y)))" (kernel-standard-env))
3)
(kh-test
"let: error on malformed binding"
(guard
(e (true :raised))
(kh-eval-in "($let ((x)) x)" (kernel-standard-env)))
:raised)
(kh-test
"let: error on non-symbol name"
(guard
(e (true :raised))
(kh-eval-in "($let ((1 2)) 1)" (kernel-standard-env)))
:raised)
;; ── $define-in! — explicit env targeting ────────────────────────
(kh-test
"define-in!: binds in chosen env, not dyn-env"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! sandbox (make-environment))" env)
(kh-eval-in "($define-in! sandbox z 77)" env)
(kernel-env-has? (kh-eval-in "sandbox" env) "z"))
true)
(kh-test
"define-in!: doesn't pollute caller"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! sandbox (make-environment))" env)
(kh-eval-in "($define-in! sandbox z 77)" env)
(kernel-env-has? env "z"))
false)
(kh-test
"define-in!: error on non-env target"
(guard
(e (true :raised))
(let
((env (kernel-standard-env)))
(kh-eval-in "($define-in! 42 x 1)" env)))
:raised)
;; ── Closure does NOT see post-definition caller binds ───────────
;; The classic "lexical scope wins over dynamic" test.
(kh-test
"lexical: closure sees its own static env"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in "($define! get-x ($lambda () x))" env)
(kh-eval-in "($define! x 999)" env)
(kh-eval-in "(get-x)" env))
999)
(kh-test
"lexical: $let-bound name invisible outside"
(guard
(e (true :raised))
(let
((env (kernel-standard-env)))
(kh-eval-in "($let ((private 42)) private)" env)
(kh-eval-in "private" env)))
:raised)
;; ── Operative + $let: hygiene compose ───────────────────────────
(kh-test
"let-inside-vau: temp doesn't escape body"
(let
((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in "($define! op ($vau () _ ($let ((x 5)) x)))" env)
(kh-eval-in "(op)" env)
(kh-eval-in "x" env))
1)
;; ── $let* — sequential let ──────────────────────────────────────
(kh-test "let*: empty bindings"
(kh-eval-in "($let* () 42)" (kernel-standard-env)) 42)
(kh-test "let*: single binding"
(kh-eval-in "($let* ((x 5)) (+ x 1))" (kernel-standard-env)) 6)
(kh-test "let*: later sees earlier"
(kh-eval-in "($let* ((x 1) (y (+ x 1)) (z (+ y 1))) z)"
(kernel-standard-env)) 3)
(kh-test "let*: bindings don't leak after"
(let ((env (kernel-standard-env)))
(kh-eval-in "($define! x 1)" env)
(kh-eval-in "($let* ((x 99) (y (+ x 1))) y)" env)
(kh-eval-in "x" env)) 1)
(kh-test "let*: same-name later binding shadows earlier"
(kh-eval-in "($let* ((x 1) (x 2)) x)" (kernel-standard-env)) 2)
(kh-test "let*: multi-expression body"
(kh-eval-in "($let* ((x 5)) ($define! double (+ x x)) double)"
(kernel-standard-env)) 10)
(kh-test "let*: error on malformed binding"
(guard (e (true :raised))
(kh-eval-in "($let* ((x)) x)" (kernel-standard-env)))
:raised)
(kh-test "let: multi-body"
(kh-eval-in "($let ((x 5)) ($define! tmp (+ x 1)) tmp)"
(kernel-standard-env)) 6)
(define kh-tests-run! (fn () {:total (+ kh-test-pass kh-test-fail) :passed kh-test-pass :failed kh-test-fail :fails kh-test-fails}))

View File

@@ -1,162 +0,0 @@
;; lib/kernel/tests/metacircular.sx — Kernel-in-Kernel demo.
;;
;; Demonstrates reflective completeness: a Kernel program implements
;; a recognisable subset of Kernel's own evaluation rules and produces
;; matching values for a battery of test programs.
;;
;; This is a SHALLOW metacircular: it dispatches on expression shape
;; itself (numbers, booleans, lists, symbols), recursively meta-evals
;; each argument of an applicative call, and delegates only to the
;; host evaluator for the leaf cases (operatives, symbol lookup). The
;; point is to show that env-as-value, first-class operatives, and
;; first-class evaluators all line up — enough so a Kernel program
;; can itself reason about Kernel programs.
(define kmc-test-pass 0)
(define kmc-test-fail 0)
(define kmc-test-fails (list))
(define
kmc-test
(fn
(name actual expected)
(if
(= actual expected)
(set! kmc-test-pass (+ kmc-test-pass 1))
(begin
(set! kmc-test-fail (+ kmc-test-fail 1))
(append! kmc-test-fails {:name name :actual actual :expected expected})))))
;; Build a Kernel env with m-eval and m-apply defined. The two refer
;; to each other and to standard primitives, so we use the standard
;; env as the static-env for both.
(define
kmc-make-env
(fn
()
(let
((env (kernel-standard-env)))
(kernel-eval
(kernel-parse
"($define! m-eval\n ($lambda (expr env)\n ($cond\n ((number? expr) expr)\n ((boolean? expr) expr)\n ((null? expr) expr)\n ((symbol? expr) (eval expr env))\n ((list? expr)\n ($let ((head-val (m-eval (car expr) env)))\n ($cond\n ((applicative? head-val)\n (apply head-val\n (map ($lambda (a) (m-eval a env)) (cdr expr))))\n (else (eval expr env)))))\n (else expr))))")
env)
env)))
(define
kmc-eval
(fn
(src)
(let
((env (kmc-make-env)))
(kernel-eval
(kernel-parse
(str "(m-eval (quote " src ") (get-current-environment))"))
env))))
;; ── literals self-evaluate via m-eval ──────────────────────────
(kmc-test
"m-eval: integer literal"
(kernel-eval
(kernel-parse "(m-eval 42 (get-current-environment))")
(kmc-make-env))
42)
(kmc-test
"m-eval: boolean true"
(kernel-eval
(kernel-parse "(m-eval #t (get-current-environment))")
(kmc-make-env))
true)
(kmc-test
"m-eval: boolean false"
(kernel-eval
(kernel-parse "(m-eval #f (get-current-environment))")
(kmc-make-env))
false)
(kmc-test
"m-eval: empty list"
(kernel-eval
(kernel-parse "(m-eval () (get-current-environment))")
(kmc-make-env))
(list))
;; ── symbol lookup goes through env ─────────────────────────────
(kmc-test
"m-eval: symbol lookup"
(let
((env (kmc-make-env)))
(kernel-eval (kernel-parse "($define! shared-x 99)") env)
(kernel-eval
(kernel-parse "(m-eval ($quote shared-x) (get-current-environment))")
env))
99)
;; ── applicative calls are dispatched by m-eval recursively ─────
(kmc-test
"m-eval: addition"
(kernel-eval
(kernel-parse "(m-eval ($quote (+ 1 2)) (get-current-environment))")
(kmc-make-env))
3)
(kmc-test
"m-eval: nested arithmetic"
(kernel-eval
(kernel-parse
"(m-eval ($quote (+ (* 2 3) (- 10 4))) (get-current-environment))")
(kmc-make-env))
12)
(kmc-test
"m-eval: variadic +"
(kernel-eval
(kernel-parse "(m-eval ($quote (+ 1 2 3 4 5)) (get-current-environment))")
(kmc-make-env))
15)
(kmc-test
"m-eval: list construction"
(kernel-eval
(kernel-parse "(m-eval ($quote (list 1 2 3)) (get-current-environment))")
(kmc-make-env))
(list 1 2 3))
(kmc-test "m-eval: cons reverse-style"
(kernel-eval
(kernel-parse "(m-eval ($quote (cons 0 (list 1 2))) (get-current-environment))")
(kmc-make-env)) (list 0 1 2))
(kmc-test "m-eval: nested apply"
(kernel-eval
(kernel-parse "(m-eval ($quote (apply + (list 10 20 30))) (get-current-environment))")
(kmc-make-env)) 60)
;; ── operatives delegate to host eval (transparently for the caller) ─
(kmc-test
"m-eval: $if true branch (via delegation)"
(kernel-eval
(kernel-parse "(m-eval ($quote ($if #t 1 2)) (get-current-environment))")
(kmc-make-env))
1)
(kmc-test
"m-eval: $if false branch"
(kernel-eval
(kernel-parse "(m-eval ($quote ($if #f 1 2)) (get-current-environment))")
(kmc-make-env))
2)
;; ── m-eval can call a user-defined lambda ──────────────────────
(kmc-test
"m-eval: user lambda call"
(let
((env (kmc-make-env)))
(kernel-eval (kernel-parse "($define! sq ($lambda (x) (* x x)))") env)
(kernel-eval
(kernel-parse "(m-eval ($quote (sq 7)) (get-current-environment))")
env))
49)
(define kmc-tests-run! (fn () {:total (+ kmc-test-pass kmc-test-fail) :passed kmc-test-pass :failed kmc-test-fail :fails kmc-test-fails}))

View File

@@ -1,158 +0,0 @@
;; lib/kernel/tests/parse.sx — exercises lib/kernel/parser.sx.
(define knl-test-pass 0)
(define knl-test-fail 0)
(define knl-test-fails (list))
(define
knl-test
(fn
(name actual expected)
(if
(= actual expected)
(set! knl-test-pass (+ knl-test-pass 1))
(begin
(set! knl-test-fail (+ knl-test-fail 1))
(append! knl-test-fails {:name name :actual actual :expected expected})))))
;; ── atoms: numbers ────────────────────────────────────────────────
(knl-test "num: integer" (kernel-parse "42") 42)
(knl-test "num: zero" (kernel-parse "0") 0)
(knl-test "num: negative integer" (kernel-parse "-7") -7)
(knl-test "num: positive sign" (kernel-parse "+5") 5)
(knl-test "num: float" (kernel-parse "3.14") 3.14)
(knl-test "num: negative float" (kernel-parse "-2.5") -2.5)
(knl-test "num: leading dot" (kernel-parse ".5") 0.5)
(knl-test "num: exponent" (kernel-parse "1e3") 1000)
(knl-test "num: exponent with sign" (kernel-parse "2.5e-1") 0.25)
(knl-test "num: capital E exponent" (kernel-parse "1E2") 100)
;; ── atoms: booleans ───────────────────────────────────────────────
(knl-test "bool: true" (kernel-parse "#t") true)
(knl-test "bool: false" (kernel-parse "#f") false)
;; ── atoms: empty list (Kernel nil) ────────────────────────────────
(knl-test "nil: ()" (kernel-parse "()") (list))
(knl-test "nil: (= () (list))" (= (kernel-parse "()") (list)) true)
;; ── atoms: symbols ────────────────────────────────────────────────
(knl-test "sym: word" (kernel-parse "foo") "foo")
(knl-test "sym: hyphenated" (kernel-parse "foo-bar") "foo-bar")
(knl-test "sym: dollar-bang" (kernel-parse "$define!") "$define!")
(knl-test "sym: question" (kernel-parse "null?") "null?")
(knl-test "sym: lt-eq" (kernel-parse "<=") "<=")
(knl-test "sym: bare plus" (kernel-parse "+") "+")
(knl-test "sym: bare minus" (kernel-parse "-") "-")
(knl-test "sym: plus-letter" (kernel-parse "+a") "+a")
(knl-test "sym: arrow" (kernel-parse "->") "->")
(knl-test "sym: dot-prefixed" (kernel-parse ".foo") ".foo")
;; ── atoms: strings ────────────────────────────────────────────────
(knl-test "str: empty" (kernel-string-value (kernel-parse "\"\"")) "")
(knl-test
"str: hello"
(kernel-string-value (kernel-parse "\"hello\""))
"hello")
(knl-test "str: predicate" (kernel-string? (kernel-parse "\"x\"")) true)
(knl-test "str: not symbol" (kernel-string? (kernel-parse "x")) false)
(knl-test
"str: escape newline"
(kernel-string-value (kernel-parse "\"a\\nb\""))
"a\nb")
(knl-test
"str: escape tab"
(kernel-string-value (kernel-parse "\"a\\tb\""))
"a\tb")
(knl-test
"str: escape quote"
(kernel-string-value (kernel-parse "\"a\\\"b\""))
"a\"b")
(knl-test
"str: escape backslash"
(kernel-string-value (kernel-parse "\"a\\\\b\""))
"a\\b")
;; ── lists ─────────────────────────────────────────────────────────
(knl-test "list: flat" (kernel-parse "(a b c)") (list "a" "b" "c"))
(knl-test
"list: nested"
(kernel-parse "(a (b c) d)")
(list "a" (list "b" "c") "d"))
(knl-test
"list: deeply nested"
(kernel-parse "(((x)))")
(list (list (list "x"))))
(knl-test
"list: mixed atoms"
(kernel-parse "(1 #t foo)")
(list 1 true "foo"))
(knl-test
"list: empty inside"
(kernel-parse "(a () b)")
(list "a" (list) "b"))
;; ── whitespace + comments ─────────────────────────────────────────
(knl-test "ws: leading" (kernel-parse " 42") 42)
(knl-test "ws: trailing" (kernel-parse "42 ") 42)
(knl-test "ws: tabs/newlines" (kernel-parse "\n\t 42 \n") 42)
(knl-test "comment: line" (kernel-parse "; nope\n42") 42)
(knl-test "comment: trailing" (kernel-parse "42 ; tail") 42)
(knl-test
"comment: inside list"
(kernel-parse "(a ; mid\n b)")
(list "a" "b"))
;; ── parse-all ─────────────────────────────────────────────────────
(knl-test "all: empty input" (kernel-parse-all "") (list))
(knl-test "all: only whitespace" (kernel-parse-all " ") (list))
(knl-test "all: only comment" (kernel-parse-all "; nope") (list))
(knl-test
"all: three forms"
(kernel-parse-all "1 2 3")
(list 1 2 3))
(knl-test
"all: mixed"
(kernel-parse-all "($if #t 1 2) foo")
(list (list "$if" true 1 2) "foo"))
;; ── classic Kernel programs (smoke) ───────────────────────────────
(knl-test
"klisp: vau form"
(kernel-parse "($vau (x e) e (eval x e))")
(list "$vau" (list "x" "e") "e" (list "eval" "x" "e")))
(knl-test
"klisp: define lambda"
(kernel-parse "($define! sq ($lambda (x) (* x x)))")
(list "$define!" "sq" (list "$lambda" (list "x") (list "*" "x" "x"))))
;; ── round-trip identity for primitive symbols ─────────────────────
(knl-test "identity: $vau" (kernel-parse "$vau") "$vau")
(knl-test "identity: $lambda" (kernel-parse "$lambda") "$lambda")
(knl-test "identity: wrap" (kernel-parse "wrap") "wrap")
(knl-test "identity: unwrap" (kernel-parse "unwrap") "unwrap")
;; ── reader macros ─────────────────────────────────────────────────
(knl-test "reader: 'foo → ($quote foo)"
(kernel-parse "'foo") (list "$quote" "foo"))
(knl-test "reader: '(a b c)"
(kernel-parse "'(a b c)") (list "$quote" (list "a" "b" "c")))
(knl-test "reader: nested quotes"
(kernel-parse "''x")
(list "$quote" (list "$quote" "x")))
(knl-test "reader: ` quasiquote"
(kernel-parse "`x") (list "$quasiquote" "x"))
(knl-test "reader: , unquote"
(kernel-parse ",x") (list "$unquote" "x"))
(knl-test "reader: ,@ unquote-splicing"
(kernel-parse ",@x") (list "$unquote-splicing" "x"))
(knl-test "reader: quasi-mix"
(kernel-parse "`(a ,b ,@c)")
(list "$quasiquote"
(list "a"
(list "$unquote" "b")
(list "$unquote-splicing" "c"))))
(knl-test "reader: quote separates from neighbouring atom"
(kernel-parse "(a 'b c)")
(list "a" (list "$quote" "b") "c"))
(define knl-tests-run! (fn () {:total (+ knl-test-pass knl-test-fail) :passed knl-test-pass :failed knl-test-fail :fails knl-test-fails}))

View File

@@ -1,445 +0,0 @@
;; lib/kernel/tests/standard.sx — exercises the Kernel standard env.
;;
;; Phase 4 tests verify that the standard env is rich enough to run
;; classic Kernel programs: factorial via recursion, list operations,
;; first-class environment manipulation. Each test starts from a fresh
;; standard env via `(kernel-standard-env)`.
(define ks-test-pass 0)
(define ks-test-fail 0)
(define ks-test-fails (list))
(define
ks-test
(fn
(name actual expected)
(if
(= actual expected)
(set! ks-test-pass (+ ks-test-pass 1))
(begin
(set! ks-test-fail (+ ks-test-fail 1))
(append! ks-test-fails {:name name :actual actual :expected expected})))))
(define
ks-eval
(fn (src) (kernel-eval (kernel-parse src) (kernel-standard-env))))
(define ks-eval-in (fn (src env) (kernel-eval (kernel-parse src) env)))
(define
ks-eval-all
(fn (src env) (kernel-eval-program (kernel-parse-all src) env)))
;; ── $if ──────────────────────────────────────────────────────────
(ks-test "if: true branch" (ks-eval "($if #t 1 2)") 1)
(ks-test "if: false branch" (ks-eval "($if #f 1 2)") 2)
(ks-test "if: predicate"
(ks-eval "($if (<=? 1 2) ($quote yes) ($quote no))") "yes")
(ks-test
"if: untaken branch not evaluated"
(ks-eval "($if #t 42 nope)")
42)
;; ── $define! + arithmetic ───────────────────────────────────────
(ks-test
"define!: returns value"
(let ((env (kernel-standard-env))) (ks-eval-in "($define! x 5)" env))
5)
(ks-test
"define!: bound in env"
(let
((env (kernel-standard-env)))
(ks-eval-in "($define! x 5)" env)
(ks-eval-in "x" env))
5)
(ks-test "arith: +" (ks-eval "(+ 2 3)") 5)
(ks-test "arith: -" (ks-eval "(- 10 4)") 6)
(ks-test "arith: *" (ks-eval "(* 6 7)") 42)
(ks-test "arith: /" (ks-eval "(/ 20 5)") 4)
(ks-test "cmp: < true" (ks-eval "(< 1 2)") true)
(ks-test "cmp: < false" (ks-eval "(< 2 1)") false)
(ks-test "cmp: >=" (ks-eval "(>=? 2 2)") true)
(ks-test "cmp: <=" (ks-eval "(<=? 2 3)") true)
(ks-test "cmp: =" (ks-eval "(=? 7 7)") true)
;; ── $sequence ────────────────────────────────────────────────────
(ks-test "sequence: empty" (ks-eval "($sequence)") nil)
(ks-test "sequence: single" (ks-eval "($sequence 99)") 99)
(ks-test
"sequence: multi-effect"
(let
((env (kernel-standard-env)))
(ks-eval-in "($sequence ($define! a 1) ($define! b 2) (+ a b))" env))
3)
;; ── list primitives ──────────────────────────────────────────────
(ks-test
"list: builds"
(ks-eval "(list 1 2 3)")
(list 1 2 3))
(ks-test "list: empty" (ks-eval "(list)") (list))
(ks-test
"cons: prepend"
(ks-eval "(cons 0 (list 1 2 3))")
(list 0 1 2 3))
(ks-test "car: head" (ks-eval "(car (list 10 20 30))") 10)
(ks-test
"cdr: tail"
(ks-eval "(cdr (list 10 20 30))")
(list 20 30))
(ks-test "length: 3" (ks-eval "(length (list 1 2 3))") 3)
(ks-test "length: 0" (ks-eval "(length (list))") 0)
(ks-test "null?: empty" (ks-eval "(null? (list))") true)
(ks-test "null?: nonempty" (ks-eval "(null? (list 1))") false)
(ks-test "pair?: empty" (ks-eval "(pair? (list))") false)
(ks-test "pair?: nonempty" (ks-eval "(pair? (list 1))") true)
;; ── $quote ───────────────────────────────────────────────────────
(ks-test "quote: symbol" (ks-eval "($quote foo)") "foo")
(ks-test
"quote: list"
(ks-eval "($quote (+ 1 2))")
(list "+" 1 2))
;; ── boolean / not ────────────────────────────────────────────────
(ks-test "not: true" (ks-eval "(not #t)") false)
(ks-test "not: false" (ks-eval "(not #f)") true)
;; ── factorial ────────────────────────────────────────────────────
(ks-test
"factorial: 5!"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
env)
(ks-eval-in "(factorial 5)" env))
120)
(ks-test
"factorial: 0! = 1"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
env)
(ks-eval-in "(factorial 0)" env))
1)
(ks-test
"factorial: 10!"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
env)
(ks-eval-in "(factorial 10)" env))
3628800)
;; ── recursive list operations ────────────────────────────────────
(ks-test
"sum: recursive over list"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! sum ($lambda (xs) ($if (null? xs) 0 (+ (car xs) (sum (cdr xs))))))"
env)
(ks-eval-in "(sum (list 1 2 3 4 5))" env))
15)
(ks-test
"len: recursive count"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! mylen ($lambda (xs) ($if (null? xs) 0 (+ 1 (mylen (cdr xs))))))"
env)
(ks-eval-in "(mylen (list 1 2 3 4))" env))
4)
(ks-test
"map-add1: build new list"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! add1-all ($lambda (xs) ($if (null? xs) (list) (cons (+ 1 (car xs)) (add1-all (cdr xs))))))"
env)
(ks-eval-in "(add1-all (list 10 20 30))" env))
(list 11 21 31))
;; ── eval as a first-class applicative ────────────────────────────
(ks-test
"eval: applies to constructed form"
(ks-eval "(eval (list ($quote +) 2 3) (get-current-environment))")
5)
(ks-test
"eval: with a fresh make-environment"
(guard
(e (true :raised))
(ks-eval "(eval ($quote (+ 1 2)) (make-environment))"))
:raised)
(ks-test
"eval: in extended env sees parent's bindings"
(let
((env (kernel-standard-env)))
(ks-eval-in "($define! shared 7)" env)
(ks-eval-in
"(eval ($quote shared) (make-environment (get-current-environment)))"
env))
7)
;; ── get-current-environment ──────────────────────────────────────
(ks-test
"get-current-environment: returns env"
(kernel-env? (ks-eval "(get-current-environment)"))
true)
(ks-test
"get-current-environment: contains $if"
(let
((env (ks-eval "(get-current-environment)")))
(kernel-env-has? env "$if"))
true)
(ks-test
"make-environment: empty"
(let ((env (ks-eval "(make-environment)"))) (kernel-env-has? env "$if"))
false)
(ks-test
"make-environment: child sees parent"
(let
((env (kernel-standard-env)))
(ks-eval-in "($define! marker 123)" env)
(let
((child (ks-eval-in "(make-environment (get-current-environment))" env)))
(kernel-env-has? child "marker")))
true)
;; ── closures and lexical scope ───────────────────────────────────
(ks-test
"closure: captures binding"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! make-adder ($lambda (n) ($lambda (x) (+ x n))))"
env)
(ks-eval-in "($define! add5 (make-adder 5))" env)
(ks-eval-in "(add5 10)" env))
15)
(ks-test
"closure: nested lookups"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! curry-add ($lambda (a) ($lambda (b) ($lambda (c) (+ a (+ b c))))))"
env)
(ks-eval-in "(((curry-add 1) 2) 3)" env))
6)
;; ── operative defined in standard env can reach $define! ─────────
(ks-test
"custom: define-via-vau"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! $let-it ($vau (name expr) e ($sequence ($define! tmp (eval expr e)) (eval (list ($quote $define!) name (list ($quote $quote) tmp)) e) tmp)))"
env)
(ks-eval-in "($let-it z 77)" env)
(ks-eval-in "z" env))
77)
;; ── quasiquote ──────────────────────────────────────────────────
(ks-test "qq: plain atom" (ks-eval "`hello") "hello")
(ks-test "qq: plain list" (ks-eval "`(a b c)") (list "a" "b" "c"))
(ks-test "qq: unquote splices value"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! x 42)" env)
(ks-eval-in "`(a ,x b)" env)) (list "a" 42 "b"))
(ks-test "qq: unquote-splicing splices list"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! xs (list 1 2 3))" env)
(ks-eval-in "`(a ,@xs b)" env)) (list "a" 1 2 3 "b"))
(ks-test "qq: unquote-splicing at end"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! xs (list 9 8))" env)
(ks-eval-in "`(a b ,@xs)" env)) (list "a" "b" 9 8))
(ks-test "qq: unquote-splicing at start"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! xs (list 1 2))" env)
(ks-eval-in "`(,@xs c)" env)) (list 1 2 "c"))
(ks-test "qq: nested list with unquote inside"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! x 5)" env)
(ks-eval-in "`(a (b ,x) c)" env))
(list "a" (list "b" 5) "c"))
(ks-test "qq: error on bare unquote-splicing into non-list"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! x 42)" env)
(guard (e (true :raised))
(ks-eval-in "`(a ,@x b)" env)))
:raised)
;; ── $cond / $when / $unless ─────────────────────────────────────
(ks-test "cond: first match"
(ks-eval "($cond (#f 1) (#t 2) (#t 3))") 2)
(ks-test "cond: else fallback"
(ks-eval "($cond (#f 1) (else 99))") 99)
(ks-test "cond: no match returns nil"
(ks-eval "($cond (#f 1) (#f 2))") nil)
(ks-test "cond: empty clauses returns nil"
(ks-eval "($cond)") nil)
(ks-test "cond: multi-expr body"
(ks-eval "($cond (#t 1 2 3))") 3)
(ks-test "cond: doesn't evaluate untaken clauses"
;; If the second clause's test were evaluated, the unbound `nope` would error.
(ks-eval "($cond (#t 7) (nope ignored))") 7)
(ks-test "cond: predicate evaluation"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! n 5)" env)
(ks-eval-in "($cond ((< n 0) ($quote negative)) ((= n 0) ($quote zero)) (else ($quote positive)))" env))
"positive")
(ks-test "when: true runs body"
(ks-eval "($when #t 1 2 3)") 3)
(ks-test "when: false returns nil"
(ks-eval "($when #f 1 2 3)") nil)
(ks-test "when: skips body when false"
(ks-eval "($when #f nope)") nil)
(ks-test "unless: false runs body"
(ks-eval "($unless #f 99)") 99)
(ks-test "unless: true returns nil"
(ks-eval "($unless #t 99)") nil)
(ks-test "unless: skips body when true"
(ks-eval "($unless #t nope)") nil)
;; ── $and? / $or? short-circuit ──────────────────────────────────
(ks-test "and: empty returns true" (ks-eval "($and?)") true)
(ks-test "and: single returns value" (ks-eval "($and? 42)") 42)
(ks-test "and: all true returns last"
(ks-eval "($and? 1 2 3)") 3)
(ks-test "and: first false short-circuits"
(ks-eval "($and? #f nope)") false)
(ks-test "and: false in middle short-circuits"
(ks-eval "($and? 1 #f nope)") false)
(ks-test "or: empty returns false" (ks-eval "($or?)") false)
(ks-test "or: single returns value" (ks-eval "($or? 42)") 42)
(ks-test "or: first truthy short-circuits"
(ks-eval "($or? 99 nope)") 99)
(ks-test "or: all false returns last"
(ks-eval "($or? #f #f #f)") false)
(ks-test "or: middle truthy"
(ks-eval "($or? #f 42 nope)") 42)
;; ── variadic arithmetic ─────────────────────────────────────────
(ks-test "+: zero args = 0" (ks-eval "(+)") 0)
(ks-test "+: one arg = arg" (ks-eval "(+ 7)") 7)
(ks-test "+: two args" (ks-eval "(+ 3 4)") 7)
(ks-test "+: five args" (ks-eval "(+ 1 2 3 4 5)") 15)
(ks-test "*: zero args = 1" (ks-eval "(*)") 1)
(ks-test "*: one arg" (ks-eval "(* 7)") 7)
(ks-test "*: four args" (ks-eval "(* 1 2 3 4)") 24)
(ks-test "-: one arg negates" (ks-eval "(- 10)") -10)
(ks-test "-: two args" (ks-eval "(- 10 3)") 7)
(ks-test "-: four args fold" (ks-eval "(- 100 1 2 3)") 94)
(ks-test "/: two args" (ks-eval "(/ 20 5)") 4)
(ks-test "/: three args fold" (ks-eval "(/ 100 2 5)") 10)
;; ── variadic chained comparison ─────────────────────────────────
(ks-test "<: chained ascending" (ks-eval "(< 1 2 3 4 5)") true)
(ks-test "<: not strict" (ks-eval "(< 1 2 2 3)") false)
(ks-test "<: anti-monotonic" (ks-eval "(< 5 3)") false)
(ks-test ">: chained descending" (ks-eval "(> 5 4 3 2 1)") true)
(ks-test "<=? ascending equals" (ks-eval "(<=? 1 1 2 3 3)") true)
(ks-test "<=? violation" (ks-eval "(<=? 1 2 1)") false)
(ks-test ">=? descending equals" (ks-eval "(>=? 3 3 2 1)") true)
;; ── list combinators ────────────────────────────────────────────
(ks-test "map: square"
(ks-eval "(map ($lambda (x) (* x x)) (list 1 2 3 4))")
(list 1 4 9 16))
(ks-test "map: empty list"
(ks-eval "(map ($lambda (x) x) (list))") (list))
(ks-test "map: identity preserves"
(ks-eval "(map ($lambda (x) x) (list 1 2 3))") (list 1 2 3))
(ks-test "map: with closure over outer"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! k 10)" env)
(ks-eval-in "(map ($lambda (x) (+ x k)) (list 1 2 3))" env))
(list 11 12 13))
(ks-test "filter: positives"
(ks-eval "(filter ($lambda (x) (< 0 x)) (list -2 -1 0 1 2))")
(list 1 2))
(ks-test "filter: empty result"
(ks-eval "(filter ($lambda (x) #f) (list 1 2 3))") (list))
(ks-test "filter: all match"
(ks-eval "(filter ($lambda (x) #t) (list 1 2 3))") (list 1 2 3))
(ks-test "reduce: sum"
(ks-eval "(reduce ($lambda (a b) (+ a b)) 0 (list 1 2 3 4 5))") 15)
(ks-test "reduce: product"
(ks-eval "(reduce ($lambda (a b) (* a b)) 1 (list 1 2 3 4))") 24)
(ks-test "reduce: empty returns init"
(ks-eval "(reduce ($lambda (a b) (+ a b)) 42 (list))") 42)
(ks-test "reduce: build list"
(ks-eval "(reduce ($lambda (acc x) (cons x acc)) () (list 1 2 3))")
(list 3 2 1))
;; ── apply ────────────────────────────────────────────────────────
(ks-test "apply: + over list"
(ks-eval "(apply + (list 1 2 3 4 5))") 15)
(ks-test "apply: lambda"
(ks-eval "(apply ($lambda (a b c) (* a (+ b c))) (list 2 3 4))") 14)
(ks-test "apply: list identity"
(ks-eval "(apply list (list 1 2 3))") (list 1 2 3))
(ks-test "apply: empty args list"
(ks-eval "(apply + (list))") 0)
(ks-test "apply: single arg list"
(ks-eval "(apply ($lambda (x) (* x 10)) (list 7))") 70)
(ks-test "apply: built via map+apply"
;; (apply + (map ($lambda (x) (* x x)) (list 1 2 3))) → 1+4+9 = 14
(ks-eval
"(apply + (map ($lambda (x) (* x x)) (list 1 2 3)))") 14)
(ks-test "apply: error on non-list args"
(guard (e (true :raised))
(ks-eval "(apply + 5)"))
:raised)
;; ── append / reverse ────────────────────────────────────────────
(ks-test "append: two lists"
(ks-eval "(append (list 1 2) (list 3 4))") (list 1 2 3 4))
(ks-test "append: three lists"
(ks-eval "(append (list 1) (list 2) (list 3))") (list 1 2 3))
(ks-test "append: empty list"
(ks-eval "(append)") (list))
(ks-test "append: one list"
(ks-eval "(append (list 1 2 3))") (list 1 2 3))
(ks-test "append: empty + nonempty"
(ks-eval "(append (list) (list 1 2))") (list 1 2))
(ks-test "append: nonempty + empty"
(ks-eval "(append (list 1 2) (list))") (list 1 2))
(ks-test "append: error on non-list"
(guard (e (true :raised))
(ks-eval "(append (list 1) 5)"))
:raised)
(ks-test "reverse: four elements"
(ks-eval "(reverse (list 1 2 3 4))") (list 4 3 2 1))
(ks-test "reverse: empty"
(ks-eval "(reverse (list))") (list))
(ks-test "reverse: single"
(ks-eval "(reverse (list 99))") (list 99))
(ks-test "reverse: double reverse is identity"
(ks-eval "(reverse (reverse (list 1 2 3)))") (list 1 2 3))
(define ks-tests-run! (fn () {:total (+ ks-test-pass ks-test-fail) :passed ks-test-pass :failed ks-test-fail :fails ks-test-fails}))

View File

@@ -1,309 +0,0 @@
;; lib/kernel/tests/vau.sx — exercises lib/kernel/runtime.sx.
;;
;; Verifies the Phase 3 promise: user-defined operatives and applicatives
;; constructible from inside the language. Tests build a Kernel
;; base-env, bind a few helper applicatives (+, *, list, =, $if), and
;; run programs that construct and use custom combiners.
(define kv-test-pass 0)
(define kv-test-fail 0)
(define kv-test-fails (list))
(define
kv-test
(fn
(name actual expected)
(if
(= actual expected)
(set! kv-test-pass (+ kv-test-pass 1))
(begin
(set! kv-test-fail (+ kv-test-fail 1))
(append! kv-test-fails {:name name :actual actual :expected expected})))))
(define kv-eval-src (fn (src env) (kernel-eval (kernel-parse src) env)))
(define
kv-make-env
(fn
()
(let
((env (kernel-base-env)))
(kernel-env-bind!
env
"+"
(kernel-make-primitive-applicative
(fn (args) (+ (first args) (nth args 1)))))
(kernel-env-bind!
env
"*"
(kernel-make-primitive-applicative
(fn (args) (* (first args) (nth args 1)))))
(kernel-env-bind!
env
"-"
(kernel-make-primitive-applicative
(fn (args) (- (first args) (nth args 1)))))
(kernel-env-bind!
env
"="
(kernel-make-primitive-applicative
(fn (args) (= (first args) (nth args 1)))))
(kernel-env-bind!
env
"list"
(kernel-make-primitive-applicative (fn (args) args)))
(kernel-env-bind!
env
"cons"
(kernel-make-primitive-applicative
(fn (args) (cons (first args) (nth args 1)))))
(kernel-env-bind!
env
"$quote"
(kernel-make-primitive-operative (fn (args dyn-env) (first args))))
(kernel-env-bind!
env
"$if"
(kernel-make-primitive-operative
(fn
(args dyn-env)
(if
(kernel-eval (first args) dyn-env)
(kernel-eval (nth args 1) dyn-env)
(kernel-eval (nth args 2) dyn-env)))))
env)))
;; ── $vau: builds an operative ───────────────────────────────────
(kv-test
"vau: identity returns first arg unevaluated"
(kv-eval-src "(($vau (a) _ a) hello)" (kv-make-env))
"hello")
(kv-test
"vau: returns args as raw expressions"
(kv-eval-src "(($vau (a b) _ (list a b)) (+ 1 2) (+ 3 4))" (kv-make-env))
(list (list "+" 1 2) (list "+" 3 4)))
(kv-test
"vau: env-param is a kernel env"
(kernel-env? (kv-eval-src "(($vau () e e))" (kv-make-env)))
true)
(kv-test
"vau: returns operative"
(kernel-operative? (kv-eval-src "($vau (x) _ x)" (kv-make-env)))
true)
(kv-test
"vau: returns operative not applicative"
(kernel-applicative? (kv-eval-src "($vau (x) _ x)" (kv-make-env)))
false)
(kv-test
"vau: zero-arg body"
(kv-eval-src "(($vau () _ 42))" (kv-make-env))
42)
(kv-test
"vau: static-env closure captured"
(let
((outer (kv-make-env)))
(kernel-env-bind! outer "captured" 17)
(let
((op (kv-eval-src "($vau () _ captured)" outer))
(caller (kv-make-env)))
(kernel-env-bind! caller "captured" 99)
(kernel-combine op (list) caller)))
17)
(kv-test
"vau: env-param exposes caller's dynamic env"
(let
((outer (kv-make-env)))
(kernel-env-bind! outer "x" 1)
(let
((op (kv-eval-src "($vau () e e)" outer)) (caller (kv-make-env)))
(kernel-env-bind! caller "x" 2)
(let
((e-val (kernel-combine op (list) caller)))
(kernel-env-lookup e-val "x"))))
2)
;; ── $lambda: applicatives evaluate their args ───────────────────
(kv-test
"lambda: identity"
(kv-eval-src "(($lambda (x) x) 42)" (kv-make-env))
42)
(kv-test
"lambda: addition"
(kv-eval-src "(($lambda (x y) (+ x y)) 3 4)" (kv-make-env))
7)
(kv-test
"lambda: args are evaluated before bind"
(kv-eval-src "(($lambda (x) x) (+ 2 3))" (kv-make-env))
5)
(kv-test
"lambda: zero args"
(kv-eval-src "(($lambda () 99))" (kv-make-env))
99)
(kv-test
"lambda: returns applicative"
(kernel-applicative? (kv-eval-src "($lambda (x) x)" (kv-make-env)))
true)
(kv-test
"lambda: returns applicative not operative"
(kernel-operative? (kv-eval-src "($lambda (x) x)" (kv-make-env)))
false)
(kv-test
"lambda: higher-order"
(kv-eval-src "(($lambda (f) (f 10)) ($lambda (x) (+ x 1)))" (kv-make-env))
11)
;; ── wrap / unwrap as user-callable applicatives ─────────────────
(kv-test
"wrap: makes applicative from operative"
(kernel-applicative? (kv-eval-src "(wrap ($vau (x) _ x))" (kv-make-env)))
true)
(kv-test
"wrap: result evaluates its arg"
(kv-eval-src "((wrap ($vau (x) _ x)) (+ 1 2))" (kv-make-env))
3)
(kv-test
"unwrap: extracts operative from applicative"
(kernel-operative? (kv-eval-src "(unwrap ($lambda (x) x))" (kv-make-env)))
true)
(kv-test
"wrap/unwrap roundtrip preserves identity"
(kv-eval-src
"(($lambda (op) (= op (unwrap (wrap op)))) ($vau (x) _ x))"
(kv-make-env))
true)
;; ── operative? / applicative? as user-visible predicates ────────
(kv-test
"operative? on vau result"
(kv-eval-src "(operative? ($vau (x) _ x))" (kv-make-env))
true)
(kv-test
"operative? on lambda result"
(kv-eval-src "(operative? ($lambda (x) x))" (kv-make-env))
false)
(kv-test
"applicative? on lambda result"
(kv-eval-src "(applicative? ($lambda (x) x))" (kv-make-env))
true)
(kv-test
"applicative? on vau result"
(kv-eval-src "(applicative? ($vau (x) _ x))" (kv-make-env))
false)
(kv-test
"operative? on number"
(kv-eval-src "(operative? 42)" (kv-make-env))
false)
;; ── Build BOTH layers from user code ────────────────────────────
;; The headline Phase 3 test: defining an operative on top of an
;; applicative defined on top of a vau.
(kv-test
"custom: applicative + operative compose"
(let
((env (kv-make-env)))
(kernel-env-bind! env "square" (kv-eval-src "($lambda (x) (* x x))" env))
(kv-eval-src "(square 4)" env))
16)
(kv-test "custom: operative captures argument syntax"
;; ($capture x) returns the raw expression `x`, regardless of value.
(let ((env (kv-make-env)))
(kernel-env-bind! env "$capture"
(kv-eval-src "($vau (form) _ form)" env))
(kv-eval-src "($capture (+ 1 2))" env))
(list "+" 1 2))
(kv-test "custom: applicative re-wraps an operative"
;; Build a captured operative, then wrap it into an applicative that
;; evaluates args before re-entry. This exercises wrap+$vau composed.
(let ((env (kv-make-env)))
(kernel-env-bind! env "id-app"
(kv-eval-src "(wrap ($vau (x) _ x))" env))
(kv-eval-src "(id-app (+ 10 20))" env))
30)
;; ── Error cases ──────────────────────────────────────────────────
(kv-test
"vau: rejects non-list formals"
(guard (e (true :raised)) (kv-eval-src "($vau x _ x)" (kv-make-env)))
:raised)
(kv-test
"vau: rejects non-symbol formal"
(guard (e (true :raised)) (kv-eval-src "($vau (1) _ x)" (kv-make-env)))
:raised)
(kv-test
"vau: rejects non-symbol env-param"
(guard (e (true :raised)) (kv-eval-src "($vau (x) 7 x)" (kv-make-env)))
:raised)
(kv-test
"vau: too few args at call site"
(guard
(e (true :raised))
(kv-eval-src "(($vau (x y) _ x) 1)" (kv-make-env)))
:raised)
(kv-test
"vau: too many args at call site"
(guard
(e (true :raised))
(kv-eval-src "(($vau (x) _ x) 1 2)" (kv-make-env)))
:raised)
(kv-test
"wrap: rejects non-operative"
(guard (e (true :raised)) (kv-eval-src "(wrap 42)" (kv-make-env)))
:raised)
(kv-test
"unwrap: rejects non-applicative"
(guard (e (true :raised)) (kv-eval-src "(unwrap 42)" (kv-make-env)))
:raised)
;; ── Multi-expression body (implicit $sequence) ──────────────────
(kv-test "lambda: two body forms — value of last"
(kv-eval-src "(($lambda (n) (+ n 1) (+ n 10)) 5)" (kv-make-env)) 15)
(kv-test "lambda: three body forms"
(kv-eval-src "(($lambda (n) n (+ n 1) (+ n 2)) 10)" (kv-make-env)) 12)
(kv-test "vau: two body forms"
(kv-eval-src "(($vau (a b) _ a (list a b)) 7 8)" (kv-make-env))
(list 7 8))
(kv-test "lambda: $define! in early body visible in later body"
(kv-eval-src
"(($lambda (n) ($define! double (+ n n)) double) 6)"
(kv-make-env)) 12)
(kv-test "lambda: zero-arg multi-body"
(kv-eval-src "(($lambda () 1 2 3))" (kv-make-env)) 3)
(define kv-tests-run! (fn () {:total (+ kv-test-pass kv-test-fail) :passed kv-test-pass :failed kv-test-fail :fails kv-test-fails}))

View File

@@ -1,590 +0,0 @@
;; lib/minikanren/clpfd.sx — Phase 6: native CLP(FD) on miniKanren.
;;
;; The substitution dict carries an extra reserved key "_fd" that holds a
;; constraint-store record:
;;
;; {:domains {var-name -> sorted-int-list}
;; :constraints (... pending constraint closures ...)}
;;
;; Domains are sorted SX lists of ints (no duplicates).
;; Constraints are functions s -> s-or-nil that propagate / re-check.
;; They are re-fired after every label binding via fd-fire-store.
(define fd-key "_fd")
;; --- domain primitives ---
(define
fd-dom-rev
(fn
(xs acc)
(cond
((empty? xs) acc)
(:else (fd-dom-rev (rest xs) (cons (first xs) acc))))))
(define
fd-dom-insert
(fn
(x desc)
(cond
((empty? desc) (list x))
((= x (first desc)) desc)
((> x (first desc)) (cons x desc))
(:else (cons (first desc) (fd-dom-insert x (rest desc)))))))
(define
fd-dom-sort-dedupe
(fn
(xs acc)
(cond
((empty? xs) (fd-dom-rev acc (list)))
(:else (fd-dom-sort-dedupe (rest xs) (fd-dom-insert (first xs) acc))))))
(define fd-dom-from-list (fn (xs) (fd-dom-sort-dedupe xs (list))))
(define fd-dom-empty? (fn (d) (empty? d)))
(define
fd-dom-singleton?
(fn (d) (and (not (empty? d)) (empty? (rest d)))))
(define fd-dom-min (fn (d) (first d)))
(define
fd-dom-last
(fn
(d)
(cond ((empty? (rest d)) (first d)) (:else (fd-dom-last (rest d))))))
(define fd-dom-max (fn (d) (fd-dom-last d)))
(define fd-dom-member? (fn (x d) (some (fn (y) (= x y)) d)))
(define
fd-dom-intersect
(fn
(a b)
(cond
((empty? a) (list))
((empty? b) (list))
((= (first a) (first b))
(cons (first a) (fd-dom-intersect (rest a) (rest b))))
((< (first a) (first b)) (fd-dom-intersect (rest a) b))
(:else (fd-dom-intersect a (rest b))))))
(define
fd-dom-without
(fn
(x d)
(cond
((empty? d) (list))
((= (first d) x) (rest d))
((> (first d) x) d)
(:else (cons (first d) (fd-dom-without x (rest d)))))))
(define
fd-dom-range
(fn
(lo hi)
(cond
((> lo hi) (list))
(:else (cons lo (fd-dom-range (+ lo 1) hi))))))
;; --- constraint store accessors ---
(define fd-store-empty (fn () {:domains {} :constraints (list)}))
(define
fd-store-of
(fn
(s)
(cond ((has-key? s fd-key) (get s fd-key)) (:else (fd-store-empty)))))
(define fd-domains-of (fn (s) (get (fd-store-of s) :domains)))
(define fd-with-store (fn (s store) (assoc s fd-key store)))
(define
fd-domain-of
(fn
(s var-name)
(let
((doms (fd-domains-of s)))
(cond ((has-key? doms var-name) (get doms var-name)) (:else nil)))))
(define
fd-set-domain
(fn
(s var-name d)
(cond
((fd-dom-empty? d) nil)
(:else
(let
((store (fd-store-of s)))
(let
((doms-prime (assoc (get store :domains) var-name d)))
(let
((store-prime (assoc store :domains doms-prime)))
(fd-with-store s store-prime))))))))
(define
fd-add-constraint
(fn
(s c)
(let
((store (fd-store-of s)))
(let
((cs-prime (cons c (get store :constraints))))
(let
((store-prime (assoc store :constraints cs-prime)))
(fd-with-store s store-prime))))))
(define
fd-fire-list
(fn
(cs s)
(cond
((empty? cs) s)
(:else
(let
((s2 ((first cs) s)))
(cond ((= s2 nil) nil) (:else (fd-fire-list (rest cs) s2))))))))
(define
fd-store-signature
(fn
(s)
(let
((doms (fd-domains-of s)))
(let
((dom-sizes (reduce (fn (acc k) (+ acc (len (get doms k)))) 0 (keys doms))))
(+ dom-sizes (len (keys s)))))))
(define
fd-fire-store
(fn
(s)
(let
((s2 (fd-fire-list (get (fd-store-of s) :constraints) s)))
(cond
((= s2 nil) nil)
((= (fd-store-signature s) (fd-store-signature s2)) s2)
(:else (fd-fire-store s2))))))
;; --- user-facing goals ---
(define
fd-in
(fn
(x dom-list)
(fn
(s)
(let
((new-dom (fd-dom-from-list dom-list)))
(let
((wx (mk-walk x s)))
(cond
((number? wx)
(cond ((fd-dom-member? wx new-dom) (unit s)) (:else mzero)))
((is-var? wx)
(let
((existing (fd-domain-of s (var-name wx))))
(let
((narrowed (cond ((= existing nil) new-dom) (:else (fd-dom-intersect existing new-dom)))))
(let
((s2 (fd-set-domain s (var-name wx) narrowed)))
(cond ((= s2 nil) mzero) (:else (unit s2)))))))
(:else mzero)))))))
;; --- fd-neq ---
(define
fd-neq-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((= wx wy) nil) (:else s)))
((and (number? wx) (is-var? wy))
(let
((y-dom (fd-domain-of s (var-name wy))))
(cond
((= y-dom nil) s)
(:else
(fd-set-domain s (var-name wy) (fd-dom-without wx y-dom))))))
((and (number? wy) (is-var? wx))
(let
((x-dom (fd-domain-of s (var-name wx))))
(cond
((= x-dom nil) s)
(:else
(fd-set-domain s (var-name wx) (fd-dom-without wy x-dom))))))
(:else s)))))
(define
fd-neq
(fn
(x y)
(fn
(s)
(let
((c (fn (s-prime) (fd-neq-prop x y s-prime))))
(let
((s2 (fd-add-constraint s c)))
(let
((s3 (c s2)))
(cond ((= s3 nil) mzero) (:else (unit s3)))))))))
;; --- fd-lt ---
(define
fd-lt-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((< wx wy) s) (:else nil)))
((and (number? wx) (is-var? wy))
(let
((yd (fd-domain-of s (var-name wy))))
(cond
((= yd nil) s)
(:else
(fd-set-domain
s
(var-name wy)
(filter (fn (v) (> v wx)) yd))))))
((and (is-var? wx) (number? wy))
(let
((xd (fd-domain-of s (var-name wx))))
(cond
((= xd nil) s)
(:else
(fd-set-domain
s
(var-name wx)
(filter (fn (v) (< v wy)) xd))))))
((and (is-var? wx) (is-var? wy))
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((or (= xd nil) (= yd nil)) s)
(:else
(let
((xd-prime (filter (fn (v) (< v (fd-dom-max yd))) xd)))
(let
((s2 (fd-set-domain s (var-name wx) xd-prime)))
(cond
((= s2 nil) nil)
(:else
(let
((yd-prime (filter (fn (v) (> v (fd-dom-min xd-prime))) yd)))
(fd-set-domain s2 (var-name wy) yd-prime))))))))))
(:else s)))))
(define
fd-lt
(fn
(x y)
(fn
(s)
(let
((c (fn (sp) (fd-lt-prop x y sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s3 (c s2)))
(cond ((= s3 nil) mzero) (:else (unit s3)))))))))
;; --- fd-lte ---
(define
fd-lte-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((<= wx wy) s) (:else nil)))
((and (number? wx) (is-var? wy))
(let
((yd (fd-domain-of s (var-name wy))))
(cond
((= yd nil) s)
(:else
(fd-set-domain
s
(var-name wy)
(filter (fn (v) (>= v wx)) yd))))))
((and (is-var? wx) (number? wy))
(let
((xd (fd-domain-of s (var-name wx))))
(cond
((= xd nil) s)
(:else
(fd-set-domain
s
(var-name wx)
(filter (fn (v) (<= v wy)) xd))))))
((and (is-var? wx) (is-var? wy))
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((or (= xd nil) (= yd nil)) s)
(:else
(let
((xd-prime (filter (fn (v) (<= v (fd-dom-max yd))) xd)))
(let
((s2 (fd-set-domain s (var-name wx) xd-prime)))
(cond
((= s2 nil) nil)
(:else
(let
((yd-prime (filter (fn (v) (>= v (fd-dom-min xd-prime))) yd)))
(fd-set-domain s2 (var-name wy) yd-prime))))))))))
(:else s)))))
(define
fd-lte
(fn
(x y)
(fn
(s)
(let
((c (fn (sp) (fd-lte-prop x y sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s3 (c s2)))
(cond ((= s3 nil) mzero) (:else (unit s3)))))))))
;; --- fd-eq ---
(define
fd-eq-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((= wx wy) s) (:else nil)))
((and (number? wx) (is-var? wy))
(let
((yd (fd-domain-of s (var-name wy))))
(cond
((and (not (= yd nil)) (not (fd-dom-member? wx yd))) nil)
(:else
(let
((s2 (mk-unify wy wx s)))
(cond ((= s2 nil) nil) (:else s2)))))))
((and (is-var? wx) (number? wy))
(let
((xd (fd-domain-of s (var-name wx))))
(cond
((and (not (= xd nil)) (not (fd-dom-member? wy xd))) nil)
(:else
(let
((s2 (mk-unify wx wy s)))
(cond ((= s2 nil) nil) (:else s2)))))))
((and (is-var? wx) (is-var? wy))
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((and (= xd nil) (= yd nil))
(let
((s2 (mk-unify wx wy s)))
(cond ((= s2 nil) nil) (:else s2))))
(:else
(let
((shared (cond ((= xd nil) yd) ((= yd nil) xd) (:else (fd-dom-intersect xd yd)))))
(cond
((fd-dom-empty? shared) nil)
(:else
(let
((s2 (fd-set-domain s (var-name wx) shared)))
(cond
((= s2 nil) nil)
(:else
(let
((s3 (fd-set-domain s2 (var-name wy) shared)))
(cond
((= s3 nil) nil)
(:else (mk-unify wx wy s3))))))))))))))
(:else s)))))
(define
fd-eq
(fn
(x y)
(fn
(s)
(let
((c (fn (sp) (fd-eq-prop x y sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s3 (c s2)))
(cond ((= s3 nil) mzero) (:else (unit s3)))))))))
;; --- labelling ---
(define
fd-try-each-value
(fn
(x dom s)
(cond
((empty? dom) mzero)
(:else
(let
((s2 (mk-unify x (first dom) s)))
(let
((s3 (cond ((= s2 nil) nil) (:else (fd-fire-store s2)))))
(let
((this-stream (cond ((= s3 nil) mzero) (:else (unit s3))))
(rest-stream (fd-try-each-value x (rest dom) s)))
(mk-mplus this-stream rest-stream))))))))
(define
fd-label-one
(fn
(x)
(fn
(s)
(let
((wx (mk-walk x s)))
(cond
((number? wx) (unit s))
((is-var? wx)
(let
((dom (fd-domain-of s (var-name wx))))
(cond
((= dom nil) mzero)
(:else (fd-try-each-value wx dom s)))))
(:else mzero))))))
(define
fd-label
(fn
(vars)
(cond
((empty? vars) succeed)
(:else (mk-conj (fd-label-one (first vars)) (fd-label (rest vars)))))))
;; --- fd-distinct (pairwise distinct via fd-neq) ---
(define
fd-distinct-from-head
(fn
(x others)
(cond
((empty? others) succeed)
(:else
(mk-conj
(fd-neq x (first others))
(fd-distinct-from-head x (rest others)))))))
(define
fd-distinct
(fn
(vars)
(cond
((empty? vars) succeed)
((empty? (rest vars)) succeed)
(:else
(mk-conj
(fd-distinct-from-head (first vars) (rest vars))
(fd-distinct (rest vars)))))))
;; --- fd-plus (x + y = z, ground-cases propagator) ---
(define
fd-bind-or-narrow
(fn
(w target s)
(cond
((number? w) (cond ((= w target) s) (:else nil)))
((is-var? w)
(let
((wd (fd-domain-of s (var-name w))))
(cond
((and (not (= wd nil)) (not (fd-dom-member? target wd))) nil)
(:else
(let
((s2 (mk-unify w target s)))
(cond ((= s2 nil) nil) (:else s2)))))))
(:else nil))))
(define
fd-plus-prop
(fn
(x y z s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)) (wz (mk-walk z s)))
(cond
((and (number? wx) (number? wy) (number? wz))
(cond ((= (+ wx wy) wz) s) (:else nil)))
((and (number? wx) (number? wy))
(fd-bind-or-narrow wz (+ wx wy) s))
((and (number? wx) (number? wz))
(fd-bind-or-narrow wy (- wz wx) s))
((and (number? wy) (number? wz))
(fd-bind-or-narrow wx (- wz wy) s))
(:else s)))))
(define
fd-plus
(fn
(x y z)
(fn
(s)
(let
((c (fn (sp) (fd-plus-prop x y z sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s3 (c s2)))
(cond ((= s3 nil) mzero) (:else (unit s3)))))))))
;; --- fd-times (x * y = z, ground-cases propagator) ---
(define
fd-times-prop
(fn
(x y z s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)) (wz (mk-walk z s)))
(cond
((and (number? wx) (number? wy) (number? wz))
(cond ((= (* wx wy) wz) s) (:else nil)))
((and (number? wx) (number? wy))
(fd-bind-or-narrow wz (* wx wy) s))
((and (number? wx) (number? wz))
(cond
((= wx 0) (cond ((= wz 0) s) (:else nil)))
((not (= (mod wz wx) 0)) nil)
(:else (fd-bind-or-narrow wy (/ wz wx) s))))
((and (number? wy) (number? wz))
(cond
((= wy 0) (cond ((= wz 0) s) (:else nil)))
((not (= (mod wz wy) 0)) nil)
(:else (fd-bind-or-narrow wx (/ wz wy) s))))
(:else s)))))
(define
fd-times
(fn
(x y z)
(fn
(s)
(let
((c (fn (sp) (fd-times-prop x y z sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s3 (c s2)))
(cond ((= s3 nil) mzero) (:else (unit s3)))))))))

View File

@@ -1,42 +0,0 @@
;; lib/minikanren/conda.sx — Phase 5 piece A: `conda`, the soft-cut.
;;
;; (conda (g0 g ...) (h0 h ...) ...)
;; — first clause whose head g0 produces ANY answer wins; ALL of g0's
;; answers are then conj'd with the rest of that clause; later
;; clauses are NOT tried.
;; — differs from condu only in not wrapping g0 in onceo: condu
;; commits to the SINGLE first answer, conda lets the head's full
;; answer-set flow into the rest of the clause.
;; (Reasoned Schemer chapter 10; Byrd 5.3.)
(define
conda-try
(fn
(clauses s)
(cond
((empty? clauses) mzero)
(:else
(let
((cl (first clauses)))
(let
((head-goal (first cl)) (rest-goals (rest cl)))
(let
((peek (stream-take 1 (head-goal s))))
(if
(empty? peek)
(conda-try (rest clauses) s)
(mk-bind (head-goal s) (mk-conj-list rest-goals))))))))))
(defmacro
conda
(&rest clauses)
(quasiquote
(fn
(s)
(conda-try
(list
(splice-unquote
(map
(fn (cl) (quasiquote (list (splice-unquote cl))))
clauses)))
s))))

View File

@@ -1,39 +0,0 @@
;; lib/minikanren/conde.sx — Phase 2 piece C: `conde`, the canonical
;; miniKanren and-or form, with implicit Zzz inverse-eta delay so recursive
;; relations like appendo terminate.
;;
;; (conde (g1a g1b ...) (g2a g2b ...) ...)
;; ≡ (mk-disj (Zzz (mk-conj g1a g1b ...))
;; (Zzz (mk-conj g2a g2b ...)) ...)
;;
;; `Zzz g` wraps a goal expression in (fn (S) (fn () (g S))) so that
;; `g`'s body isn't constructed until the surrounding fn is applied to a
;; substitution AND the returned thunk is forced. This is what gives
;; miniKanren its laziness — recursive goal definitions can be `(conde
;; ... (... (recur ...)))` without infinite descent at construction time.
;;
;; Hygiene: the substitution parameter is gensym'd so that user goal
;; expressions which themselves bind `s` (e.g. `(appendo l s ls)`) keep
;; their lexical `s` and don't accidentally reference the wrapper's
;; substitution. Without gensym, miniKanren relations that follow the
;; common (l s ls) parameter convention are silently miscompiled.
(defmacro
Zzz
(g)
(let
((s-sym (gensym "zzz-s-")))
(quasiquote
(fn ((unquote s-sym)) (fn () ((unquote g) (unquote s-sym)))))))
(defmacro
conde
(&rest clauses)
(quasiquote
(mk-disj
(splice-unquote
(map
(fn
(clause)
(quasiquote (Zzz (mk-conj (splice-unquote clause)))))
clauses)))))

View File

@@ -1,58 +0,0 @@
;; lib/minikanren/condu.sx — Phase 2 piece D: `condu` and `onceo`.
;;
;; Both are commitment forms (no backtracking into discarded options):
;;
;; (onceo g) — succeeds at most once: takes the first answer
;; stream-take produces from (g s).
;;
;; (condu (g0 g ...) (h0 h ...) ...)
;; — first clause whose head goal succeeds wins; only
;; the first answer of the head is propagated to the
;; rest of that clause; later clauses are not tried.
;; (Reasoned Schemer chapter 10; Byrd 5.4.)
(define
onceo
(fn
(g)
(fn
(s)
(let
((peek (stream-take 1 (g s))))
(if (empty? peek) mzero (unit (first peek)))))))
;; condu-try — runtime walker over a list of clauses (each clause a list of
;; goals). Forces the head with stream-take 1; if head fails, recurse to
;; the next clause; if head succeeds, commits its single answer through
;; the rest of the clause.
(define
condu-try
(fn
(clauses s)
(cond
((empty? clauses) mzero)
(:else
(let
((cl (first clauses)))
(let
((head-goal (first cl)) (rest-goals (rest cl)))
(let
((peek (stream-take 1 (head-goal s))))
(if
(empty? peek)
(condu-try (rest clauses) s)
((mk-conj-list rest-goals) (first peek))))))))))
(defmacro
condu
(&rest clauses)
(quasiquote
(fn
(s)
(condu-try
(list
(splice-unquote
(map
(fn (cl) (quasiquote (list (splice-unquote cl))))
clauses)))
s))))

View File

@@ -1,25 +0,0 @@
;; lib/minikanren/defrel.sx — Prolog-style defrel macro.
;;
;; (defrel (NAME ARG1 ARG2 ...)
;; (CLAUSE1 ...)
;; (CLAUSE2 ...)
;; ...)
;;
;; expands to
;;
;; (define NAME (fn (ARG1 ARG2 ...) (conde (CLAUSE1 ...) (CLAUSE2 ...))))
;;
;; This puts each clause's goals immediately after the head, mirroring
;; Prolog's `name(Args) :- goals.` shape. Clauses are conde-conjoined
;; goals — `Zzz`-wrapping is automatic via `conde`, so recursive
;; relations terminate on partial answers.
(defmacro
defrel
(head &rest clauses)
(let
((name (first head)) (args (rest head)))
(list
(quote define)
name
(list (quote fn) args (cons (quote conde) clauses)))))

View File

@@ -1,25 +0,0 @@
;; lib/minikanren/fd.sx — Phase 6 piece A: minimal finite-domain helpers.
;;
;; A full CLP(FD) engine (arc consistency, native integer domains, fd-plus
;; etc.) is Phase 6 proper. For now we expose two small relations layered
;; on the existing list machinery — they're sufficient for permutation
;; puzzles, the N-queens-style core of constraint solving:
;;
;; (ino x dom) — x is a member of dom (alias for membero with the
;; constraint-store-friendly argument order).
;; (all-distincto l) — all elements of l are pairwise distinct.
;;
;; all-distincto uses nafc + membero on the tail — it requires the head
;; element of each recursive step to be ground enough for membero to be
;; finitary, so order matters: prefer (in x dom) goals BEFORE
;; (all-distincto (list x ...)) so values get committed first.
(define ino (fn (x dom) (membero x dom)))
(define
all-distincto
(fn
(l)
(conde
((nullo l))
((fresh (a d) (conso a d l) (nafc (membero a d)) (all-distincto d))))))

View File

@@ -1,23 +0,0 @@
;; lib/minikanren/fresh.sx — Phase 2 piece B: `fresh` for introducing
;; logic variables inside a goal body.
;;
;; (fresh (x y z) goal1 goal2 ...)
;; ≡ (let ((x (make-var)) (y (make-var)) (z (make-var)))
;; (mk-conj goal1 goal2 ...))
;;
;; A macro rather than a function so user-named vars are real lexical
;; bindings — which is also what miniKanren convention expects.
;; The empty-vars form (fresh () goal ...) is just a goal grouping.
(defmacro
fresh
(vars &rest goals)
(quasiquote
(let
(unquote (map (fn (v) (list v (list (quote make-var)))) vars))
(mk-conj (splice-unquote goals)))))
;; call-fresh — functional alternative for code that builds goals
;; programmatically:
;; ((call-fresh (fn (x) (== x 7))) empty-s) → ({:_.N 7})
(define call-fresh (fn (f) (fn (s) ((f (make-var)) s))))

View File

@@ -1,58 +0,0 @@
;; lib/minikanren/goals.sx — Phase 2 piece B: core goals.
;;
;; A goal is a function (fn (s) → stream-of-substitutions).
;; Goals built here:
;; succeed — always returns (unit s)
;; fail — always returns mzero
;; == — unifies two terms; succeeds with a singleton, else fails
;; ==-check — opt-in occurs-checked equality
;; conj2 / mk-conj — sequential conjunction of goals
;; disj2 / mk-disj — interleaved disjunction of goals (raw — `conde` adds
;; the implicit-conj-per-clause sugar in a later commit)
(define succeed (fn (s) (unit s)))
(define fail (fn (s) mzero))
(define
==
(fn
(u v)
(fn
(s)
(let ((s2 (mk-unify u v s))) (if (= s2 nil) mzero (unit s2))))))
(define
==-check
(fn
(u v)
(fn
(s)
(let ((s2 (mk-unify-check u v s))) (if (= s2 nil) mzero (unit s2))))))
(define conj2 (fn (g1 g2) (fn (s) (mk-bind (g1 s) g2))))
(define disj2 (fn (g1 g2) (fn (s) (mk-mplus (g1 s) (g2 s)))))
;; Fold goals in a list. (mk-conj-list ()) ≡ succeed; (mk-disj-list ()) ≡ fail.
(define
mk-conj-list
(fn
(gs)
(cond
((empty? gs) succeed)
((empty? (rest gs)) (first gs))
(:else (conj2 (first gs) (mk-conj-list (rest gs)))))))
(define
mk-disj-list
(fn
(gs)
(cond
((empty? gs) fail)
((empty? (rest gs)) (first gs))
(:else (disj2 (first gs) (mk-disj-list (rest gs)))))))
(define mk-conj (fn (&rest gs) (mk-conj-list gs)))
(define mk-disj (fn (&rest gs) (mk-disj-list gs)))

View File

@@ -1,151 +0,0 @@
;; lib/minikanren/intarith.sx — fast integer arithmetic via project.
;;
;; These are ground-only escapes into host arithmetic. They run at native
;; speed (host ints) but require their arguments to walk to actual numbers
;; — they are not relational the way `pluso` (Peano) is. Use them when
;; the puzzle size makes Peano impractical.
;;
;; Naming: `-i` suffix marks "integer-only" goals.
(define
pluso-i
(fn
(a b c)
(project
(a b)
(if (and (number? a) (number? b)) (== c (+ a b)) fail))))
(define
minuso-i
(fn
(a b c)
(project
(a b)
(if (and (number? a) (number? b)) (== c (- a b)) fail))))
(define
*o-i
(fn
(a b c)
(project
(a b)
(if (and (number? a) (number? b)) (== c (* a b)) fail))))
(define
lto-i
(fn
(a b)
(project
(a b)
(if (and (number? a) (and (number? b) (< a b))) succeed fail))))
(define
lteo-i
(fn
(a b)
(project
(a b)
(if (and (number? a) (and (number? b) (<= a b))) succeed fail))))
(define
neqo-i
(fn
(a b)
(project
(a b)
(if (and (number? a) (and (number? b) (not (= a b)))) succeed fail))))
(define numbero (fn (x) (project (x) (if (number? x) succeed fail))))
(define stringo (fn (x) (project (x) (if (string? x) succeed fail))))
(define symbolo (fn (x) (project (x) (if (symbol? x) succeed fail))))
(define
even-i
(fn (n) (project (n) (if (and (number? n) (even? n)) succeed fail))))
(define
odd-i
(fn (n) (project (n) (if (and (number? n) (odd? n)) succeed fail))))
(define
sortedo
(fn
(l)
(conde
((nullo l))
((fresh (a) (== l (list a))))
((fresh (a b rest mid) (conso a mid l) (conso b rest mid) (lteo-i a b) (sortedo mid))))))
(define
mino
(fn
(l m)
(conde
((fresh (a) (== l (list a)) (== m a)))
((fresh (a d rest-min) (conso a d l) (mino d rest-min) (conde ((lteo-i a rest-min) (== m a)) ((lto-i rest-min a) (== m rest-min))))))))
(define
maxo
(fn
(l m)
(conde
((fresh (a) (== l (list a)) (== m a)))
((fresh (a d rest-max) (conso a d l) (maxo d rest-max) (conde ((lteo-i rest-max a) (== m a)) ((lto-i a rest-max) (== m rest-max))))))))
(define
sumo
(fn
(l total)
(conde
((nullo l) (== total 0))
((fresh (a d rest-sum) (conso a d l) (sumo d rest-sum) (pluso-i a rest-sum total))))))
(define
producto
(fn
(l total)
(conde
((nullo l) (== total 1))
((fresh (a d rest-prod) (conso a d l) (producto d rest-prod) (*o-i a rest-prod total))))))
(define
lengtho-i
(fn
(l n)
(conde
((nullo l) (== n 0))
((fresh (a d n-1) (conso a d l) (lengtho-i d n-1) (pluso-i 1 n-1 n))))))
(define
enumerate-from-i
(fn
(start l result)
(conde
((nullo l) (nullo result))
((fresh (a d r-rest start-prime) (conso a d l) (conso (list start a) r-rest result) (pluso-i 1 start start-prime) (enumerate-from-i start-prime d r-rest))))))
(define enumerate-i (fn (l result) (enumerate-from-i 0 l result)))
(define
counto
(fn
(x l n)
(conde
((nullo l) (== n 0))
((fresh (a d n-rest) (conso a d l) (conde ((== a x) (counto x d n-rest) (pluso-i 1 n-rest n)) ((nafc (== a x)) (counto x d n))))))))
(define
mk-arith-prog
(fn
(start step len)
(cond
((= len 0) (list))
(:else (cons start (mk-arith-prog (+ start step) step (- len 1)))))))
(define
arith-progo
(fn
(start step len result)
(project (start step len) (== result (mk-arith-prog start step len)))))

View File

@@ -1,76 +0,0 @@
;; lib/minikanren/matche.sx — Phase 5 piece D: pattern matching over terms.
;;
;; (matche TARGET
;; (PATTERN1 g1 g2 ...)
;; (PATTERN2 g1 ...)
;; ...)
;;
;; Pattern grammar:
;; _ wildcard — fresh anonymous var
;; x plain symbol — fresh var, bind by name
;; ATOM literal (number, string, boolean) — must equal
;; :keyword keyword literal — emitted bare (keywords self-evaluate
;; to their string name in SX, so quoting them changes
;; their type from string to keyword)
;; () empty list — must equal
;; (p1 p2 ... pn) list pattern — recurse on each element
;;
;; The macro expands to a `conde` whose clauses are
;; `((fresh (vars-in-pat) (== target pat-expr) body...))`.
;;
;; Repeated symbol names within a pattern produce the same fresh var, so
;; they unify by `==`. Fixed-length list patterns only — head/tail
;; destructuring uses `(fresh (a d) (conso a d target) body)` directly.
;;
;; Note: the macro builds the expansion via `cons` / `list` rather than a
;; quasiquote — quasiquote does not recurse into nested lambda bodies in
;; SX, so `\`(matche-clause (quote ,target) cl)` left literal
;; `(unquote target)` in the output.
(define matche-symbol-var? (fn (s) (symbol? s)))
(define
matche-collect-vars-acc
(fn
(pat acc)
(cond
((matche-symbol-var? pat)
(if (some (fn (s) (= s pat)) acc) acc (append acc (list pat))))
((and (list? pat) (not (empty? pat)))
(reduce (fn (a p) (matche-collect-vars-acc p a)) acc pat))
(:else acc))))
(define
matche-collect-vars
(fn (pat) (matche-collect-vars-acc pat (list))))
(define
matche-pattern->expr
(fn
(pat)
(cond
((matche-symbol-var? pat) pat)
((and (list? pat) (empty? pat)) (list (quote list)))
((list? pat) (cons (quote list) (map matche-pattern->expr pat)))
((keyword? pat) pat)
(:else (list (quote quote) pat)))))
(define
matche-clause
(fn
(target cl)
(let
((pat (first cl)) (body (rest cl)))
(let
((vars (matche-collect-vars pat)))
(let
((pat-expr (matche-pattern->expr pat)))
(list
(cons
(quote fresh)
(cons vars (cons (list (quote ==) target pat-expr) body)))))))))
(defmacro
matche
(target &rest clauses)
(cons (quote conde) (map (fn (cl) (matche-clause target cl)) clauses)))

View File

@@ -1,24 +0,0 @@
;; lib/minikanren/nafc.sx — Phase 5 piece C: negation as finite failure.
;;
;; (nafc g)
;; succeeds (yields the input substitution) if g has zero answers
;; against that substitution; fails (mzero) if g has at least one.
;;
;; Caveat: `nafc` is unsound under the open-world assumption. It only
;; makes sense for goals over fully-ground terms, or with the explicit
;; understanding that adding more facts could flip the answer. Use
;; `(project (...) ...)` to ensure the relevant vars are ground first.
;;
;; Caveat 2: stream-take forces g for at least one answer; if g is
;; infinitely-ground (say, a divergent search over an unbound list),
;; nafc itself will diverge. Standard miniKanren limitation.
(define
nafc
(fn
(g)
(fn
(s)
(let
((peek (stream-take 1 (g s))))
(if (empty? peek) (unit s) mzero)))))

View File

@@ -1,51 +0,0 @@
;; lib/minikanren/peano.sx — Peano-encoded natural-number relations.
;;
;; Same encoding as `lengtho`: zero is the keyword `:z`; successors are
;; `(:s n)`. So 3 = `(:s (:s (:s :z)))`. `(:z)` and `(:s ...)` are normal
;; SX values that unify positionally — no special primitives needed.
;;
;; Peano arithmetic is the canonical miniKanren way to test addition /
;; multiplication / less-than relationally without an FD constraint store.
;; (CLP(FD) integers come in Phase 6.)
(define zeroo (fn (n) (== n :z)))
(define succ-of (fn (n m) (== m (list :s n))))
(define
pluso
(fn
(a b c)
(conde
((== a :z) (== b c))
((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (pluso a-1 b c-1))))))
(define minuso (fn (a b c) (pluso b c a)))
(define lteo (fn (a b) (fresh (k) (pluso a k b))))
(define lto (fn (a b) (fresh (sa) (succ-of a sa) (lteo sa b))))
(define
eveno
(fn
(n)
(conde
((== n :z))
((fresh (m) (== n (list :s (list :s m))) (eveno m))))))
(define
oddo
(fn
(n)
(conde
((== n (list :s :z)))
((fresh (m) (== n (list :s (list :s m))) (oddo m))))))
(define
*o
(fn
(a b c)
(conde
((== a :z) (== c :z))
((fresh (a-1 ab-1) (== a (list :s a-1)) (*o a-1 b ab-1) (pluso b ab-1 c))))))

View File

@@ -1,25 +0,0 @@
;; lib/minikanren/project.sx — Phase 5 piece B: `project`.
;;
;; (project (x y) g1 g2 ...)
;; — rebinds each named var to (mk-walk* var s) within the body's
;; lexical scope, then runs the conjunction of the body goals on
;; the same substitution. Use to escape into regular SX (arithmetic,
;; string ops, host predicates) when you need a ground value.
;;
;; If any of the projected vars is still unbound at this point, the body
;; sees the raw `(:var NAME)` term — that is intentional and lets you
;; mix project with `(== ground? var)` patterns or with conda guards.
;;
;; Hygiene: substitution parameter is gensym'd so it doesn't capture user
;; vars (`s` is a popular relation parameter name).
(defmacro
project
(vars &rest goals)
(let
((s-sym (gensym "proj-s-")))
(quasiquote
(fn
((unquote s-sym))
((let (unquote (map (fn (v) (list v (list (quote mk-walk*) v s-sym))) vars)) (mk-conj (splice-unquote goals)))
(unquote s-sym))))))

View File

@@ -1,67 +0,0 @@
;; lib/minikanren/queens.sx — N-queens via ino + all-distincto + project.
;;
;; Encoding: q = (c1 c2 ... cn) where ci is the column of the queen in
;; row i. Each ci ∈ {1..n}; all distinct (no two queens share a column);
;; no two queens on the same diagonal (|ci - cj| ≠ |i - j| for i ≠ j).
;;
;; The diagonal check uses `project` to escape into host arithmetic
;; once both column values are ground.
(define
safe-diag
(fn
(a b dist)
(project (a b) (if (= (abs (- a b)) dist) fail succeed))))
(define
safe-cell-vs-rest
(fn
(c c-row others next-row)
(cond
((empty? others) succeed)
(:else
(mk-conj
(safe-diag c (first others) (- next-row c-row))
(safe-cell-vs-rest c c-row (rest others) (+ next-row 1)))))))
(define
all-cells-safe
(fn
(cols start-row)
(cond
((empty? cols) succeed)
(:else
(mk-conj
(safe-cell-vs-rest
(first cols)
start-row
(rest cols)
(+ start-row 1))
(all-cells-safe (rest cols) (+ start-row 1)))))))
(define
range-1-to-n
(fn
(n)
(cond
((= n 0) (list))
(:else (append (range-1-to-n (- n 1)) (list n))))))
(define
ino-each
(fn
(cols dom)
(cond
((empty? cols) succeed)
(:else (mk-conj (ino (first cols) dom) (ino-each (rest cols) dom))))))
(define
queens-cols
(fn
(cols n)
(let
((dom (range-1-to-n n)))
(mk-conj
(ino-each cols dom)
(all-distincto cols)
(all-cells-safe cols 1)))))

View File

@@ -1,361 +0,0 @@
;; lib/minikanren/relations.sx — Phase 4 standard relations.
;;
;; Programs use native SX lists as data. Relations decompose lists via the
;; tagged cons-cell shape `(:cons h t)` because SX has no improper pairs;
;; the unifier treats `(:cons h t)` and the native list `(h . t)` as
;; equivalent, and `mk-walk*` flattens cons cells back to flat lists for
;; reification.
;; --- pair / list shape relations ---
(define nullo (fn (l) (== l (list))))
(define pairo (fn (p) (fresh (a d) (== p (mk-cons a d)))))
(define caro (fn (p a) (fresh (d) (== p (mk-cons a d)))))
(define cdro (fn (p d) (fresh (a) (== p (mk-cons a d)))))
(define conso (fn (a d p) (== p (mk-cons a d))))
(define firsto caro)
(define resto cdro)
(define
listo
(fn (l) (conde ((nullo l)) ((fresh (a d) (conso a d l) (listo d))))))
;; --- appendo: the canary ---
;;
;; (appendo l s ls) — `ls` is the concatenation of `l` and `s`.
;; Runs forwards (l, s known → ls), backwards (ls known → all (l, s) pairs),
;; and bidirectionally (mix of bound + unbound).
(define
appendo
(fn
(l s ls)
(conde
((nullo l) (== s ls))
((fresh (a d res) (conso a d l) (conso a res ls) (appendo d s res))))))
;; --- membero ---
;; (membero x l) — x appears (at least once) in l.
(define
appendo3
(fn
(l1 l2 l3 result)
(fresh (l12) (appendo l1 l2 l12) (appendo l12 l3 result))))
(define
partitiono
(fn
(pred l yes no)
(conde
((nullo l) (nullo yes) (nullo no))
((fresh (a d y-rest n-rest) (conso a d l) (conde ((pred a) (conso a y-rest yes) (== no n-rest) (partitiono pred d y-rest n-rest)) ((nafc (pred a)) (== yes y-rest) (conso a n-rest no) (partitiono pred d y-rest n-rest))))))))
(define
foldr-o
(fn
(rel l acc result)
(conde
((nullo l) (== result acc))
((fresh (a d r-rest) (conso a d l) (foldr-o rel d acc r-rest) (rel a r-rest result))))))
(define
foldl-o
(fn
(rel l acc result)
(conde
((nullo l) (== result acc))
((fresh (a d new-acc) (conso a d l) (rel acc a new-acc) (foldl-o rel d new-acc result))))))
(define
flat-mapo
(fn
(rel l result)
(conde
((nullo l) (nullo result))
((fresh (a d a-result rest-result) (conso a d l) (rel a a-result) (flat-mapo rel d rest-result) (appendo a-result rest-result result))))))
(define
nub-o
(fn
(l result)
(conde
((nullo l) (nullo result))
((fresh (a d r-rest) (conso a d l) (conde ((membero a d) (nub-o d result)) ((nafc (membero a d)) (conso a r-rest result) (nub-o d r-rest))))))))
(define
take-while-o
(fn
(pred l result)
(conde
((nullo l) (nullo result))
((fresh (a d r-rest) (conso a d l) (conde ((pred a) (conso a r-rest result) (take-while-o pred d r-rest)) ((nafc (pred a)) (== result (list)))))))))
(define
drop-while-o
(fn
(pred l result)
(conde
((nullo l) (nullo result))
((fresh (a d) (conso a d l) (conde ((pred a) (drop-while-o pred d result)) ((nafc (pred a)) (== result l))))))))
(define
membero
(fn
(x l)
(conde
((fresh (d) (conso x d l)))
((fresh (a d) (conso a d l) (membero x d))))))
(define
not-membero
(fn
(x l)
(conde
((nullo l))
((fresh (a d) (conso a d l) (nafc (== a x)) (not-membero x d))))))
(define
subseto
(fn
(l1 l2)
(conde
((nullo l1))
((fresh (a d) (conso a d l1) (membero a l2) (subseto d l2))))))
(define
reverseo
(fn
(l r)
(conde
((nullo l) (nullo r))
((fresh (a d res-rev) (conso a d l) (reverseo d res-rev) (appendo res-rev (list a) r))))))
(define
rev-acco
(fn
(l acc result)
(conde
((nullo l) (== result acc))
((fresh (a d acc-prime) (conso a d l) (conso a acc acc-prime) (rev-acco d acc-prime result))))))
(define rev-2o (fn (l result) (rev-acco l (list) result)))
(define palindromeo (fn (l) (fresh (rev) (reverseo l rev) (== l rev))))
(define prefixo (fn (p l) (fresh (rest) (appendo p rest l))))
(define suffixo (fn (s l) (fresh (front) (appendo front s l))))
(define
subo
(fn
(s l)
(fresh
(front-and-s back front)
(appendo front-and-s back l)
(appendo front s front-and-s))))
(define
selecto
(fn
(x rest l)
(conde
((conso x rest l))
((fresh (a d r) (conso a d l) (conso a r rest) (selecto x r d))))))
(define
lengtho
(fn
(l n)
(conde
((nullo l) (== n :z))
((fresh (a d n-1) (conso a d l) (== n (list :s n-1)) (lengtho d n-1))))))
(define
inserto
(fn
(a l p)
(conde
((conso a l p))
((fresh (h t pt) (conso h t l) (conso h pt p) (inserto a t pt))))))
(define
permuteo
(fn
(l p)
(conde
((nullo l) (nullo p))
((fresh (a d perm-d) (conso a d l) (permuteo d perm-d) (inserto a perm-d p))))))
(define
flatteno
(fn
(tree flat)
(conde
((nullo tree) (nullo flat))
((pairo tree)
(fresh
(h t hf tf)
(conso h t tree)
(flatteno h hf)
(flatteno t tf)
(appendo hf tf flat)))
((nafc (nullo tree)) (nafc (pairo tree)) (== flat (list tree))))))
(define
rembero
(fn
(x l out)
(conde
((nullo l) (nullo out))
((fresh (a d) (conso a d l) (== a x) (== out d)))
((fresh (a d res) (conso a d l) (nafc (== a x)) (conso a res out) (rembero x d res))))))
(define
removeo-allo
(fn
(x l result)
(conde
((nullo l) (nullo result))
((fresh (a d) (conso a d l) (== a x) (removeo-allo x d result)))
((fresh (a d r-rest) (conso a d l) (nafc (== a x)) (conso a r-rest result) (removeo-allo x d r-rest))))))
(define
assoco
(fn
(key pairs val)
(fresh
(rest)
(conde
((conso (list key val) rest pairs))
((fresh (other) (conso other rest pairs) (assoco key rest val)))))))
(define
nth-o
(fn
(n l elem)
(conde
((== n :z) (fresh (d) (conso elem d l)))
((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (nth-o n-1 d elem))))))
(define
samelengtho
(fn
(l1 l2)
(conde
((nullo l1) (nullo l2))
((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (samelengtho d d-prime))))))
(define
mapo
(fn
(rel l1 l2)
(conde
((nullo l1) (nullo l2))
((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (rel a a-prime) (mapo rel d d-prime))))))
(define
iterate-no
(fn
(rel n x result)
(conde
((== n :z) (== result x))
((fresh (n-1 mid) (== n (list :s n-1)) (rel x mid) (iterate-no rel n-1 mid result))))))
(define
pairlisto
(fn
(l1 l2 pairs)
(conde
((nullo l1) (nullo l2) (nullo pairs))
((fresh (a1 d1 a2 d2 d-pairs) (conso a1 d1 l1) (conso a2 d2 l2) (conso (list a1 a2) d-pairs pairs) (pairlisto d1 d2 d-pairs))))))
(define
zip-with-o
(fn
(rel l1 l2 result)
(conde
((nullo l1) (nullo l2) (nullo result))
((fresh (a1 d1 a2 d2 a-result d-result) (conso a1 d1 l1) (conso a2 d2 l2) (rel a1 a2 a-result) (conso a-result d-result result) (zip-with-o rel d1 d2 d-result))))))
(define
swap-firsto
(fn
(l result)
(fresh
(a b rest mid-l mid-r)
(conso a mid-l l)
(conso b rest mid-l)
(conso b mid-r result)
(conso a rest mid-r))))
(define
everyo
(fn
(rel l)
(conde
((nullo l))
((fresh (a d) (conso a d l) (rel a) (everyo rel d))))))
(define
someo
(fn
(rel l)
(conde
((fresh (a d) (conso a d l) (rel a)))
((fresh (a d) (conso a d l) (someo rel d))))))
(define
lasto
(fn
(l x)
(conde
((conso x (list) l))
((fresh (a d) (conso a d l) (lasto d x))))))
(define
init-o
(fn
(l init)
(conde
((fresh (x) (conso x (list) l) (== init (list))))
((fresh (a d d-init) (conso a d l) (conso a d-init init) (init-o d d-init))))))
(define
tako
(fn
(n l prefix)
(conde
((== n :z) (== prefix (list)))
((fresh (n-1 a d p-rest) (== n (list :s n-1)) (conso a d l) (conso a p-rest prefix) (tako n-1 d p-rest))))))
(define
dropo
(fn
(n l suffix)
(conde
((== n :z) (== suffix l))
((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (dropo n-1 d suffix))))))
(define
repeato
(fn
(x n result)
(conde
((== n :z) (== result (list)))
((fresh (n-1 r-rest) (== n (list :s n-1)) (conso x r-rest result) (repeato x n-1 r-rest))))))
(define
concato
(fn
(lists result)
(conde
((nullo lists) (nullo result))
((fresh (h t r-rest) (conso h t lists) (appendo h r-rest result) (concato t r-rest))))))

View File

@@ -1,56 +0,0 @@
;; lib/minikanren/run.sx — Phase 3: drive a goal + reify the query var.
;;
;; reify-name N — make the canonical "_.N" reified symbol.
;; reify-s term rs — walk term in rs, add a mapping from each fresh
;; unbound var to its _.N name (left-to-right order).
;; reify q s — walk* q in s, build reify-s, walk* again to
;; substitute reified names in.
;; run-n n q-name g... — defmacro: bind q-name to a fresh var, conj goals,
;; take ≤ n answers from the stream, reify each
;; through q-name. n = -1 takes all (used by run*).
;; run* — defmacro: (run* q g...) ≡ (run-n -1 q g...)
;; run — defmacro: (run n q g...) ≡ (run-n n q g...)
;; The two-segment form is the standard TRS API.
(define reify-name (fn (n) (make-symbol (str "_." n))))
(define
reify-s
(fn
(term rs)
(let
((w (mk-walk term rs)))
(cond
((is-var? w) (extend (var-name w) (reify-name (len rs)) rs))
((mk-list-pair? w) (reduce (fn (acc a) (reify-s a acc)) rs w))
(:else rs)))))
(define
reify
(fn
(term s)
(let
((w (mk-walk* term s)))
(let ((rs (reify-s w (empty-subst)))) (mk-walk* w rs)))))
(defmacro
run-n
(n q-name &rest goals)
(quasiquote
(let
(((unquote q-name) (make-var)))
(map
(fn (s) (reify (unquote q-name) s))
(stream-take
(unquote n)
((mk-conj (splice-unquote goals)) empty-s))))))
(defmacro
run*
(q-name &rest goals)
(quasiquote (run-n -1 (unquote q-name) (splice-unquote goals))))
(defmacro
run
(n q-name &rest goals)
(quasiquote (run-n (unquote n) (unquote q-name) (splice-unquote goals))))

View File

@@ -1,66 +0,0 @@
;; lib/minikanren/stream.sx — Phase 2 piece A: lazy streams of substitutions.
;;
;; SX has no improper pairs (cons requires a list cdr), so we use a
;; tagged stream-cell shape for mature stream elements:
;;
;; stream ::= mzero empty (the SX empty list)
;; | (:s HEAD TAIL) mature cell, TAIL is a stream
;; | thunk (fn () ...) → stream when forced
;;
;; HEAD is a substitution dict. TAIL is again a stream (possibly a thunk),
;; which is what gives us laziness — mk-mplus can return a mature head with
;; a thunk in the tail, deferring the rest of the search.
(define mzero (list))
(define s-cons (fn (h t) (list :s h t)))
(define
s-cons?
(fn (s) (and (list? s) (not (empty? s)) (= (first s) :s))))
(define s-car (fn (s) (nth s 1)))
(define s-cdr (fn (s) (nth s 2)))
(define unit (fn (s) (s-cons s mzero)))
(define stream-pause? (fn (s) (and (not (list? s)) (callable? s))))
;; mk-mplus — interleave two streams. If s1 is paused we suspend and
;; swap (Reasoned Schemer "interleave"); otherwise mature-cons head with
;; mk-mplus of the rest.
(define
mk-mplus
(fn
(s1 s2)
(cond
((empty? s1) s2)
((stream-pause? s1) (fn () (mk-mplus s2 (s1))))
(:else (s-cons (s-car s1) (mk-mplus (s-cdr s1) s2))))))
;; mk-bind — apply goal g to every substitution in stream s, mk-mplus-ing.
(define
mk-bind
(fn
(s g)
(cond
((empty? s) mzero)
((stream-pause? s) (fn () (mk-bind (s) g)))
(:else (mk-mplus (g (s-car s)) (mk-bind (s-cdr s) g))))))
;; stream-take — force up to n results out of a (possibly lazy) stream
;; into a flat SX list of substitutions. n = -1 means take all.
(define
stream-take
(fn
(n s)
(cond
((= n 0) (list))
((empty? s) (list))
((stream-pause? s) (stream-take n (s)))
(:else
(cons
(s-car s)
(stream-take
(if (= n -1) -1 (- n 1))
(s-cdr s)))))))

View File

@@ -1,157 +0,0 @@
;; lib/minikanren/tabling.sx — Phase 7 piece A: naive memoization.
;;
;; A `table-2` wrapper for 2-arg relations (input, output). Caches by
;; ground input (walked at call time). On hit, replays the cached output
;; values; on miss, runs the relation, collects all output values from
;; the answer stream, stores, then replays.
;;
;; Limitations of naive memoization (vs proper SLG / producer-consumer
;; tabling):
;; - Each call must terminate before its result enters the cache —
;; so cyclic recursive calls with the SAME ground input would still
;; diverge (not addressed here).
;; - Caching by full ground walk only; partially-ground args fall
;; through to the underlying relation.
;;
;; Despite the limitations, naive memoization is enough for the
;; canonical demo: Fibonacci goes from exponential to linear because
;; each fib(k) result is computed at most once.
;;
;; Cache lifetime: a single global mk-tab-cache. Use `(mk-tab-clear!)`
;; between independent queries.
(define mk-tab-cache {})
(define mk-tab-clear! (fn () (set! mk-tab-cache {})))
(define
mk-tab-lookup
(fn
(key)
(cond
((has-key? mk-tab-cache key) (get mk-tab-cache key))
(:else :miss))))
(define
mk-tab-store!
(fn (key vals) (set! mk-tab-cache (assoc mk-tab-cache key vals))))
(define
mk-tab-ground-term?
(fn
(t)
(cond
((is-var? t) false)
((mk-cons-cell? t)
(and
(mk-tab-ground-term? (mk-cons-head t))
(mk-tab-ground-term? (mk-cons-tail t))))
((mk-list-pair? t) (every? mk-tab-ground-term? t))
(:else true))))
(define
mk-tab-replay-vals
(fn
(vals output s)
(cond
((empty? vals) mzero)
(:else
(let
((sp (mk-unify output (first vals) s)))
(let
((this-stream (cond ((= sp nil) mzero) (:else (unit sp)))))
(mk-mplus this-stream (mk-tab-replay-vals (rest vals) output s))))))))
(define
table-2
(fn
(name rel-fn)
(fn
(input output)
(fn
(s)
(let
((winput (mk-walk* input s)))
(cond
((mk-tab-ground-term? winput)
(let
((key (str name "@" winput)))
(let
((cached (mk-tab-lookup key)))
(cond
((= cached :miss)
(let
((all-substs (stream-take -1 ((rel-fn input output) s))))
(let
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
(begin
(mk-tab-store! key vals)
(mk-tab-replay-vals vals output s)))))
(:else (mk-tab-replay-vals cached output s))))))
(:else ((rel-fn input output) s))))))))
;; --- table-1: 1-arg relation (one input, no output to cache) ---
;; The relation is a predicate `(p input)` that succeeds or fails.
;; Cache stores either :ok or :no.
(define
table-1
(fn
(name rel-fn)
(fn
(input)
(fn
(s)
(let
((winput (mk-walk* input s)))
(cond
((mk-tab-ground-term? winput)
(let
((key (str name "@1@" winput)))
(let
((cached (mk-tab-lookup key)))
(cond
((= cached :miss)
(let
((stream ((rel-fn input) s)))
(let
((peek (stream-take 1 stream)))
(cond
((empty? peek)
(begin (mk-tab-store! key :no) mzero))
(:else (begin (mk-tab-store! key :ok) stream))))))
((= cached :ok) (unit s))
((= cached :no) mzero)
(:else mzero)))))
(:else ((rel-fn input) s))))))))
;; --- table-3: 3-arg relation (input1 input2 output) ---
;; Cache keyed by (input1, input2). Output values cached as a list.
(define
table-3
(fn
(name rel-fn)
(fn
(i1 i2 output)
(fn
(s)
(let
((wi1 (mk-walk* i1 s)) (wi2 (mk-walk* i2 s)))
(cond
((and (mk-tab-ground-term? wi1) (mk-tab-ground-term? wi2))
(let
((key (str name "@3@" wi1 "/" wi2)))
(let
((cached (mk-tab-lookup key)))
(cond
((= cached :miss)
(let
((all-substs (stream-take -1 ((rel-fn i1 i2 output) s))))
(let
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
(begin
(mk-tab-store! key vals)
(mk-tab-replay-vals vals output s)))))
(:else (mk-tab-replay-vals cached output s))))))
(:else ((rel-fn i1 i2 output) s))))))))

View File

@@ -1,49 +0,0 @@
;; lib/minikanren/tests/appendo3.sx — 3-list append.
(mk-test
"appendo3-forward"
(run*
q
(appendo3
(list 1 2)
(list 3 4)
(list 5 6)
q))
(list
(list 1 2 3 4 5 6)))
(mk-test
"appendo3-empty-everything"
(run* q (appendo3 (list) (list) (list) q))
(list (list)))
(mk-test
"appendo3-recover-middle"
(run*
q
(appendo3
(list 1 2)
q
(list 5 6)
(list 1 2 3 4 5 6)))
(list (list 3 4)))
(mk-test
"appendo3-empty-middle"
(run*
q
(appendo3
(list 1 2)
(list)
(list 3 4)
q))
(list (list 1 2 3 4)))
(mk-test
"appendo3-empty-first-and-last"
(run*
q
(appendo3 (list) (list 1 2 3) (list) q))
(list (list 1 2 3)))
(mk-tests-run!)

View File

@@ -1,33 +0,0 @@
;; lib/minikanren/tests/arith-prog.sx — arithmetic progression generation.
(mk-test
"arith-progo-zero-len"
(run* q (arith-progo 5 1 0 q))
(list (list)))
(mk-test
"arith-progo-1-to-5"
(run* q (arith-progo 1 1 5 q))
(list (list 1 2 3 4 5)))
(mk-test
"arith-progo-evens-from-0"
(run* q (arith-progo 0 2 5 q))
(list (list 0 2 4 6 8)))
(mk-test
"arith-progo-descending"
(run* q (arith-progo 10 -1 4 q))
(list (list 10 9 8 7)))
(mk-test
"arith-progo-zero-step"
(run* q (arith-progo 7 0 3 q))
(list (list 7 7 7)))
(mk-test
"arith-progo-negative-start"
(run* q (arith-progo -3 2 4 q))
(list (list -3 -1 1 3)))
(mk-tests-run!)

View File

@@ -1,54 +0,0 @@
;; lib/minikanren/tests/btree-walko.sx — walk a leaves-of-binary-tree relation
;; using matche dispatch on (:leaf v) and (:node left right) patterns.
(define
btree-walko
(fn
(tree v)
(matche
tree
((:leaf x) (== v x))
((:node l r) (conde ((btree-walko l v)) ((btree-walko r v)))))))
;; A small test tree: ((1 2) (3 (4 5))).
(define
test-btree
(list
:node (list :node (list :leaf 1) (list :leaf 2))
(list
:node (list :leaf 3)
(list :node (list :leaf 4) (list :leaf 5)))))
(mk-test
"btree-walko-enumerates-all-leaves"
(let
((leaves (run* q (btree-walko test-btree q))))
(and
(= (len leaves) 5)
(and
(some (fn (l) (= l 1)) leaves)
(and
(some (fn (l) (= l 2)) leaves)
(and
(some (fn (l) (= l 3)) leaves)
(and
(some (fn (l) (= l 4)) leaves)
(some (fn (l) (= l 5)) leaves)))))))
true)
(mk-test
"btree-walko-find-3-membership"
(run 1 q (btree-walko test-btree 3))
(list (make-symbol "_.0")))
(mk-test
"btree-walko-find-99-not-present"
(run* q (btree-walko test-btree 99))
(list))
(mk-test
"btree-walko-leaf-only"
(run* q (btree-walko (list :leaf 42) q))
(list 42))
(mk-tests-run!)

View File

@@ -1,87 +0,0 @@
;; lib/minikanren/tests/classics.sx — small classic-style puzzles that
;; exercise the full system end to end (relations + conde + matche +
;; fresh + run*). Each test is a self-contained miniKanren program.
;; -----------------------------------------------------------------------
;; Pet puzzle (3 friends, 3 pets, 1-each).
;; -----------------------------------------------------------------------
(mk-test
"classics-pet-puzzle"
(run*
q
(fresh
(a b c)
(== q (list a b c))
(permuteo (list :dog :cat :fish) (list a b c))
(== b :fish)
(conde ((== a :cat)) ((== a :fish)))))
(list (list :cat :fish :dog)))
;; -----------------------------------------------------------------------
;; Family-relations puzzle (uses membero on a fact list).
;; -----------------------------------------------------------------------
(define
parent-facts
(list
(list "alice" "bob")
(list "alice" "carol")
(list "bob" "dave")
(list "carol" "eve")
(list "dave" "frank")))
(define parento (fn (x y) (membero (list x y) parent-facts)))
(define grandparento (fn (x z) (fresh (y) (parento x y) (parento y z))))
(mk-test
"classics-grandparents-of-frank"
(run* q (grandparento q "frank"))
(list "bob"))
(mk-test
"classics-grandchildren-of-alice"
(run* q (grandparento "alice" q))
(list "dave" "eve"))
;; -----------------------------------------------------------------------
;; Symbolic differentiation, matche-driven.
;; Variable :x: d/dx x = 1
;; Sum (:+ a b): d/dx (a+b) = (da + db)
;; Product (:* a b): d/dx (a*b) = (da*b + a*db)
;; -----------------------------------------------------------------------
(define
diffo
(fn
(expr var d)
(matche
expr
(:x (== d 1))
((:+ a b)
(fresh
(da db)
(== d (list :+ da db))
(diffo a var da)
(diffo b var db)))
((:* a b)
(fresh
(da db)
(== d (list :+ (list :* da b) (list :* a db)))
(diffo a var da)
(diffo b var db))))))
(mk-test "classics-diff-of-x" (run* q (diffo :x :x q)) (list 1))
(mk-test
"classics-diff-of-x-plus-x"
(run* q (diffo (list :+ :x :x) :x q))
(list (list :+ 1 1)))
(mk-test
"classics-diff-of-x-times-x"
(run* q (diffo (list :* :x :x) :x q))
(list (list :+ (list :* 1 :x) (list :* :x 1))))
(mk-tests-run!)

View File

@@ -1,52 +0,0 @@
;; lib/minikanren/tests/clpfd-distinct.sx — fd-distinct (alldifferent).
(mk-test
"fd-distinct-empty"
(run* q (fd-distinct (list)))
(list (make-symbol "_.0")))
(mk-test
"fd-distinct-singleton"
(run* q (fd-distinct (list 5)))
(list (make-symbol "_.0")))
(mk-test
"fd-distinct-pair-distinct"
(run* q (fd-distinct (list 1 2)))
(list (make-symbol "_.0")))
(mk-test
"fd-distinct-pair-equal-fails"
(run* q (fd-distinct (list 5 5)))
(list))
(mk-test
"fd-distinct-3-perms-of-3"
(let
((res (run* q (fresh (a b c) (fd-in a (list 1 2 3)) (fd-in b (list 1 2 3)) (fd-in c (list 1 2 3)) (fd-distinct (list a b c)) (fd-label (list a b c)) (== q (list a b c))))))
(= (len res) 6))
true)
(mk-test
"fd-distinct-4-perms-of-4-count"
(let
((res (run* q (fresh (a b c d) (fd-in a (list 1 2 3 4)) (fd-in b (list 1 2 3 4)) (fd-in c (list 1 2 3 4)) (fd-in d (list 1 2 3 4)) (fd-distinct (list a b c d)) (fd-label (list a b c d)) (== q (list a b c d))))))
(= (len res) 24))
true)
(mk-test
"fd-distinct-pigeonhole-fails"
(run*
q
(fresh
(a b c d)
(fd-in a (list 1 2 3))
(fd-in b (list 1 2 3))
(fd-in c (list 1 2 3))
(fd-in d (list 1 2 3))
(fd-distinct (list a b c d))
(fd-label (list a b c d))
(== q (list a b c d))))
(list))
(mk-tests-run!)

View File

@@ -1,133 +0,0 @@
;; lib/minikanren/tests/clpfd-domains.sx — Phase 6 piece B: domain primitives.
;; --- domain construction ---
(mk-test
"fd-dom-from-list-sorts"
(fd-dom-from-list
(list 3 1 2 1 5))
(list 1 2 3 5))
(mk-test "fd-dom-from-list-empty" (fd-dom-from-list (list)) (list))
(mk-test
"fd-dom-from-list-single"
(fd-dom-from-list (list 7))
(list 7))
(mk-test
"fd-dom-range-1-5"
(fd-dom-range 1 5)
(list 1 2 3 4 5))
(mk-test "fd-dom-range-empty" (fd-dom-range 5 1) (list))
;; --- predicates ---
(mk-test "fd-dom-empty-yes" (fd-dom-empty? (list)) true)
(mk-test "fd-dom-empty-no" (fd-dom-empty? (list 1)) false)
(mk-test "fd-dom-singleton-yes" (fd-dom-singleton? (list 5)) true)
(mk-test
"fd-dom-singleton-multi"
(fd-dom-singleton? (list 1 2))
false)
(mk-test "fd-dom-singleton-empty" (fd-dom-singleton? (list)) false)
(mk-test
"fd-dom-min"
(fd-dom-min (list 3 7 9))
3)
(mk-test
"fd-dom-max"
(fd-dom-max (list 3 7 9))
9)
(mk-test
"fd-dom-member-yes"
(fd-dom-member?
3
(list 1 2 3 4))
true)
(mk-test
"fd-dom-member-no"
(fd-dom-member?
9
(list 1 2 3 4))
false)
;; --- intersect / without ---
(mk-test
"fd-dom-intersect"
(fd-dom-intersect
(list 1 2 3 4 5)
(list 2 4 6))
(list 2 4))
(mk-test
"fd-dom-intersect-disjoint"
(fd-dom-intersect
(list 1 2 3)
(list 4 5 6))
(list))
(mk-test
"fd-dom-intersect-empty"
(fd-dom-intersect (list) (list 1 2 3))
(list))
(mk-test
"fd-dom-intersect-equal"
(fd-dom-intersect
(list 1 2 3)
(list 1 2 3))
(list 1 2 3))
(mk-test
"fd-dom-without-mid"
(fd-dom-without
3
(list 1 2 3 4 5))
(list 1 2 4 5))
(mk-test
"fd-dom-without-missing"
(fd-dom-without 9 (list 1 2 3))
(list 1 2 3))
(mk-test
"fd-dom-without-min"
(fd-dom-without 1 (list 1 2 3))
(list 2 3))
;; --- store accessors ---
(mk-test "fd-domain-of-unset" (fd-domain-of {} "x") nil)
(mk-test
"fd-domain-of-set"
(let
((s (fd-set-domain {} "x" (list 1 2 3))))
(fd-domain-of s "x"))
(list 1 2 3))
(mk-test
"fd-set-domain-empty-fails"
(fd-set-domain {} "x" (list))
nil)
(mk-test
"fd-set-domain-overrides"
(let
((s (fd-set-domain {} "x" (list 1 2 3))))
(fd-domain-of (fd-set-domain s "x" (list 5)) "x"))
(list 5))
(mk-test
"fd-set-domain-multiple-vars"
(let
((s (fd-set-domain (fd-set-domain {} "x" (list 1)) "y" (list 2 3))))
(list (fd-domain-of s "x") (fd-domain-of s "y")))
(list (list 1) (list 2 3)))
(mk-tests-run!)

View File

@@ -1,120 +0,0 @@
;; lib/minikanren/tests/clpfd-in-label.sx — fd-in (domain narrowing) + fd-label.
;; --- fd-in: domain narrowing ---
(mk-test
"fd-in-bare-label"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-label (list x))
(== q x)))
(list 1 2 3 4 5))
(mk-test
"fd-in-intersection"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-in x (list 3 4 5 6 7))
(fd-label (list x))
(== q x)))
(list 3 4 5))
(mk-test
"fd-in-disjoint-empty"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3))
(fd-in x (list 7 8 9))
(fd-label (list x))
(== q x)))
(list))
(mk-test
"fd-in-singleton-domain"
(run*
q
(fresh (x) (fd-in x (list 5)) (fd-label (list x)) (== q x)))
(list 5))
;; --- ground value checks the domain ---
(mk-test
"fd-in-ground-in-domain"
(run*
q
(fresh
(x)
(== x 3)
(fd-in x (list 1 2 3 4 5))
(== q x)))
(list 3))
(mk-test
"fd-in-ground-not-in-domain"
(run*
q
(fresh
(x)
(== x 9)
(fd-in x (list 1 2 3 4 5))
(== q x)))
(list))
;; --- fd-label across multiple vars ---
(mk-test
"fd-label-multiple-vars"
(let
((res (run* q (fresh (a b) (fd-in a (list 1 2 3)) (fd-in b (list 10 20)) (fd-label (list a b)) (== q (list a b))))))
(= (len res) 6))
true)
(mk-test
"fd-label-empty-vars"
(run* q (fd-label (list)))
(list (make-symbol "_.0")))
;; --- composition with regular goals ---
(mk-test
"fd-in-with-membero-style-filtering"
(run*
q
(fresh
(x)
(fd-in
x
(list
1
2
3
4
5
6
7
8
9
10))
(fd-label (list x))
(== q x)))
(list
1
2
3
4
5
6
7
8
9
10))
(mk-tests-run!)

View File

@@ -1,82 +0,0 @@
;; lib/minikanren/tests/clpfd-neq.sx — fd-neq with constraint propagation.
;; --- ground / domain interaction ---
(mk-test
"fd-neq-ground-distinct"
(run*
q
(fresh
(x)
(fd-neq x 5)
(fd-in x (list 4 5 6))
(fd-label (list x))
(== q x)))
(list 4 6))
(mk-test
"fd-neq-ground-equal-fails"
(run* q (fresh (x) (== x 5) (fd-neq x 5) (== q x)))
(list))
(mk-test
"fd-neq-symmetric"
(run*
q
(fresh
(x)
(fd-neq 7 x)
(fd-in x (list 5 6 7 8 9))
(fd-label (list x))
(== q x)))
(list 5 6 8 9))
;; --- two vars with overlapping domains ---
(mk-test
"fd-neq-pair-from-3"
(let
((res (run* q (fresh (x y) (fd-in x (list 1 2 3)) (fd-in y (list 1 2 3)) (fd-neq x y) (fd-label (list x y)) (== q (list x y))))))
(= (len res) 6))
true)
(mk-test
"fd-all-distinct-3-of-3"
(let
((res (run* q (fresh (a b c) (fd-in a (list 1 2 3)) (fd-in b (list 1 2 3)) (fd-in c (list 1 2 3)) (fd-neq a b) (fd-neq a c) (fd-neq b c) (fd-label (list a b c)) (== q (list a b c))))))
(= (len res) 6))
true)
(mk-test
"fd-pigeonhole-fails"
(run*
q
(fresh
(a b c)
(fd-in a (list 1 2))
(fd-in b (list 1 2))
(fd-in c (list 1 2))
(fd-neq a b)
(fd-neq a c)
(fd-neq b c)
(fd-label (list a b c))
(== q (list a b c))))
(list))
;; --- propagation when one side becomes ground ---
(mk-test
"fd-neq-propagates-after-ground"
(run*
q
(fresh
(x y)
(fd-in x (list 1 2 3))
(fd-in y (list 1 2 3))
(fd-neq x y)
(== x 2)
(fd-label (list y))
(== q y)))
(list 1 3))
(mk-tests-run!)

View File

@@ -1,128 +0,0 @@
;; lib/minikanren/tests/clpfd-ord.sx — fd-lt / fd-lte / fd-eq.
;; --- fd-lt ---
(mk-test
"fd-lt-narrows-x-against-num"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lt x 3)
(fd-label (list x))
(== q x)))
(list 1 2))
(mk-test
"fd-lt-narrows-x-against-num-symmetric"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lt 3 x)
(fd-label (list x))
(== q x)))
(list 4 5))
(mk-test
"fd-lt-pair-ordered"
(let
((res (run* q (fresh (x y) (fd-in x (list 1 2 3 4)) (fd-in y (list 1 2 3 4)) (fd-lt x y) (fd-label (list x y)) (== q (list x y))))))
(= (len res) 6))
true)
(mk-test
"fd-lt-impossible-fails"
(run*
q
(fresh
(x)
(fd-in x (list 5 6 7))
(fd-lt x 3)
(fd-label (list x))
(== q x)))
(list))
;; --- fd-lte ---
(mk-test
"fd-lte-includes-equal"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lte x 3)
(fd-label (list x))
(== q x)))
(list 1 2 3))
(mk-test
"fd-lte-equal-bound"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lte 3 x)
(fd-label (list x))
(== q x)))
(list 3 4 5))
;; --- fd-eq ---
(mk-test
"fd-eq-bind"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-eq x 3)
(== q x)))
(list 3))
(mk-test
"fd-eq-out-of-domain-fails"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3))
(fd-eq x 5)
(== q x)))
(list))
(mk-test
"fd-eq-two-vars-share-domain"
(run*
q
(fresh
(x y)
(fd-in x (list 1 2 3))
(fd-in y (list 2 3 4))
(fd-eq x y)
(fd-label (list x y))
(== q (list x y))))
(list (list 2 2) (list 3 3)))
;; --- combine fd-lt + fd-neq for "between" puzzle ---
(mk-test
"fd-lt-neq-combined"
(run*
q
(fresh
(x y z)
(fd-in x (list 1 2 3))
(fd-in y (list 1 2 3))
(fd-in z (list 1 2 3))
(fd-lt x y)
(fd-lt y z)
(fd-label (list x y z))
(== q (list x y z))))
(list (list 1 2 3)))
(mk-tests-run!)

View File

@@ -1,62 +0,0 @@
;; lib/minikanren/tests/clpfd-plus.sx — fd-plus (x + y = z).
(mk-test
"fd-plus-all-ground"
(run* q (fresh (z) (fd-plus 2 3 z) (== q z)))
(list 5))
(mk-test
"fd-plus-recover-x"
(run* q (fresh (x) (fd-plus x 3 5) (== q x)))
(list 2))
(mk-test
"fd-plus-recover-y"
(run* q (fresh (y) (fd-plus 2 y 5) (== q y)))
(list 3))
(mk-test
"fd-plus-impossible-fails"
(run*
q
(fresh
(z)
(fd-plus 2 3 z)
(== z 99)
(== q z)))
(list))
(mk-test
"fd-plus-domain-check"
(run*
q
(fresh
(x)
(fd-in x (list 3 4 5))
(fd-plus x 3 5)
(== q x)))
(list))
(mk-test
"fd-plus-pairs-summing-to-5"
(run*
q
(fresh
(x y)
(fd-in x (list 1 2 3 4))
(fd-in y (list 1 2 3 4))
(fd-plus x y 5)
(fd-label (list x y))
(== q (list x y))))
(list
(list 1 4)
(list 2 3)
(list 3 2)
(list 4 1)))
(mk-test
"fd-plus-z-derived"
(run* q (fresh (z) (fd-plus 7 8 z) (== q z)))
(list 15))
(mk-tests-run!)

View File

@@ -1,85 +0,0 @@
;; lib/minikanren/tests/clpfd-times.sx — fd-times (x * y = z).
(mk-test
"fd-times-3-4"
(run* q (fresh (z) (fd-times 3 4 z) (== q z)))
(list 12))
(mk-test
"fd-times-recover-divisor"
(run* q (fresh (x) (fd-times x 5 30) (== q x)))
(list 6))
(mk-test
"fd-times-non-divisible-fails"
(run* q (fresh (x) (fd-times x 5 31) (== q x)))
(list))
(mk-test
"fd-times-by-zero"
(run* q (fresh (z) (fd-times 0 99 z) (== q z)))
(list 0))
(mk-test
"fd-times-zero-by-anything-zero"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3))
(fd-times x 0 0)
(fd-label (list x))
(== q x)))
(list 1 2 3))
(mk-test
"fd-times-12-divisor-pairs"
(run*
q
(fresh
(x y)
(fd-in
x
(list
1
2
3
4
5
6))
(fd-in
y
(list
1
2
3
4
5
6))
(fd-times x y 12)
(fd-label (list x y))
(== q (list x y))))
(list
(list 2 6)
(list 3 4)
(list 4 3)
(list 6 2)))
(mk-test
"fd-times-square-of-each"
(run*
q
(fresh
(x z)
(fd-in x (list 1 2 3 4 5))
(fd-times x x z)
(fd-label (list x))
(== q (list x z))))
(list
(list 1 1)
(list 2 4)
(list 3 9)
(list 4 16)
(list 5 25)))
(mk-tests-run!)

View File

@@ -1,75 +0,0 @@
;; lib/minikanren/tests/conda.sx — Phase 5 piece A tests for `conda`.
;; --- conda commits to first non-failing head, keeps ALL its answers ---
(mk-test
"conda-first-clause-keeps-all"
(run*
q
(conda
((mk-disj (== q 1) (== q 2)))
((== q 100))))
(list 1 2))
(mk-test
"conda-skips-failing-head"
(run*
q
(conda
((== 1 2))
((mk-disj (== q 10) (== q 20)))))
(list 10 20))
(mk-test
"conda-all-fail"
(run*
q
(conda ((== 1 2)) ((== 3 4))))
(list))
(mk-test "conda-no-clauses" (run* q (conda)) (list))
;; --- conda DIFFERS from condu: conda keeps all head answers ---
(mk-test
"conda-vs-condu-divergence"
(list
(run*
q
(conda
((mk-disj (== q 1) (== q 2)))
((== q 100))))
(run*
q
(condu
((mk-disj (== q 1) (== q 2)))
((== q 100)))))
(list (list 1 2) (list 1)))
;; --- conda head's rest-goals run on every head answer ---
(mk-test
"conda-rest-goals-run-on-all-answers"
(run*
q
(fresh
(x r)
(conda
((mk-disj (== x 1) (== x 2))
(== r (list :tag x))))
(== q r)))
(list (list :tag 1) (list :tag 2)))
;; --- if rest-goals fail on a head answer, that head answer is filtered;
;; the clause does not fall through to next clauses (per soft-cut). ---
(mk-test
"conda-rest-fails-no-fallthrough"
(run*
q
(conda
((mk-disj (== q 1) (== q 2)) (== q 99))
((== q 200))))
(list))
(mk-tests-run!)

View File

@@ -1,89 +0,0 @@
;; lib/minikanren/tests/conde.sx — Phase 2 piece C tests for `conde`.
;;
;; Note on ordering: conde clauses are wrapped in Zzz (inverse-eta delay),
;; so applying the conde goal to a substitution returns thunks. mk-mplus
;; suspends-and-swaps when its left operand is paused, giving fair
;; interleaving — this is exactly what makes recursive relations work,
;; but it does mean conde answers can interleave rather than appear in
;; strict left-to-right clause order.
;; --- single-clause conde ≡ conj of clause body ---
(mk-test
"conde-one-clause"
(let ((q (mk-var "q"))) (run* q (conde ((== q 7)))))
(list 7))
(mk-test
"conde-one-clause-multi-goals"
(let
((q (mk-var "q")))
(run* q (conde ((fresh (x) (== x 5) (== q (list x x)))))))
(list (list 5 5)))
;; --- multi-clause: produces one row per clause (interleaved) ---
(mk-test
"conde-three-clauses-as-set"
(let
((qs (run* q (conde ((== q 1)) ((== q 2)) ((== q 3))))))
(and
(= (len qs) 3)
(and
(some (fn (x) (= x 1)) qs)
(and
(some (fn (x) (= x 2)) qs)
(some (fn (x) (= x 3)) qs)))))
true)
(mk-test
"conde-mixed-success-failure-as-set"
(let
((qs (run* q (conde ((== q "a")) ((== 1 2)) ((== q "b"))))))
(and
(= (len qs) 2)
(and (some (fn (x) (= x "a")) qs) (some (fn (x) (= x "b")) qs))))
true)
;; --- conde with conjuncts inside clauses ---
(mk-test
"conde-clause-conj-as-set"
(let
((rows (run* q (fresh (x y) (conde ((== x 1) (== y 10)) ((== x 2) (== y 20))) (== q (list x y))))))
(and
(= (len rows) 2)
(and
(some (fn (r) (= r (list 1 10))) rows)
(some (fn (r) (= r (list 2 20))) rows))))
true)
;; --- nested conde ---
(mk-test
"conde-nested-yields-three"
(let
((qs (run* q (conde ((conde ((== q 1)) ((== q 2)))) ((== q 3))))))
(and
(= (len qs) 3)
(and
(some (fn (x) (= x 1)) qs)
(and
(some (fn (x) (= x 2)) qs)
(some (fn (x) (= x 3)) qs)))))
true)
;; --- conde all clauses fail → empty stream ---
(mk-test
"conde-all-fail"
(run*
q
(conde ((== 1 2)) ((== 3 4))))
(list))
;; --- empty conde: no clauses ⇒ fail ---
(mk-test "conde-no-clauses" (run* q (conde)) (list))
(mk-tests-run!)

View File

@@ -1,86 +0,0 @@
;; lib/minikanren/tests/condu.sx — Phase 2 piece D tests for `onceo` and `condu`.
;; --- onceo: at most one answer ---
(mk-test
"onceo-single-success-passes-through"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((onceo (== q 7)) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 7))
(mk-test
"onceo-multi-success-trimmed-to-one"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((onceo (mk-disj (== q 1) (== q 2) (== q 3))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 1))
(mk-test
"onceo-failure-stays-failure"
((onceo (== 1 2)) empty-s)
(list))
(mk-test
"onceo-conde-trimmed"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((onceo (conde ((== q "a")) ((== q "b")))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list "a"))
;; --- condu: first clause with successful head wins ---
(mk-test
"condu-first-clause-wins"
(let
((q (mk-var "q")))
(let
((res (stream-take 10 ((condu ((== q 1)) ((== q 2))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 1))
(mk-test
"condu-skips-failing-head"
(let
((q (mk-var "q")))
(let
((res (stream-take 10 ((condu ((== 1 2)) ((== q 100)) ((== q 200))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 100))
(mk-test
"condu-all-fail-empty"
((condu ((== 1 2)) ((== 3 4)))
empty-s)
(list))
(mk-test "condu-empty-clauses-fail" ((condu) empty-s) (list))
;; --- condu commits head's first answer; rest-goals can still backtrack
;; within that committed substitution but cannot revisit other heads. ---
(mk-test
"condu-head-onceo-rest-runs"
(let
((q (mk-var "q")) (r (mk-var "r")))
(let
((res (stream-take 10 ((condu ((mk-disj (== q 1) (== q 2)) (== r 99))) empty-s))))
(map (fn (s) (list (mk-walk q s) (mk-walk r s))) res)))
(list (list 1 99)))
(mk-test
"condu-rest-goals-can-fail-the-clause"
(let
((q (mk-var "q")))
(let
((res (stream-take 10 ((condu ((== q 1) (== 2 3)) ((== q 99))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list))
(mk-tests-run!)

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