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:
2026-03-29 18:28:10 +02:00
parent 10a7fcb576
commit 0e806b9e5d
6 changed files with 156 additions and 23 deletions
+64 -16
View File
@@ -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);
self.output
.push_str(&format!(" <Metadata: {:?}>\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!(" <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;