litex-lang 0.9.68-beta

A simple formal proof language and verifier, learnable in 2 hours
Documentation
#[cfg(test)]
mod lit_file_runner_tests {
    use std::fs;
    use std::path::{Path, PathBuf};
    use std::time::Instant;

    use crate::pipeline::{render_run_source_code_output, run_source_code};
    use crate::prelude::*;

    /// Collect ```litex``` bodies. A block is omitted when the last non-empty line before its opening
    /// fence is exactly `<!-- litex:skip-test -->` (for snippets that are illustrative only).
    fn extract_litex_fenced_blocks(markdown: &str) -> Vec<String> {
        const SKIP_MARKER: &str = "<!-- litex:skip-test -->";
        let mut blocks: Vec<String> = Vec::new();
        let mut in_litex = false;
        let mut skip_this_block = false;
        let mut current = String::new();
        let mut prev_non_empty_outside_block: Option<&str> = None;

        for line in markdown.lines() {
            let trimmed_start = line.trim_start();
            if trimmed_start.starts_with("```") {
                let info = trimmed_start[3..].trim();
                if in_litex {
                    if !skip_this_block {
                        let trimmed = current.trim();
                        if !trimmed.is_empty() {
                            blocks.push(trimmed.to_string());
                        }
                    }
                    current.clear();
                    in_litex = false;
                    skip_this_block = false;
                    prev_non_empty_outside_block = None;
                } else if info == "litex" {
                    in_litex = true;
                    skip_this_block = prev_non_empty_outside_block == Some(SKIP_MARKER);
                    current.clear();
                }
            } else if in_litex {
                if !skip_this_block {
                    if !current.is_empty() {
                        current.push('\n');
                    }
                    current.push_str(line);
                }
            } else {
                let t = line.trim();
                if !t.is_empty() {
                    prev_non_empty_outside_block = Some(t);
                }
            }
        }
        blocks
    }

    fn collect_markdown_files_sorted(docs_dir: &Path) -> Vec<PathBuf> {
        let mut out: Vec<PathBuf> = Vec::new();
        fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
            let read_dir = match fs::read_dir(dir) {
                Ok(entries) => entries,
                Err(_) => return,
            };
            for entry in read_dir.flatten() {
                let path = entry.path();
                let Ok(file_type) = entry.file_type() else {
                    continue;
                };
                if file_type.is_dir() {
                    walk(&path, out);
                } else if path.extension().is_some_and(|e| e == "md") {
                    out.push(path);
                }
            }
        }
        walk(docs_dir, &mut out);
        out.sort();
        out
    }

    #[test]
    fn run_tmp() {
        let tmp_lit_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("examples")
            .join("tmp.lit");

        assert!(
            tmp_lit_path.is_file(),
            "examples/tmp.lit must exist at {:?}",
            tmp_lit_path
        );

        let tmp_lit_content = match fs::read_to_string(&tmp_lit_path) {
            Ok(content) => content,
            Err(read_error) => panic!("failed to read {:?}: {}", tmp_lit_path, read_error),
        };
        if tmp_lit_content.trim().is_empty() {
            println!("examples/tmp.lit is empty; skip run_tmp");
            return;
        }

        let path_str = match tmp_lit_path.to_str() {
            Some(path_string) => path_string,
            None => panic!("{:?} must be valid UTF-8", tmp_lit_path),
        };

        let mut runtime = Runtime::new_with_builtin_code();
        runtime.new_file_path_new_env_new_name_scope(path_str);
        let normalized_source = remove_windows_carriage_return(tmp_lit_content.as_str());

        let start_time = Instant::now();
        let (stmt_results, runtime_error) =
            run_source_code(normalized_source.as_str(), &mut runtime);
        let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;

        let (run_succeeded, run_output) =
            render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);

        let status_label = if run_succeeded { "OK" } else { "FAILED" };
        println!(
            "{}\n=== [{}] {:?} ({:.2} ms user file only) ===\n",
            run_output, path_str, status_label, duration_ms
        );
    }

    #[test]
    fn run_examples() {
        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let examples_directory_path = manifest_dir.join("examples");

        let read_directory = match fs::read_dir(&examples_directory_path) {
            Ok(entries) => entries,
            Err(read_error) => panic!(
                "failed to read {:?}: {}",
                examples_directory_path, read_error
            ),
        };

        let mut lit_file_paths: Vec<PathBuf> = Vec::new();
        for directory_entry_result in read_directory {
            let directory_entry = match directory_entry_result {
                Ok(entry) => entry,
                Err(read_error) => panic!("failed to read directory entry: {}", read_error),
            };
            let lit_file_path = directory_entry.path();
            if !lit_file_path.is_file() {
                continue;
            }
            let extension_is_lit = match lit_file_path.extension() {
                Some(ext) => ext == "lit",
                None => false,
            };
            if extension_is_lit {
                lit_file_paths.push(lit_file_path);
            }
        }
        lit_file_paths.sort();

        let builtin_start = Instant::now();
        let mut runtime = Runtime::new_with_builtin_code();
        let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;

        let mut file_name_and_duration_ms_list: Vec<(String, f64)> = Vec::new();
        let mut every_file_run_ok = true;
        let mut examples_ran = false;
        let mut examples_phase_wall_ms: f64 = 0.0;

        if lit_file_paths.is_empty() {
            println!("--- examples folder: no .lit files ---");
        } else {
            examples_ran = true;
            let examples_wall_start = Instant::now();
            let first_lit_path_str = match lit_file_paths[0].to_str() {
                Some(path_string) => path_string,
                None => panic!("{:?} must be valid UTF-8", lit_file_paths[0]),
            };
            runtime.new_file_path_new_env_new_name_scope(first_lit_path_str);

            for (file_index, lit_file_path) in lit_file_paths.iter().enumerate() {
                let lit_file_path_str = match lit_file_path.to_str() {
                    Some(path_string) => path_string,
                    None => panic!("{:?} must be valid UTF-8", lit_file_path),
                };

                let file_label_for_report = match lit_file_path.file_name() {
                    Some(os_file_name) => match os_file_name.to_str() {
                        Some(name_string) => String::from(name_string),
                        None => format!("{:?}", lit_file_path),
                    },
                    None => format!("{:?}", lit_file_path),
                };

                if file_index > 0 {
                    runtime.clear_current_env_and_parse_name_scope();
                    runtime.set_current_user_lit_file_path(lit_file_path_str);
                }

                let source_code = match fs::read_to_string(lit_file_path) {
                    Ok(content) => content,
                    Err(read_error) => panic!("failed to read {:?}: {}", lit_file_path, read_error),
                };
                let normalized_source = remove_windows_carriage_return(&source_code);

                let start_time_for_one_file = Instant::now();
                let (stmt_results, runtime_error) =
                    run_source_code(normalized_source.as_str(), &mut runtime);
                let duration_ms_for_one_file =
                    start_time_for_one_file.elapsed().as_secs_f64() * 1000.0;

                let (run_succeeded, run_output) =
                    render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);

                if !run_succeeded {
                    every_file_run_ok = false;
                    println!(
                        "=== [{}] {:?} ===\n{}\n",
                        "FAILED", lit_file_path, run_output
                    );
                    break;
                }

                file_name_and_duration_ms_list
                    .push((file_label_for_report, duration_ms_for_one_file));
            }
            examples_phase_wall_ms = examples_wall_start.elapsed().as_secs_f64() * 1000.0;
        }

        if every_file_run_ok && examples_ran {
            let number_of_lit_files = file_name_and_duration_ms_list.len();

            println!(
                "--- examples folder: {} .lit files, all OK, timing ---",
                number_of_lit_files
            );
            println!("  builtin init (once): {:.2} ms", builtin_duration_ms);
            let mut sum_of_per_file_duration_ms: f64 = 0.0;
            for (file_name, duration_ms) in file_name_and_duration_ms_list.iter() {
                println!("  {}  {:.2} ms", file_name, duration_ms);
                sum_of_per_file_duration_ms += *duration_ms;
            }
            println!(
                "  sum of user-file runs: {:.2} ms",
                sum_of_per_file_duration_ms
            );
            println!(
                "  examples phase (wall): {:.2} ms",
                examples_phase_wall_ms
            );
        }

        if !every_file_run_ok {
            return;
        }

        let docs_dir = manifest_dir.join("docs");
        if !docs_dir.is_dir() {
            println!(
                "--- docs folder missing at {:?}; skip markdown litex blocks ---",
                docs_dir
            );
            println!(
                "--- phase timing: examples {:.2} ms | docs {:.2} ms (skipped) ---",
                examples_phase_wall_ms, 0.0_f64
            );
            return;
        }

        let md_paths = collect_markdown_files_sorted(&docs_dir);
        let mut doc_snippets: Vec<(String, String)> = Vec::new();
        for md_path in md_paths.iter() {
            let rel_label = md_path
                .strip_prefix(&manifest_dir)
                .unwrap_or(md_path)
                .display()
                .to_string();
            let md_content = match fs::read_to_string(md_path) {
                Ok(content) => content,
                Err(read_error) => panic!("failed to read {:?}: {}", md_path, read_error),
            };
            for (block_index, block) in extract_litex_fenced_blocks(&md_content)
                .into_iter()
                .enumerate()
            {
                doc_snippets.push((format!("{} ```litex```#{}", rel_label, block_index), block));
            }
        }

        if doc_snippets.is_empty() {
            println!("--- docs: no ```litex``` fenced blocks ---");
            println!(
                "--- phase timing: examples {:.2} ms | docs {:.2} ms (no blocks) ---",
                examples_phase_wall_ms, 0.0_f64
            );
            return;
        }

        if !examples_ran {
            runtime.new_file_path_new_env_new_name_scope("docs/```litex``` snippets");
        }

        println!(
            "--- docs: {} ```litex``` block(s) in {} markdown file(s) ---",
            doc_snippets.len(),
            md_paths.len()
        );

        let docs_wall_start = Instant::now();
        let mut doc_durations_ms: Vec<(String, f64)> = Vec::new();
        for (snippet_index, (label, source_code)) in doc_snippets.iter().enumerate() {
            if examples_ran || snippet_index > 0 {
                runtime.clear_current_env_and_parse_name_scope();
            }
            runtime.set_current_user_lit_file_path(label.as_str());

            let normalized_source = remove_windows_carriage_return(source_code);
            let start_snippet = Instant::now();
            let (stmt_results, runtime_error) =
                run_source_code(normalized_source.as_str(), &mut runtime);
            let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;

            let (run_succeeded, run_output) =
                render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);

            if !run_succeeded {
                panic!("docs litex snippet FAILED: {}\n{}\n", label, run_output);
            }

            doc_durations_ms.push((label.clone(), duration_ms));
        }
        let docs_phase_wall_ms = docs_wall_start.elapsed().as_secs_f64() * 1000.0;

        for (label, duration_ms) in doc_durations_ms.iter() {
            println!("  OK  {:.2} ms  {}", duration_ms, label);
        }
        println!(
            "--- phase timing: examples {:.2} ms | docs {:.2} ms ---",
            examples_phase_wall_ms, docs_phase_wall_ms
        );
    }
}