CST and AST¶
The compiler keeps two views of every parsed file: a lossless CST built on rowan, and a typed AST that casts CST nodes to per-syntax wrapper structs.
CST (mty-syntax)¶
The CST is a rowan green/red tree of
SyntaxKind-tagged nodes. It is:
- lossless — whitespace, comments, and even error-recovery debris are preserved as trivia tokens;
- immutable-shared — green nodes are interned and cheap to clone;
- navigable —
SyntaxNodecursors give parents, children, siblings, and text spans; - language-agnostic — rowan does not know what
FN_DECLmeans.
The Mighty-specific wiring is in
crates/mty-syntax/src/language.rs:
pub enum Mighty {}
impl rowan::Language for Mighty { ... }
pub type SyntaxNode = rowan::SyntaxNode<Mighty>;
pub type SyntaxToken = rowan::SyntaxToken<Mighty>;
pub type GreenNode = rowan::GreenNode;
AST (mty-ast)¶
The AST view wraps CST nodes in typed structs. It is generated by a single declarative macro:
Every wrapper implements the AstNode trait:
pub trait AstNode: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(node: SyntaxNode) -> Option<Self>;
fn syntax(&self) -> &SyntaxNode;
}
Cast a SyntaxNode into an FnDecl and access typed children:
let file: File = File::cast(SyntaxNode::new_root(green))?;
for item in file.items() {
if let Some(f) = FnDecl::cast(item) {
println!("fn {}", f.name()?.text());
}
}
The generated wrappers are in
crates/mty-ast/src/generated.rs.
Accessors are written by hand for now (one impl per wrapper). When
the AST grows large enough to make this tedious, replace
generated.rs with a real codegen step.
Why both?¶
The CST is the right shape for formatting, syntax highlighting, and
error recovery: it preserves everything in the source. The AST is the
right shape for lowering and analysis: it gives you typed children
without having to switch on SyntaxKind in every visitor. Sharing one
green tree across both views is rowan's main draw.