Skip to main content

litex/
main_test.rs

1// cargo test run_examples -- --nocapture
2// cargo test run_the_mechanics_markdown_files -- --nocapture
3// cargo test run_all -- --nocapture
4
5#[cfg(test)]
6mod lit_file_runner_tests {
7    use std::fs;
8    use std::path::{Path, PathBuf};
9    use std::time::Instant;
10
11    use crate::pipeline::{render_run_source_code_output, run_source_code};
12    use crate::prelude::*;
13
14    const LARGE_TEST_STACK_SIZE: usize = 16 * 1024 * 1024;
15    const SLOWEST_RUNS_TO_PRINT: usize = 10;
16
17    fn run_with_large_stack(test_name: &str, f: impl FnOnce() + Send + 'static) {
18        std::thread::Builder::new()
19            .name(test_name.to_string())
20            .stack_size(LARGE_TEST_STACK_SIZE)
21            .spawn(f)
22            .unwrap()
23            .join()
24            .unwrap();
25    }
26
27    /// Collect ```litex``` bodies. A block is omitted when the last non-empty line before its opening
28    /// fence is exactly `<!-- litex:skip-test -->` (for snippets that are illustrative only).
29    /// The line number is 1-based: the markdown line where the opening ` ```litex ` fence starts.
30    fn extract_litex_fenced_blocks(markdown: &str) -> Vec<(usize, String)> {
31        const SKIP_MARKER: &str = "<!-- litex:skip-test -->";
32        let mut blocks: Vec<(usize, String)> = Vec::new();
33        let mut in_litex = false;
34        let mut skip_this_block = false;
35        let mut current = String::new();
36        let mut prev_non_empty_outside_block: Option<&str> = None;
37        let mut fence_open_line: usize = 0;
38
39        for (line_index_zero, line) in markdown.lines().enumerate() {
40            let line_number_1based = line_index_zero + 1;
41            let trimmed_start = line.trim_start();
42            if trimmed_start.starts_with("```") {
43                let info = trimmed_start[3..].trim();
44                if in_litex {
45                    if !skip_this_block {
46                        let trimmed = current.trim();
47                        if !trimmed.is_empty() {
48                            blocks.push((fence_open_line, trimmed.to_string()));
49                        }
50                    }
51                    current.clear();
52                    in_litex = false;
53                    skip_this_block = false;
54                    prev_non_empty_outside_block = None;
55                } else if info == "litex" {
56                    in_litex = true;
57                    fence_open_line = line_number_1based;
58                    skip_this_block = prev_non_empty_outside_block == Some(SKIP_MARKER);
59                    current.clear();
60                }
61            } else if in_litex {
62                if !skip_this_block {
63                    if !current.is_empty() {
64                        current.push('\n');
65                    }
66                    current.push_str(line);
67                }
68            } else {
69                let t = line.trim();
70                if !t.is_empty() {
71                    prev_non_empty_outside_block = Some(t);
72                }
73            }
74        }
75        blocks
76    }
77
78    fn collect_markdown_files_under_dir_sorted(root: &Path) -> Vec<PathBuf> {
79        let mut out: Vec<PathBuf> = Vec::new();
80        if !root.is_dir() {
81            return out;
82        }
83        fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
84            let read_dir = match fs::read_dir(dir) {
85                Ok(entries) => entries,
86                Err(_) => return,
87            };
88            for entry in read_dir.flatten() {
89                let path = entry.path();
90                let Ok(file_type) = entry.file_type() else {
91                    continue;
92                };
93                if file_type.is_dir() {
94                    walk(&path, out);
95                } else if path.extension().is_some_and(|e| e == "md") {
96                    out.push(path);
97                }
98            }
99        }
100        walk(root, &mut out);
101        out.sort();
102        out
103    }
104
105    fn litex_snippets_from_markdown_files(
106        manifest_dir: &Path,
107        md_paths: &[PathBuf],
108    ) -> Vec<(String, String, String)> {
109        let mut out: Vec<(String, String, String)> = Vec::new();
110        for md_path in md_paths {
111            let rel_label = md_path
112                .strip_prefix(manifest_dir)
113                .unwrap_or(md_path)
114                .display()
115                .to_string();
116            let md_current_path_str = md_path.to_string_lossy().into_owned();
117            let md_content = match fs::read_to_string(md_path) {
118                Ok(content) => content,
119                Err(read_error) => panic!("failed to read {:?}: {}", md_path, read_error),
120            };
121            for (block_index, (md_line, block)) in extract_litex_fenced_blocks(&md_content)
122                .into_iter()
123                .enumerate()
124            {
125                out.push((
126                    format!(
127                        "{} ```litex```#{} (md line {})",
128                        rel_label, block_index, md_line
129                    ),
130                    block,
131                    md_current_path_str.clone(),
132                ));
133            }
134        }
135        out
136    }
137
138    fn run_single_the_mechanics_chapter_markdown_file_impl(
139        chapter_filename: &str,
140        chapter_label: &str,
141    ) {
142        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
143        let chapter_path = manifest_dir
144            .join("The-Mechanics-of-Litex-Proof")
145            .join(chapter_filename);
146        assert!(
147            chapter_path.is_file(),
148            "{} markdown file must exist at {:?}",
149            chapter_label,
150            chapter_path
151        );
152
153        let snippets = litex_snippets_from_markdown_files(&manifest_dir, &[chapter_path.clone()]);
154        assert!(
155            !snippets.is_empty(),
156            "{} markdown file must contain ```litex``` blocks",
157            chapter_label
158        );
159
160        let mut runtime = Runtime::new_with_builtin_code();
161        runtime.new_file_path_new_env_new_name_scope(snippets[0].2.as_str());
162
163        let mut snippet_durations_ms: Vec<(String, f64)> = Vec::new();
164        let wall_start = Instant::now();
165        for (snippet_index, (label, source_code, md_path_for_run_file)) in
166            snippets.iter().enumerate()
167        {
168            if snippet_index > 0 {
169                runtime.clear_current_env_and_parse_name_scope();
170                runtime.set_current_user_lit_file_path(md_path_for_run_file.as_str());
171            }
172
173            let normalized_source = remove_windows_carriage_return(source_code);
174            let start_snippet = Instant::now();
175            let (stmt_results, runtime_error) =
176                run_source_code(normalized_source.as_str(), &mut runtime);
177            let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
178
179            let (run_succeeded, run_output) =
180                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
181
182            if !run_succeeded {
183                panic!(
184                    "{} markdown litex snippet FAILED:\n{}\n>>> FAILED snippet (open .md here): {}\n",
185                    chapter_label, run_output, label
186                );
187            }
188
189            snippet_durations_ms.push((label.clone(), duration_ms));
190        }
191
192        println!(
193            "--- {} markdown: {} ```litex``` block(s), all OK ({:.2} ms wall) ---",
194            chapter_label,
195            snippets.len(),
196            wall_start.elapsed().as_secs_f64() * 1000.0
197        );
198        print_slowest_run_labels(
199            format!("{} markdown snippets", chapter_label).as_str(),
200            snippet_durations_ms.as_slice(),
201        );
202        for (label, duration_ms) in snippet_durations_ms.iter() {
203            println!("  OK  {:.2} ms  {}", duration_ms, label);
204        }
205    }
206
207    fn run_tmp_lit_file(file_name: &str) {
208        let tmp_lit_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
209            .join("examples")
210            .join(file_name);
211
212        assert!(
213            tmp_lit_path.is_file(),
214            "examples/{} must exist at {:?}",
215            file_name,
216            tmp_lit_path
217        );
218
219        let tmp_lit_content = match fs::read_to_string(&tmp_lit_path) {
220            Ok(content) => content,
221            Err(read_error) => panic!("failed to read {:?}: {}", tmp_lit_path, read_error),
222        };
223        if tmp_lit_content.trim().is_empty() {
224            println!("examples/{} is empty; skip run", file_name);
225            return;
226        }
227
228        let path_str = match tmp_lit_path.to_str() {
229            Some(path_string) => path_string,
230            None => panic!("{:?} must be valid UTF-8", tmp_lit_path),
231        };
232
233        let mut runtime = Runtime::new_with_builtin_code();
234        runtime.new_file_path_new_env_new_name_scope(path_str);
235        let normalized_source = remove_windows_carriage_return(tmp_lit_content.as_str());
236
237        let start_time = Instant::now();
238        let (stmt_results, runtime_error) =
239            run_source_code(normalized_source.as_str(), &mut runtime);
240        let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;
241
242        let (run_succeeded, run_output) =
243            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
244
245        let status_label = if run_succeeded { "OK" } else { "FAILED" };
246        println!(
247            "{}\n=== [{}] {:?} ({:.2} ms user file only) ===\n",
248            run_output, path_str, status_label, duration_ms
249        );
250        let error_json = match &runtime_error {
251            Some(error) => display_runtime_error_json(&runtime, error, false),
252            None => run_output.clone(),
253        };
254        assert!(
255            run_succeeded,
256            "examples/{} failed.\n\n>>> Litex error JSON:\n{}\n\n=== [{}] {:?} ({:.2} ms user file only) ===",
257            file_name, error_json, path_str, status_label, duration_ms
258        );
259    }
260
261    #[test]
262    fn run_tmp0() {
263        run_tmp_lit_file("tmp.lit");
264    }
265
266    #[test]
267    fn run_tmp2() {
268        run_tmp_lit_file("tmp2.lit");
269    }
270
271    #[test]
272    fn run_tmp3() {
273        run_tmp_lit_file("tmp3.lit");
274    }
275
276    #[test]
277    fn run_tmp4() {
278        run_tmp_lit_file("tmp4.lit");
279    }
280
281    #[test]
282    fn list_set_membership_implies_equality_or() {
283        let source_code = r#"
284forall a set:
285    a = 1 or a = 2 or a = 3
286    =>:
287        a $in {1, 2, 3}
288"#;
289
290        let mut runtime = Runtime::new_with_builtin_code();
291        runtime.new_file_path_new_env_new_name_scope("list_set_membership_implies_equality_or");
292        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
293        let (run_succeeded, run_output) =
294            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
295
296        assert!(
297            run_succeeded,
298            "list_set_membership_implies_equality_or failed:\n{}",
299            run_output
300        );
301    }
302
303    #[test]
304    fn dependent_fn_param_set_uses_previous_arg() {
305        let source_code = r#"
306have f fn(n N_pos, x closed_range(1, n)) R
307f(3, 2) = f(3, 2)
308by fn as set: f
309"#;
310
311        let mut runtime = Runtime::new_with_builtin_code();
312        runtime.new_file_path_new_env_new_name_scope("dependent_fn_param_set_uses_previous_arg");
313        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
314        let (run_succeeded, run_output) =
315            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
316
317        assert!(
318            run_succeeded,
319            "dependent_fn_param_set_uses_previous_arg failed:\n{}",
320            run_output
321        );
322    }
323
324    #[test]
325    fn fn_return_set_cannot_depend_on_params() {
326        let source_code = r#"
327have f fn(n N_pos) closed_range(1, n)
328"#;
329
330        let mut runtime = Runtime::new_with_builtin_code();
331        runtime.new_file_path_new_env_new_name_scope("fn_return_set_cannot_depend_on_params");
332        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
333        let (run_succeeded, run_output) =
334            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
335
336        assert!(
337            !run_succeeded,
338            "dependent return set should fail, but succeeded:\n{}",
339            run_output
340        );
341        assert!(
342            run_output.contains("function return set cannot depend on function parameters [n]"),
343            "dependent return set failure had unexpected output:\n{}",
344            run_output
345        );
346    }
347
348    #[test]
349    fn known_equality_implies_weak_order() {
350        let source_code = r#"
351have a, b R
352know a = b
353a <= b
354a >= b
355"#;
356
357        let mut runtime = Runtime::new_with_builtin_code();
358        runtime.new_file_path_new_env_new_name_scope("known_equality_implies_weak_order");
359        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
360        let (run_succeeded, run_output) =
361            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
362
363        assert!(
364            run_succeeded,
365            "known_equality_implies_weak_order failed:\n{}",
366            run_output
367        );
368    }
369
370    #[test]
371    fn pow_with_nonnegative_base_and_positive_real_exponent_is_well_defined() {
372        let source_code = r#"
373have fn sqrt(x R: x >= 0) R = x^(1/2)
374"#;
375
376        let mut runtime = Runtime::new_with_builtin_code();
377        runtime.new_file_path_new_env_new_name_scope(
378            "pow_with_nonnegative_base_and_positive_real_exponent_is_well_defined",
379        );
380        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
381        let (run_succeeded, run_output) =
382            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
383
384        assert!(
385            run_succeeded,
386            "pow_with_nonnegative_base_and_positive_real_exponent_is_well_defined failed:\n{}",
387            run_output
388        );
389    }
390
391    #[test]
392    fn sqrt_core_builtin_rules() {
393        let source_code = r#"
394sqrt(0) = 0
395sqrt(1) = 1
396
397forall x R:
398    x >= 0
399    =>:
400        (sqrt(x))^2 = x
401
402forall x R:
403    x > 0
404    =>:
405        sqrt(x) > 0
406"#;
407
408        let mut runtime = Runtime::new_with_builtin_code();
409        runtime.new_file_path_new_env_new_name_scope("sqrt_core_builtin_rules");
410        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
411        let (run_succeeded, run_output) =
412            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
413
414        assert!(
415            run_succeeded,
416            "sqrt_core_builtin_rules failed:\n{}",
417            run_output
418        );
419    }
420
421    #[test]
422    fn weak_order_does_not_recursively_prove_equality() {
423        let source_code = r#"
424have a, b R
425know a <= b
426a = b
427"#;
428
429        let mut runtime = Runtime::new_with_builtin_code();
430        runtime
431            .new_file_path_new_env_new_name_scope("weak_order_does_not_recursively_prove_equality");
432        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
433        let (run_succeeded, run_output) =
434            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
435
436        assert!(
437            !run_succeeded,
438            "recursive equality/order proof should fail, but succeeded:\n{}",
439            run_output
440        );
441    }
442
443    #[test]
444    fn hidden_file_path_output_omits_source_fields() {
445        let source_code = "x = 1";
446        let path = "/private/tmp/litex-hidden-source-test.lit";
447
448        let mut runtime = Runtime::new_with_builtin_code();
449        runtime.new_file_path_new_env_new_name_scope(path);
450        runtime.module_manager.hide_file_paths_in_output = true;
451        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
452        let (run_succeeded, run_output) =
453            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
454
455        assert!(!run_succeeded);
456        assert!(!run_output.contains("\"source\""));
457        assert!(!run_output.contains(path));
458        assert!(run_output.contains("\"line\": 1"));
459    }
460
461    #[test]
462    fn show_file_path_output_keeps_source_fields() {
463        let source_code = "x = 1";
464        let path = "/private/tmp/litex-show-source-test.lit";
465
466        let mut runtime = Runtime::new_with_builtin_code();
467        runtime.new_file_path_new_env_new_name_scope(path);
468        runtime.module_manager.hide_file_paths_in_output = false;
469        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
470        let (run_succeeded, run_output) =
471            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
472
473        assert!(!run_succeeded);
474        assert!(run_output.contains("\"source\""));
475        assert!(run_output.contains(path));
476    }
477
478    #[test]
479    fn hidden_file_path_run_file_output_omits_run_file_path() {
480        let run_file_path = std::env::temp_dir().join("litex-hidden-run-file-test.lit");
481        fs::write(&run_file_path, "1 = 1\n").unwrap();
482        let run_file_path_string = run_file_path.to_string_lossy().into_owned();
483        let source_code = format!("run_file \"{}\"", run_file_path_string);
484
485        let mut runtime = Runtime::new_with_builtin_code();
486        runtime.new_file_path_new_env_new_name_scope("repl");
487        runtime.module_manager.hide_file_paths_in_output = true;
488        let (stmt_results, runtime_error) = run_source_code(source_code.as_str(), &mut runtime);
489        let (run_succeeded, run_output) =
490            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
491
492        let _ = fs::remove_file(&run_file_path);
493        assert!(run_succeeded, "run_file failed:\n{}", run_output);
494        assert!(run_output.contains("\"stmt\": \"run_file\""));
495        assert!(!run_output.contains(run_file_path_string.as_str()));
496        assert!(!run_output.contains("\"source\""));
497    }
498
499    #[test]
500    fn citation_verified_by_type_reflects_cited_stmt_kind() {
501        let source_code = r#"
502abstract_prop p(x)
503know forall x R:
504    $p(x)
505$p(2)
506let a R:
507    a = 1
508a = 1
509prop q(x R):
510    x = 1
511$q(1)
512"#;
513
514        let mut runtime = Runtime::new_with_builtin_code();
515        runtime.new_file_path_new_env_new_name_scope(
516            "citation_verified_by_type_reflects_cited_stmt_kind",
517        );
518        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
519        let (run_succeeded, run_output) =
520            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
521
522        assert!(
523            run_succeeded,
524            "citation_verified_by_type_reflects_cited_stmt_kind failed:\n{}",
525            run_output
526        );
527        assert!(run_output.contains("\"type\": \"cite forall fact\""));
528        assert!(run_output.contains("\"type\": \"cite atomic fact\""));
529        assert!(run_output.contains("\"type\": \"cite prop def\""));
530    }
531
532    #[test]
533    fn run_file_from_path() {
534        run_with_large_stack("run_file_from_path_large_stack", run_file_from_path_impl);
535    }
536
537    #[test]
538    fn run_file_in_std_from_folder_name() {
539        run_with_large_stack(
540            "run_file_in_std_from_folder_name_large_stack",
541            run_file_in_std_from_folder_name_impl,
542        );
543    }
544
545    fn run_file_in_std_from_folder_name_impl() {
546        let source_code = "run_file trigonometry\n\nsin(0) = 0\ncos(0) = 1";
547
548        let mut runtime = Runtime::new_with_builtin_code();
549        runtime.new_file_path_new_env_new_name_scope("repl");
550        let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
551        let (run_succeeded, run_output) =
552            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
553
554        assert!(run_succeeded, "run_file in std failed:\n{}", run_output);
555        assert!(run_output.contains("\"type\": \"RunFileInStd\""));
556        assert!(run_output.contains("\"stmt\": \"run_file trigonometry\""));
557        assert!(run_output.contains("\"stmt\": \"sin(0) = 0\""));
558        assert!(run_output.contains("\"stmt\": \"cos(0) = 1\""));
559    }
560
561    fn run_file_from_path_impl() {
562        let path: String = "./examples/strong_induc.lit".to_string();
563        let file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path);
564        assert!(
565            file_path.is_absolute(),
566            "path must be an absolute path: {:?}",
567            file_path
568        );
569        assert!(
570            file_path.is_file(),
571            "path must point to a file: {:?}",
572            file_path
573        );
574
575        let source_code = match fs::read_to_string(&file_path) {
576            Ok(content) => content,
577            Err(read_error) => panic!("failed to read {:?}: {}", file_path, read_error),
578        };
579        let path_str = match file_path.to_str() {
580            Some(path_string) => path_string,
581            None => panic!("{:?} must be valid UTF-8", file_path),
582        };
583
584        let mut runtime = Runtime::new_with_builtin_code();
585        runtime.new_file_path_new_env_new_name_scope(path_str);
586        let normalized_source = remove_windows_carriage_return(source_code.as_str());
587
588        let start_time = Instant::now();
589        let (stmt_results, runtime_error) =
590            run_source_code(normalized_source.as_str(), &mut runtime);
591        let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;
592
593        let (run_succeeded, run_output) =
594            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
595        let status_label = if run_succeeded { "OK" } else { "FAILED" };
596        println!(
597            "{}\n=== [{}] {:?} ({:.2} ms user file only) ===\n",
598            run_output, path_str, status_label, duration_ms
599        );
600        let error_json = match &runtime_error {
601            Some(error) => display_runtime_error_json(&runtime, error, false),
602            None => run_output.clone(),
603        };
604        assert!(
605            run_succeeded,
606            "Litex file failed: {}\n\n>>> Litex error JSON:\n{}\n\n=== [{}] {:?} ({:.2} ms user file only) ===",
607            path_str, error_json, path_str, status_label, duration_ms
608        );
609    }
610
611    #[test]
612    fn run_the_mechanics_markdown_files() {
613        run_with_large_stack(
614            "run_the_mechanics_markdown_files_large_stack",
615            run_the_mechanics_markdown_files_impl,
616        );
617    }
618
619    #[test]
620    fn run_the_mechanics_chapter_1_markdown_file() {
621        run_with_large_stack(
622            "run_the_mechanics_chapter_1_markdown_file_large_stack",
623            run_the_mechanics_chapter_1_markdown_file_impl,
624        );
625    }
626
627    fn run_the_mechanics_chapter_1_markdown_file_impl() {
628        run_single_the_mechanics_chapter_markdown_file_impl(
629            "Chapter_1_Proofs_By_Calculation.md",
630            "Chapter 1",
631        );
632    }
633
634    #[test]
635    fn run_the_mechanics_chapter_2_markdown_file() {
636        run_with_large_stack(
637            "run_the_mechanics_chapter_2_markdown_file_large_stack",
638            run_the_mechanics_chapter_2_markdown_file_impl,
639        );
640    }
641
642    fn run_the_mechanics_chapter_2_markdown_file_impl() {
643        run_single_the_mechanics_chapter_markdown_file_impl(
644            "Chapter_2_Proofs_With_Structure.md",
645            "Chapter 2",
646        );
647    }
648
649    #[test]
650    fn run_the_mechanics_chapter_3_markdown_file() {
651        run_with_large_stack(
652            "run_the_mechanics_chapter_3_markdown_file_large_stack",
653            run_the_mechanics_chapter_3_markdown_file_impl,
654        );
655    }
656
657    fn run_the_mechanics_chapter_3_markdown_file_impl() {
658        run_single_the_mechanics_chapter_markdown_file_impl(
659            "Chapter_3_Parity_And_Divisibility.md",
660            "Chapter 3",
661        );
662    }
663
664    #[test]
665    fn run_the_mechanics_chapter_4_markdown_file() {
666        run_with_large_stack(
667            "run_the_mechanics_chapter_4_markdown_file_large_stack",
668            run_the_mechanics_chapter_4_markdown_file_impl,
669        );
670    }
671
672    fn run_the_mechanics_chapter_4_markdown_file_impl() {
673        run_single_the_mechanics_chapter_markdown_file_impl(
674            "Chapter_4_Proofs_With_Structure_II.md",
675            "Chapter 4",
676        );
677    }
678
679    #[test]
680    fn run_the_mechanics_chapter_5_markdown_file() {
681        run_with_large_stack(
682            "run_the_mechanics_chapter_5_markdown_file_large_stack",
683            run_the_mechanics_chapter_5_markdown_file_impl,
684        );
685    }
686
687    fn run_the_mechanics_chapter_5_markdown_file_impl() {
688        run_single_the_mechanics_chapter_markdown_file_impl("Chapter_5_Logic.md", "Chapter 5");
689    }
690
691    #[test]
692    fn run_the_mechanics_chapter_7_markdown_file() {
693        run_with_large_stack(
694            "run_the_mechanics_chapter_7_markdown_file_large_stack",
695            run_the_mechanics_chapter_7_markdown_file_impl,
696        );
697    }
698
699    fn run_the_mechanics_chapter_7_markdown_file_impl() {
700        run_single_the_mechanics_chapter_markdown_file_impl(
701            "Chapter_7_Number_Theory.md",
702            "Chapter 7",
703        );
704    }
705
706    #[test]
707    fn run_the_mechanics_chapter_9_markdown_file() {
708        run_with_large_stack(
709            "run_the_mechanics_chapter_9_markdown_file_large_stack",
710            run_the_mechanics_chapter_9_markdown_file_impl,
711        );
712    }
713
714    fn run_the_mechanics_chapter_9_markdown_file_impl() {
715        run_single_the_mechanics_chapter_markdown_file_impl("Chapter_9_Sets.md", "Chapter 9");
716    }
717
718    #[test]
719    fn run_the_mechanics_chapter_10_markdown_file() {
720        run_with_large_stack(
721            "run_the_mechanics_chapter_10_markdown_file_large_stack",
722            run_the_mechanics_chapter_10_markdown_file_impl,
723        );
724    }
725
726    fn run_the_mechanics_chapter_10_markdown_file_impl() {
727        run_single_the_mechanics_chapter_markdown_file_impl(
728            "Chapter_10_Relations.md",
729            "Chapter 10",
730        );
731    }
732
733    fn run_the_mechanics_markdown_files_impl() {
734        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
735        let mechanics_dir = manifest_dir.join("The-Mechanics-of-Litex-Proof");
736        assert!(
737            mechanics_dir.is_dir(),
738            "The-Mechanics-of-Litex-Proof must exist at {:?}",
739            mechanics_dir
740        );
741
742        let md_paths = collect_markdown_files_under_dir_sorted(&mechanics_dir);
743        assert!(
744            !md_paths.is_empty(),
745            "The-Mechanics-of-Litex-Proof must contain markdown files"
746        );
747
748        let mut snippets_by_file: Vec<Vec<(String, String, String)>> = Vec::new();
749        let mut total_snippet_count: usize = 0;
750        for md_path in md_paths.iter() {
751            let snippets = litex_snippets_from_markdown_files(&manifest_dir, &[md_path.clone()]);
752            total_snippet_count += snippets.len();
753            snippets_by_file.push(snippets);
754        }
755        assert!(
756            total_snippet_count > 0,
757            "The-Mechanics-of-Litex-Proof markdown files must contain ```litex``` blocks"
758        );
759
760        let mut runtime = Runtime::new_with_builtin_code();
761
762        let mut snippet_durations_ms: Vec<(String, f64)> = Vec::new();
763        let mut failed_labels: Vec<String> = Vec::new();
764        let wall_start = Instant::now();
765        let mut file_count_with_snippets: usize = 0;
766        let mut snippet_count_run: usize = 0;
767        for snippets in snippets_by_file.iter() {
768            if snippets.is_empty() {
769                continue;
770            }
771
772            file_count_with_snippets += 1;
773
774            for (label, source_code, md_path_for_run_file) in snippets.iter() {
775                if snippet_count_run == 0 {
776                    runtime.new_file_path_new_env_new_name_scope(md_path_for_run_file.as_str());
777                } else {
778                    runtime.clear_current_env_and_parse_name_scope();
779                    runtime.set_current_user_lit_file_path(md_path_for_run_file.as_str());
780                }
781                snippet_count_run += 1;
782
783                let normalized_source = remove_windows_carriage_return(source_code);
784                let start_snippet = Instant::now();
785                let run_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
786                    run_source_code(normalized_source.as_str(), &mut runtime)
787                }));
788                let (stmt_results, runtime_error) = match run_result {
789                    Ok(result) => result,
790                    Err(panic_payload) => {
791                        let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
792                        let panic_message =
793                            if let Some(message) = panic_payload.downcast_ref::<&str>() {
794                                message.to_string()
795                            } else if let Some(message) = panic_payload.downcast_ref::<String>() {
796                                message.clone()
797                            } else {
798                                "non-string panic payload".to_string()
799                            };
800                        println!(
801                            "=== [PANICKED] The-Mechanics-of-Litex-Proof markdown snippet ({:.2} ms) ===\n{}\n>>> PANICKED snippet (open .md here): {}\n",
802                            duration_ms, panic_message, label
803                        );
804                        failed_labels.push(label.clone());
805                        break;
806                    }
807                };
808                let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
809
810                let (run_succeeded, run_output) =
811                    render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
812
813                if !run_succeeded {
814                    let status_label = if run_output.contains("\"error_type\": \"UnknownError\"") {
815                        "UNKNOWN"
816                    } else {
817                        "FAILED"
818                    };
819                    println!(
820                        "=== [{}] The-Mechanics-of-Litex-Proof markdown snippet ({:.2} ms) ===\n{}\n>>> {} snippet (open .md here): {}\n",
821                        status_label, duration_ms, run_output, status_label, label
822                    );
823                    failed_labels.push(label.clone());
824                    break;
825                }
826                snippet_durations_ms.push((label.clone(), duration_ms));
827            }
828        }
829
830        let status_text = if failed_labels.is_empty() {
831            "all OK"
832        } else {
833            "completed with failures"
834        };
835        println!(
836            "--- The-Mechanics-of-Litex-Proof markdown: {} ```litex``` block(s) in {} markdown file(s), {} ({:.2} ms wall) ---",
837            total_snippet_count,
838            file_count_with_snippets,
839            status_text,
840            wall_start.elapsed().as_secs_f64() * 1000.0
841        );
842        for (label, duration_ms) in snippet_durations_ms.iter() {
843            println!("  OK  {:.2} ms  {}", duration_ms, label);
844        }
845
846        if !failed_labels.is_empty() {
847            println!("--- The-Mechanics-of-Litex-Proof markdown failed snippets ---");
848            for label in failed_labels.iter() {
849                println!("{}", label);
850            }
851        }
852        assert!(
853            failed_labels.is_empty(),
854            "The-Mechanics-of-Litex-Proof markdown has {} failing snippet(s); see output above",
855            failed_labels.len()
856        );
857    }
858
859    /// All `*.lit` files under `manifest_dir/subdir`, recursively (e.g. `examples/subdir/foo.lit`).
860    /// Sorted by full path after collection. Empty if `subdir` is missing or has no `.lit` files.
861    fn collect_lit_files_recursive_under(manifest_dir: &Path, subdir: &str) -> Vec<PathBuf> {
862        let dir_path = manifest_dir.join(subdir);
863        if !dir_path.is_dir() {
864            println!("--- {} {:?}: directory missing; skip ---", subdir, dir_path);
865            return Vec::new();
866        }
867        fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
868            let read_directory = match fs::read_dir(dir) {
869                Ok(entries) => entries,
870                Err(read_error) => panic!("failed to read {:?}: {}", dir, read_error),
871            };
872            for directory_entry_result in read_directory {
873                let directory_entry = match directory_entry_result {
874                    Ok(entry) => entry,
875                    Err(read_error) => panic!("failed to read directory entry: {}", read_error),
876                };
877                let path = directory_entry.path();
878                let Ok(file_type) = directory_entry.file_type() else {
879                    continue;
880                };
881                if file_type.is_dir() {
882                    walk(&path, out);
883                } else if path.extension().is_some_and(|ext| ext == "lit") {
884                    out.push(path);
885                }
886            }
887        }
888        let mut lit_file_paths = Vec::new();
889        walk(&dir_path, &mut lit_file_paths);
890        lit_file_paths.sort();
891        lit_file_paths
892    }
893
894    /// Single footer: builtin + per-phase sums/walls + `phase timing` line.
895    fn print_run_examples_timing_summary(
896        builtin_duration_ms: f64,
897        examples_ran: bool,
898        example_runs_ms: &[(String, f64)],
899        examples_phase_wall_ms: f64,
900        doc_runs_ms: &[(String, f64)],
901        docs_phase_wall_ms: f64,
902    ) {
903        let examples_sum_ms: f64 = example_runs_ms.iter().map(|(_, ms)| *ms).sum();
904        let docs_sum_ms: f64 = doc_runs_ms.iter().map(|(_, ms)| *ms).sum();
905        println!("--- timing (summary) ---");
906        println!("  builtin init (once): {:.2} ms", builtin_duration_ms);
907        if examples_ran {
908            println!(
909                "  phase 1 (examples/*.lit + docs/Manual ```litex```): sum of runs: {:.2} ms  |  wall: {:.2} ms",
910                examples_sum_ms, examples_phase_wall_ms
911            );
912        }
913        println!(
914                "  remaining markdown ```litex``` snippets (README + docs excluding docs/Manual; see phase 1): sum of runs: {:.2} ms  |  wall: {:.2} ms",
915                docs_sum_ms, docs_phase_wall_ms
916            );
917        println!(
918            "--- phase timing: phase1 {:.2} ms | docs {:.2} ms ---",
919            examples_phase_wall_ms, docs_phase_wall_ms
920        );
921
922        let mut all_runs_ms: Vec<(String, f64)> = Vec::new();
923        for (label, duration_ms) in example_runs_ms.iter() {
924            all_runs_ms.push((format!("phase 1: {}", label), *duration_ms));
925        }
926        for (label, duration_ms) in doc_runs_ms.iter() {
927            all_runs_ms.push((format!("docs: {}", label), *duration_ms));
928        }
929        print_slowest_run_labels("all examples/docs runs", all_runs_ms.as_slice());
930    }
931
932    fn print_slowest_run_labels(title: &str, run_durations_ms: &[(String, f64)]) {
933        if run_durations_ms.is_empty() {
934            return;
935        }
936
937        let mut sorted_runs = run_durations_ms.to_vec();
938        sorted_runs.sort_by(|left, right| {
939            right
940                .1
941                .partial_cmp(&left.1)
942                .unwrap_or(std::cmp::Ordering::Equal)
943        });
944
945        let count_to_print = SLOWEST_RUNS_TO_PRINT.min(sorted_runs.len());
946        println!(
947            "--- slowest {}: top {} of {} ---",
948            title,
949            count_to_print,
950            sorted_runs.len()
951        );
952        for (index, (label, duration_ms)) in sorted_runs.iter().take(count_to_print).enumerate() {
953            println!("  {:>2}. {:.2} ms  {}", index + 1, duration_ms, label);
954        }
955    }
956
957    #[test]
958    fn run_examples() {
959        run_with_large_stack("run_examples_large_stack", run_examples_impl);
960    }
961
962    #[test]
963    fn run_all() {
964        run_with_large_stack("run_all_large_stack", run_all_impl);
965    }
966
967    // Local workflow helper: run math500 temporary snippets without touching golitex/examples.
968    //
969    // This test is intentionally non-failing when the sibling repo doesn't exist, so CI won't care.
970    // It is meant for local iteration while authoring snippets under ../MATH-500-litex/tmp/.
971    #[test]
972    fn run_math500_tmp() {
973        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
974        let local_tmp_path = manifest_dir.join("tmp").join("math500_work.lit");
975        let sibling_tmp_path = match manifest_dir.parent() {
976            Some(parent) => parent
977                .join("MATH-500-litex")
978                .join("tmp")
979                .join("math500_work.lit"),
980            None => PathBuf::new(),
981        };
982
983        let math500_tmp_path = if local_tmp_path.is_file() {
984            local_tmp_path
985        } else if sibling_tmp_path.is_file() {
986            sibling_tmp_path
987        } else {
988            println!("--- run_math500_tmp: skip (missing file) ---");
989            println!("  checked: {}", local_tmp_path.display());
990            if !sibling_tmp_path.as_os_str().is_empty() {
991                println!("  checked: {}", sibling_tmp_path.display());
992            }
993            return;
994        };
995        println!(
996            "--- run_math500_tmp: using {} ---",
997            math500_tmp_path.display()
998        );
999
1000        let source_code = match fs::read_to_string(&math500_tmp_path) {
1001            Ok(content) => content,
1002            Err(read_error) => panic!("failed to read {:?}: {}", math500_tmp_path, read_error),
1003        };
1004
1005        #[derive(Clone)]
1006        struct Snippet {
1007            label: String,
1008            source: String,
1009        }
1010
1011        // Split by "# test/..." markers so we can clear env between problems, while keeping one
1012        // Runtime alive (like run_examples does).
1013        let mut snippets: Vec<Snippet> = Vec::new();
1014        let mut current_lines: Vec<String> = Vec::new();
1015        let mut current_label: Option<String> = None;
1016
1017        for line in source_code.lines() {
1018            if line.starts_with("# test/") {
1019                if let Some(label) = current_label.take() {
1020                    let body = current_lines.join("\n");
1021                    if !body.trim().is_empty() {
1022                        snippets.push(Snippet {
1023                            label,
1024                            source: body,
1025                        });
1026                    }
1027                }
1028                current_lines.clear();
1029                current_label = Some(line.trim().to_string());
1030            }
1031            if current_label.is_some() {
1032                current_lines.push(line.to_string());
1033            }
1034        }
1035        if let Some(label) = current_label.take() {
1036            let body = current_lines.join("\n");
1037            if !body.trim().is_empty() {
1038                snippets.push(Snippet {
1039                    label,
1040                    source: body,
1041                });
1042            }
1043        }
1044        if snippets.is_empty() {
1045            // Fallback: run whole file as one snippet.
1046            snippets.push(Snippet {
1047                label: "math500_work.lit (whole file)".to_string(),
1048                source: source_code,
1049            });
1050        }
1051
1052        let path_for_runtime = match math500_tmp_path.to_str() {
1053            Some(path_string) => path_string,
1054            None => panic!("{:?} must be valid UTF-8", math500_tmp_path),
1055        };
1056
1057        let mut runtime = Runtime::new_with_builtin_code();
1058        runtime.new_file_path_new_env_new_name_scope(path_for_runtime);
1059
1060        let mut durations_ms: Vec<(String, f64)> = Vec::new();
1061        for (snippet_index, snippet) in snippets.iter().enumerate() {
1062            if snippet_index > 0 {
1063                runtime.clear_current_env_and_parse_name_scope();
1064                runtime.set_current_user_lit_file_path(path_for_runtime);
1065            }
1066
1067            let normalized_source = remove_windows_carriage_return(snippet.source.as_str());
1068            let start_time = Instant::now();
1069            let (stmt_results, runtime_error) =
1070                run_source_code(normalized_source.as_str(), &mut runtime);
1071            let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;
1072
1073            let (run_succeeded, run_output) =
1074                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1075
1076            durations_ms.push((snippet.label.clone(), duration_ms));
1077
1078            if !run_succeeded {
1079                print_slowest_run_labels(
1080                    "math500 snippets before failure",
1081                    durations_ms.as_slice(),
1082                );
1083                panic!(
1084                    "math500 snippet FAILED:\n{}\n>>> FAILED snippet: {}\n",
1085                    run_output, snippet.label
1086                );
1087            }
1088        }
1089
1090        print_slowest_run_labels("math500 snippets", durations_ms.as_slice());
1091        for (label, duration_ms) in durations_ms.iter() {
1092            println!("  OK  {:.2} ms  {}", duration_ms, label);
1093        }
1094    }
1095
1096    #[test]
1097    fn run_math500_litex_simple() {
1098        run_with_large_stack(
1099            "run_math500_litex_simple_large_stack",
1100            run_math500_litex_simple_impl,
1101        );
1102    }
1103
1104    #[test]
1105    fn run_math500_litex_all() {
1106        run_with_large_stack(
1107            "run_math500_litex_all_large_stack",
1108            run_math500_litex_all_impl,
1109        );
1110    }
1111
1112    fn run_math500_litex_simple_impl() {
1113        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1114        let completed_dir = manifest_dir
1115            .join("MATH-500-litex")
1116            .join("math-500")
1117            .join("finished");
1118        assert!(
1119            completed_dir.is_dir(),
1120            "MATH-500-litex/math-500/finished must exist at {:?}",
1121            completed_dir
1122        );
1123        run_math500_litex_lit_dir(&completed_dir);
1124    }
1125
1126    fn run_math500_litex_all_impl() {
1127        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1128        let lit_dir = manifest_dir
1129            .join("MATH-500-litex")
1130            .join("math-500")
1131            .join("unfinished");
1132        assert!(
1133            lit_dir.is_dir(),
1134            "MATH-500-litex/math-500/unfinished must exist at {:?}",
1135            lit_dir
1136        );
1137        run_math500_litex_lit_dir(&lit_dir);
1138    }
1139
1140    fn run_math500_litex_lit_dir(base_dir: &Path) {
1141        fn collect_lit_files(dir: &Path, out: &mut Vec<PathBuf>) {
1142            let read_directory = fs::read_dir(dir)
1143                .unwrap_or_else(|read_error| panic!("failed to read {:?}: {}", dir, read_error));
1144            for directory_entry_result in read_directory {
1145                let directory_entry = directory_entry_result.unwrap_or_else(|read_error| {
1146                    panic!("failed to read directory entry: {}", read_error)
1147                });
1148                let path = directory_entry.path();
1149                let file_type = directory_entry
1150                    .file_type()
1151                    .unwrap_or_else(|file_type_error| {
1152                        panic!(
1153                            "failed to read file type for {:?}: {}",
1154                            path, file_type_error
1155                        )
1156                    });
1157                if file_type.is_dir() {
1158                    collect_lit_files(path.as_path(), out);
1159                } else if path.extension().is_some_and(|ext| ext == "lit") {
1160                    out.push(path);
1161                }
1162            }
1163        }
1164
1165        let mut lit_paths: Vec<PathBuf> = Vec::new();
1166        collect_lit_files(base_dir, &mut lit_paths);
1167        lit_paths.sort();
1168
1169        assert!(
1170            !lit_paths.is_empty(),
1171            "{:?} must contain at least one .lit file",
1172            base_dir
1173        );
1174
1175        let base_dir_str = base_dir.to_string_lossy().to_string();
1176
1177        let builtin_start = Instant::now();
1178        let mut runtime = Runtime::new_with_builtin_code();
1179        let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1180        runtime.new_file_path_new_env_new_name_scope(base_dir_str.as_str());
1181
1182        let run_wall_start = Instant::now();
1183        let mut total_count: usize = 0;
1184        let mut failed_labels: Vec<String> = Vec::new();
1185        let mut total_solution_duration_ms: f64 = 0.0;
1186
1187        for lit_path in lit_paths.iter() {
1188            if total_count > 0 {
1189                runtime.clear_current_env_and_parse_name_scope();
1190            }
1191
1192            let lit_path_str = lit_path.to_string_lossy().to_string();
1193            runtime.set_current_user_lit_file_path(lit_path_str.as_str());
1194
1195            let relative_label = match lit_path.strip_prefix(base_dir) {
1196                Ok(relative_path) => relative_path.to_string_lossy().to_string(),
1197                Err(_) => lit_path_str.clone(),
1198            };
1199
1200            let litex_code = fs::read_to_string(lit_path).unwrap_or_else(|read_error| {
1201                panic!("failed to read {:?}: {}", lit_path, read_error)
1202            });
1203            let litex_code = litex_code.trim();
1204            if litex_code.is_empty() {
1205                println!("--- [SKIP] empty .lit file: {} ---", relative_label);
1206                continue;
1207            }
1208
1209            let normalized_source = remove_windows_carriage_return(litex_code);
1210
1211            let start_time_for_one_solution = Instant::now();
1212            let (stmt_results, runtime_error) =
1213                run_source_code(normalized_source.as_str(), &mut runtime);
1214            let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1215            total_solution_duration_ms += duration_ms;
1216
1217            let (run_succeeded, run_output) =
1218                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1219
1220            total_count += 1;
1221            if !run_succeeded {
1222                let label = relative_label.clone();
1223                println!(
1224                    "=== [FAILED] math500-litex simple at .lit file {} ({:.2} ms) ===\n{}\n",
1225                    relative_label, duration_ms, run_output
1226                );
1227                failed_labels.push(label);
1228            }
1229        }
1230
1231        let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1232        println!("--- math500-litex simple timing (summary) ---");
1233        println!("  builtin init (once): {:.2} ms", builtin_duration_ms);
1234        println!(
1235            "  snippets: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1236            total_count, total_solution_duration_ms, run_wall_ms
1237        );
1238
1239        if failed_labels.is_empty() {
1240            println!("--- math500-litex simple: all snippets OK ---");
1241            return;
1242        }
1243
1244        println!("--- math500-litex simple failed unique_id ---");
1245        for label in failed_labels.iter() {
1246            println!("{}", label);
1247        }
1248        panic!(
1249            "math500-litex simple failed for {} of {} item(s)",
1250            failed_labels.len(),
1251            total_count
1252        );
1253    }
1254
1255    fn run_all_impl() {
1256        run_examples_impl();
1257        run_the_mechanics_markdown_files_impl();
1258    }
1259
1260    #[test]
1261    fn run_the_mechanics_of_litex_proof() {
1262        run_with_large_stack(
1263            "run_the_mechanics_of_litex_proof_large_stack",
1264            run_the_mechanics_of_litex_proof_impl,
1265        );
1266    }
1267
1268    fn run_the_mechanics_of_litex_proof_impl() {
1269        run_the_mechanics_markdown_files_impl();
1270    }
1271
1272    fn run_examples_impl() {
1273        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1274        let lit_file_paths = collect_lit_files_recursive_under(&manifest_dir, "examples");
1275
1276        let manual_md_dir = manifest_dir.join("docs").join("Manual");
1277        let manual_md_paths = collect_markdown_files_under_dir_sorted(&manual_md_dir);
1278        let manual_snippets = litex_snippets_from_markdown_files(&manifest_dir, &manual_md_paths);
1279
1280        #[derive(Clone)]
1281        struct Phase1Item {
1282            report_label: String,
1283            source: String,
1284            path_for_runtime: String,
1285        }
1286
1287        let mut phase1_items: Vec<Phase1Item> = Vec::new();
1288        for lit_file_path in lit_file_paths.iter() {
1289            let lit_file_path_str = match lit_file_path.to_str() {
1290                Some(path_string) => path_string,
1291                None => panic!("{:?} must be valid UTF-8", lit_file_path),
1292            };
1293            let file_label_for_report = match lit_file_path.strip_prefix(&manifest_dir) {
1294                Ok(rel) => rel.display().to_string(),
1295                Err(_) => match lit_file_path.file_name() {
1296                    Some(os_file_name) => match os_file_name.to_str() {
1297                        Some(name_string) => String::from(name_string),
1298                        None => format!("{:?}", lit_file_path),
1299                    },
1300                    None => format!("{:?}", lit_file_path),
1301                },
1302            };
1303            let source_code = match fs::read_to_string(lit_file_path) {
1304                Ok(content) => content,
1305                Err(read_error) => panic!("failed to read {:?}: {}", lit_file_path, read_error),
1306            };
1307            phase1_items.push(Phase1Item {
1308                report_label: file_label_for_report,
1309                source: source_code,
1310                path_for_runtime: lit_file_path_str.to_string(),
1311            });
1312        }
1313        for (label, block, md_path_str) in manual_snippets.iter() {
1314            phase1_items.push(Phase1Item {
1315                report_label: label.clone(),
1316                source: block.clone(),
1317                path_for_runtime: md_path_str.clone(),
1318            });
1319        }
1320
1321        let builtin_start = Instant::now();
1322        let mut runtime = Runtime::new_with_builtin_code();
1323        let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1324
1325        let mut file_label_and_duration_ms_list: Vec<(String, f64)> = Vec::new();
1326        let mut every_file_run_ok = true;
1327        let mut examples_ran = false;
1328        let mut examples_phase_wall_ms: f64 = 0.0;
1329
1330        if phase1_items.is_empty() {
1331            println!("--- phase 1: no examples/*.lit and no docs/Manual ```litex``` snippets ---");
1332        } else {
1333            examples_ran = true;
1334            let examples_wall_start = Instant::now();
1335            let first_path = phase1_items[0].path_for_runtime.as_str();
1336            runtime.new_file_path_new_env_new_name_scope(first_path);
1337
1338            for (item_index, item) in phase1_items.iter().enumerate() {
1339                if item_index > 0 {
1340                    runtime.clear_current_env_and_parse_name_scope();
1341                    runtime.set_current_user_lit_file_path(item.path_for_runtime.as_str());
1342                }
1343
1344                let normalized_source = remove_windows_carriage_return(item.source.as_str());
1345
1346                let start_time_for_one_file = Instant::now();
1347                let (stmt_results, runtime_error) =
1348                    run_source_code(normalized_source.as_str(), &mut runtime);
1349                let duration_ms_for_one_file =
1350                    start_time_for_one_file.elapsed().as_secs_f64() * 1000.0;
1351
1352                let (run_succeeded, run_output) =
1353                    render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1354
1355                if !run_succeeded {
1356                    every_file_run_ok = false;
1357                    file_label_and_duration_ms_list
1358                        .push((item.report_label.clone(), duration_ms_for_one_file));
1359                    print_slowest_run_labels(
1360                        "phase 1 runs before failure",
1361                        file_label_and_duration_ms_list.as_slice(),
1362                    );
1363                    println!(
1364                        "=== [{}] {} ===\n{}\n>>> FAILED snippet (open .md here): {}\n",
1365                        "FAILED", item.report_label, run_output, item.report_label
1366                    );
1367                    break;
1368                }
1369
1370                file_label_and_duration_ms_list
1371                    .push((item.report_label.clone(), duration_ms_for_one_file));
1372            }
1373            examples_phase_wall_ms = examples_wall_start.elapsed().as_secs_f64() * 1000.0;
1374        }
1375
1376        if every_file_run_ok && examples_ran {
1377            println!(
1378                "--- phase 1: {} run(s) (examples/*.lit + docs/Manual ```litex```), all OK ---",
1379                file_label_and_duration_ms_list.len()
1380            );
1381            print_slowest_run_labels("phase 1 runs", file_label_and_duration_ms_list.as_slice());
1382            for (file_label, duration_ms) in file_label_and_duration_ms_list.iter() {
1383                println!("  {}  {:.2} ms", file_label, duration_ms);
1384            }
1385        }
1386
1387        assert!(
1388            every_file_run_ok,
1389            "examples or docs/Manual litex snippet failed; see output above"
1390        );
1391
1392        let docs_dir = manifest_dir.join("docs");
1393        if !docs_dir.is_dir() {
1394            println!(
1395                "--- docs folder missing at {:?}; skip markdown litex blocks ---",
1396                docs_dir
1397            );
1398            print_run_examples_timing_summary(
1399                builtin_duration_ms,
1400                examples_ran,
1401                file_label_and_duration_ms_list.as_slice(),
1402                examples_phase_wall_ms,
1403                &[],
1404                0.0,
1405            );
1406            return;
1407        }
1408
1409        let mut md_paths_all: Vec<PathBuf> = Vec::new();
1410        let readme_path = manifest_dir.join("README.md");
1411        if readme_path.is_file() {
1412            md_paths_all.push(readme_path);
1413        }
1414        md_paths_all.extend(collect_markdown_files_under_dir_sorted(&docs_dir));
1415        md_paths_all.sort();
1416        let manual_prefix = manifest_dir.join("docs").join("Manual");
1417        let md_paths: Vec<PathBuf> = md_paths_all
1418            .into_iter()
1419            .filter(|p| !p.starts_with(&manual_prefix))
1420            .collect();
1421
1422        // (test report label, fenced litex body, current markdown path string for relative run_file resolution)
1423        let mut doc_snippets: Vec<(String, String, String)> = Vec::new();
1424        for md_path in md_paths.iter() {
1425            let rel_label = md_path
1426                .strip_prefix(&manifest_dir)
1427                .unwrap_or(md_path)
1428                .display()
1429                .to_string();
1430            let md_current_path_str = md_path.to_string_lossy().into_owned();
1431            let md_content = match fs::read_to_string(md_path) {
1432                Ok(content) => content,
1433                Err(read_error) => panic!("failed to read {:?}: {}", md_path, read_error),
1434            };
1435            for (block_index, (md_line, block)) in extract_litex_fenced_blocks(&md_content)
1436                .into_iter()
1437                .enumerate()
1438            {
1439                doc_snippets.push((
1440                    format!(
1441                        "{} ```litex```#{} (md line {})",
1442                        rel_label, block_index, md_line
1443                    ),
1444                    block,
1445                    md_current_path_str.clone(),
1446                ));
1447            }
1448        }
1449
1450        if doc_snippets.is_empty() {
1451            println!("--- remaining markdown: no ```litex``` fenced blocks ---");
1452            print_run_examples_timing_summary(
1453                builtin_duration_ms,
1454                examples_ran,
1455                file_label_and_duration_ms_list.as_slice(),
1456                examples_phase_wall_ms,
1457                &[],
1458                0.0,
1459            );
1460            return;
1461        }
1462
1463        if !examples_ran {
1464            runtime.new_file_path_new_env_new_name_scope("remaining markdown ```litex``` snippets");
1465        }
1466
1467        println!(
1468            "--- remaining markdown: {} ```litex``` block(s) in {} markdown file(s) ---",
1469            doc_snippets.len(),
1470            md_paths.len()
1471        );
1472
1473        let docs_wall_start = Instant::now();
1474        let mut doc_durations_ms: Vec<(String, f64)> = Vec::new();
1475        for (snippet_index, (label, source_code, md_path_for_run_file)) in
1476            doc_snippets.iter().enumerate()
1477        {
1478            if examples_ran || snippet_index > 0 {
1479                runtime.clear_current_env_and_parse_name_scope();
1480            }
1481            runtime.set_current_user_lit_file_path(md_path_for_run_file.as_str());
1482
1483            let normalized_source = remove_windows_carriage_return(source_code);
1484            let start_snippet = Instant::now();
1485            let (stmt_results, runtime_error) =
1486                run_source_code(normalized_source.as_str(), &mut runtime);
1487            let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
1488
1489            let (run_succeeded, run_output) =
1490                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1491
1492            doc_durations_ms.push((label.clone(), duration_ms));
1493
1494            if !run_succeeded {
1495                print_slowest_run_labels(
1496                    "remaining markdown snippets before failure",
1497                    doc_durations_ms.as_slice(),
1498                );
1499                panic!(
1500                    "docs litex snippet FAILED:\n{}\n>>> FAILED snippet (open .md here): {}\n",
1501                    run_output, label
1502                );
1503            }
1504        }
1505        let docs_phase_wall_ms = docs_wall_start.elapsed().as_secs_f64() * 1000.0;
1506
1507        print_slowest_run_labels("remaining markdown snippets", doc_durations_ms.as_slice());
1508        for (label, duration_ms) in doc_durations_ms.iter() {
1509            println!("  OK  {:.2} ms  {}", duration_ms, label);
1510        }
1511        print_run_examples_timing_summary(
1512            builtin_duration_ms,
1513            examples_ran,
1514            file_label_and_duration_ms_list.as_slice(),
1515            examples_phase_wall_ms,
1516            doc_durations_ms.as_slice(),
1517            docs_phase_wall_ms,
1518        );
1519    }
1520
1521    #[test]
1522    fn run_gsm8k_solutions() {
1523        run_with_large_stack("run_gsm8k_solutions_large_stack", run_gsm8k_solutions_impl);
1524    }
1525
1526    fn run_gsm8k_solutions_impl() {
1527        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1528        let jsonl_paths = vec![
1529            manifest_dir
1530                .join("scripts")
1531                .join("gsm8k-litex")
1532                .join("train.jsonl"),
1533            manifest_dir
1534                .join("scripts")
1535                .join("gsm8k-litex")
1536                .join("test.jsonl"),
1537        ];
1538
1539        for jsonl_path in jsonl_paths.iter() {
1540            if !jsonl_path.is_file() {
1541                println!(
1542                    "--- gsm8k jsonl file missing at {:?}; skip gsm8k solutions ---",
1543                    jsonl_path
1544                );
1545                return;
1546            }
1547        }
1548
1549        let builtin_start = Instant::now();
1550        let mut runtime = Runtime::new_with_builtin_code();
1551        let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1552
1553        let run_wall_start = Instant::now();
1554        let mut total_count: usize = 0;
1555        let mut failed_labels: Vec<String> = Vec::new();
1556        let mut total_solution_duration_ms: f64 = 0.0;
1557
1558        for jsonl_path in jsonl_paths.iter() {
1559            run_gsm8k_jsonl_file(
1560                jsonl_path,
1561                &mut runtime,
1562                &mut total_count,
1563                &mut failed_labels,
1564                &mut total_solution_duration_ms,
1565            );
1566        }
1567
1568        let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1569        println!("--- gsm8k timing (summary) ---");
1570        println!("  builtin init (once): {:.2} ms", builtin_duration_ms);
1571        println!(
1572            "  solutions: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1573            total_count, total_solution_duration_ms, run_wall_ms
1574        );
1575
1576        if failed_labels.is_empty() {
1577            println!("--- gsm8k: all train/test solutions OK ---");
1578            return;
1579        }
1580
1581        println!("--- gsm8k failed titles ---");
1582        for label in failed_labels.iter() {
1583            println!("{}", label);
1584        }
1585        panic!(
1586            "gsm8k solution run failed for {} of {} item(s)",
1587            failed_labels.len(),
1588            total_count
1589        );
1590    }
1591
1592    fn run_gsm8k_jsonl_file(
1593        jsonl_path: &Path,
1594        runtime: &mut Runtime,
1595        total_count: &mut usize,
1596        failed_labels: &mut Vec<String>,
1597        total_solution_duration_ms: &mut f64,
1598    ) {
1599        let jsonl_path_str = match jsonl_path.to_str() {
1600            Some(path_string) => path_string.to_string(),
1601            None => panic!("{:?} must be valid UTF-8", jsonl_path),
1602        };
1603
1604        let jsonl_content = match fs::read_to_string(&jsonl_path) {
1605            Ok(content) => content,
1606            Err(read_error) => panic!("failed to read {:?}: {}", jsonl_path, read_error),
1607        };
1608
1609        if *total_count == 0 {
1610            runtime.new_file_path_new_env_new_name_scope(jsonl_path_str.as_str());
1611        } else {
1612            runtime.clear_current_env_and_parse_name_scope();
1613            runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1614        }
1615
1616        for (line_index, line) in jsonl_content.lines().enumerate() {
1617            if line.trim().is_empty() {
1618                continue;
1619            }
1620
1621            if *total_count > 0 || line_index > 0 {
1622                runtime.clear_current_env_and_parse_name_scope();
1623                runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1624            }
1625
1626            let title = jsonl_string_field(line, "title").unwrap_or_else(|error_message| {
1627                panic!(
1628                    "failed to parse title in {:?} line {}: {}",
1629                    jsonl_path,
1630                    line_index + 1,
1631                    error_message
1632                )
1633            });
1634            let solution = jsonl_string_field(line, "solution").unwrap_or_else(|error_message| {
1635                panic!(
1636                    "failed to parse solution in {:?} line {} ({}): {}",
1637                    jsonl_path,
1638                    line_index + 1,
1639                    title,
1640                    error_message
1641                )
1642            });
1643            let normalized_source = remove_windows_carriage_return(solution.as_str());
1644
1645            let start_time_for_one_solution = Instant::now();
1646            let (stmt_results, runtime_error) =
1647                run_source_code(normalized_source.as_str(), runtime);
1648            let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1649            *total_solution_duration_ms += duration_ms;
1650
1651            let (run_succeeded, run_output) =
1652                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1653
1654            *total_count += 1;
1655            if !run_succeeded {
1656                let label = format!(
1657                    "{}:{}",
1658                    jsonl_path
1659                        .file_name()
1660                        .and_then(|file_name| file_name.to_str())
1661                        .unwrap_or("gsm8k jsonl"),
1662                    title
1663                );
1664                println!(
1665                    "=== [FAILED] {} at jsonl line {} ({:.2} ms) ===\n{}\n",
1666                    label,
1667                    line_index + 1,
1668                    duration_ms,
1669                    run_output
1670                );
1671                failed_labels.push(label);
1672            }
1673
1674            if *total_count % 1000 == 0 {
1675                println!(
1676                    "--- gsm8k progress: {} solution(s), {} failure(s) ---",
1677                    total_count,
1678                    failed_labels.len()
1679                );
1680            }
1681        }
1682    }
1683
1684    #[test]
1685    fn run_metamathqa_litex_solutions() {
1686        run_with_large_stack(
1687            "run_metamathqa_litex_solutions_large_stack",
1688            run_metamathqa_litex_solutions_impl,
1689        );
1690    }
1691
1692    #[test]
1693    fn run_minif2f_litex_completed() {
1694        run_with_large_stack(
1695            "run_minif2f_litex_completed_large_stack",
1696            run_minif2f_litex_completed_impl,
1697        );
1698    }
1699
1700    fn run_minif2f_litex_completed_impl() {
1701        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1702        let jsonl_path = manifest_dir
1703            .parent()
1704            .unwrap_or(manifest_dir.as_path())
1705            .join("MiniF2F-litex")
1706            .join("litex_completed.jsonl");
1707        assert!(
1708            jsonl_path.is_file(),
1709            "MiniF2F-litex/litex_completed.jsonl must exist at {:?}",
1710            jsonl_path
1711        );
1712
1713        let jsonl_path_str = match jsonl_path.to_str() {
1714            Some(path_string) => path_string.to_string(),
1715            None => panic!("{:?} must be valid UTF-8", jsonl_path),
1716        };
1717        let jsonl_content = match fs::read_to_string(&jsonl_path) {
1718            Ok(content) => content,
1719            Err(read_error) => panic!("failed to read {:?}: {}", jsonl_path, read_error),
1720        };
1721
1722        let builtin_start = Instant::now();
1723        let mut runtime = Runtime::new_with_builtin_code();
1724        let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1725        runtime.new_file_path_new_env_new_name_scope(jsonl_path_str.as_str());
1726
1727        let run_wall_start = Instant::now();
1728        let mut total_count: usize = 0;
1729        let mut failed_labels: Vec<String> = Vec::new();
1730        let mut total_solution_duration_ms: f64 = 0.0;
1731
1732        for (line_index, line) in jsonl_content.lines().enumerate() {
1733            if line.trim().is_empty() {
1734                continue;
1735            }
1736            if total_count > 0 {
1737                runtime.clear_current_env_and_parse_name_scope();
1738                runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1739            }
1740
1741            let name = jsonl_string_field(line, "name").unwrap_or_else(|error_message| {
1742                panic!(
1743                    "failed to parse name in {:?} line {}: {}",
1744                    jsonl_path,
1745                    line_index + 1,
1746                    error_message
1747                )
1748            });
1749            let litex_code =
1750                jsonl_string_field(line, "litex_code").unwrap_or_else(|error_message| {
1751                    panic!(
1752                        "failed to parse litex_code in {:?} line {} ({}): {}",
1753                        jsonl_path,
1754                        line_index + 1,
1755                        name,
1756                        error_message
1757                    )
1758                });
1759
1760            let normalized_source = remove_windows_carriage_return(litex_code.as_str());
1761            let start_time_for_one_solution = Instant::now();
1762            let (stmt_results, runtime_error) =
1763                run_source_code(normalized_source.as_str(), &mut runtime);
1764            let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1765            total_solution_duration_ms += duration_ms;
1766
1767            let (run_succeeded, run_output) =
1768                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1769
1770            total_count += 1;
1771            if !run_succeeded {
1772                let label = format!("{}:{}", line_index + 1, name);
1773                println!(
1774                    "=== [FAILED] MiniF2F-litex at jsonl line {} ({:.2} ms): {} ===\n{}\n",
1775                    line_index + 1,
1776                    duration_ms,
1777                    name,
1778                    run_output
1779                );
1780                failed_labels.push(label);
1781            }
1782        }
1783
1784        let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1785        println!("--- MiniF2F-litex timing (summary) ---");
1786        println!("  builtin init (once): {:.2} ms", builtin_duration_ms);
1787        println!(
1788            "  completed snippets: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1789            total_count, total_solution_duration_ms, run_wall_ms
1790        );
1791
1792        if failed_labels.is_empty() {
1793            println!("--- MiniF2F-litex: all completed snippets OK ---");
1794            return;
1795        }
1796
1797        println!("--- MiniF2F-litex failed names ---");
1798        for label in failed_labels.iter() {
1799            println!("{}", label);
1800        }
1801        panic!(
1802            "MiniF2F-litex completed snippet run failed for {} of {} item(s)",
1803            failed_labels.len(),
1804            total_count
1805        );
1806    }
1807
1808    fn run_metamathqa_litex_solutions_impl() {
1809        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1810        let jsonl_path = manifest_dir
1811            .join("scripts")
1812            .join("MetaMathQA-litex")
1813            .join("MetaMathQA.jsonl");
1814        assert!(
1815            jsonl_path.is_file(),
1816            "MetaMathQA-litex jsonl file must exist at {:?}",
1817            jsonl_path
1818        );
1819
1820        let builtin_start = Instant::now();
1821        let mut runtime = Runtime::new_with_builtin_code();
1822        let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1823
1824        let run_wall_start = Instant::now();
1825        let mut total_count: usize = 0;
1826        let mut failed_labels: Vec<String> = Vec::new();
1827        let mut total_solution_duration_ms: f64 = 0.0;
1828
1829        run_metamathqa_jsonl_file(
1830            &jsonl_path,
1831            &mut runtime,
1832            &mut total_count,
1833            &mut failed_labels,
1834            &mut total_solution_duration_ms,
1835        );
1836
1837        let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1838        println!("--- MetaMathQA-litex timing (summary) ---");
1839        println!("  builtin init (once): {:.2} ms", builtin_duration_ms);
1840        println!(
1841            "  solutions: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1842            total_count, total_solution_duration_ms, run_wall_ms
1843        );
1844
1845        if failed_labels.is_empty() {
1846            println!("--- MetaMathQA-litex: all solutions OK ---");
1847            return;
1848        }
1849
1850        println!("--- MetaMathQA-litex failed titles ---");
1851        for label in failed_labels.iter() {
1852            println!("{}", label);
1853        }
1854        panic!(
1855            "MetaMathQA-litex solution run failed for {} of {} item(s)",
1856            failed_labels.len(),
1857            total_count
1858        );
1859    }
1860
1861    fn run_metamathqa_jsonl_file(
1862        jsonl_path: &Path,
1863        runtime: &mut Runtime,
1864        total_count: &mut usize,
1865        failed_labels: &mut Vec<String>,
1866        total_solution_duration_ms: &mut f64,
1867    ) {
1868        let jsonl_path_str = match jsonl_path.to_str() {
1869            Some(path_string) => path_string.to_string(),
1870            None => panic!("{:?} must be valid UTF-8", jsonl_path),
1871        };
1872
1873        let jsonl_content = match fs::read_to_string(jsonl_path) {
1874            Ok(content) => content,
1875            Err(read_error) => panic!("failed to read {:?}: {}", jsonl_path, read_error),
1876        };
1877
1878        runtime.new_file_path_new_env_new_name_scope(jsonl_path_str.as_str());
1879
1880        for (line_index, line) in jsonl_content.lines().enumerate() {
1881            if line.trim().is_empty() {
1882                continue;
1883            }
1884
1885            if line_index > 0 {
1886                runtime.clear_current_env_and_parse_name_scope();
1887                runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1888            }
1889
1890            let title = jsonl_string_field(line, "title").unwrap_or_else(|error_message| {
1891                panic!(
1892                    "failed to parse title in {:?} line {}: {}",
1893                    jsonl_path,
1894                    line_index + 1,
1895                    error_message
1896                )
1897            });
1898            let solution = jsonl_string_field(line, "solution").unwrap_or_else(|error_message| {
1899                panic!(
1900                    "failed to parse solution in {:?} line {} ({}): {}",
1901                    jsonl_path,
1902                    line_index + 1,
1903                    title,
1904                    error_message
1905                )
1906            });
1907            let normalized_source = remove_windows_carriage_return(solution.as_str());
1908
1909            let start_time_for_one_solution = Instant::now();
1910            let (stmt_results, runtime_error) =
1911                run_source_code(normalized_source.as_str(), runtime);
1912            let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1913            *total_solution_duration_ms += duration_ms;
1914
1915            let (run_succeeded, run_output) =
1916                render_run_source_code_output(runtime, &stmt_results, &runtime_error, false);
1917
1918            *total_count += 1;
1919            if !run_succeeded {
1920                let label = format!("{}:{}", line_index + 1, title);
1921                println!(
1922                    "=== [FAILED] MetaMathQA-litex at jsonl line {} ({:.2} ms): {} ===\n{}\n",
1923                    line_index + 1,
1924                    duration_ms,
1925                    title,
1926                    run_output
1927                );
1928                failed_labels.push(label);
1929            }
1930
1931            if *total_count % 100 == 0 {
1932                println!(
1933                    "--- MetaMathQA-litex progress: {} solution(s), {} failure(s) ---",
1934                    total_count,
1935                    failed_labels.len()
1936                );
1937            }
1938        }
1939    }
1940
1941    fn jsonl_string_field(line: &str, key: &str) -> Result<String, String> {
1942        let field_name = format!("\"{}\"", key);
1943        let field_start = line
1944            .find(field_name.as_str())
1945            .ok_or_else(|| format!("missing JSON field `{}`", key))?;
1946        let after_field_name = field_start + field_name.len();
1947        let colon_offset = line[after_field_name..]
1948            .find(':')
1949            .ok_or_else(|| format!("missing `:` after JSON field `{}`", key))?;
1950        let mut value_start = after_field_name + colon_offset + 1;
1951        while value_start < line.len() && line.as_bytes()[value_start].is_ascii_whitespace() {
1952            value_start += 1;
1953        }
1954        parse_json_string_at(line, value_start)
1955    }
1956
1957    fn parse_json_string_at(line: &str, start_index: usize) -> Result<String, String> {
1958        if start_index >= line.len() || line.as_bytes()[start_index] != b'"' {
1959            return Err("JSON field value must be a string".to_string());
1960        }
1961
1962        let mut result = String::new();
1963        let mut chars = line[start_index + 1..].chars();
1964        while let Some(ch) = chars.next() {
1965            if ch == '"' {
1966                return Ok(result);
1967            }
1968            if ch != '\\' {
1969                result.push(ch);
1970                continue;
1971            }
1972
1973            let escaped = chars
1974                .next()
1975                .ok_or_else(|| "unfinished JSON escape".to_string())?;
1976            match escaped {
1977                '"' => result.push('"'),
1978                '\\' => result.push('\\'),
1979                '/' => result.push('/'),
1980                'b' => result.push('\u{0008}'),
1981                'f' => result.push('\u{000c}'),
1982                'n' => result.push('\n'),
1983                'r' => result.push('\r'),
1984                't' => result.push('\t'),
1985                'u' => {
1986                    let mut hex = String::new();
1987                    for _ in 0..4 {
1988                        hex.push(
1989                            chars
1990                                .next()
1991                                .ok_or_else(|| "unfinished JSON unicode escape".to_string())?,
1992                        );
1993                    }
1994                    let code = u32::from_str_radix(hex.as_str(), 16)
1995                        .map_err(|_| format!("invalid JSON unicode escape: {}", hex))?;
1996                    let decoded = char::from_u32(code)
1997                        .ok_or_else(|| format!("invalid JSON unicode code point: {}", hex))?;
1998                    result.push(decoded);
1999                }
2000                other => return Err(format!("unknown JSON escape: \\{}", other)),
2001            }
2002        }
2003
2004        Err("unterminated JSON string".to_string())
2005    }
2006}