interpreter in rust
This commit is contained in:
parent
ac2621e431
commit
860eb6dfa4
138
rust/rox/src/interpreter.rs
Normal file
138
rust/rox/src/interpreter.rs
Normal 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)),
|
||||
}
|
||||
}
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user