use crate::tokens::Token; /* --------- * * AST nodes * * --------- */ /// The AST node for the program #[derive(Default, Debug, Clone)] pub struct ProgramNode(pub Vec); /// An AST node that represents a statement. #[derive(Debug, Clone)] pub enum StmtNode { /// A variable declaration VarDecl(Token, Option), /// An single expression Expression(ExprNode), /// The print statement Print(ExprNode), /// A block containing multiple statements. Block(Vec), /// A conditional statement. IfStmt { condition: ExprNode, then_branch: Box, else_branch: Option>, }, /// Loop statement. LoopStmt { label: Option, condition: ExprNode, body: Box, after_body: Option>, }, /// Break or continue statement. LoopControlStmt { is_break: bool, loop_name: Option, }, } /// An AST node that represents an expression. #[derive(Debug, Clone)] pub enum ExprNode { /// Assignment to a variable. Assignment { name: Token, value: Box }, /// Logical binary expression. Logical { left: Box, operator: Token, right: Box, }, /// Binary expression. Binary { left: Box, operator: Token, right: Box, }, /// Unary expression. Unary { operator: Token, right: Box, }, /// Grouping expression, containing a sub-expression. Grouping { expression: Box }, /// A litteral value, represented by the corresponding token. Litteral { value: Token }, /// A reference to a variable. Variable { name: Token }, /// A function call. Call { /// Expression that corresponds to the callable. callee: Box, /// Right parenthesis that closes the list of arguments. Used to /// report errors. right_paren: Token, /// The list of function arguments. arguments: Vec, }, } /* -------------------------------- * * Dumper trait and implementations * * -------------------------------- */ /// This trait should be implemented by nodes to allow AST dumps. pub trait AstDumper { /// Dump the node as a string. fn dump(&self) -> String; } impl AstDumper for ProgramNode { fn dump(&self) -> String { self.0 .iter() .map(|node| node.dump()) .collect::>() .join(" ") } } impl AstDumper for StmtNode { fn dump(&self) -> String { match self { Self::VarDecl(name, Some(expr)) => format!("( var {} {} )", name.lexeme, expr.dump()), Self::VarDecl(name, None) => format!("( var {} nil )", name.lexeme), Self::Expression(expr) => expr.dump(), Self::Print(expr) => format!("(print {})", expr.dump()), Self::Block(stmts) => format!( "( {} )", stmts .iter() .map(|s| s.dump()) .collect::>() .join(" ") ), Self::IfStmt { condition, then_branch, else_branch, } => match else_branch { None => format!("( if {} {} () )", condition.dump(), then_branch.dump()), Some(stmt) => format!( "( if {} {} {} )", condition.dump(), then_branch.dump(), stmt.dump() ), }, Self::LoopStmt { label, condition, body, after_body, } => { let ltxt = if let Some(label) = label { format!("@{} ", label.lexeme) } else { "".to_string() }; let abtxt = if let Some(after_body) = after_body { format!("{} ", after_body.dump()) } else { "".to_string() }; format!( "( {}loop {} {} {})", ltxt, condition.dump(), body.dump(), abtxt ) } Self::LoopControlStmt { is_break, loop_name, } => { let stmt = if *is_break { "break" } else { "continue" }; match loop_name { Some(name) => format!("( {} {} )", stmt, name.lexeme), None => format!("( {} )", stmt), } } } } } impl AstDumper for ExprNode { fn dump(&self) -> String { match self { Self::Assignment { name, value } => format!("( = {} {} )", name.lexeme, value.dump()), Self::Logical { left, operator, right, } => format!("( {} {} {} )", operator.lexeme, left.dump(), right.dump()), Self::Binary { left, operator, right, } => format!("( {} {} {} )", operator.lexeme, left.dump(), right.dump()), Self::Unary { operator, right } => format!("( {} {} )", operator.lexeme, right.dump()), Self::Grouping { expression } => format!("( {} )", expression.dump()), Self::Variable { name } => name.lexeme.clone(), Self::Litteral { value } => { if value.is_litteral() { value.lexeme.clone() } else { panic!("Unexpected token type for token {:#?}", value) } } ExprNode::Call { callee, right_paren: _, arguments, } => { let callee = callee.dump(); if arguments.len() > 0 { format!( "( call {} {} )", callee, arguments .iter() .map(|arg| arg.dump()) .collect::>() .join(" ") ) } else { format!("( call {} )", callee) } } } } }