feat: Implement AST macros and templates

This commit introduces support for AST macros and templates, enabling
users to define and use custom, reusable components within the visual
DSL.

Key changes include:
- A new `MacroRegistry` to manage macro definitions.
- A `MacroExpander` to process macro calls and expand templates.
- A `MacroEvaluator` trait for evaluating expressions during expansion.
- The `Expansion` node in `BoundKind` to preserve the original macro
  call and its expanded form for debugging.
- Updates to the `Binder`, `Dumper`, and `UpvalueAnalyzer` to handle the
  new macro constructs.
- New examples demonstrating various macro functionalities like
  `unless`, splicing, and nested macros.
This commit is contained in:
Michael Schimmel
2026-02-18 12:51:07 +01:00
parent 98deb8f3fe
commit 94fc6bf56d
23 changed files with 798 additions and 67 deletions
+37 -19
View File
@@ -84,30 +84,45 @@ impl Default for CompilerApp {
impl CompilerApp {
fn compile(&mut self) {
let start = std::time::Instant::now();
let start_total = std::time::Instant::now();
self.trace_logs.clear();
let result = if self.debug_enabled {
match self.env.run_debug(&self.source_code) {
Ok((res, logs)) => {
self.trace_logs = logs;
self.active_tab = AppTab::Trace;
res
}
Err(e) => Err(e),
}
} else {
self.env.run_script(&self.source_code)
};
let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration, std::time::Duration, std::time::Duration), String> {
let start_compile = std::time::Instant::now();
let compiled = self.env.compile(&self.source_code)?;
let duration_compile = start_compile.elapsed();
match result {
Ok(val) => {
let duration = start.elapsed();
let start_link = std::time::Instant::now();
let linked = self.env.link(compiled);
let duration_link = start_link.elapsed();
let start_run = std::time::Instant::now();
let result = if self.debug_enabled {
let mut vm = myc::ast::vm::VM::new(self.env.global_values.clone());
let mut observer = myc::ast::vm::TracingObserver::new();
let res = vm.run_with_observer(&mut observer, &linked);
self.trace_logs = observer.logs;
self.active_tab = AppTab::Trace;
res?
} else {
self.env.run(&linked)?
};
let duration_run = start_run.elapsed();
Ok((result, duration_compile, duration_link, duration_run))
})();
match res {
Ok((val, d_compile, d_link, d_run)) => {
let d_total = start_total.elapsed();
let now = chrono::Local::now().format("%H:%M:%S").to_string();
self.output_log = format!(
"Execution Successful.\nResult: {}\n\nDuration: {:?}\nFinished at: {}",
"Execution Successful.\nResult: {}\n\nPhases:\n - Compile: {:?}\n - Link: {:?}\n - Run: {:?}\n\nTotal Duration: {:?}\nFinished at: {}",
val,
duration,
d_compile,
d_link,
d_run,
d_total,
now,
);
if !self.debug_enabled {
@@ -466,6 +481,9 @@ fn highlight_myc(ctx: &egui::Context, code: &str) -> egui::text::LayoutJob {
bracket_depth = bracket_depth.saturating_sub(1);
color = color_bracket[bracket_depth % 3];
}
'`' | '~' | '@' => {
color = color_kw;
}
'0'..='9' => {
while let Some(&next) = chars.peek() {
if next.is_ascii_digit() || next == '.' {
@@ -480,7 +498,7 @@ fn highlight_myc(ctx: &egui::Context, code: &str) -> egui::text::LayoutJob {
text.push(chars.next().unwrap());
} else { break; }
}
if ["fn", "def", "do", "if", "assign", "recur", "cond"].contains(&text.as_str()) {
if ["fn", "def", "do", "if", "assign", "recur", "cond", "macro"].contains(&text.as_str()) {
color = color_kw;
}
}