interpreter in rust

This commit is contained in:
Sebastian Hugentobler 2025-02-11 11:19:56 +01:00
parent ac2621e431
commit 860eb6dfa4
2 changed files with 153 additions and 10 deletions

138
rust/rox/src/interpreter.rs Normal file
View File

@ -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<Literal> 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<Value, InterpreterError> {
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<Value, InterpreterError> {
Ok(literal.into())
}
/// Evaluate the inner expression.
fn grouping(inner: Expression) -> Result<Value, InterpreterError> {
evaluate(inner)
}
/// Evaluate the expression on the right and use its result when evaluating the unary operator.
fn unary(op: Token, right: Expression) -> Result<Value, InterpreterError> {
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<Value, InterpreterError> {
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)),
}
}

View File

@ -11,6 +11,7 @@ use tracing::{error, info};
pub mod cli; pub mod cli;
pub mod expression; pub mod expression;
pub mod interpreter;
pub mod keywords; pub mod keywords;
pub mod parser; pub mod parser;
pub mod scanner; pub mod scanner;
@ -30,11 +31,7 @@ pub mod tokenizer {
/// 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)?;
let tokens = scanner::tokenize(&input); run(&input);
match parser::generate_ast(tokens) {
Ok(ast) => info!("{ast:?}"),
Err(e) => error!("{e}"),
}
Ok(()) Ok(())
} }
@ -51,10 +48,18 @@ pub fn repl() {
Err(e) => error!("{}", e), Err(e) => error!("{}", e),
} }
let input = input.trim().to_string(); let input = input.trim().to_string();
let tokens = scanner::tokenize(&input); run(&input);
match parser::generate_ast(tokens) { }
Ok(ast) => info!("{ast:?}"), }
Err(e) => error!("{e}"),
} /// 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}"),
} }
} }