statements in rust

This commit is contained in:
Sebastian Hugentobler 2025-02-12 10:30:51 +01:00
parent 15b331f447
commit a629ddca05
6 changed files with 434 additions and 194 deletions

View File

@ -0,0 +1,66 @@
use std::collections::HashMap;
use thiserror::Error;
use crate::{token::Token, value::Value};
#[derive(Error, Debug)]
pub enum EnvironmentError {
#[error("line {0}: undefined variable: {1}")]
UndefinedVariable(usize, String),
}
/// Environment mapping variable names to their respective values. Can have an optional enclosing
/// environment. The outermost (global) environment has no enclosing one.
#[derive(Default, Debug, Clone)]
pub struct Environment {
values: HashMap<String, Value>,
enclosing: Option<Box<Environment>>,
}
impl Environment {
pub fn with_enclosing(enclosing: Environment) -> Self {
Self {
values: HashMap::default(),
enclosing: Some(Box::new(enclosing)),
}
}
/// Define a new variable binding with a value.
///
/// Overwrite any binding of the same name.
pub fn define(&mut self, name: String, value: Value) {
self.values.insert(name, value);
}
/// Assign a new value to an existing (defined) variable. Error if there is no such variable in
/// this environment or any of the enclosing ones.
pub fn assign(&mut self, token: &Token, value: Value) -> Result<(), EnvironmentError> {
if self.values.contains_key(token.lexeme.as_str()) {
self.values.insert(token.lexeme.clone(), value);
Ok(())
} else if let Some(enclosing) = &mut self.enclosing {
enclosing.assign(token, value)
} else {
Err(EnvironmentError::UndefinedVariable(
token.line,
token.lexeme.clone(),
))
}
}
/// Get the value of an existing (defined) variable. Error if there is no such variable in
/// this environment or any of the enclosing ones.
pub fn get(&self, token: &Token) -> Result<Value, EnvironmentError> {
if let Some(v) = self.values.get(token.lexeme.as_str()) {
Ok(v.clone())
} else if let Some(enclosing) = &self.enclosing {
enclosing.get(token)
} else {
Err(EnvironmentError::UndefinedVariable(
token.line,
token.lexeme.clone(),
))
}
}
}

View File

@ -1,7 +1,12 @@
use crate::token::{self, Token}; use crate::token::{self, Token};
/// Enumeration of all types of expressions.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Expression { pub enum Expression {
Assign {
name: Token,
value: Box<Expression>,
},
Binary { Binary {
left: Box<Expression>, left: Box<Expression>,
operator: Token, operator: Token,
@ -17,4 +22,7 @@ pub enum Expression {
operator: Token, operator: Token,
right: Box<Expression>, right: Box<Expression>,
}, },
Variable {
name: Token,
},
} }

View File

@ -1,51 +1,154 @@
use thiserror::Error; use thiserror::Error;
use tracing::error;
use crate::{ use crate::{
environment::{Environment, EnvironmentError},
expression::Expression, expression::Expression,
statement::Statement,
token::{Literal, Token, TokenType}, token::{Literal, Token, TokenType},
value::Value, value::Value,
}; };
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum InterpreterError { pub enum InterpreterError {
#[error("line {0}: MINUS unary expression expects a number on the right")] #[error("[line {0}] MINUS unary expression expects a number on the right")]
UnaryExpressionNotANumber(usize), UnaryExpressionNotANumber(usize),
#[error("line {0}: unknown unary operator: {1}")] #[error("[line {0}] unknown unary operator: {1}")]
UnaryOperatorUnknown(usize, String), UnaryOperatorUnknown(usize, String),
#[error("line {0}: unknown binary operator: {1}")] #[error("[line {0}] unknown binary operator: {1}")]
BinaryOperatorUnknown(usize, String), BinaryOperatorUnknown(usize, String),
#[error("[line {0}] left or right is not a number.")]
BinaryExpressionNeedsNumber(usize),
#[error("[line {0}] left or right is neither a number nor string.")]
BinaryExpressionNeedsNumberOrString(usize),
#[error("{0}")]
UndefinedVariable(EnvironmentError),
} }
/// Interpreter for the Lox language.
#[derive(Default, Debug)]
pub struct Interpreter {
environment: Environment,
}
impl Interpreter {
/// Try to evaluate an expression and return its result. /// Try to evaluate an expression and return its result.
pub fn evaluate(expression: Expression) -> Result<Value, InterpreterError> { pub fn run(&mut self, statements: Vec<Statement>) -> Result<(), InterpreterError> {
for stmt in statements {
match self.execute(stmt) {
Ok(_) => {}
Err(e) => error!("{e}"),
};
}
Ok(())
}
///Execute a statement.
fn execute(&mut self, statement: Statement) -> Result<(), InterpreterError> {
match statement {
Statement::Block(statements) => {
let sub_env = Environment::with_enclosing(self.environment.clone());
self.block(statements, sub_env)?
}
Statement::Print(expression) => self.print_statement(expression)?,
Statement::Expression(expression) => {
self.evaluate(expression)?;
}
Statement::Var { name, initializer } => self.var_statement(name, *initializer)?,
};
Ok(())
}
/// Execute all statements within a block, using a new environment (with the old one as the
/// enclosing one).
fn block(
&mut self,
statements: Vec<Statement>,
environment: Environment,
) -> Result<(), InterpreterError> {
let prev_env = &self.environment.clone();
self.environment = environment;
for stmt in statements {
if let Err(e) = self.execute(stmt) {
error!("{e}");
}
}
self.environment = prev_env.clone();
Ok(())
}
/// Evaluate an expression and return its value.
fn evaluate(&mut self, expression: Expression) -> Result<Value, InterpreterError> {
match expression { match expression {
Expression::Literal { value } => literal(value), Expression::Literal { value } => self.literal(value),
Expression::Grouping { expression } => grouping(*expression), Expression::Grouping { expression } => self.grouping(*expression),
Expression::Unary { Expression::Unary {
operator: op, operator: op,
right, right,
} => unary(op, *right), } => self.unary(op, *right),
Expression::Binary { Expression::Binary {
left, left,
operator, operator,
right, right,
} => binary(*left, operator, *right), } => self.binary(*left, operator, *right),
Expression::Variable { name } => self.var_expression(&name),
Expression::Assign { name, value } => self.assign(&name, *value),
} }
} }
/// Evaluate an expression and print its value to stdout.
fn print_statement(&mut self, expression: Expression) -> Result<(), InterpreterError> {
let value = self.evaluate(expression)?;
println!("{value}");
Ok(())
}
/// Initialize a variable with an initializer expression or nil.
fn var_statement(
&mut self,
name: Token,
initializer: Option<Expression>,
) -> Result<(), InterpreterError> {
let value = if let Some(initializer) = initializer {
self.evaluate(initializer)
} else {
Ok(Value::Nil)
}?;
self.environment.define(name.lexeme, value);
Ok(())
}
/// Assign the value of an expression to a variable.
fn assign(&mut self, name: &Token, value: Expression) -> Result<Value, InterpreterError> {
let value = self.evaluate(value)?;
self.environment
.assign(name, value.clone())
.map_err(InterpreterError::UndefinedVariable)?;
Ok(value)
}
/// Convert the literal value into a Value. /// Convert the literal value into a Value.
fn literal(literal: Literal) -> Result<Value, InterpreterError> { fn literal(&self, literal: Literal) -> Result<Value, InterpreterError> {
Ok(literal.into()) Ok(literal.into())
} }
/// Evaluate the inner expression. /// Evaluate the inner expression.
fn grouping(inner: Expression) -> Result<Value, InterpreterError> { fn grouping(&mut self, inner: Expression) -> Result<Value, InterpreterError> {
evaluate(inner) self.evaluate(inner)
} }
/// Evaluate the expression on the right and use its result when evaluating the unary operator. /// Evaluate the expression on the right and use its result when evaluating the unary operator.
fn unary(op: Token, right: Expression) -> Result<Value, InterpreterError> { fn unary(&mut self, op: Token, right: Expression) -> Result<Value, InterpreterError> {
let right = evaluate(right)?; let right = self.evaluate(right)?;
match op.token_type { match op.token_type {
TokenType::Minus => { TokenType::Minus => {
@ -60,8 +163,15 @@ fn unary(op: Token, right: Expression) -> Result<Value, InterpreterError> {
} }
} }
/// Get the value of a variable.
fn var_expression(&mut self, name: &Token) -> Result<Value, InterpreterError> {
self.environment
.get(name)
.map_err(InterpreterError::UndefinedVariable)
}
/// Calculate number operations. /// Calculate number operations.
fn number_op(left: f64, op: TokenType, right: f64) -> f64 { fn number_op(&self, left: f64, op: TokenType, right: f64) -> f64 {
match op { match op {
TokenType::Minus => left - right, TokenType::Minus => left - right,
TokenType::Plus => left + right, TokenType::Plus => left + right,
@ -72,7 +182,7 @@ fn number_op(left: f64, op: TokenType, right: f64) -> f64 {
} }
/// Calculate boolean operations. /// Calculate boolean operations.
fn boolean_op(left: f64, op: TokenType, right: f64) -> bool { fn boolean_op(&self, left: f64, op: TokenType, right: f64) -> bool {
match op { match op {
TokenType::Greater => left > right, TokenType::Greater => left > right,
TokenType::GreaterEqual => left >= right, TokenType::GreaterEqual => left >= right,
@ -84,23 +194,35 @@ fn boolean_op(left: f64, op: TokenType, right: f64) -> bool {
/// Evaluate the left and right expressions (in that order) and then combine them with the /// Evaluate the left and right expressions (in that order) and then combine them with the
/// specified operator. /// specified operator.
fn binary(left: Expression, op: Token, right: Expression) -> Result<Value, InterpreterError> { fn binary(
let left = evaluate(left)?; &mut self,
let right = evaluate(right)?; left: Expression,
op: Token,
right: Expression,
) -> Result<Value, InterpreterError> {
let left = self.evaluate(left)?;
let right = self.evaluate(right)?;
match op.token_type { match op.token_type {
TokenType::Minus | TokenType::Slash | TokenType::Star | TokenType::Plus => { TokenType::Minus | TokenType::Slash | TokenType::Star | TokenType::Plus => {
if let (Value::Number(left), Value::Number(right)) = (left, right) { if let (Value::Number(left), Value::Number(right)) = (left.clone(), right.clone()) {
Ok(Value::Number(number_op(left, op.token_type, right))) Ok(Value::Number(self.number_op(left, op.token_type, right)))
} else if let (Value::String(left), Value::String(right)) = (left, right) {
Ok(Value::String(format!("{}{}", left.clone(), right.clone())))
} else { } else {
todo!() Err(InterpreterError::BinaryExpressionNeedsNumberOrString(
op.line,
))
} }
} }
TokenType::Greater | TokenType::GreaterEqual | TokenType::Less | TokenType::LessEqual => { TokenType::Greater
| TokenType::GreaterEqual
| TokenType::Less
| TokenType::LessEqual => {
if let (Value::Number(left), Value::Number(right)) = (left, right) { if let (Value::Number(left), Value::Number(right)) = (left, right) {
Ok(Value::Boolean(boolean_op(left, op.token_type, right))) Ok(Value::Boolean(self.boolean_op(left, op.token_type, right)))
} else { } else {
todo!() Err(InterpreterError::BinaryExpressionNeedsNumber(op.line))
} }
} }
TokenType::BangEqual => Ok(Value::Boolean(left != right)), TokenType::BangEqual => Ok(Value::Boolean(left != right)),
@ -108,3 +230,4 @@ fn binary(left: Expression, op: Token, right: Expression) -> Result<Value, Inter
_ => Err(InterpreterError::BinaryOperatorUnknown(op.line, op.lexeme)), _ => Err(InterpreterError::BinaryOperatorUnknown(op.line, op.lexeme)),
} }
} }
}

View File

@ -7,14 +7,17 @@ use std::{
path::Path, path::Path,
}; };
use tracing::{error, info}; use interpreter::Interpreter;
use tracing::error;
pub mod cli; pub mod cli;
pub mod environment;
pub mod expression; pub mod expression;
pub mod interpreter; pub mod interpreter;
pub mod keywords; pub mod keywords;
pub mod parser; pub mod parser;
pub mod scanner; pub mod scanner;
pub mod statement;
pub mod token; pub mod token;
pub mod tokenizer { pub mod tokenizer {
pub mod comment; pub mod comment;
@ -32,13 +35,16 @@ pub mod value;
/// Read the source code in a file and scan it to tokens. /// Read the source code in a file and scan it to tokens.
pub fn compile(source: &Path) -> Result<(), io::Error> { pub fn compile(source: &Path) -> Result<(), io::Error> {
let input = fs::read_to_string(source)?; let input = fs::read_to_string(source)?;
run(&input); let mut interpreter = Interpreter::default();
run(&input, &mut interpreter);
Ok(()) Ok(())
} }
/// Run a Lox REPL until SIGINT. /// Run a Lox REPL until SIGINT.
pub fn repl() { pub fn repl() {
let mut interpreter = Interpreter::default();
loop { loop {
print!("> "); print!("> ");
let _ = io::stdout().flush(); let _ = io::stdout().flush();
@ -49,16 +55,16 @@ pub fn repl() {
Err(e) => error!("{}", e), Err(e) => error!("{}", e),
} }
let input = input.trim().to_string(); let input = input.trim().to_string();
run(&input); run(&input, &mut interpreter);
} }
} }
/// Evaluate a Lox input string and print errors or output. /// Evaluate a Lox input string and print errors or output.
fn run(input: &str) { fn run(input: &str, interpreter: &mut Interpreter) {
let tokens = scanner::tokenize(input); let tokens = scanner::tokenize(input);
match parser::generate_ast(tokens) { match parser::ast(tokens) {
Ok(ast) => match interpreter::evaluate(ast) { Ok(ast) => match interpreter.run(ast) {
Ok(value) => println!("{value}"), Ok(_) => {}
Err(e) => error!("{e}"), Err(e) => error!("{e}"),
}, },
Err(e) => error!("{e}"), Err(e) => error!("{e}"),

View File

@ -3,6 +3,7 @@ use tracing::error;
use crate::{ use crate::{
expression::Expression, expression::Expression,
statement::Statement,
token::{ token::{
self, Token, self, Token,
TokenType::{self, *}, TokenType::{self, *},
@ -13,14 +14,24 @@ use crate::{
pub enum ParserError { pub enum ParserError {
#[error("empty token stream")] #[error("empty token stream")]
NoTokens, NoTokens,
#[error("line {0}: expected expression")] #[error("[line {0}] expected expression")]
ExpressionExpected(usize), ExpressionExpected(usize),
#[error("line {0}: expected ')' after expression.")] #[error("[line {0}] expected ')' after expression.")]
ParenAfterExpression(usize), ParenAfterExpression(usize),
#[error("Out of bounds access at index {0}.")] #[error("[Out of bounds access at index {0}.")]
OutOfBoundsAccess(usize), OutOfBoundsAccess(usize),
#[error("line {0}: literal expected.")] #[error("[line {0}] literal expected.")]
LiteralExpected(usize), LiteralExpected(usize),
#[error("[line {0}] expected ';' after value.")]
SemicolonAfterValueExpected(usize),
#[error("[line {0}] expected ';' after expression.")]
SemicolonAfterExpressionExpected(usize),
#[error("[line {0}] expected variable name.")]
VariableNameExpected(usize),
#[error("[line {0}] invalid assignment target.")]
InvalidAssignmentTarget(usize),
#[error("[line {0}] expected '}}' after block.")]
RightBraceAfterBlockExpected(usize),
} }
/// Parse the Lox language. /// Parse the Lox language.
@ -43,6 +54,23 @@ impl Parser {
}) })
} }
/// Parse all tokens to a list of statements for execution.
fn run(&mut self) -> Result<Vec<Statement>, ParserError> {
let mut statements = Vec::new();
while !self.is_at_end() {
match self.declaration() {
Ok(x) => statements.push(x),
Err(e) => {
error!("{e}");
self.synchronize();
}
}
}
Ok(statements)
}
/// Check if any of the provided types match the type of the current token. /// Check if any of the provided types match the type of the current token.
/// ///
/// If so, advance the current token. /// If so, advance the current token.
@ -117,7 +145,99 @@ impl Parser {
/// expression -> equality ; /// expression -> equality ;
fn expression(&mut self) -> Result<Expression, ParserError> { fn expression(&mut self) -> Result<Expression, ParserError> {
self.equality() self.assignment()
}
fn declaration(&mut self) -> Result<Statement, ParserError> {
if self.matches(&[Var]) {
self.var_declaration()
} else {
self.statement()
}
}
fn statement(&mut self) -> Result<Statement, ParserError> {
if self.matches(&[Print]) {
self.print_statement()
} else if self.matches(&[LeftBrace]) {
Ok(Statement::Block(self.block()?))
} else {
self.expression_statement()
}
}
fn print_statement(&mut self) -> Result<Statement, ParserError> {
let value = self.expression()?;
let line = self.current_token.line;
self.consume(&Semicolon)
.ok_or(ParserError::SemicolonAfterValueExpected(line))?;
Ok(Statement::Print(value))
}
fn var_declaration(&mut self) -> Result<Statement, ParserError> {
let line = self.current_token.line;
let name = self
.consume(&Identifier)
.ok_or(ParserError::VariableNameExpected(line))?
.clone();
let initializer = if self.matches(&[Equal]) {
Some(self.expression()?)
} else {
None
};
self.consume(&Semicolon)
.ok_or(ParserError::SemicolonAfterExpressionExpected(line))?;
Ok(Statement::Var {
name,
initializer: Box::new(initializer),
})
}
fn expression_statement(&mut self) -> Result<Statement, ParserError> {
let expr = self.expression()?;
let line = self.current_token.line;
self.consume(&Semicolon)
.ok_or(ParserError::SemicolonAfterExpressionExpected(line))?;
Ok(Statement::Expression(expr))
}
fn block(&mut self) -> Result<Vec<Statement>, ParserError> {
let mut statements = Vec::new();
while !self.check(&RightBrace) && !self.is_at_end() {
statements.push(self.declaration()?);
}
let line = self.previous()?.line;
self.consume(&RightBrace)
.ok_or(ParserError::RightBraceAfterBlockExpected(line))?;
Ok(statements)
}
fn assignment(&mut self) -> Result<Expression, ParserError> {
let expr = self.equality()?;
if self.matches(&[Equal]) {
let equals = self.previous()?.clone();
let value = self.assignment()?;
if let Expression::Variable { name } = expr {
Ok(Expression::Assign {
name,
value: Box::new(value),
})
} else {
Err(ParserError::InvalidAssignmentTarget(equals.line))
}
} else {
Ok(expr)
}
} }
/// equality -> comparison ( ( "!=" | "==" ) comparison )* ; /// equality -> comparison ( ( "!=" | "==" ) comparison )* ;
@ -169,6 +289,9 @@ impl Parser {
Ok(Expression::Literal { Ok(Expression::Literal {
value: token::Literal::Nil, value: token::Literal::Nil,
}) })
} else if self.matches(&[Identifier]) {
let prev = self.previous()?.clone();
Ok(Expression::Variable { name: prev })
} else if self.matches(&[Number, String]) { } else if self.matches(&[Number, String]) {
let prev = self.previous()?; let prev = self.previous()?;
let value = prev let value = prev
@ -205,106 +328,7 @@ impl Parser {
} }
/// Try to parse the provided tokens into an AST. /// Try to parse the provided tokens into an AST.
pub fn generate_ast(tokens: Vec<Token>) -> Result<Expression, ParserError> { pub fn ast(tokens: Vec<Token>) -> Result<Vec<Statement>, ParserError> {
let mut parser = Parser::new(tokens)?; let mut parser = Parser::new(tokens)?;
parser.expression() parser.run()
}
#[cfg(test)]
mod tests {
use crate::{
expression::Expression,
token::{Literal, Token, TokenType},
};
use super::generate_ast;
#[test]
fn simple_expression() {
let ast = generate_ast(vec![
Token {
token_type: TokenType::Number,
lexeme: "3".into(),
literal: Some(Literal::Number(3.0)),
line: 1,
},
Token {
token_type: TokenType::Star,
lexeme: "*".into(),
literal: None,
line: 1,
},
Token {
token_type: TokenType::Number,
lexeme: "4".into(),
literal: Some(Literal::Number(4.0)),
line: 1,
},
Token {
token_type: TokenType::Plus,
lexeme: "+".into(),
literal: None,
line: 1,
},
Token {
token_type: TokenType::Number,
lexeme: "2".into(),
literal: Some(Literal::Number(2.0)),
line: 1,
},
Token {
token_type: TokenType::Star,
lexeme: "*".into(),
literal: None,
line: 1,
},
Token {
token_type: TokenType::Number,
lexeme: "6".into(),
literal: Some(Literal::Number(6.0)),
line: 1,
},
])
.unwrap();
assert_eq!(
ast,
Expression::Binary {
left: Box::new(Expression::Binary {
left: Box::new(Expression::Literal {
value: Literal::Number(3.0)
}),
operator: Token {
token_type: TokenType::Star,
lexeme: "*".into(),
literal: None,
line: 1
},
right: Box::new(Expression::Literal {
value: Literal::Number(4.0)
})
}),
operator: Token {
token_type: TokenType::Plus,
lexeme: "+".into(),
literal: None,
line: 1
},
right: Box::new(Expression::Binary {
left: Box::new(Expression::Literal {
value: Literal::Number(2.0)
}),
operator: Token {
token_type: TokenType::Star,
lexeme: "*".into(),
literal: None,
line: 1
},
right: Box::new(Expression::Literal {
value: Literal::Number(6.0)
})
})
}
)
}
} }

13
rust/rox/src/statement.rs Normal file
View File

@ -0,0 +1,13 @@
use crate::{expression::Expression, token::Token};
/// Enumeration of all types of statements.
#[derive(Debug, Clone, PartialEq)]
pub enum Statement {
Block(Vec<Statement>),
Print(Expression),
Expression(Expression),
Var {
name: Token,
initializer: Box<Option<Expression>>,
},
}