From 0e806b9e5dd8627e38620b719b2997d66760a457 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 29 Mar 2026 18:28:10 +0200 Subject: [PATCH] feat: Add compact AST dump Introduce a new `dump_ast_compact` function for the environment and Dumper. This function provides a simplified, human-readable representation of the AST, focusing on inferred types and purity markers instead of detailed debug information. This change adds: - A `compact` flag to the `Dumper` struct. - The `dump_compact` method to the `Dumper`. - The `dump_ast_compact` method to the `Environment`. - A new `dump_ast_compact` command-line argument for the `ast` binary. - A new MCP server endpoint `dump_ast_compact`. - A `CompactMetadata` trait to abstract over different metadata types for compact display. - Updates to documentation and CLI help messages. --- src/ast/compiler/dumper.rs | 80 ++++++++++++++++++++++++++++++-------- src/ast/environment.rs | 9 +++++ src/ast/mcp_server.rs | 20 +++++++++- src/ast/nodes.rs | 53 ++++++++++++++++++++++++- src/bin/ast.rs | 11 +++++- src/main.rs | 6 +-- 6 files changed, 156 insertions(+), 23 deletions(-) diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index dbb66f8..36afc57 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -1,17 +1,33 @@ -use crate::ast::nodes::{CompilerPhase, Node, NodeKind}; +use crate::ast::nodes::{CompactMetadata, CompilerPhase, Node, NodeKind}; /// Human-readable AST dumper for the bound AST. pub struct Dumper { output: String, indent: usize, + /// When `true`, emits a compact format suitable for GUI display: + /// type annotations via `Display` and purity symbols instead of raw debug structs. + compact: bool, } impl Dumper { - /// Produces a formatted string representation of the given bound AST node and its children. + /// Produces a verbose debug representation of the given AST node and its children. pub fn dump(node: &Node

) -> String { let mut dumper = Self { output: String::new(), indent: 0, + compact: false, + }; + dumper.visit(node); + dumper.output + } + + /// Produces a compact, human-readable representation suitable for GUI display. + /// Shows inferred types and purity (`!` / `~`) instead of raw debug structs. + pub fn dump_compact(node: &Node

) -> String { + let mut dumper = Self { + output: String::new(), + indent: 0, + compact: true, }; dumper.visit(node); dumper.output @@ -26,8 +42,16 @@ impl Dumper { fn log(&mut self, label: &str, node: &Node

) { self.write_indent(); self.output.push_str(label); - self.output - .push_str(&format!(" \n", node.ty)); + if self.compact { + if let Some(annotation) = node.ty.compact_label() { + self.output.push_str(" → "); + self.output.push_str(&annotation); + } + } else { + self.output + .push_str(&format!(" ", node.ty)); + } + self.output.push('\n'); } fn visit(&mut self, node: &Node

) { @@ -37,7 +61,12 @@ impl Dumper { self.log(&format!("Constant: {}", v), node); } NodeKind::Identifier { symbol, binding } => { - self.log(&format!("Identifier: {} ({:?})", symbol.name, binding), node) + let label = if self.compact { + format!("Identifier: {}", symbol.name) + } else { + format!("Identifier: {} ({:?})", symbol.name, binding) + }; + self.log(&label, node); } NodeKind::FieldAccessor(k) => self.log(&format!("FieldAccessor: .{}", k.name()), node), @@ -50,7 +79,12 @@ impl Dumper { } NodeKind::Assign { target, value, info } => { - self.log(&format!("Assign: {:?}", info), node); + let label = if self.compact { + "Assign".to_string() + } else { + format!("Assign: {:?}", info) + }; + self.log(&label, node); self.indent += 1; self.write_indent(); self.output.push_str("Target:\n"); @@ -66,7 +100,12 @@ impl Dumper { value, info, } => { - self.log(&format!("Def ({:?})", info), node); + let label = if self.compact { + "Def".to_string() + } else { + format!("Def ({:?})", info) + }; + self.log(&label, node); self.indent += 1; self.write_indent(); self.output.push_str("Pattern:\n"); @@ -106,7 +145,12 @@ impl Dumper { body, info, } => { - self.log(&format!("Lambda ({:?})", info), node); + let label = if self.compact { + "Lambda".to_string() + } else { + format!("Lambda ({:?})", info) + }; + self.log(&label, node); self.indent += 1; self.write_indent(); @@ -157,10 +201,12 @@ impl Dumper { } NodeKind::Record { fields, layout } => { - self.log( - &format!("Record (Layout: {:?})", layout), - node, - ); + let label = if self.compact { + "Record".to_string() + } else { + format!("Record (Layout: {:?})", layout) + }; + self.log(&label, node); self.indent += 1; for (key, val) in fields { self.write_indent(); @@ -181,10 +227,12 @@ impl Dumper { original_call, expanded, } => { - self.log( - &format!("Expansion (Original: {:?})", original_call.kind), - node, - ); + let label = if self.compact { + "Expansion".to_string() + } else { + format!("Expansion (Original: {:?})", original_call.kind) + }; + self.log(&label, node); self.indent += 1; self.visit(expanded); self.indent -= 1; diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 7efd9dd..40ef8d7 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -725,6 +725,15 @@ impl Environment { Ok(Dumper::dump(&linked)) } + /// Like [`dump_ast`] but produces a compact, GUI-friendly representation: + /// inferred types via `Display` and purity symbols (`!` / `~`) instead of raw debug output. + pub fn dump_ast_compact(&self, source: &str) -> Result { + self.preload_dependencies(source, None)?; + let compiled = self.compile(source).into_result()?; + let linked = self.link(compiled); + Ok(Dumper::dump_compact(&linked)) + } + pub fn compile(&self, source: &str) -> CompilationResult { if let Err(e) = self.preload_dependencies(source, None) { return CompilationResult::error(format!("Dependency Error: {}", e)); diff --git a/src/ast/mcp_server.rs b/src/ast/mcp_server.rs index 1a3e3a7..1ec9cbc 100644 --- a/src/ast/mcp_server.rs +++ b/src/ast/mcp_server.rs @@ -70,10 +70,12 @@ impl MycMcpServer { } } - /// Dumps the compiled and optimized AST for a Myc expression. + /// Dumps the compiled and optimized AST for a Myc expression (verbose debug format). #[tool(description = "Compile a Myc expression and return a human-readable dump of \ the compiled AST. Useful for understanding how the compiler transforms code \ - through its optimization and lowering passes.")] + through its optimization and lowering passes. \ + Returns the full debug format including binding addresses and stack offsets. \ + Use dump_ast_compact for a cleaner view focused on types and purity.")] fn dump_ast(&self, Parameters(CodeInput { code }): Parameters) -> String { let env = Self::make_env(); match env.dump_ast(&code) { @@ -82,6 +84,20 @@ impl MycMcpServer { } } + /// Dumps the compiled AST in compact, human-readable format. + #[tool(description = "Compile a Myc expression and return a compact AST dump showing \ + inferred types (e.g. 'fn([int]) -> bool') and purity markers ('!' for impure, \ + '~' for side-effect-free). '[tail]' marks tail-call positions. \ + Omits internal details like stack offsets and binding addresses. \ + Prefer this over dump_ast when you want to understand the type structure of an expression.")] + fn dump_ast_compact(&self, Parameters(CodeInput { code }): Parameters) -> String { + let env = Self::make_env(); + match env.dump_ast_compact(&code) { + Ok(dump) => dump, + Err(e) => format!("Error: {}", e), + } + } + /// Checks the syntax and types of a Myc expression without executing it. #[tool(description = "Check the syntax and types of a Myc expression without running it. \ Returns 'OK' if the code is valid, or a list of compiler diagnostics on error. \ diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 1057d13..1a23bb1 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -95,10 +95,61 @@ pub enum DeclarationKind { Parameter, } +// ── CompactMetadata trait ────────────────────────────────────────────────────── + +/// Provides a compact, human-readable annotation for node metadata. +/// Used by the GUI AST dump to show type and purity without verbose debug output. +pub trait CompactMetadata { + /// Returns a short annotation such as `"int"`, `"fn([]) -> bool !"`. + /// Returns `None` for phases that carry no type information. + fn compact_label(&self) -> Option; +} + +impl CompactMetadata for () { + fn compact_label(&self) -> Option { + None + } +} + +impl CompactMetadata for StaticType { + fn compact_label(&self) -> Option { + Some(format!("{self}")) + } +} + +impl CompactMetadata for NodeMetrics { + fn compact_label(&self) -> Option { + Some(format!( + "{}{}", + self.original.ty, + purity_suffix(self.purity) + )) + } +} + +impl CompactMetadata for RuntimeMetadata { + fn compact_label(&self) -> Option { + Some(format!( + "{}{}{}", + self.ty, + if self.is_tail { " [tail]" } else { "" }, + purity_suffix(self.original.ty.purity) + )) + } +} + +fn purity_suffix(p: Purity) -> &'static str { + match p { + Purity::Pure => "", + Purity::SideEffectFree => " ~", + Purity::Impure => " !", + } +} + // ── CompilerPhase trait ──────────────────────────────────────────────────────── pub trait CompilerPhase: std::fmt::Debug + 'static { - type Metadata: std::fmt::Debug + Clone + PartialEq; + type Metadata: std::fmt::Debug + Clone + PartialEq + CompactMetadata; type LocalAddress: std::fmt::Debug + Clone + Copy + PartialEq + Eq + std::hash::Hash; /// Semantic role and address of an identifier (reference vs. declaration). diff --git a/src/bin/ast.rs b/src/bin/ast.rs index fc0a3ea..8bc3c94 100644 --- a/src/bin/ast.rs +++ b/src/bin/ast.rs @@ -26,10 +26,14 @@ struct Cli { #[arg(short, long)] update_bench: bool, - /// Dump the compiled AST + /// Dump the compiled AST (verbose debug format) #[arg(short, long)] dump: bool, + /// Dump the compiled AST in compact, human-readable format (types + purity) + #[arg(long)] + dump_compact: bool, + /// Run with TracingObserver enabled #[arg(short, long)] trace: bool, @@ -129,6 +133,11 @@ fn main() { Ok(dump) => println!("{}", dump), Err(e) => eprintln!("Error dumping AST: {}", e), } + } else if cli.dump_compact { + match env.dump_ast_compact(&content) { + Ok(dump) => println!("{}", dump), + Err(e) => eprintln!("Error dumping AST: {}", e), + } } else if cli.trace { execute_trace(&env, &content); } else { diff --git a/src/main.rs b/src/main.rs index 40ae6a0..3129070 100644 --- a/src/main.rs +++ b/src/main.rs @@ -162,10 +162,10 @@ impl CompilerApp { fn dump_ast(&mut self) { self.env = Environment::new(); self.env.optimization = true; // Enable optimization for AST dump - - match self.env.dump_ast(&self.source_code) { + + match self.env.dump_ast_compact(&self.source_code) { Ok(dump) => { - self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump); + self.output_log = format!("--- AST DUMP ---\n\n{}", dump); } Err(e) => { self.output_log = format!("Error during AST Binding: {}", e);