From 860eb6dfa4c67acea4dd55b1c14692f53983d641 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 11 Feb 2025 11:19:56 +0100 Subject: [PATCH] interpreter in rust --- rust/rox/src/interpreter.rs | 138 ++++++++++++++++++++++++++++++++++++ rust/rox/src/lib.rs | 25 ++++--- 2 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 rust/rox/src/interpreter.rs diff --git a/rust/rox/src/interpreter.rs b/rust/rox/src/interpreter.rs new file mode 100644 index 0000000..85f151f --- /dev/null +++ b/rust/rox/src/interpreter.rs @@ -0,0 +1,138 @@ +use thiserror::Error; + +use crate::{ + expression::Expression, + token::{Literal, Token, TokenType}, +}; + +#[derive(Error, Debug)] +pub enum InterpreterError { + #[error("line {0}: MINUS unary expression expects a number on the right")] + UnaryExpressionNotANumber(usize), + #[error("line {0}: unknown unary operator: {1}")] + UnaryOperatorUnknown(usize, String), + #[error("line {0}: unknown binary operator: {1}")] + BinaryOperatorUnknown(usize, String), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + String(String), + Number(f64), + Boolean(bool), + Nil, +} + +impl Value { + fn is_truthy(&self) -> bool { + match self { + Value::Nil => false, + Value::Boolean(b) => *b, + _ => true, + } + } +} + +impl From for Value { + fn from(value: Literal) -> Self { + match value { + Literal::String(x) => Value::String(x), + Literal::Number(x) => Value::Number(x), + Literal::Boolean(x) => Value::Boolean(x), + Literal::Nil => Value::Nil, + } + } +} + +/// Try to evaluate an expression and return its result. +pub fn evaluate(expression: Expression) -> Result { + match expression { + Expression::Literal { value } => literal(value), + Expression::Grouping { expression } => grouping(*expression), + Expression::Unary { + operator: op, + right, + } => unary(op, *right), + Expression::Binary { + left, + operator, + right, + } => binary(*left, operator, *right), + } +} + +/// Convert the literal value into a Value. +fn literal(literal: Literal) -> Result { + Ok(literal.into()) +} + +/// Evaluate the inner expression. +fn grouping(inner: Expression) -> Result { + evaluate(inner) +} + +/// Evaluate the expression on the right and use its result when evaluating the unary operator. +fn unary(op: Token, right: Expression) -> Result { + let right = evaluate(right)?; + + match op.token_type { + TokenType::Minus => { + if let Value::Number(val) = right { + Ok(Value::Number(-val)) + } else { + Err(InterpreterError::UnaryExpressionNotANumber(op.line)) + } + } + TokenType::Bang => Ok(Value::Boolean(!right.is_truthy())), + _ => Err(InterpreterError::UnaryOperatorUnknown(op.line, op.lexeme)), + } +} + +/// Calculate number operations. +fn number_op(left: f64, op: TokenType, right: f64) -> f64 { + match op { + TokenType::Minus => left - right, + TokenType::Plus => left + right, + TokenType::Slash => left / right, + TokenType::Star => left * right, + _ => unreachable!(), + } +} + +/// Calculate boolean operations. +fn boolean_op(left: f64, op: TokenType, right: f64) -> bool { + match op { + TokenType::Greater => left > right, + TokenType::GreaterEqual => left >= right, + TokenType::Less => left < right, + TokenType::LessEqual => left <= right, + _ => unreachable!(), + } +} + +/// Evaluate the left and right expressions (in that order) and then combine them with the +/// specified operator. +fn binary(left: Expression, op: Token, right: Expression) -> Result { + let left = evaluate(left)?; + let right = evaluate(right)?; + + match op.token_type { + TokenType::Minus | TokenType::Slash | TokenType::Star | TokenType::Plus => { + if let (Value::Number(left), Value::Number(right)) = (left, right) { + Ok(Value::Number(number_op(left, op.token_type, right))) + } else { + todo!() + } + } + TokenType::Greater | TokenType::GreaterEqual | TokenType::Less | TokenType::LessEqual => { + if let (Value::Number(left), Value::Number(right)) = (left, right) { + Ok(Value::Boolean(boolean_op(left, op.token_type, right))) + } else { + todo!() + } + } + TokenType::BangEqual => Ok(Value::Boolean(left != right)), + TokenType::EqualEqual => Ok(Value::Boolean(left == right)), + _ => Err(InterpreterError::BinaryOperatorUnknown(op.line, op.lexeme)), + } +} diff --git a/rust/rox/src/lib.rs b/rust/rox/src/lib.rs index 975ba85..d185fb0 100644 --- a/rust/rox/src/lib.rs +++ b/rust/rox/src/lib.rs @@ -11,6 +11,7 @@ use tracing::{error, info}; pub mod cli; pub mod expression; +pub mod interpreter; pub mod keywords; pub mod parser; pub mod scanner; @@ -30,11 +31,7 @@ pub mod tokenizer { /// Read the source code in a file and scan it to tokens. pub fn compile(source: &Path) -> Result<(), io::Error> { let input = fs::read_to_string(source)?; - let tokens = scanner::tokenize(&input); - match parser::generate_ast(tokens) { - Ok(ast) => info!("{ast:?}"), - Err(e) => error!("{e}"), - } + run(&input); Ok(()) } @@ -51,10 +48,18 @@ pub fn repl() { Err(e) => error!("{}", e), } let input = input.trim().to_string(); - let tokens = scanner::tokenize(&input); - match parser::generate_ast(tokens) { - Ok(ast) => info!("{ast:?}"), - Err(e) => error!("{e}"), - } + run(&input); + } +} + +/// Evaluate a Lox input string and print errors or output. +fn run(input: &str) { + let tokens = scanner::tokenize(input); + match parser::generate_ast(tokens) { + Ok(ast) => match interpreter::evaluate(ast) { + Ok(value) => info!("{value:?}"), + Err(e) => error!("{e}"), + }, + Err(e) => error!("{e}"), } }