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.
This commit is contained in:
+63
-15
@@ -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<P: CompilerPhase>(node: &Node<P>) -> 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<P: CompilerPhase>(node: &Node<P>) -> 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<P: CompilerPhase>(&mut self, label: &str, node: &Node<P>) {
|
||||
self.write_indent();
|
||||
self.output.push_str(label);
|
||||
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!(" <Metadata: {:?}>\n", node.ty));
|
||||
.push_str(&format!(" <Metadata: {:?}>", node.ty));
|
||||
}
|
||||
self.output.push('\n');
|
||||
}
|
||||
|
||||
fn visit<P: CompilerPhase>(&mut self, node: &Node<P>) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String, String> {
|
||||
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));
|
||||
|
||||
+18
-2
@@ -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<CodeInput>) -> 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<CodeInput>) -> 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. \
|
||||
|
||||
+52
-1
@@ -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<String>;
|
||||
}
|
||||
|
||||
impl CompactMetadata for () {
|
||||
fn compact_label(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactMetadata for StaticType {
|
||||
fn compact_label(&self) -> Option<String> {
|
||||
Some(format!("{self}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactMetadata for NodeMetrics {
|
||||
fn compact_label(&self) -> Option<String> {
|
||||
Some(format!(
|
||||
"{}{}",
|
||||
self.original.ty,
|
||||
purity_suffix(self.purity)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactMetadata for RuntimeMetadata {
|
||||
fn compact_label(&self) -> Option<String> {
|
||||
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).
|
||||
|
||||
+10
-1
@@ -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 {
|
||||
|
||||
+2
-2
@@ -163,9 +163,9 @@ impl CompilerApp {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user