Add support for variable declaration

master
Nick Krichevsky 2024-05-21 22:03:18 -04:00
parent b9b7f0b387
commit 0b4d4d6d21
7 changed files with 205 additions and 73 deletions

View File

@ -47,6 +47,7 @@ fn do_ast_codegen<W: Write>(mut output: W) {
("Grouping", "expr: Box<Expr>"), ("Grouping", "expr: Box<Expr>"),
("Unary", "expr: Box<Expr>, operator: Token"), ("Unary", "expr: Box<Expr>, operator: Token"),
("Literal", "value: LiteralValue"), ("Literal", "value: LiteralValue"),
("Variable", "name: Token"),
]); ]);
define_ast(&mut output, "Expr", &expr_types).expect("failed to generate ast values"); define_ast(&mut output, "Expr", &expr_types).expect("failed to generate ast values");
@ -59,6 +60,7 @@ fn do_ast_codegen<W: Write>(mut output: W) {
let statement_types = BTreeMap::from([ let statement_types = BTreeMap::from([
("Expression", "expression: Expr"), ("Expression", "expression: Expr"),
("Print", "expression: Expr"), ("Print", "expression: Expr"),
("Var", "name: Token, initializer: Option<Expr>"),
]); ]);
define_ast(&mut output, "Stmt", &statement_types).expect("failed to generate ast values"); define_ast(&mut output, "Stmt", &statement_types).expect("failed to generate ast values");

View File

@ -1,4 +1,7 @@
use std::fmt::{self, Display, Formatter}; use std::{
fmt::{self, Display, Formatter},
string::ParseError,
};
use crate::{ use crate::{
ast::{Expr, ExprVisitor, LiteralValue, Stmt, StmtVisitor}, ast::{Expr, ExprVisitor, LiteralValue, Stmt, StmtVisitor},
@ -6,60 +9,13 @@ use crate::{
ScriptError, ScriptError,
}; };
#[derive(Debug, Clone, PartialEq)] use self::{
pub enum EvaluatedValue { environment::Environment,
Number(f64), value::{AssumedTypeError, EvaluatedValue},
String(String), };
Boolean(bool),
Nil,
}
#[derive(thiserror::Error, Debug)] mod environment;
#[error("{0}")] mod value;
pub struct AssumedTypeError(String);
macro_rules! impl_value_try_from {
($output_type: ty, $pattern: pat => $val: expr, $err_msg: literal) => {
impl TryFrom<EvaluatedValue> for $output_type {
type Error = AssumedTypeError;
fn try_from(value: EvaluatedValue) -> Result<Self, Self::Error> {
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,
}
}
}
impl From<&LiteralValue> for EvaluatedValue { impl From<&LiteralValue> for EvaluatedValue {
fn from(value: &LiteralValue) -> Self { fn from(value: &LiteralValue) -> Self {
@ -85,17 +41,37 @@ impl Display for EvaluatedValue {
} }
} }
struct Interpreter; #[derive(Debug, Default)]
pub struct Interpreter {
pub fn evaluate(statements: &[Stmt]) -> Result<(), ScriptError> { env: Environment,
for statement in statements {
Interpreter.visit_stmt(statement)?;
}
Ok(())
} }
impl ExprVisitor<Result<EvaluatedValue, ScriptError>> 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<Result<EvaluatedValue, ScriptError>> for InterpreterRunner<'_> {
fn visit_literal(&mut self, value: &LiteralValue) -> Result<EvaluatedValue, ScriptError> { fn visit_literal(&mut self, value: &LiteralValue) -> Result<EvaluatedValue, ScriptError> {
Ok(value.into()) Ok(value.into())
} }
@ -192,20 +168,40 @@ impl ExprVisitor<Result<EvaluatedValue, ScriptError>> for Interpreter {
), ),
} }
} }
fn visit_variable(&mut self, name: &Token) -> Result<EvaluatedValue, ScriptError> {
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<Result<(), ScriptError>> for Interpreter { impl StmtVisitor<Result<(), ScriptError>> for InterpreterRunner<'_> {
fn visit_print(&mut self, expression: &Expr) -> Result<(), ScriptError> { fn visit_print(&mut self, expression: &Expr) -> Result<(), ScriptError> {
let value = <Self as ExprVisitor<Result<EvaluatedValue, ScriptError>>>::visit_expr( let value = self.visit_expr(expression)?;
self, expression,
)?;
println!("{value}"); println!("{value}");
Ok(()) Ok(())
} }
fn visit_expression(&mut self, expression: &Expr) -> Result<(), ScriptError> { fn visit_expression(&mut self, expression: &Expr) -> Result<(), ScriptError> {
<Self as ExprVisitor<Result<EvaluatedValue, ScriptError>>>::visit_expr(self, expression) self.visit_expr(expression).map(|_| ())
.map(|_| ()) }
fn visit_var(&mut self, name: &Token, initializer: &Option<Expr>) -> 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(())
} }
} }

22
src/eval/environment.rs Normal file
View File

@ -0,0 +1,22 @@
use std::collections::HashMap;
use super::value::EvaluatedValue;
#[derive(Debug, Default)]
pub struct Environment {
values: HashMap<String, EvaluatedValue>,
}
impl Environment {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, name: &str) -> Option<&EvaluatedValue> {
self.values.get(name)
}
pub fn set<S: Into<String>>(&mut self, name: S, value: EvaluatedValue) {
self.values.insert(name.into(), value);
}
}

57
src/eval/value.rs Normal file
View File

@ -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<EvaluatedValue> for $output_type {
type Error = AssumedTypeError;
fn try_from(value: EvaluatedValue) -> Result<Self, Self::Error> {
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,
}
}
}

View File

@ -1,6 +1,5 @@
use itertools::{FoldWhile, Itertools}; use itertools::{FoldWhile, Itertools};
use std::iter::{self, Peekable}; use std::iter::{self, Peekable};
use thiserror::Error; use thiserror::Error;
use crate::ScriptError; use crate::ScriptError;

View File

@ -1,5 +1,7 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
use eval::Interpreter;
use crate::ast::{Expr, ExprVisitor}; use crate::ast::{Expr, ExprVisitor};
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
@ -47,7 +49,9 @@ pub fn run(script: &str) -> Result<(), ScriptErrors> {
return Err(errors.into()); 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])) eval_res.map_err(|err| ScriptErrors(vec![err]))
} }
@ -94,6 +98,10 @@ impl ExprVisitor<String> for ASTPrinter {
ast::LiteralValue::String(s) => format!("\"{}\"", s.clone()), ast::LiteralValue::String(s) => format!("\"{}\"", s.clone()),
} }
} }
fn visit_variable(&mut self, name: &lex::Token) -> String {
name.lexeme().to_owned()
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -24,7 +24,7 @@ pub fn parse_program<I: Iterator<Item = Token>, F: FnMut(ScriptError)>(
break; break;
} }
match parse_statement(peekable_iter) { match parse_declaration(peekable_iter) {
Ok(stmt) => statements.push(stmt), Ok(stmt) => statements.push(stmt),
Err(error) => { Err(error) => {
on_error(ScriptError { on_error(ScriptError {
@ -33,7 +33,7 @@ pub fn parse_program<I: Iterator<Item = Token>, F: FnMut(ScriptError)>(
line: error.line.unwrap_or_default(), line: error.line.unwrap_or_default(),
location: String::new(), location: String::new(),
}); });
todo!() todo!("{}", error.message);
} }
} }
} }
@ -70,6 +70,53 @@ fn synchronize_to_next_statement<I: Iterator<Item = Token>>(iter: &mut I) {
} }
} }
fn parse_declaration<I: Iterator<Item = Token>>(
iter: &mut Peekable<I>,
) -> Result<Stmt, ParseError> {
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<I: Iterator<Item = Token>>(
iter: &mut Peekable<I>,
) -> Result<Stmt, ParseError> {
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<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Stmt, ParseError> { fn parse_statement<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Stmt, ParseError> {
if match_token_kind!(iter, TokenKind::Print).is_ok() { if match_token_kind!(iter, TokenKind::Print).is_ok() {
parse_statement_containing_expression(iter, |expression| Stmt::Print { expression }) parse_statement_containing_expression(iter, |expression| Stmt::Print { expression })
@ -198,6 +245,7 @@ fn parse_primary<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Ex
line: Some(token.line()), line: Some(token.line()),
}) })
} }
TokenKind::Identifier(_name) => Ok(Expr::Variable { name: token }),
_ => Err(ParseError { _ => Err(ParseError {
message: "Expected an expression".to_string(), message: "Expected an expression".to_string(),