My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

OxCaml compat: cppo preprocessing, version guards, and new exercises

- Add cppo preprocessing for merlin-js and x-ocaml workers to support
conditional compilation with OXCAML flag
- Guard day10 packages with enabled_if >= 5.3.0 since they need recent OCaml
- Remove fatal odoc warnings from dune-workspace (handled per-package now)
- Bump merlin-js dune lang to 3.17
- Add warning suppression flags where needed (-w -58, -w -67)
- Add interactive extension exercise pages (FOCS 2020/2024/2025, OxCaml
stack allocation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+733 -8
+1
day10/analysis/dune
··· 1 1 (executables 2 2 (names universe_compat real_world conflict_analysis) 3 + (enabled_if (>= %{ocaml_version} 5.3.0)) 3 4 (libraries unix))
+1
day10/bin/dune
··· 1 1 (executable 2 2 (public_name day10) 3 3 (name main) 4 + (enabled_if (>= %{ocaml_version} 5.3.0)) 4 5 (package day10) 5 6 (libraries opam-0install yojson ppx_deriving_yojson.runtime cmdliner dockerfile day10_lib) 6 7 (preprocess
+2
day10/dune-project
··· 17 17 18 18 (package 19 19 (name day10) 20 + (allow_empty) 20 21 (synopsis "A short synopsis") 21 22 (description "A longer description") 22 23 (depends ··· 31 32 32 33 (package 33 34 (name day10-web) 35 + (allow_empty) 34 36 (synopsis "Web dashboard for day10 documentation status") 35 37 (description "Status dashboard for package maintainers and operators") 36 38 (depends
+1
day10/lib/dune
··· 1 1 (library 2 2 (name day10_lib) 3 + (enabled_if (>= %{ocaml_version} 5.3.0)) 3 4 (libraries unix str yojson) 4 5 (modules atomic_swap build_lock gc progress run_log))
+6
day10/tests/unit/dune
··· 1 1 (executable 2 2 (name test_atomic_swap) 3 + (enabled_if (>= %{ocaml_version} 5.3.0)) 3 4 (libraries day10_lib)) 4 5 5 6 (executable 6 7 (name test_gc) 8 + (enabled_if (>= %{ocaml_version} 5.3.0)) 7 9 (libraries day10_lib)) 8 10 9 11 (executable 10 12 (name test_run_log) 13 + (enabled_if (>= %{ocaml_version} 5.3.0)) 11 14 (libraries day10_lib unix yojson)) 12 15 13 16 (executable 14 17 (name test_run_data) 18 + (enabled_if (>= %{ocaml_version} 5.3.0)) 15 19 (libraries day10_web_data unix yojson)) 16 20 17 21 (executable 18 22 (name test_package_data) 23 + (enabled_if (>= %{ocaml_version} 5.3.0)) 19 24 (libraries day10_web_data unix)) 20 25 21 26 ; Run all tests with: dune runtest 22 27 (rule 23 28 (alias runtest) 29 + (enabled_if (>= %{ocaml_version} 5.3.0)) 24 30 (deps test_atomic_swap.exe test_gc.exe test_run_log.exe test_run_data.exe test_package_data.exe) 25 31 (action (progn 26 32 (run ./test_atomic_swap.exe)
+1
day10/web/data/dune
··· 1 1 (library 2 2 (name day10_web_data) 3 + (enabled_if (>= %{ocaml_version} 5.3.0)) 3 4 (libraries unix yojson day10_lib) 4 5 (modules run_data package_data layer_data lock_data progress_data))
+1
day10/web/dune
··· 1 1 (executable 2 2 (name main) 3 + (enabled_if (>= %{ocaml_version} 5.3.0)) 3 4 (public_name day10-web) 4 5 (package day10-web) 5 6 (libraries dream day10_lib day10_web_data day10_web_views cmdliner unix yojson))
+1
day10/web/views/dune
··· 1 1 (library 2 2 (name day10_web_views) 3 + (enabled_if (>= %{ocaml_version} 5.3.0)) 3 4 (libraries dream day10_web_data) 4 5 (modules layout dashboard runs packages live_log))
-1
dune-workspace
··· 3 3 (env 4 4 (dev 5 5 (odoc 6 - (warnings fatal) 7 6 (html_flags --shell docsite) 8 7 )))
+3
js_top_worker/dune
··· 1 + (env 2 + (_ 3 + (ocamlopt_flags (:standard -w -58))))
+1
js_top_worker/test/browser/dune
··· 12 12 (name test_worker) 13 13 (modes js) 14 14 (modules test_worker) 15 + (ocamlopt_flags (:standard -w -58)) 15 16 (link_flags (-linkall)) 16 17 (preprocess (pps js_of_ocaml-ppx)) 17 18 (js_of_ocaml
+1 -1
merlin-js/dune-project
··· 1 - (lang dune 3.0) 1 + (lang dune 3.17) 2 2 3 3 (name merlin-js)
+1
merlin-js/src/extension/dune
··· 1 1 (library 2 2 (name merlin_codemirror) 3 3 (public_name merlin-js.code-mirror) 4 + (flags (:standard -w -67)) 4 5 (libraries 5 6 brr 6 7 merlin_client
+15
merlin-js/src/worker/dune
··· 1 + ; Generate worker.ml from worker.cppo.ml with conditional OXCAML flag 2 + 3 + (rule 4 + (targets worker.ml) 5 + (deps (:x worker.cppo.ml)) 6 + (enabled_if (not %{ocaml-config:ox})) 7 + (action (run %{bin:cppo} -V OCAML:%{ocaml_version} %{x} -o %{targets}))) 8 + 9 + (rule 10 + (targets worker.ml) 11 + (deps (:x worker.cppo.ml)) 12 + (enabled_if %{ocaml-config:ox}) 13 + (action (run %{bin:cppo} -V OCAML:%{ocaml_version} -D OXCAML %{x} -o %{targets}))) 14 + 1 15 (library 2 16 (name worker) 3 17 (public_name merlin-js.worker) 18 + (ocamlopt_flags (:standard -w -58)) 4 19 (js_of_ocaml 5 20 (javascript_files stubs.js)) 6 21 (preprocess (pps js_of_ocaml-ppx))
+6 -1
merlin-js/src/worker/worker.ml merlin-js/src/worker/worker.cppo.ml
··· 50 50 | None -> ()) dcs.dcs_toplevel_modules; 51 51 52 52 let new_load ~allow_hidden ~unit_name = 53 - let filename = filename_of_module unit_name in 53 + #if defined OXCAML 54 + let unit_name_str = Ocaml_typing.Compilation_unit.Name.to_string unit_name in 55 + #else 56 + let unit_name_str = unit_name in 57 + #endif 58 + let filename = filename_of_module unit_name_str in 54 59 let fs_name = Filename.(concat stdlib_path filename) in 55 60 (* Check if it's already been downloaded. This will be the 56 61 case for all toplevel cmis. Also check whether we're supposed
+143
odoc-interactive-extension/doc/focs_2020_q2.mld
··· 1 + {0 Simplified Mastermind} 2 + 3 + @x-ocaml.universe https://jon.ludl.am/ocaml/ 4 + 5 + {e Cambridge Computer Science Tripos Part IA -- 2020 -- Paper 1, Question 2} 6 + 7 + In this exercise, we will develop a game engine to play a simplified version 8 + of the game of {e Mastermind}. 9 + 10 + In simplified Mastermind, player A selects a list of [n] colours among 3 11 + possible colours: [Red], [Green] and [Blue] (e.g., [[Red; Red; Green; Blue]] 12 + if n = 4). Player B has to guess player A's list by proposing lists of colours 13 + in sequence. Every time player B proposes a list, she gets feedback in the 14 + form of a number [x] -- the number of colours that are in the correct 15 + position. 16 + 17 + For example, if player A's list is [[Red; Red; Green; Blue]] and player B 18 + guessed [[Red; Green; Green; Red]], then x = 2 (the first [Red] and the 19 + [Green] are at the right positions). 20 + 21 + We will use the following type throughout: 22 + 23 + {@ocaml[ 24 + type colour = Red | Green | Blue 25 + 26 + exception SizeMismatch 27 + ]} 28 + 29 + {1 Part (a): Feedback function} 30 + 31 + Given two colour lists, write a function [feedback] that counts how many 32 + positions have matching colours. The first argument [a] is player A's list, 33 + and the second [b] is player B's guess. Raise [SizeMismatch] if the lengths 34 + don't match. 35 + 36 + {@ocaml exercise id=part_a[ 37 + let feedback (a : colour list) (b : colour list) : int = failwith "TODO" 38 + ]} 39 + 40 + {@ocaml test for=part_a[ 41 + let () = 42 + let check a b expected = 43 + let got = feedback a b in 44 + Printf.printf "feedback => %d %s\n" got 45 + (if got = expected then "(correct)" else "(WRONG)"); 46 + assert (got = expected) 47 + in 48 + check [Red; Red; Green; Blue] [Red; Green; Green; Red] 2; 49 + check [Red; Green; Blue] [Red; Green; Blue] 3; 50 + check [Red; Red; Red] [Blue; Blue; Blue] 0; 51 + check [Red; Green; Blue] [Blue; Red; Green] 0; 52 + print_endline "All tests passed!" 53 + ]} 54 + 55 + {1 Part (b): Using currying} 56 + 57 + Using currying, define a [test] function that takes a list proposed by player B 58 + and returns [x]. This function should assume that player A's list is 59 + [[Blue; Green; Red]]. 60 + 61 + {@ocaml exercise id=part_b[ 62 + let test : colour list -> int = failwith "TODO" 63 + ]} 64 + 65 + {@ocaml test for=part_b[ 66 + let () = 67 + let check guess expected = 68 + let got = test guess in 69 + Printf.printf "test => %d %s\n" got 70 + (if got = expected then "(correct)" else "(WRONG)"); 71 + assert (got = expected) 72 + in 73 + check [Blue; Green; Red] 3; 74 + check [Red; Green; Blue] 1; 75 + check [Blue; Blue; Blue] 1; 76 + print_endline "All tests passed!" 77 + ]} 78 + 79 + {1 Part (c): Type of [test]} 80 + 81 + What is the type of [test] in Part (b)? 82 + 83 + Think about this, then check by evaluating: 84 + 85 + {@ocaml[ 86 + test 87 + ]} 88 + 89 + {1 Part (d): Generating all colour lists} 90 + 91 + Write a function [generate_lists] that generates all possible colour lists of 92 + a given length [n]. 93 + 94 + {e Tip:} [generate_lists 2] should output 3{^2} = 9 lists. 95 + 96 + {@ocaml exercise id=part_d[ 97 + let rec generate_lists (n : int) : colour list list = failwith "TODO" 98 + ]} 99 + 100 + {@ocaml test for=part_d[ 101 + let () = 102 + let check n expected = 103 + let got = List.length (generate_lists n) in 104 + Printf.printf "generate_lists %d: %d lists %s\n" n got 105 + (if got = expected then "(correct)" else "(WRONG)"); 106 + assert (got = expected) 107 + in 108 + check 1 3; 109 + check 2 9; 110 + check 3 27; 111 + print_endline "All tests passed!" 112 + ]} 113 + 114 + {1 Part (e): Finding valid lists} 115 + 116 + Given a colour list guess by player B and feedback [x], write a function 117 + [valid_lists] that returns all possible lists that player A could have chosen 118 + (i.e. all lists that would produce the given feedback for the given guess). 119 + 120 + {@ocaml exercise id=part_e[ 121 + let valid_lists (b : colour list) (x : int) : colour list list = 122 + failwith "TODO" 123 + ]} 124 + 125 + {@ocaml test for=part_e[ 126 + let () = 127 + (* If B guesses [Red; Green; Blue] and gets feedback 3, 128 + the only valid list is [Red; Green; Blue] itself *) 129 + let v = valid_lists [Red; Green; Blue] 3 in 130 + Printf.printf "valid_lists [R;G;B] 3: %d list(s) %s\n" 131 + (List.length v) 132 + (if List.length v = 1 then "(correct)" else "(WRONG)"); 133 + assert (List.length v = 1); 134 + 135 + (* If B guesses [Red; Red; Red] and gets feedback 0, 136 + no position can be Red: should be 2^3 = 8 lists *) 137 + let v2 = valid_lists [Red; Red; Red] 0 in 138 + Printf.printf "valid_lists [R;R;R] 0: %d list(s) %s\n" 139 + (List.length v2) 140 + (if List.length v2 = 8 then "(correct)" else "(WRONG)"); 141 + assert (List.length v2 = 8); 142 + print_endline "All tests passed!" 143 + ]}
+170
odoc-interactive-extension/doc/focs_2024_q1.mld
··· 1 + {0 Statistical Analysis with Fold, Map, and Filter} 2 + 3 + @x-ocaml.universe https://jon.ludl.am/ocaml/ 4 + 5 + {e Cambridge Computer Science Tripos Part IA -- 2024 -- Paper 1, Question 1} 6 + 7 + The chief examiner of a Tripos course would like to do some statistical 8 + analysis of the results of an exam. There are 150 students who have taken an 9 + exam consisting of 10 questions, of which each student has answered 6. If a 10 + student has not attempted a question, this is indicated by a zero in the list. 11 + 12 + {@ocaml[ 13 + type marks = int list 14 + 15 + (* Some sample data to work with *) 16 + let results : marks list = [ 17 + [ 30; 25; 20; 0; 0; 18; 30; 0; 0; 8 ]; 18 + [ 27; 0; 18; 9; 0; 30; 28; 0; 0; 17 ]; 19 + [ 10; 18; 0; 7; 0; 29; 25; 0; 1; 0 ]; 20 + [ 0; 22; 15; 0; 28; 0; 0; 19; 30; 12 ]; 21 + [ 15; 0; 0; 20; 25; 0; 18; 30; 0; 10 ]; 22 + [ 0; 30; 28; 0; 0; 15; 22; 0; 18; 0 ]; 23 + [ 20; 0; 0; 12; 30; 25; 0; 0; 15; 28 ]; 24 + [ 0; 15; 22; 0; 18; 0; 30; 28; 0; 10 ]; 25 + ] 26 + ]} 27 + 28 + The mean and standard deviation of a [marks] value can be calculated as: 29 + 30 + {ul 31 + {- Mean: the sum of values divided by the count} 32 + {- Standard deviation: the square root of the average squared deviation 33 + from the mean (using n-1 in the denominator)}} 34 + 35 + {1 Part (a): Core higher-order functions} 36 + 37 + Define [fold], [map], and [filter]: 38 + 39 + {@ocaml exercise id=fold[ 40 + let rec fold (f : 'a -> 'b -> 'a) (acc : 'a) (l : 'b list) : 'a = 41 + failwith "TODO" 42 + ]} 43 + 44 + {@ocaml test for=fold[ 45 + let () = 46 + let sum = fold ( + ) 0 [1; 2; 3; 4; 5] in 47 + Printf.printf "fold (+) 0 [1..5] = %d (expected 15)\n" sum; 48 + let product = fold ( * ) 1 [1; 2; 3; 4; 5] in 49 + Printf.printf "fold (*) 1 [1..5] = %d (expected 120)\n" product; 50 + let reversed = fold (fun acc x -> x :: acc) [] [1; 2; 3] in 51 + Printf.printf "reverse [1;2;3] = [%s] (expected [3;2;1])\n" 52 + (String.concat "; " (List.map string_of_int reversed)) 53 + ]} 54 + 55 + {@ocaml exercise id=map[ 56 + let rec map (f : 'a -> 'b) (l : 'a list) : 'b list = 57 + failwith "TODO" 58 + ]} 59 + 60 + {@ocaml test for=map[ 61 + let () = 62 + let doubled = map (fun x -> x * 2) [1; 2; 3] in 63 + Printf.printf "map (*2) [1;2;3] = [%s] (expected [2;4;6])\n" 64 + (String.concat "; " (List.map string_of_int doubled)); 65 + let lengths = map String.length ["hello"; "world"; "!"] in 66 + Printf.printf "map length [\"hello\";\"world\";\"!\"] = [%s] (expected [5;5;1])\n" 67 + (String.concat "; " (List.map string_of_int lengths)) 68 + ]} 69 + 70 + {@ocaml exercise id=filter[ 71 + let rec filter (p : 'a -> bool) (l : 'a list) : 'a list = 72 + failwith "TODO" 73 + ]} 74 + 75 + {@ocaml test for=filter[ 76 + let () = 77 + let evens = filter (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5; 6] in 78 + Printf.printf "filter even [1..6] = [%s] (expected [2;4;6])\n" 79 + (String.concat "; " (List.map string_of_int evens)); 80 + let non_zero = filter (fun x -> x <> 0) [0; 3; 0; 5; 0] in 81 + Printf.printf "filter non_zero = [%s] (expected [3;5])\n" 82 + (String.concat "; " (List.map string_of_int non_zero)) 83 + ]} 84 + 85 + {1 Part (b): Mean and standard deviation} 86 + 87 + The examiner evaluates performance by taking a [marks] value, filtering out 88 + the zeros, and then calculating the mean and standard deviation. Write a 89 + function that does this and returns an appropriate type. Remember that the 90 + result is not defined for some [marks] values (e.g. all zeros). 91 + 92 + {@ocaml[ 93 + (* These are available for your use *) 94 + let float_of_int = float_of_int 95 + let sqrt = sqrt 96 + ]} 97 + 98 + {@ocaml exercise id=part_b[ 99 + type stats = { mean : float; std : float } 100 + 101 + let compute_stats (m : marks) : stats option = failwith "TODO" 102 + ]} 103 + 104 + {@ocaml test for=part_b[ 105 + let () = 106 + (* Test with the first row: [30; 25; 20; 0; 0; 18; 30; 0; 0; 8] *) 107 + (* Non-zero values: [30; 25; 20; 18; 30; 8], mean = 131/6 ~ 21.83 *) 108 + match compute_stats [30; 25; 20; 0; 0; 18; 30; 0; 0; 8] with 109 + | Some s -> 110 + Printf.printf "mean = %.2f (expected ~21.83)\n" s.mean; 111 + Printf.printf "std = %.2f (expected ~8.33)\n" s.std 112 + | None -> 113 + print_endline "Got None (unexpected)"; 114 + match compute_stats [0; 0; 0; 0] with 115 + | Some _ -> print_endline "Got Some for all-zeros (unexpected)" 116 + | None -> print_endline "All zeros => None (correct)" 117 + ]} 118 + 119 + {1 Part (c): Per-question statistics} 120 + 121 + The examiner wants to assess the exam itself by looking at the mean and 122 + standard deviation for each question. First define an auxiliary function [nth] 123 + that returns the nth item of a list (0-indexed), then write functions to 124 + calculate per-question statistics. 125 + 126 + {@ocaml exercise id=part_c[ 127 + let rec nth (n : int) (l : 'a list) : 'a option = failwith "TODO" 128 + 129 + (* qmean q results: mean mark for question q across all students 130 + (only counting students who attempted it, i.e. non-zero marks) *) 131 + let qmean (q : int) (rs : marks list) : float = failwith "TODO" 132 + 133 + (* qstd q results: standard deviation for question q *) 134 + let qstd (q : int) (rs : marks list) : float = failwith "TODO" 135 + ]} 136 + 137 + {@ocaml test for=part_c[ 138 + let () = 139 + Printf.printf "Per-question statistics for sample data:\n\n"; 140 + Printf.printf " Q# Mean Std\n"; 141 + Printf.printf " -- ---- ---\n"; 142 + for q = 0 to 9 do 143 + Printf.printf " %2d %5.1f %5.1f\n" q (qmean q results) (qstd q results) 144 + done 145 + ]} 146 + 147 + {1 Explore further} 148 + 149 + Now that you have the building blocks, try some explorations: 150 + 151 + {@ocaml[ 152 + (* Which question had the highest average mark? *) 153 + let best_question = 154 + let means = List.init 10 (fun q -> (q, qmean q results)) in 155 + let best = List.fold_left 156 + (fun (bq, bm) (q, m) -> if m > bm then (q, m) else (bq, bm)) 157 + (-1, 0.0) means 158 + in 159 + Printf.printf "Best question: Q%d (mean %.1f)\n" (fst best) (snd best) 160 + ]} 161 + 162 + {@ocaml[ 163 + (* Per-student statistics *) 164 + let () = 165 + List.iteri (fun i row -> 166 + match compute_stats row with 167 + | Some s -> Printf.printf "Student %d: mean=%.1f std=%.1f\n" i s.mean s.std 168 + | None -> Printf.printf "Student %d: no answers\n" i 169 + ) results 170 + ]}
+149
odoc-interactive-extension/doc/focs_2025_q2.mld
··· 1 + {0 Expression Evaluation and Polish Notation} 2 + 3 + @x-ocaml.universe https://jon.ludl.am/ocaml/ 4 + 5 + {e Cambridge Computer Science Tripos Part IA -- 2025 -- Paper 1, Question 2} 6 + 7 + The following type definition allows the representation of some mathematical 8 + expressions as an OCaml value: 9 + 10 + {@ocaml[ 11 + type expr = 12 + | Add of expr * expr 13 + | Mul of expr * expr 14 + | Number of int 15 + ]} 16 + 17 + {1 Part (a): Encoding an expression} 18 + 19 + Write the OCaml value that corresponds to the expression [(1+4)*(10+2)]. 20 + 21 + {@ocaml exercise id=part_a[ 22 + let example : expr = failwith "TODO" 23 + ]} 24 + 25 + {@ocaml test for=part_a[ 26 + let () = 27 + match example with 28 + | Mul (Add (Number 1, Number 4), Add (Number 10, Number 2)) -> 29 + print_endline "Correct!" 30 + | _ -> 31 + print_endline "Not quite -- check the structure of your expression." 32 + ]} 33 + 34 + {1 Part (b): Evaluating expressions} 35 + 36 + Write a function that will evaluate the numerical result of an [expr] argument. 37 + 38 + {@ocaml exercise id=part_b[ 39 + let rec eval (e : expr) : int = failwith "TODO" 40 + ]} 41 + 42 + {@ocaml test for=part_b[ 43 + let () = 44 + let tests = [ 45 + (Number 42, 42); 46 + (Add (Number 1, Number 2), 3); 47 + (Mul (Number 3, Number 4), 12); 48 + (Mul (Add (Number 1, Number 4), Add (Number 10, Number 2)), 60); 49 + ] in 50 + List.iter (fun (e, expected) -> 51 + let got = eval e in 52 + if got = expected then 53 + Printf.printf "eval => %d (correct)\n" got 54 + else 55 + Printf.printf "eval => %d (expected %d)\n" got expected 56 + ) tests 57 + ]} 58 + 59 + {1 Part (c): Polish notation tokens} 60 + 61 + Another way to represent these expressions is to use "Polish notation". In this 62 + notation, the operator precedes its operands, and unlike the usual infix 63 + notation there is no need for parentheses. For example, the expression in 64 + part (a) would be written: 65 + 66 + {v * + 1 4 + 10 2 v} 67 + 68 + Define a type [t] that could be used in a list to represent any expression 69 + that can be described with type [expr] above. 70 + 71 + {@ocaml exercise id=part_c[ 72 + (* Define your type t here *) 73 + type t = TODO 74 + ]} 75 + 76 + {2 Hint} 77 + 78 + You need three kinds of token: one for each operator, and one for numbers. 79 + A [t list] should be able to represent any Polish notation expression. 80 + 81 + {1 Part (d): One step of reduction} 82 + 83 + The Polish notation expression above can be reduced step by step: 84 + 85 + {v 86 + * + 1 4 + 10 2 87 + * 5 + 10 2 88 + * 5 12 89 + 60 90 + v} 91 + 92 + Once you have defined your type [t], write a function [reduce] that performs 93 + one step of reduction. It should find the leftmost operator that is followed 94 + by two numbers and replace all three tokens with a single number. 95 + 96 + Here is one possible type definition for [t] and a framework for [reduce]: 97 + 98 + {@ocaml[ 99 + (* A possible type definition -- adjust to match your own *) 100 + type t = Plus | Times | Num of int 101 + ]} 102 + 103 + {@ocaml exercise id=part_d[ 104 + let rec reduce (tokens : t list) : t list = failwith "TODO" 105 + ]} 106 + 107 + Try it out: 108 + 109 + {@ocaml test for=part_d[ 110 + let show ts = 111 + String.concat " " (List.map (function 112 + | Plus -> "+" 113 + | Times -> "*" 114 + | Num n -> string_of_int n 115 + ) ts) 116 + 117 + let () = 118 + let start = [Times; Plus; Num 1; Num 4; Plus; Num 10; Num 2] in 119 + Printf.printf "Step 0: %s\n" (show start); 120 + let step1 = reduce start in 121 + Printf.printf "Step 1: %s\n" (show step1); 122 + let step2 = reduce step1 in 123 + Printf.printf "Step 2: %s\n" (show step2); 124 + let step3 = reduce step2 in 125 + Printf.printf "Step 3: %s\n" (show step3) 126 + ]} 127 + 128 + {1 Part (e): Full reduction} 129 + 130 + Write a function [reduce_all] that reduces a [t list] until it cannot be 131 + simplified any further, and returns the simplest value. 132 + 133 + {@ocaml exercise id=part_e[ 134 + let rec reduce_all (tokens : t list) : t list = failwith "TODO" 135 + ]} 136 + 137 + {@ocaml test for=part_e[ 138 + let () = 139 + let tests = [ 140 + ([Num 42], "42 (single number)"); 141 + ([Plus; Num 3; Num 4], "3 + 4"); 142 + ([Times; Plus; Num 1; Num 4; Plus; Num 10; Num 2], "(1+4) * (10+2)"); 143 + ([Plus; Times; Num 2; Num 3; Times; Num 4; Num 5], "(2*3) + (4*5)"); 144 + ] in 145 + List.iter (fun (tokens, desc) -> 146 + let result = reduce_all tokens in 147 + Printf.printf "%s => %s\n" desc (show result) 148 + ) tests 149 + ]}
+164
odoc-interactive-extension/doc/oxcaml_stack_alloc.mld
··· 1 + {0 Introduction to Stack Allocation} 2 + 3 + @x-ocaml.universe https://jon.ludl.am/oxcaml/ 4 + @x-ocaml.worker https://jon.ludl.am/oxcaml/worker.js 5 + 6 + {i Based on the OxCaml documentation at {{:https://oxcaml.org/documentation/stack-allocation/intro/}oxcaml.org}.} 7 + 8 + In OCaml, values are normally allocated on the garbage-collected heap. OxCaml 9 + can instead allocate values on the {e stack}, which offers two performance 10 + advantages: the same few hot cache lines are constantly reused (lower cache 11 + footprint), and stack allocations never trigger a GC -- making them safe for 12 + zero-alloc, low-latency code. 13 + 14 + {1 The [stack_] keyword} 15 + 16 + Use [stack_] before an allocation to force it onto the stack: 17 + 18 + {@ocaml no-merlin[ 19 + let () = 20 + let x = stack_ (1, 2, 3) in 21 + let a, b, c = x in 22 + Printf.printf "(%d, %d, %d)\n" a b c 23 + ]} 24 + 25 + The keyword works {e shallowly} -- it only affects the immediately following 26 + allocation. Most types can be stack-allocated: tuples, records, variants, 27 + closures, and boxed numbers. 28 + 29 + {@ocaml no-merlin[ 30 + type point = { x : float; y : float } 31 + 32 + let () = 33 + let p = stack_ { x = 3.0; y = 4.0 } in 34 + Printf.printf "distance = %.2f\n" (Float.sqrt (p.x *. p.x +. p.y *. p.y)) 35 + ]} 36 + 37 + {1 Regions and escaping} 38 + 39 + Stack-allocated values live in a {e region} (usually a function body) and 40 + must not escape it. The type-checker enforces this. Try uncommenting the 41 + last line to see the error: 42 + 43 + {@ocaml no-merlin[ 44 + let make_pair () = 45 + let p = stack_ (42, 99) in 46 + let a, b = p in 47 + Printf.printf "pair: (%d, %d)\n" a b 48 + (* p -- uncommenting would give: "This value escapes its region" *) 49 + 50 + let () = make_pair () 51 + ]} 52 + 53 + {1 Local parameters} 54 + 55 + To pass a stack-allocated value to a function, the function must promise not 56 + to let it escape. This is done with [@ local]: 57 + 58 + {@ocaml no-merlin[ 59 + let sum_pair (p @ local) = 60 + let a, b = p in 61 + a + b 62 + 63 + let () = 64 + let p = stack_ (42, 99) in 65 + Printf.printf "sum = %d\n" (sum_pair p) 66 + ]} 67 + 68 + A function with [@ local] parameters can be called with {e either} stack- or 69 + heap-allocated values -- the annotation only constrains the function's 70 + implementation, not its callers. Here [sum_pair] computes a global [int] 71 + result from a local tuple, so the result can safely escape. 72 + 73 + {1 [let mutable] for loop variables} 74 + 75 + OxCaml's [let mutable] provides mutable local variables that are always 76 + stack-allocated, avoiding any heap allocation: 77 + 78 + {@ocaml no-merlin[ 79 + let sum_to n = 80 + let mutable total = 0 in 81 + for i = 1 to n do 82 + total <- total + i 83 + done; 84 + total 85 + 86 + let () = Printf.printf "sum to 100 = %d\n" (sum_to 100) 87 + ]} 88 + 89 + Compare this with the traditional approach using a [ref] (which allocates 90 + on the heap): 91 + 92 + {@ocaml no-merlin[ 93 + let sum_to_ref n = 94 + let total = ref 0 in 95 + for i = 1 to n do 96 + total := !total + i 97 + done; 98 + !total 99 + 100 + let () = Printf.printf "sum to 100 = %d\n" (sum_to_ref 100) 101 + ]} 102 + 103 + {1 Returning local values with [exclave_]} 104 + 105 + The [exclave_] keyword lets a function allocate in its {e caller's} region, 106 + enabling the caller to stack-allocate the result: 107 + 108 + {@ocaml no-merlin[ 109 + module Counter : sig 110 + type t 111 + val make : unit -> t @ local 112 + val next : t @ local -> int 113 + end = struct 114 + type t = int ref 115 + let make () = exclave_ ref 0 116 + let next c = 117 + let x = !c in 118 + incr c; 119 + x 120 + end 121 + 122 + let () = 123 + let c = Counter.make () in 124 + Printf.printf "next: %d\n" (Counter.next c); 125 + Printf.printf "next: %d\n" (Counter.next c); 126 + Printf.printf "next: %d\n" (Counter.next c) 127 + ]} 128 + 129 + {1 Inference} 130 + 131 + In practice, you rarely need to write [stack_] explicitly. The compiler 132 + infers stack allocation whenever possible: 133 + 134 + {@ocaml no-merlin[ 135 + let use_callback ~(f : _ @ local -> _) = 136 + let pair = (1, 2) in (* inferred as stack-allocated *) 137 + f pair 138 + 139 + let () = 140 + use_callback ~f:(fun p -> 141 + let a, b = p in 142 + Printf.printf "got: (%d, %d)\n" a b) 143 + ]} 144 + 145 + Without the [@ local] annotation on [f], the pair would need to be heap- 146 + allocated since the compiler couldn't prove [f] won't capture it. 147 + 148 + {1 The [global_] field annotation} 149 + 150 + When you need to extract a value from a stack-allocated record and return it, 151 + mark the field [global_]: 152 + 153 + {@ocaml no-merlin[ 154 + type 'a box = { global_ value : 'a; tag : string } 155 + 156 + let get_value (b @ local) = b.value (* always safe to return *) 157 + 158 + let () = 159 + let b = stack_ { value = 42; tag = "answer" } in 160 + Printf.printf "value = %d\n" (get_value b) 161 + ]} 162 + 163 + The [global_] annotation means the field's contents are always on the heap, 164 + even when the record itself is stack-allocated.
+44
x-ocaml/worker/dune
··· 1 + ; Generate eval.ml from eval.cppo.ml with conditional OXCAML flag 2 + 3 + (rule 4 + (targets eval.ml) 5 + (deps (:x eval.cppo.ml)) 6 + (enabled_if (not %{ocaml-config:ox})) 7 + (action (run %{bin:cppo} -V OCAML:%{ocaml_version} %{x} -o %{targets}))) 8 + 9 + (rule 10 + (targets eval.ml) 11 + (deps (:x eval.cppo.ml)) 12 + (enabled_if %{ocaml-config:ox}) 13 + (action (run %{bin:cppo} -V OCAML:%{ocaml_version} -D OXCAML %{x} -o %{targets}))) 14 + 15 + ; Generate ocamlfmt.ml from ocamlfmt.cppo.ml with conditional OXCAML flag 16 + 17 + (rule 18 + (targets ocamlfmt.ml) 19 + (deps (:x ocamlfmt.cppo.ml)) 20 + (enabled_if (not %{ocaml-config:ox})) 21 + (action (run %{bin:cppo} -V OCAML:%{ocaml_version} %{x} -o %{targets}))) 22 + 23 + (rule 24 + (targets ocamlfmt.ml) 25 + (deps (:x ocamlfmt.cppo.ml)) 26 + (enabled_if %{ocaml-config:ox}) 27 + (action (run %{bin:cppo} -V OCAML:%{ocaml_version} -D OXCAML %{x} -o %{targets}))) 28 + 29 + ; On OxCaml, ocamlformat-lib conflicts with js_of_ocaml-compiler (inconsistent 30 + ; Ocaml_common), so we exclude it. ocamlfmt.ml becomes a no-op stub on OxCaml. 1 31 (library 2 32 (name x_worker) 33 + (enabled_if (not %{ocaml-config:ox})) 34 + (ocamlopt_flags (:standard -w -58)) 3 35 (libraries 4 36 brr 5 37 js_of_ocaml ··· 10 42 ocamlformat-lib 11 43 ocamlformat-lib.parser_extended 12 44 ocamlformat-lib.format_)) 45 + 46 + (library 47 + (name x_worker) 48 + (enabled_if %{ocaml-config:ox}) 49 + (ocamlopt_flags (:standard -w -58)) 50 + (libraries 51 + brr 52 + js_of_ocaml 53 + js_of_ocaml-toplevel 54 + x-ocaml.protocol 55 + x-ocaml.lib 56 + merlin-js.worker)) 13 57 14 58 (rule 15 59 (targets export.txt)
+11 -1
x-ocaml/worker/eval.ml x-ocaml/worker/eval.cppo.ml
··· 18 18 List.fold_left 19 19 (fun t ident -> 20 20 let name = Translmod.toplevel_name ident in 21 + #if defined OXCAML 22 + let v = Toploop.getvalue name in 23 + #else 21 24 let v = Topeval.getvalue name in 25 + #endif 22 26 String_map.add name v t) 23 27 t idents 24 28 25 - let restore t = String_map.iter (fun name v -> Topeval.setvalue name v) t 29 + let restore t = String_map.iter (fun name v -> 30 + #if defined OXCAML 31 + Toploop.setvalue name v 32 + #else 33 + Topeval.setvalue name v 34 + #endif 35 + ) t 26 36 end 27 37 28 38 module Environment = struct
+11 -4
x-ocaml/worker/ocamlfmt.ml x-ocaml/worker/ocamlfmt.cppo.ml
··· 1 + #if defined OXCAML 2 + (* On OxCaml, ocamlformat-lib conflicts with js_of_ocaml-compiler 3 + (inconsistent Ocaml_common). Formatting is disabled. *) 4 + let configure _ = () 5 + let fmt source = source 6 + #else 1 7 open Ocamlformat_stdlib 2 8 open Ocamlformat_lib 3 - module Format_ = Ocamlformat_format.Format_ 9 + module Ocamlfmt_format = Ocamlformat_format.Format_ 4 10 module Parser_extended = Ocamlformat_parser_extended 5 11 6 12 let default_conf = Ocamlformat_lib.Conf.default ··· 45 51 let ast = ast ~conf source in 46 52 let with_buffer_formatter ~buffer_size k = 47 53 let buffer = Buffer.create buffer_size in 48 - let fs = Format_.formatter_of_buffer buffer in 54 + let fs = Ocamlfmt_format.formatter_of_buffer buffer in 49 55 Fmt.eval fs k; 50 - Format_.pp_print_flush fs (); 51 - if Buffer.length buffer > 0 then Format_.pp_print_newline fs (); 56 + Ocamlfmt_format.pp_print_flush fs (); 57 + if Buffer.length buffer > 0 then Ocamlfmt_format.pp_print_newline fs (); 52 58 Buffer.contents buffer 53 59 in 54 60 let print (ast : _ Parse_with_comments.with_comments) = ··· 66 72 67 73 let fmt source = 68 74 match !conf with `Disable -> source | `Conf conf -> fmt ~conf source 75 + #endif