After trying some tree structures in Rust, I finally decided to built up a linearized tree, e.g. something like
struct AST {
exprs: Vec<Expr>,
}
enum Expr {
LiteralInt(i32),
OpAdd(ExprRef, ExprRef),
}
struct ExprRef(usize);
impl AST {
// ...
fn add_expr(&mut self, expr: Expr) -> ExprRef {
self.exprs.push(expr);
ExprRef(self.exprs.len() - 1)
}
}
Hence, when parsing an expression (e.g. "+ + 3 4 1"
) the parsers needs to mutate AST
, by pushing new expressions into it and using the ExprRef
for further expressions.
So, I've thought of something like
fn literal_int(ast: &mut AST) -> impl FnMut(&str) -> IResult<&str, ExprRef> {
move |input: &str| {
// ...
let expr_ref = ast.add_expr(/* ... */);
// ...
Ok((input, expr_ref))
}
}
This works, as long as I don't use branching combinators, since, you might have guessed it already, there would be the need for multiple mutable borrows of ast
! E.g.
fn factor(ast: &mut AST) -> impl FnMut(&str) -> IResult<&str, ExprRef> {
move |input: &str| {
alt((
literal_int(ast),
parens(ast, term) // cannot borrow `ast` as mutable again
))(input)
}
}
I try to achieve parsing into this sort of AST
with nom
, so I'm open for any suggestions in the right direction, even if this means I have to go a different route with the parsers. But it seems that some state is involved here anyway.