From b1ca16149d540b4f03cbe416fdfd210085d1c594 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 17 Feb 2026 14:21:31 +0100 Subject: [PATCH] Add chrono dependency and example files This commit introduces the `chrono` dependency to the project, enabling the use of time-related functionalities. Additionally, it adds several example files (`closure.myc`, `fib.myc`, `hof.myc`) to showcase the language's features like closures, recursion, and higher-order functions. The `Cargo.lock` and `Cargo.toml` files have been updated to reflect the new dependency. The `integration_test.rs` file now includes a test to run all `.myc` files found in the `examples` directory, ensuring their correctness. The `main.rs` file has also been updated to load and display these examples in the UI. --- Cargo.lock | 38 +++++++++++++++++ Cargo.toml | 2 + examples/closure.myc | 10 +++++ examples/fib.myc | 10 +++++ examples/hof.myc | 8 ++++ src/integration_test.rs | 38 ++++++++++++++++- src/main.rs | 91 ++++++++++++++++++++++------------------- 7 files changed, 154 insertions(+), 43 deletions(-) create mode 100644 examples/closure.myc create mode 100644 examples/fib.myc create mode 100644 examples/hof.myc diff --git a/Cargo.lock b/Cargo.lock index 25fb60d..c94064c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,19 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + [[package]] name = "clap" version = "4.5.59" @@ -1480,6 +1493,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -1841,6 +1878,7 @@ dependencies = [ name = "myc" version = "0.1.0" dependencies = [ + "chrono", "clap", "eframe", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 85a9a4e..468567a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,10 @@ name = "myc" version = "0.1.0" edition = "2024" +default-run = "myc" [dependencies] eframe = "0.33.3" lazy_static = "1.4.0" clap = { version = "4.5", features = ["derive"] } +chrono = "0.4" diff --git a/examples/closure.myc b/examples/closure.myc new file mode 100644 index 0000000..98f649f --- /dev/null +++ b/examples/closure.myc @@ -0,0 +1,10 @@ +;; Closure Scope Test +;; Output: 15 +(do + (def make-adder (fn [x] + (fn [y] (+ x y)))) + + (def add5 (make-adder 5)) + + (add5 10) +) diff --git a/examples/fib.myc b/examples/fib.myc new file mode 100644 index 0000000..5aa538d --- /dev/null +++ b/examples/fib.myc @@ -0,0 +1,10 @@ +;; Fibonacci Recursive +;; Output: 55 +(do + (def fib (fn [n] + (if (< n 2) + n + (+ (fib (- n 1)) (fib (- n 2)))))) + + (fib 10) +) diff --git a/examples/hof.myc b/examples/hof.myc new file mode 100644 index 0000000..fd70109 --- /dev/null +++ b/examples/hof.myc @@ -0,0 +1,8 @@ +;; Higher-Order Function Example +;; Output: 25 +(do + (def apply (fn [f x] (f x))) + (def square (fn [x] (* x x))) + + (apply square 5) +) diff --git a/src/integration_test.rs b/src/integration_test.rs index d58a2b3..6b085b1 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -6,8 +6,9 @@ mod tests { use crate::ast::compiler::binder::Binder; use crate::ast::vm::VM; use crate::ast::types::Value; - use crate::ast::nodes::UntypedKind; + use crate::ast::environment::Environment; + use std::fs; #[test] fn test_parse_integer_constant() { @@ -89,4 +90,39 @@ mod tests { Err(e) => panic!("VM Error: {}", e), } } + + #[test] + fn test_examples() { + let entries = fs::read_dir("examples").expect("Could not read examples directory"); + for entry in entries { + let entry = entry.expect("Invalid entry"); + let path = entry.path(); + if path.extension().map_or(false, |ext| ext == "myc") { + let content = fs::read_to_string(&path).expect("Could not read file"); + + // Skip benchmarks during normal functional tests + if content.contains(";; Benchmark") { + continue; + } + + // Find expected output tag: ;; Output: + let expected_output = content.lines() + .find(|line| line.starts_with(";; Output:")) + .map(|line| line.replace(";; Output:", "").trim().to_string()); + + if let Some(expected) = expected_output { + let env = Environment::new(); + let result = env.run_script(&content); + + match result { + Ok(val) => { + let val_str = format!("{}", val); + assert_eq!(val_str, expected, "Example {:?} failed: expected {}, got {}", path, expected, val_str); + } + Err(e) => panic!("Example {:?} failed with error: {}", path, e), + } + } + } + } + } } diff --git a/src/main.rs b/src/main.rs index 99e512f..636baaa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,15 +13,37 @@ fn main() -> eframe::Result { ) } +struct Example { + name: String, + content: String, + is_benchmark: bool, +} + struct CompilerApp { source_code: String, output_log: String, env: Environment, is_first_frame: bool, + examples: Vec, } impl Default for CompilerApp { fn default() -> Self { + let examples = std::fs::read_dir("examples") + .map(|entries| { + entries + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().map_or(false, |ext| ext == "myc")) + .filter_map(|e| { + let name = e.file_name().into_string().ok()?; + let content = std::fs::read_to_string(e.path()).ok()?; + let is_benchmark = content.contains(";; Benchmark"); + Some(Example { name, content, is_benchmark }) + }) + .collect() + }) + .unwrap_or_default(); + Self { source_code: String::from(r#" (do @@ -30,33 +52,29 @@ impl Default for CompilerApp { n (+ (fib (- n 1)) (fib (- n 2)))))) - (def result (fib 10)) - - (def data { - :name "Fibonacci" - :input 10 - :output result - :sequence [0 1 1 2 3 5 8 13 21 34 55] - }) - - data + (fib 10) ) "#), output_log: String::from("Ready to compile..."), env: Environment::new(), is_first_frame: true, + examples, } } } impl CompilerApp { fn compile(&mut self) { + let start = std::time::Instant::now(); match self.env.run_script(&self.source_code) { Ok(result) => { + let duration = start.elapsed(); + let now = chrono::Local::now().format("%H:%M:%S").to_string(); self.output_log = format!( - "Execution Successful.\nResult: {}\n\nFinished at {:?}", + "Execution Successful.\nResult: {}\n\nDuration: {:?}\nFinished at: {}", result, - std::time::SystemTime::now(), + duration, + now, ); } Err(e) => { @@ -68,6 +86,25 @@ impl CompilerApp { impl eframe::App for CompilerApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Left Side Panel: Examples + egui::SidePanel::left("examples_panel") + .resizable(true) + .default_width(150.0) + .show(ctx, |ui| { + ui.heading("Examples"); + ui.add_space(5.0); + for example in &self.examples { + let label = if example.is_benchmark { + format!("⚡ {}", example.name) + } else { + example.name.clone() + }; + if ui.button(label).clicked() { + self.source_code = example.content.clone(); + } + } + }); + // Bottom Panel: Output Log & Controls (Resizable) egui::TopBottomPanel::bottom("bottom_panel") .resizable(true) @@ -137,33 +174,3 @@ impl eframe::App for CompilerApp { }); } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::types::Value; - - #[test] - fn test_basic_eval() { - let env = Environment::new(); - let result = env.run_script("(+ 1 2)").unwrap(); - if let Value::Int(i) = result { - assert_eq!(i, 3); - } else if let Value::Float(f) = result { - assert_eq!(f, 3.0); - } else { - panic!("Expected number result"); - } - } - - #[test] - fn test_type_error() { - let env = Environment::new(); - // This should fail because 'if' condition must be bool (and currently '+' returns Any/Float/Int) - // Wait, '+' returns Any currently in my registration. - // Let's try something that definitely fails type check. - let result = env.run_script("(if 1 2 3)"); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Condition must be boolean")); - } -}