AST - Replaced dumper

* Rewrote as a kind of "pretty" printer
  * It isn't actually pretty but it does restore Lox code from the AST.
This commit is contained in:
Emmanuel BENOîT 2023-01-08 15:25:27 +01:00
parent 728c7a3857
commit 1c5efe7e62
3 changed files with 332 additions and 204 deletions

View file

@ -147,205 +147,3 @@ pub enum ExprNode {
/// A get expression.
Get(GetExpr),
}
/* -------------------------------- *
* 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::<Vec<String>>()
.join(" ")
}
}
/// Function formatting macro.
macro_rules! fmt_fun_decl {
($fs:literal, $fd:expr) => {
format!(
$fs,
$fd.name.lexeme,
$fd.params
.iter()
.map(|token| &token.lexeme as &str)
.collect::<Vec<&str>>()
.join(" "),
$fd.body
.iter()
.map(|stmt| stmt.dump())
.collect::<Vec<String>>()
.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::FunDecl(fun_decl) => fmt_fun_decl!("( fun {} ({}) {} )", fun_decl),
Self::ClassDecl(decl) => format!(
"( class {} {} )",
decl.name.lexeme,
decl.methods
.iter()
.map(|fun_decl| fmt_fun_decl!("( {} ({}) {} )", fun_decl))
.collect::<Vec<String>>()
.join(" "),
),
Self::Expression(expr) => expr.dump(),
Self::Print(expr) => format!("(print {})", expr.dump()),
Self::Block(stmts) => format!(
"( {} )",
stmts
.iter()
.map(|s| s.dump())
.collect::<Vec<String>>()
.join(" ")
),
Self::If {
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::Loop {
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::LoopControl {
is_break,
loop_name,
} => {
let stmt = if *is_break { "break" } else { "continue" };
match loop_name {
Some(name) => format!("( {} {} )", stmt, name.lexeme),
None => format!("( {} )", stmt),
}
}
Self::Return { token: _, value } => match value {
Some(expr) => format!("( return {} )", expr.dump()),
None => "( return )".to_owned(),
},
}
}
}
impl AstDumper for ExprNode {
fn dump(&self) -> String {
match self {
Self::Assignment { name, value, id } => {
format!("{{#{}}}( = {} {} )", id, 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, id } => format!("{{#{}}}{}", id, name.lexeme),
Self::Litteral { value } => {
if value.is_litteral() {
value.lexeme.clone()
} else {
panic!("Unexpected token type for token {:#?}", value)
}
}
ExprNode::Lambda { params, body } => {
format!(
"( fun ({}) {} )",
params
.iter()
.map(|token| &token.lexeme as &str)
.collect::<Vec<&str>>()
.join(" "),
body.iter()
.map(|stmt| stmt.dump())
.collect::<Vec<String>>()
.join(" ")
)
}
ExprNode::Call {
callee,
right_paren: _,
arguments,
} => {
let callee = callee.dump();
if arguments.is_empty() {
format!("( call {} )", callee)
} else {
format!(
"( call {} {} )",
callee,
arguments
.iter()
.map(|arg| arg.dump())
.collect::<Vec<String>>()
.join(" ")
)
}
}
ExprNode::Get(get_expr) => {
format!(
"( get {} {} )",
get_expr.instance.dump(),
get_expr.name.lexeme
)
}
}
}
}

329
src/dumper.rs Normal file
View file

@ -0,0 +1,329 @@
use std::fmt::Write;
use crate::{
ast::{ExprNode, ProgramNode, StmtNode},
tokens::Token,
};
/* -------------------------------- *
* Dumper trait and implementations *
* -------------------------------- */
#[allow(dead_code)]
pub fn dump_program(ast: &ProgramNode) -> String {
let mut dumper = Dumper::default();
dump_statement_list(&mut dumper, &ast.0);
dumper
.lines
.iter()
.map(DumperLine::to_string)
.collect::<Vec<String>>()
.join("\n")
}
fn fun_decl_params(params: &[Token]) -> String {
params
.iter()
.map(|token| &token.lexeme as &str)
.collect::<Vec<&str>>()
.join(" ")
}
fn dump_statement_list(dumper: &mut Dumper, statements: &[StmtNode]) {
for stmt in statements {
dump_statement(dumper, stmt);
}
}
fn dump_substatement(dumper: &mut Dumper, statement: &StmtNode) {
let depth_change = match statement {
StmtNode::Block(_) => 0,
_ => 1,
};
dumper.depth += depth_change;
dump_statement(dumper, statement);
dumper.depth -= depth_change;
}
fn dump_statement(dumper: &mut Dumper, stmt: &StmtNode) {
match stmt {
StmtNode::VarDecl(name, Some(expr)) => {
dumper.integrate(
&format!("var {} = ", name.lexeme),
dump_expression(expr),
";",
);
}
StmtNode::VarDecl(name, None) => {
dumper.add_line(format!("var {};", name.lexeme));
}
StmtNode::FunDecl(fun_decl) => {
dumper.add_line(format!(
"fun {} ({}) {{",
fun_decl.name.lexeme,
fun_decl_params(&fun_decl.params),
));
if !fun_decl.body.is_empty() {
dumper.depth += 1;
dump_statement_list(dumper, &fun_decl.body);
dumper.depth -= 1;
dumper.add_line(String::default());
}
dumper.current_line().push_str("}");
}
StmtNode::ClassDecl(decl) => {
dumper.add_line(format!("class {} {{", decl.name.lexeme));
if !decl.methods.is_empty() {
dumper.depth += 1;
for method in decl.methods.iter() {
dumper.add_line(format!(
"{} ({}) {{",
method.name.lexeme,
fun_decl_params(&method.params)
));
dumper.depth += 1;
dump_statement_list(dumper, &method.body);
dumper.depth -= 1;
dumper.add_line("}".to_owned());
}
dumper.depth -= 1;
dumper.add_line(String::default());
}
dumper.current_line().push_str("}");
}
StmtNode::Expression(expr) => {
dumper.integrate("", dump_expression(expr), ";");
}
StmtNode::Print(expr) => {
dumper.integrate("print ", dump_expression(expr), ";");
}
StmtNode::Block(stmts) => {
dumper.add_line("{".to_owned());
dumper.depth += 1;
dump_statement_list(dumper, stmts);
dumper.depth -= 1;
dumper.add_line("}".to_owned());
}
StmtNode::If {
condition,
then_branch,
else_branch,
} => {
dumper.integrate("if (", dump_expression(condition), ")");
dump_substatement(dumper, then_branch);
if let Some(else_branch) = else_branch {
dumper.add_line("else".to_owned());
dump_substatement(dumper, else_branch);
}
}
StmtNode::Loop {
label,
condition,
body,
after_body,
} => {
if let Some(label) = label {
dumper.add_line(format!("@{} ", label.lexeme));
};
dumper.integrate("while (", dump_expression(condition), ")");
if let Some(after_body) = after_body {
let mut statements = match body.as_ref() {
StmtNode::Block(block) => block.clone(),
other => vec![other.clone()],
};
match after_body.as_ref() {
StmtNode::Block(block) => statements.extend(block.clone()),
other => statements.push(other.clone()),
};
if statements.len() == 1 {
dump_substatement(dumper, &statements[0]);
} else {
dump_substatement(dumper, &StmtNode::Block(statements));
}
} else {
dump_substatement(dumper, body);
}
}
StmtNode::LoopControl {
is_break,
loop_name,
} => {
let stmt = if *is_break { "break" } else { "continue" };
dumper.add_line(match loop_name {
Some(name) => format!("{} {};", stmt, name.lexeme),
None => format!("{};", stmt),
});
}
StmtNode::Return { token: _, value } => match value {
Some(expr) => dumper.integrate("return ", dump_expression(expr), ";"),
None => dumper.add_line("return;".to_owned()),
},
}
}
/* ----------- *
* Expressions *
* ----------- */
fn dump_expression(expr: &ExprNode) -> Dumper {
let mut dumper = Dumper::default();
dumper.add_line(String::default());
dump_expr_node(&mut dumper, expr);
dumper
}
fn dump_expr_node(dumper: &mut Dumper, expr: &ExprNode) {
match expr {
ExprNode::Assignment { name, value, id: _ } => {
dumper
.current_line()
.write_fmt(format_args!("{} = ", name.lexeme))
.unwrap();
dump_expr_node(dumper, value);
}
ExprNode::Logical {
left,
operator,
right,
} => {
dump_expr_node(dumper, left);
dumper.current_line().push_str(" ");
dumper.current_line().push_str(&operator.lexeme);
dumper.current_line().push_str(" ");
dump_expr_node(dumper, right);
}
ExprNode::Binary {
left,
operator,
right,
} => {
dump_expr_node(dumper, left);
dumper.current_line().push_str(" ");
dumper.current_line().push_str(&operator.lexeme);
dumper.current_line().push_str(" ");
dump_expr_node(dumper, right);
}
ExprNode::Unary { operator, right } => {
dumper.current_line().push_str(&operator.lexeme);
dump_expr_node(dumper, right);
}
ExprNode::Grouping { expression } => {
dumper.current_line().push_str("(");
dump_expr_node(dumper, expression);
dumper.current_line().push_str(")");
}
ExprNode::Litteral { value } => {
dumper.current_line().push_str(&value.lexeme);
}
ExprNode::Variable { name, id: _ } => {
dumper.current_line().push_str(&name.lexeme);
}
ExprNode::Lambda { params, body } => {
dumper
.current_line()
.write_fmt(format_args!("fun ({}) {{", fun_decl_params(params)))
.unwrap();
if !body.is_empty() {
dumper.depth += 1;
dump_statement_list(dumper, body);
dumper.add_line(String::default());
dumper.depth -= 1;
}
dumper.current_line().push_str("}");
}
ExprNode::Call {
callee,
right_paren: _,
arguments,
} => {
dump_expr_node(dumper, callee);
dumper.current_line().push_str("(");
for (i, argument) in arguments.iter().enumerate() {
dump_expr_node(dumper, argument);
if arguments.len() - 1 > i {
dumper.current_line().push_str(", ");
}
}
dumper.current_line().push_str(")");
}
ExprNode::Get(_) => todo!(),
}
}
/* ------------ *
* Dumper state *
* ------------ */
/// The state of the program dumper.
#[derive(Debug, Default)]
struct Dumper {
depth: usize,
lines: Vec<DumperLine>,
}
/// A line generated while dumping the program.
#[derive(Debug)]
struct DumperLine {
text: String,
depth: usize,
}
// Convert dumped lines to strings.
impl ToString for DumperLine {
fn to_string(&self) -> String {
"\t".repeat(self.depth) + &self.text
}
}
impl Dumper {
fn integrate(&mut self, prefix: &str, other: Dumper, suffix: &str) {
match other.lines.len() {
0 => self.add_line(format!("{}{}", prefix, suffix)),
1 => self.add_line(format!("{}{}{}", prefix, other.lines[0].text, suffix)),
len => {
other
.lines
.into_iter()
.enumerate()
.for_each(|(lnb, mut line)| match lnb {
0 => self.add_line(format!("{}{}", prefix, line.text)),
l if l == len - 1 => self.add_line(format!("{}{}", line.text, suffix)),
_ => {
line.depth += self.depth;
self.lines.push(line);
}
});
}
}
}
fn add_line(&mut self, line: String) {
self.lines.push(DumperLine {
text: line,
depth: self.depth,
})
}
fn current_line(&mut self) -> &mut String {
let line = self.lines.len() - 1;
&mut self.lines[line].text
}
}

View file

@ -1,4 +1,5 @@
mod ast;
mod dumper;
mod errors;
mod interpreter;
mod parser;
@ -13,7 +14,7 @@ use std::{
};
#[cfg(feature = "dump_ast")]
use ast::AstDumper;
use dumper::dump_program;
use errors::{ErrorHandler, SloxResult};
use interpreter::{evaluate, Value};
use parser::Parser;
@ -34,7 +35,7 @@ fn run(source: String) -> SloxResult<()> {
let parser = Parser::new(tokens);
let ast = parser.parse(&mut error_handler)?;
#[cfg(feature = "dump_ast")]
println!("AST generated ! {}", ast.dump());
println!("AST generated ! {}", dump_program(&ast));
let resolved_vars = error_handler.report_or_continue(resolve_variables(&ast))?;
let value = error_handler.report_or_continue(evaluate(&ast, resolved_vars))?;