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>"),
|
("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");
|
||||||
|
|
134
src/eval.rs
134
src/eval.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 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;
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -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)]
|
||||||
|
|
52
src/parse.rs
52
src/parse.rs
|
@ -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(),
|
||||||
|
|
Loading…
Reference in New Issue