Add support for variable declaration
parent
b9b7f0b387
commit
0b4d4d6d21
2
build.rs
2
build.rs
|
@ -47,6 +47,7 @@ fn do_ast_codegen<W: Write>(mut output: W) {
|
|||
("Grouping", "expr: Box<Expr>"),
|
||||
("Unary", "expr: Box<Expr>, 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<W: Write>(mut output: W) {
|
|||
let statement_types = BTreeMap::from([
|
||||
("Expression", "expression: Expr"),
|
||||
("Print", "expression: Expr"),
|
||||
("Var", "name: Token, initializer: Option<Expr>"),
|
||||
]);
|
||||
|
||||
define_ast(&mut output, "Stmt", &statement_types).expect("failed to generate ast values");
|
||||
|
|
124
src/eval.rs
124
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<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,
|
||||
}
|
||||
}
|
||||
}
|
||||
mod environment;
|
||||
mod value;
|
||||
|
||||
impl From<&LiteralValue> for EvaluatedValue {
|
||||
fn from(value: &LiteralValue) -> Self {
|
||||
|
@ -85,17 +41,37 @@ impl Display for EvaluatedValue {
|
|||
}
|
||||
}
|
||||
|
||||
struct Interpreter;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Interpreter {
|
||||
env: Environment,
|
||||
}
|
||||
|
||||
pub fn evaluate(statements: &[Stmt]) -> Result<(), ScriptError> {
|
||||
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 {
|
||||
Interpreter.visit_stmt(statement)?;
|
||||
runner.visit_stmt(statement)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprVisitor<Result<EvaluatedValue, ScriptError>> for Interpreter {
|
||||
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> {
|
||||
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> {
|
||||
let value = <Self as ExprVisitor<Result<EvaluatedValue, ScriptError>>>::visit_expr(
|
||||
self, expression,
|
||||
)?;
|
||||
let value = self.visit_expr(expression)?;
|
||||
println!("{value}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_expression(&mut self, expression: &Expr) -> Result<(), ScriptError> {
|
||||
<Self as ExprVisitor<Result<EvaluatedValue, ScriptError>>>::visit_expr(self, expression)
|
||||
.map(|_| ())
|
||||
self.visit_expr(expression).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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use itertools::{FoldWhile, Itertools};
|
||||
use std::iter::{self, Peekable};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ScriptError;
|
||||
|
|
10
src/lib.rs
10
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<String> for ASTPrinter {
|
|||
ast::LiteralValue::String(s) => format!("\"{}\"", s.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_variable(&mut self, name: &lex::Token) -> String {
|
||||
name.lexeme().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
52
src/parse.rs
52
src/parse.rs
|
@ -24,7 +24,7 @@ pub fn parse_program<I: Iterator<Item = Token>, 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<I: Iterator<Item = Token>, 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<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> {
|
||||
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<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Ex
|
|||
line: Some(token.line()),
|
||||
})
|
||||
}
|
||||
TokenKind::Identifier(_name) => Ok(Expr::Variable { name: token }),
|
||||
|
||||
_ => Err(ParseError {
|
||||
message: "Expected an expression".to_string(),
|
||||
|
|
Loading…
Reference in New Issue