Expand description
§ast_lang
The syntax-tree substrate for the -lang family: the trait an AST node
implements and the generic machinery that walks and rewrites a tree of those
nodes. It owns no grammar — a language brings its own node type — so the same
traversal code serves every language built on it.
§Model
ast-lang builds on the pattern arena_lang establishes: a language defines a
single node type N (almost always an enum), stores its nodes in an
Arena<N>, and wires the tree with Id<N> handles rather than
references — so a parent can hold its children without tangling the borrow
checker. A node implements Node to report its span, enumerate
its child handles (each_child), and rebuild itself with
remapped children (map_children). Everything else is
generic:
walkdrives aVisitorover the tree, depth-first and iteratively, so even a very deep tree never overflows the call stack. The visitor steers the traversal with aFlowand accumulates whatever it needs.transformrebuilds a tree into a destination arena, passing each node through a closure — the rewrite (fold) operation, also iterative.
§Quickstart
use ast_lang::{transform, walk, Arena, Flow, Id, Node, Span, Visitor};
// A language defines its node type; ast-lang carries the rest.
#[derive(Clone)]
enum Expr {
Lit(i64, Span),
Add(Id<Expr>, Id<Expr>, Span),
}
impl Node for Expr {
fn span(&self) -> Span {
match self {
Expr::Lit(_, s) | Expr::Add(_, _, s) => *s,
}
}
fn each_child(&self, f: &mut dyn FnMut(Id<Self>)) {
if let Expr::Add(a, b, _) = self {
f(*a);
f(*b);
}
}
fn map_children(&self, f: &mut dyn FnMut(Id<Self>) -> Id<Self>) -> Self {
match self {
Expr::Lit(v, s) => Expr::Lit(*v, *s),
Expr::Add(a, b, s) => Expr::Add(f(*a), f(*b), *s),
}
}
}
let mut arena = Arena::new();
let one = arena.alloc(Expr::Lit(1, Span::new(0, 1)));
let two = arena.alloc(Expr::Lit(2, Span::new(4, 5)));
let add = arena.alloc(Expr::Add(one, two, Span::new(0, 5)));
// Walk: sum the literals.
struct Sum(i64);
impl Visitor<Expr> for Sum {
fn enter(&mut self, _: &Arena<Expr>, _: Id<Expr>, node: &Expr) -> Flow {
if let Expr::Lit(v, _) = node {
self.0 += *v;
}
Flow::Continue
}
}
let mut sum = Sum(0);
walk(&arena, add, &mut sum);
assert_eq!(sum.0, 3);
// Transform: deep-copy into a fresh arena, doubling each literal.
let mut doubled = Arena::new();
let _ = transform(&arena, add, &mut doubled, |node| match node {
Expr::Lit(v, s) => Expr::Lit(v * 2, s),
other => other,
});§Features
std(default) — the standard library; without it the crate isno_std(it always needsalloc). Forwards tospan-lang/stdandarena-lang/std.
§Stability
The public surface is being designed across the 0.x series and freezes at
1.0.0, after which it follows Semantic Versioning: no breaking changes before
2.0, additions arrive in minor releases, and the MSRV (Rust 1.85) only rises
in a minor. The frozen surface is catalogued in
docs/API.md.
Structs§
- Arena
- A typed arena that allocates values and hands back stable
Idhandles. - Id
- A small, copyable, type-tagged handle to one value in an
Arena. - Span
- A half-open byte range
start..endinto a single source.
Enums§
Traits§
- Node
- The contract a language’s syntax-tree node satisfies so the generic traversals in this crate can walk and rebuild it.
- Visitor
- A read-only traversal over a tree of
Nodes.