Add egui_extras dependency

Adds `egui_extras` to Cargo.toml and Cargo.lock to enable enhanced table
widgets.
Also introduces a new `StateTab` enum and integrates the
`egui_extras::TableBuilder` to display global variables in a structured
table within the application's state panel. This enhances the debugging
and introspection capabilities of the compiler UI by providing a clear
view of the environment's global state.
This commit is contained in:
2026-03-30 15:41:25 +02:00
parent 3628802fc8
commit 9013f262ce
5 changed files with 240 additions and 1 deletions
+35
View File
@@ -587,6 +587,41 @@ impl Environment {
}
/// Returns all user-defined global bindings with their name, type and current value.
///
/// Iterates over the user scopes (index 1+), skipping the frozen RTL scope.
/// Root-level bindings use `Address::Local(VirtualId)` in the scope map —
/// the Local→Global translation only happens at resolve time in the binder,
/// so we treat Local slots as global indices here.
pub fn list_user_globals(&self) -> Vec<(String, StaticType, Value)> {
let scopes = self.root_scopes.borrow();
let types = self.root_types.borrow();
let user_vals = self.user_values.borrow();
let rtl_slot_count = self.rtl.slot_count as usize;
let mut result = Vec::new();
for scope in scopes.iter().skip(1) {
for (sym, info) in &scope.locals {
let slot = match info.addr {
Address::Global(idx) => idx.0 as usize,
Address::Local(vid) => vid.0 as usize,
Address::Upvalue(_) => continue,
};
if slot >= rtl_slot_count {
let user_idx = slot - rtl_slot_count;
let ty = types.get(slot).cloned().unwrap_or(StaticType::Any);
let val = user_vals.get(user_idx).cloned().unwrap_or(Value::Void);
result.push((sym.name.to_string(), ty, val));
}
}
}
result.sort_by(|a, b| a.0.cmp(&b.0));
result
}
/// Returns the names of all bindings registered in the fixed RTL scope.
/// Useful for introspection (e.g., MCP server listing available built-ins).
pub fn list_bindings(&self) -> Vec<String> {
+79
View File
@@ -1,4 +1,5 @@
use eframe::egui;
use egui_extras::{Column, TableBuilder};
use myc::ast::compiler::emitter;
use myc::ast::environment::Environment;
use myc::ast::parser::Parser;
@@ -29,6 +30,13 @@ enum AppTab {
Trace,
}
#[derive(PartialEq)]
enum StateTab {
Globals,
Dataflow,
Series,
}
struct CompilerApp {
source_code: String,
current_example_path: Option<PathBuf>,
@@ -38,6 +46,7 @@ struct CompilerApp {
trace_logs: Vec<String>,
active_tab: AppTab,
debug_enabled: bool,
state_tab: StateTab,
env: Environment,
is_first_frame: bool,
examples: Vec<Example>,
@@ -86,6 +95,7 @@ impl Default for CompilerApp {
trace_logs: Vec::new(),
active_tab: AppTab::Output,
debug_enabled: false,
state_tab: StateTab::Globals,
env,
is_first_frame: true,
examples,
@@ -573,6 +583,75 @@ impl eframe::App for CompilerApp {
}
});
// Right Side Panel: Environment State Inspector
egui::SidePanel::right("state_panel")
.resizable(true)
.default_width(300.0)
.min_width(200.0)
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut self.state_tab, StateTab::Globals, "Globals");
ui.selectable_value(&mut self.state_tab, StateTab::Dataflow, "Dataflow");
ui.selectable_value(&mut self.state_tab, StateTab::Series, "Series");
});
ui.separator();
match self.state_tab {
StateTab::Globals => {
let globals = self.env.list_user_globals();
let scopes = self.env.root_scopes.borrow();
let rtl_slots = self.env.rtl.slot_count;
let total_slots = *self.env.root_slot_count.borrow();
let scope_details: Vec<String> = scopes.iter().enumerate().map(|(i, s)| {
let entries: Vec<String> = s.locals.iter().map(|(sym, info)| {
format!("{}={:?}", sym.name, info.addr)
}).collect();
format!("S{}[{}]", i, entries.join(", "))
}).collect();
drop(scopes);
ui.label(format!(
"DEBUG: rtl={}, total={}, found={}", rtl_slots, total_slots, globals.len()
));
for s in &scope_details {
ui.label(s);
}
if globals.is_empty() {
ui.label("No user globals. Run a script first.");
} else {
let available = ui.available_height();
TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.min_scrolled_height(available)
.column(Column::auto().at_least(60.0))
.column(Column::auto().at_least(60.0))
.column(Column::remainder().at_least(80.0))
.header(18.0, |mut header| {
header.col(|ui| { ui.strong("Name"); });
header.col(|ui| { ui.strong("Type"); });
header.col(|ui| { ui.strong("Value"); });
})
.body(|mut body| {
for (name, ty, val) in &globals {
body.row(18.0, |mut row| {
row.col(|ui| { ui.monospace(name); });
row.col(|ui| { ui.monospace(ty.to_string()); });
row.col(|ui| { ui.monospace(val.to_string()); });
});
}
});
}
}
StateTab::Dataflow => {
ui.label("Stream/Pipe dataflow graph will appear here after execution.");
}
StateTab::Series => {
ui.label("Series contents will appear here after execution.");
}
}
});
// Central Panel: Source Code Editor (Fills remaining space)
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Source Code Editor");