diff --git a/build.rs b/build.rs index b09b1af..4b235ff 100644 --- a/build.rs +++ b/build.rs @@ -47,6 +47,7 @@ fn do_ast_codegen(mut output: W) { ("Grouping", "expr: Box"), ("Unary", "expr: Box, operator: Token"), ("Literal", "value: LiteralValue"), + ("Variable", "name: Token"), ]); define_ast(&mut output, "Expr", &expr_types).expect("failed to generate ast values"); @@ -59,6 +60,7 @@ fn do_ast_codegen(mut output: W) { let statement_types = BTreeMap::from([ ("Expression", "expression: Expr"), ("Print", "expression: Expr"), + ("Var", "name: Token, initializer: Option"), ]); define_ast(&mut output, "Stmt", &statement_types).expect("failed to generate ast values"); diff --git a/src/eval.rs b/src/eval.rs index 86bef7e..7ae02ed 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Display, Formatter}; +use std::{ + fmt::{self, Display, Formatter}, + string::ParseError, +}; use crate::{ ast::{Expr, ExprVisitor, LiteralValue, Stmt, StmtVisitor}, @@ -6,60 +9,13 @@ use crate::{ ScriptError, }; -#[derive(Debug, Clone, PartialEq)] -pub enum EvaluatedValue { - Number(f64), - String(String), - Boolean(bool), - Nil, -} +use self::{ + environment::Environment, + value::{AssumedTypeError, EvaluatedValue}, +}; -#[derive(thiserror::Error, Debug)] -#[error("{0}")] -pub struct AssumedTypeError(String); - -macro_rules! impl_value_try_from { - ($output_type: ty, $pattern: pat => $val: expr, $err_msg: literal) => { - impl TryFrom for $output_type { - type Error = AssumedTypeError; - - fn try_from(value: EvaluatedValue) -> Result { - match value { - $pattern => Ok($val), - _ => Err(AssumedTypeError($err_msg.to_string())), - } - } - } - }; -} - -impl_value_try_from!( - f64, - EvaluatedValue::Number(n) => n, - "value is not of type number" -); - -impl_value_try_from!( - String, - EvaluatedValue::String(s) => s, - "value is not of type string" -); - -impl_value_try_from!( - bool, - EvaluatedValue::Boolean(b) => b, - "value is not of type boolean" -); - -impl EvaluatedValue { - fn is_truthy(&self) -> bool { - match self { - EvaluatedValue::Nil => false, - EvaluatedValue::Boolean(value) => *value, - _ => true, - } - } -} +mod environment; +mod value; impl From<&LiteralValue> for EvaluatedValue { fn from(value: &LiteralValue) -> Self { @@ -85,17 +41,37 @@ impl Display for EvaluatedValue { } } -struct Interpreter; - -pub fn evaluate(statements: &[Stmt]) -> Result<(), ScriptError> { - for statement in statements { - Interpreter.visit_stmt(statement)?; - } - - Ok(()) +#[derive(Debug, Default)] +pub struct Interpreter { + env: Environment, } -impl ExprVisitor> for Interpreter { +impl Interpreter { + pub fn new() -> Self { + Self::default() + } + + pub fn evaluate(&mut self, statements: &[Stmt]) -> Result<(), ScriptError> { + let mut runner = InterpreterRunner::new(self); + for statement in statements { + runner.visit_stmt(statement)?; + } + + Ok(()) + } +} + +struct InterpreterRunner<'a> { + interpreter: &'a mut Interpreter, +} + +impl<'a> InterpreterRunner<'a> { + pub fn new(interpreter: &'a mut Interpreter) -> Self { + Self { interpreter } + } +} + +impl ExprVisitor> for InterpreterRunner<'_> { fn visit_literal(&mut self, value: &LiteralValue) -> Result { Ok(value.into()) } @@ -192,20 +168,40 @@ impl ExprVisitor> for Interpreter { ), } } + + fn visit_variable(&mut self, name: &Token) -> Result { + self.interpreter + .env + .get(name.lexeme()) + .ok_or_else(|| ScriptError { + message: format!("Undefined variable: {}", name.lexeme()), + location: String::new(), + line: name.line(), + }) + // TODO: AAAAA this cloning sucks + .cloned() + } } -impl StmtVisitor> for Interpreter { +impl StmtVisitor> for InterpreterRunner<'_> { fn visit_print(&mut self, expression: &Expr) -> Result<(), ScriptError> { - let value = >>::visit_expr( - self, expression, - )?; + let value = self.visit_expr(expression)?; println!("{value}"); Ok(()) } fn visit_expression(&mut self, expression: &Expr) -> Result<(), ScriptError> { - >>::visit_expr(self, expression) - .map(|_| ()) + self.visit_expr(expression).map(|_| ()) + } + + fn visit_var(&mut self, name: &Token, initializer: &Option) -> Result<(), ScriptError> { + let initialized_value = initializer + .as_ref() + .map_or(Ok(EvaluatedValue::Nil), |expr| self.visit_expr(expr))?; + + self.interpreter.env.set(name.lexeme(), initialized_value); + + Ok(()) } } diff --git a/src/eval/environment.rs b/src/eval/environment.rs new file mode 100644 index 0000000..60f3c4c --- /dev/null +++ b/src/eval/environment.rs @@ -0,0 +1,22 @@ +use std::collections::HashMap; + +use super::value::EvaluatedValue; + +#[derive(Debug, Default)] +pub struct Environment { + values: HashMap, +} + +impl Environment { + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, name: &str) -> Option<&EvaluatedValue> { + self.values.get(name) + } + + pub fn set>(&mut self, name: S, value: EvaluatedValue) { + self.values.insert(name.into(), value); + } +} diff --git a/src/eval/value.rs b/src/eval/value.rs new file mode 100644 index 0000000..5c6a0f1 --- /dev/null +++ b/src/eval/value.rs @@ -0,0 +1,57 @@ +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq)] + +pub enum EvaluatedValue { + Number(f64), + String(String), + Boolean(bool), + Nil, +} + +#[derive(thiserror::Error, Debug)] +#[error("{0}")] +pub struct AssumedTypeError(pub String); + +macro_rules! impl_value_try_from { + ($output_type: ty, $pattern: pat => $val: expr, $err_msg: literal) => { + impl TryFrom for $output_type { + type Error = AssumedTypeError; + + fn try_from(value: EvaluatedValue) -> Result { + match value { + $pattern => Ok($val), + _ => Err(AssumedTypeError($err_msg.to_string())), + } + } + } + }; +} + +impl_value_try_from!( + f64, + EvaluatedValue::Number(n) => n, + "value is not of type number" +); + +impl_value_try_from!( + String, + EvaluatedValue::String(s) => s, + "value is not of type string" +); + +impl_value_try_from!( + bool, + EvaluatedValue::Boolean(b) => b, + "value is not of type boolean" +); + +impl EvaluatedValue { + pub fn is_truthy(&self) -> bool { + match self { + EvaluatedValue::Nil => false, + EvaluatedValue::Boolean(value) => *value, + _ => true, + } + } +} diff --git a/src/lex.rs b/src/lex.rs index 8c15afd..6f3de44 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -1,6 +1,5 @@ use itertools::{FoldWhile, Itertools}; use std::iter::{self, Peekable}; - use thiserror::Error; use crate::ScriptError; diff --git a/src/lib.rs b/src/lib.rs index ed0eb1b..c52c1a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![warn(clippy::pedantic)] +use eval::Interpreter; + use crate::ast::{Expr, ExprVisitor}; use std::fmt::{self, Display, Formatter}; @@ -47,7 +49,9 @@ pub fn run(script: &str) -> Result<(), ScriptErrors> { return Err(errors.into()); } - let eval_res = eval::evaluate(&parsed); + // TODO: we will need to have some way to persist this for REPL runs + let mut interpreter = Interpreter::new(); + let eval_res = interpreter.evaluate(&parsed); eval_res.map_err(|err| ScriptErrors(vec![err])) } @@ -94,6 +98,10 @@ impl ExprVisitor for ASTPrinter { ast::LiteralValue::String(s) => format!("\"{}\"", s.clone()), } } + + fn visit_variable(&mut self, name: &lex::Token) -> String { + name.lexeme().to_owned() + } } #[cfg(test)] diff --git a/src/parse.rs b/src/parse.rs index 1af7b8b..b9d3696 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -24,7 +24,7 @@ pub fn parse_program, F: FnMut(ScriptError)>( break; } - match parse_statement(peekable_iter) { + match parse_declaration(peekable_iter) { Ok(stmt) => statements.push(stmt), Err(error) => { on_error(ScriptError { @@ -33,7 +33,7 @@ pub fn parse_program, F: FnMut(ScriptError)>( line: error.line.unwrap_or_default(), location: String::new(), }); - todo!() + todo!("{}", error.message); } } } @@ -70,6 +70,53 @@ fn synchronize_to_next_statement>(iter: &mut I) { } } +fn parse_declaration>( + iter: &mut Peekable, +) -> Result { + let parse_res = if let Some(var_token) = match_next_token(iter, &TokenKind::Var) { + parse_var_declaration(iter).map_err(|err| ParseError { + line: Some(var_token.line()), + ..err + }) + } else { + parse_statement(iter) + }; + + if parse_res.is_err() { + synchronize_to_next_statement(iter); + } + + parse_res +} + +fn parse_var_declaration>( + iter: &mut Peekable, +) -> Result { + let name_token = + match_token_kind!(iter, TokenKind::Identifier(_name)).map_err(|maybe_token| { + ParseError { + message: "Expected variable name".to_string(), + line: maybe_token.map(Token::line), + } + })?; + + let initializer = if match_token_kind!(iter, TokenKind::Equal).is_ok() { + Some(parse_expression(iter)?) + } else { + None + }; + + match_token_kind!(iter, TokenKind::SemiColon).map_err(|maybe_token| ParseError { + message: "Expected ';' after variable declaration".to_string(), + line: maybe_token.map(Token::line), + })?; + + Ok(Stmt::Var { + name: name_token, + initializer, + }) +} + fn parse_statement>(iter: &mut Peekable) -> Result { if match_token_kind!(iter, TokenKind::Print).is_ok() { parse_statement_containing_expression(iter, |expression| Stmt::Print { expression }) @@ -198,6 +245,7 @@ fn parse_primary>(iter: &mut Peekable) -> Result Ok(Expr::Variable { name: token }), _ => Err(ParseError { message: "Expected an expression".to_string(),