Files
RustAst/src/main.rs
T
Michael Schimmel 56b8e8389b Refactor optimizer with aggressive collapsing
The optimizer has been refactored to separate its phases and introduce
more aggressive collapsing capabilities.

Phase 2 (Cracking) now focuses on stateless transformations, making it
easier to reason about and potentially parallelize.

Phase 2.5 (Aggressive Collapsing) has been introduced, enabling
optimizations like:
- Beta-reduction for lambda literals.
- Cracking and inlining for constant closures.
- Folding intrinsics for constant arithmetic.
- Short-circuiting conditional expressions.

These changes aim to improve performance by reducing redundant
computations and code bloat. The maximum number of optimization passes
has also been increased to 5 to allow for more complex transformations.
2026-02-21 19:42:09 +01:00

670 lines
23 KiB
Rust

use eframe::egui;
use myc::ast::environment::Environment;
use std::path::PathBuf;
fn main() -> eframe::Result {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
..Default::default()
};
eframe::run_native(
"Compiler GUI",
options,
Box::new(|_cc| Ok(Box::new(CompilerApp::default()))),
)
}
struct Example {
name: String,
path: PathBuf,
content: String,
}
#[derive(PartialEq)]
enum AppTab {
Output,
Trace,
}
struct CompilerApp {
source_code: String,
current_example_path: Option<PathBuf>,
save_as_name: String,
show_save_as: bool,
output_log: String,
trace_logs: Vec<String>,
active_tab: AppTab,
debug_enabled: bool,
env: Environment,
is_first_frame: bool,
examples: Vec<Example>,
}
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().is_some_and(|ext| ext == "myc"))
.filter_map(|e| {
let path = e.path();
let name = e.file_name().into_string().ok()?;
let content = std::fs::read_to_string(&path).ok()?;
Some(Example {
name,
path,
content,
})
})
.collect()
})
.unwrap_or_default();
Self {
source_code: String::from(
r#"
(do
(def fib (fn [n]
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2))))))
(fib 10)
)
"#,
),
current_example_path: None,
save_as_name: String::new(),
show_save_as: false,
output_log: String::from(""),
trace_logs: Vec::new(),
active_tab: AppTab::Output,
debug_enabled: false,
env: Environment::new(),
is_first_frame: true,
examples,
}
}
}
impl CompilerApp {
fn compile(&mut self) {
let start_total = std::time::Instant::now();
self.trace_logs.clear();
// Reset environment for a fresh run
self.env = Environment::new();
self.env.optimization_level = 2;
let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration), String> {
let start_run = std::time::Instant::now();
let result = if self.debug_enabled {
let (res, logs) = self.env.run_debug(&self.source_code)?;
// Batch-truncate logs for GUI performance
let mut display_logs = logs;
if display_logs.len() > 1000 {
display_logs.truncate(1000);
display_logs.push("... [Trace truncated to 1,000 entries] ...".to_string());
}
for line in &mut display_logs {
if line.len() > 255
&& let Some((idx, _)) = line.char_indices().nth(255)
{
line.truncate(idx);
line.push_str("...");
}
}
self.trace_logs = display_logs;
self.active_tab = AppTab::Trace;
res?
} else {
self.env.run_script(&self.source_code)?
};
let duration_run = start_run.elapsed();
Ok((result, duration_run))
})();
match res {
Ok((val, d_run)) => {
let d_total = start_total.elapsed();
let now = chrono::Local::now().format("%H:%M:%S").to_string();
let mut stats = format!(
"Execution Successful.\nResult: {}\n\nTotal Duration: {:?}\nFinished at: {}",
val, d_total, now,
);
if !self.debug_enabled {
stats.push_str(&format!("\nRun Phase: {:?}", d_run));
}
self.output_log = stats;
if !self.debug_enabled {
self.active_tab = AppTab::Output;
}
}
Err(e) => {
self.output_log = format!("Error during Compilation/Execution: {}", e);
self.active_tab = AppTab::Output;
}
}
}
fn dump_ast(&mut self) {
self.env = Environment::new();
match self.env.dump_ast(&self.source_code) {
Ok(dump) => {
self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump);
}
Err(e) => {
self.output_log = format!("Error during AST Binding: {}", e);
}
}
}
fn save(&mut self) {
if let Some(path) = &self.current_example_path {
match std::fs::write(path, &self.source_code) {
Ok(_) => {
self.output_log = format!("Successfully saved to {:?}", path);
// Update the cached content in examples list so it stays in sync
if let Some(ex) = self.examples.iter_mut().find(|e| e.path == *path) {
ex.content = self.source_code.clone();
}
}
Err(e) => {
self.output_log = format!("Failed to save: {}", e);
}
}
} else {
self.output_log = "No file loaded to save. Use 'Examples' to load one.".to_string();
}
}
fn save_as(&mut self) {
if self.save_as_name.trim().is_empty() {
self.output_log = "Error: Please enter a filename.".to_string();
return;
}
let mut filename = self.save_as_name.trim().to_string();
if !filename.ends_with(".myc") {
filename.push_str(".myc");
}
let path = PathBuf::from("examples").join(filename);
match std::fs::write(&path, &self.source_code) {
Ok(_) => {
self.output_log = format!("Successfully saved new file to {:?}", path);
self.current_example_path = Some(path);
self.show_save_as = false;
self.save_as_name.clear();
self.refresh_examples();
}
Err(e) => {
self.output_log = format!("Failed to save: {}", e);
}
}
}
fn refresh_examples(&mut self) {
if let Ok(entries) = std::fs::read_dir("examples") {
self.examples = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "myc"))
.filter_map(|e| {
let path = e.path();
let name = e.file_name().into_string().ok()?;
let content = std::fs::read_to_string(&path).ok()?;
Some(Example {
name,
path,
content,
})
})
.collect();
}
}
fn run_all_tests(&mut self) {
use myc::utils::tester;
let results = tester::run_functional_tests();
let mut log = String::from("FUNCTIONAL TEST RESULTS:\n\n");
let mut passed = 0;
for res in &results {
log.push_str(&format!(
"{}: {} - {}\n",
if res.success { "✅" } else { "❌" },
res.name,
res.message
));
if res.success {
passed += 1;
}
}
log.push_str(&format!("\nTotal: {}/{}", passed, results.len()));
self.output_log = log;
}
fn run_benchmarks(&mut self, update: bool) {
use myc::utils::tester;
if cfg!(debug_assertions) && !update {
self.output_log = String::from(
"⚠️ WARNING: Benchmarks in Debug mode are inaccurate!\nUse Release build for real measurements.\n\n",
);
} else {
self.output_log = String::from(if update {
"UPDATING BASELINES...\n\n"
} else {
"RUNNING BENCHMARKS...\n\n"
});
}
let results = tester::run_benchmarks(update);
let mut log = self.output_log.clone();
for res in results {
let diff = res
.diff_pct
.map_or(String::new(), |d| format!(" ({:+.1}%)", d));
log.push_str(&format!(
"{}: {} - {:?}{}\n",
res.status, res.name, res.median, diff
));
}
self.output_log = log;
}
}
impl eframe::App for CompilerApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Top Panel: Menu Bar & Toolbar
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::MenuBar::new().ui(ui, |ui: &mut egui::Ui| {
ui.menu_button("File", |ui: &mut egui::Ui| {
let file_name = self
.current_example_path
.as_ref()
.map(|p| {
p.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned()
})
.unwrap_or_else(|| "None".to_string());
if ui.button(format!("Save {} (Ctrl+S)", file_name)).clicked() {
self.save();
ui.close();
}
if ui.button("Save As...").clicked() {
self.show_save_as = true;
ui.close();
}
});
ui.menu_button("Build", |ui: &mut egui::Ui| {
if ui.button("Compile (Shift+Enter)").clicked() {
self.compile();
ui.close();
}
if ui.button("Dump AST").clicked() {
self.dump_ast();
ui.close();
}
ui.separator();
ui.checkbox(&mut self.debug_enabled, "Debug Mode");
});
ui.menu_button("Tools", |ui: &mut egui::Ui| {
if ui.button("Test All").clicked() {
self.run_all_tests();
ui.close();
}
ui.separator();
let is_debug = cfg!(debug_assertions);
ui.add_enabled_ui(!is_debug, |ui: &mut egui::Ui| {
let btn = ui.button("Run Benchmarks");
if is_debug {
btn.on_hover_text("Benchmarks are only available in Release builds.");
} else if btn.clicked() {
self.run_benchmarks(false);
ui.close();
}
let btn_base = ui.button("Update Baselines");
if is_debug {
btn_base.on_hover_text(
"Updating baselines is only allowed in Release builds.",
);
} else if btn_base.clicked() {
self.run_benchmarks(true);
ui.close();
}
});
});
});
ui.separator();
// Toolbar (Speed Buttons)
ui.horizontal(|ui| {
ui.style_mut().spacing.item_spacing.x = 4.0; // tighter spacing for speed buttons
if ui
.button("▶ Run")
.on_hover_text("Compile and Run (Shift+Enter)")
.clicked()
{
self.compile();
}
if ui
.button("💾 Save")
.on_hover_text("Save current file (Ctrl+S)")
.clicked()
{
self.save();
}
ui.separator();
if ui
.button("🔍 AST")
.on_hover_text("Dump Bound AST")
.clicked()
{
self.dump_ast();
}
ui.separator();
ui.toggle_value(&mut self.debug_enabled, "🐛 Debug");
ui.separator();
if ui
.button("🧪 Test")
.on_hover_text("Run all functional tests")
.clicked()
{
self.run_all_tests();
}
ui.separator();
let is_debug = cfg!(debug_assertions);
ui.add_enabled_ui(!is_debug, |ui| {
let bench_btn = ui.button("⏱ Bench");
if is_debug {
bench_btn.on_hover_text("Benchmarks are only available in Release builds.");
} else if bench_btn.clicked() {
self.run_benchmarks(false);
}
});
});
ui.add_space(2.0);
});
// 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);
egui::ScrollArea::vertical().show(ui, |ui| {
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
for example in &self.examples {
if ui.button(&example.name).clicked() {
self.source_code = example.content.clone();
self.current_example_path = Some(example.path.clone());
}
}
});
});
});
// Bottom Panel: Output Log & Controls (Resizable)
egui::TopBottomPanel::bottom("bottom_panel")
.resizable(true)
.min_height(100.0)
.default_height(200.0)
.max_height(ctx.available_rect().height() * 0.5)
.show(ctx, |ui| {
ui.add_space(5.0);
if self.show_save_as {
ui.group(|ui| {
ui.horizontal(|ui| {
ui.label("New Filename:");
ui.text_edit_singleline(&mut self.save_as_name);
if ui.button("Save Now").clicked() {
self.save_as();
}
if ui.button("Cancel").clicked() {
self.show_save_as = false;
}
});
});
ui.add_space(5.0);
}
ui.horizontal(|ui| {
ui.selectable_value(&mut self.active_tab, AppTab::Output, "Output");
ui.selectable_value(&mut self.active_tab, AppTab::Trace, "Execution Trace");
ui.add_space(20.0);
if ui.button("Copy Content").clicked() {
let text = match self.active_tab {
AppTab::Output => self.output_log.clone(),
AppTab::Trace => self.trace_logs.join("\n"),
};
ui.ctx().copy_text(text);
}
if ui.button("Clear Log").clicked() {
self.output_log.clear();
self.trace_logs.clear();
}
});
ui.add_space(5.0);
// Log Output Area
match self.active_tab {
AppTab::Output => {
egui::ScrollArea::both()
.id_salt("output_log_scroll")
.auto_shrink(false)
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.output_log)
.font(egui::TextStyle::Monospace)
.hint_text("Ready to compile...")
.code_editor()
.lock_focus(false)
.desired_width(f32::INFINITY)
.frame(false),
);
});
}
AppTab::Trace => {
egui::ScrollArea::both()
.id_salt("trace_scroll")
.auto_shrink(false)
.show(ui, |ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
for line in &self.trace_logs {
ui.label(line);
}
});
});
}
}
});
// Central Panel: Source Code Editor (Fills remaining space)
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Source Code Editor");
ui.label("Input Source:");
let editor_id = ui.id().with("source_code_editor");
// Handle Shift+Enter shortcut BEFORE the editor handles it
let compile_shortcut =
egui::KeyboardShortcut::new(egui::Modifiers::SHIFT, egui::Key::Enter);
if ui.input_mut(|i| i.consume_shortcut(&compile_shortcut)) {
self.compile();
}
let save_shortcut = egui::KeyboardShortcut::new(egui::Modifiers::COMMAND, egui::Key::S);
if ui.input_mut(|i| i.consume_shortcut(&save_shortcut)) {
self.save();
}
// Set initial focus
if self.is_first_frame {
ui.ctx().memory_mut(|m| m.request_focus(editor_id));
self.is_first_frame = false;
}
let mut layouter = |ui: &egui::Ui, buffer: &dyn egui::TextBuffer, _wrap_width: f32| {
let mut layout_job = highlight_myc(ui.ctx(), buffer.as_str());
layout_job.wrap.max_width = f32::INFINITY; // Disable wrapping for code editor
ui.painter().layout_job(layout_job)
};
egui::ScrollArea::both()
.id_salt("source_editor_scroll")
.auto_shrink(false)
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut self.source_code)
.id(editor_id)
.font(egui::TextStyle::Monospace)
.code_editor()
.lock_focus(true)
.desired_width(f32::INFINITY)
.frame(false)
.layouter(&mut layouter),
);
});
});
}
}
fn highlight_myc(ctx: &egui::Context, code: &str) -> egui::text::LayoutJob {
let mut job = egui::text::LayoutJob::default();
let theme = &ctx.style().visuals;
let color_kw = egui::Color32::from_rgb(86, 156, 214); // Blue
let color_str = egui::Color32::from_rgb(206, 145, 120); // Orange/Brown
let color_num = egui::Color32::from_rgb(181, 206, 168); // Greenish
let color_comment = egui::Color32::from_rgb(106, 153, 85); // Green
let color_bracket = [
egui::Color32::from_rgb(255, 215, 0), // Gold
egui::Color32::from_rgb(218, 112, 214), // Orchid
egui::Color32::from_rgb(24, 131, 215), // Blue
];
let mut chars = code.chars().peekable();
let mut bracket_depth = 0;
while let Some(c) = chars.next() {
let mut text = String::from(c);
let mut color = theme.widgets.noninteractive.text_color();
match c {
';' => {
while let Some(&next) = chars.peek() {
if next == '\n' {
break;
}
text.push(chars.next().unwrap());
}
color = color_comment;
}
'"' => {
while let Some(next) = chars.next() {
text.push(next);
if next == '"' {
break;
}
if next == '\\'
&& let Some(escaped) = chars.next()
{
text.push(escaped);
}
}
color = color_str;
}
':' => {
while let Some(&next) = chars.peek() {
if next.is_whitespace() || "()[]{}".contains(next) {
break;
}
text.push(chars.next().unwrap());
}
color = color_kw;
}
'(' | '[' | '{' => {
color = color_bracket[bracket_depth % 3];
bracket_depth += 1;
}
')' | ']' | '}' => {
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 == '.' {
text.push(chars.next().unwrap());
} else {
break;
}
}
color = color_num;
}
_ if c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c) => {
while let Some(&next) = chars.peek() {
if next.is_alphanumeric() || "+-*/<=>!$%&_?.".contains(next) {
text.push(chars.next().unwrap());
} else {
break;
}
}
if ["fn", "def", "do", "if", "assign", "recur", "cond", "macro"]
.contains(&text.as_str())
{
color = color_kw;
}
}
_ => {}
}
job.append(
&text,
0.0,
egui::TextFormat {
font_id: egui::FontId::monospace(14.0),
color,
..Default::default()
},
);
}
job
}