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)), } }