diff --git a/Cargo.lock b/Cargo.lock index 150c4ed..1285d7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "itertools" version = "0.11.0" @@ -355,6 +361,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" name = "saft" version = "0.1.0" dependencies = [ + "indoc", "lalrpop", "lalrpop-util", ] diff --git a/Cargo.toml b/Cargo.toml index 33e3289..2b88c3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +indoc = "2.0.5" lalrpop-util = { version = "0.20.2", features = ["lexer", "unicode"] } [build-dependencies] diff --git a/src/ast.rs b/src/ast.rs index a66981c..f090a12 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -23,11 +23,15 @@ pub enum Stmt { }, } +#[derive(Debug)] +pub struct TrailBlock(pub Vec>, pub Option>>); + #[derive(Debug)] pub enum Expr { Int(i32), Bool(bool), Var(Spanned), + Block(TrailBlock), Call { expr: Box>, args: Vec>, diff --git a/src/eval.rs b/src/eval.rs index 2624b98..ae38076 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,9 +1,12 @@ +use std::collections::HashMap; + use crate::{ast, span::Span}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Value { Int(i32), Bool(bool), + Nil, } impl Value { @@ -11,6 +14,7 @@ impl Value { match self { Value::Int(_) => ValueType::Int, Value::Bool(_) => ValueType::Bool, + Value::Nil => ValueType::Nil, } } } @@ -18,6 +22,7 @@ impl Value { pub enum ValueType { Int, Bool, + Nil, } impl std::fmt::Display for ValueType { @@ -25,6 +30,7 @@ impl std::fmt::Display for ValueType { match self { ValueType::Int => write!(f, "int"), ValueType::Bool => write!(f, "bool"), + ValueType::Nil => write!(f, "nil"), } } } @@ -36,7 +42,69 @@ pub enum EvaluatorError { Text(String, Span), } -pub struct Evaluator {} +pub struct Evaluator { + call_frames: Vec, +} + +struct CallFrame { + scopes: Vec>, +} + +struct Scopes {} + +impl CallFrame { + fn new() -> Self { + Self { + scopes: vec![HashMap::new()], + } + } + + fn enter_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + fn exit_scope(&mut self) { + self.scopes.pop().unwrap(); + } + + /// Assign a value to a field in the scopes. Return true if successful + /// + /// * `ident`: The field + /// * `value`: The value to assign the field to + fn assign(&mut self, ident: &str, value: Value) -> bool { + for scope in self.scopes.iter_mut().rev() { + if let Some(cell) = scope.get_mut(ident) { + *cell = value; + return true; + } + } + + false + } + + /// Declare a value in the outermost scope. Fails if a value is already declared by that name. + /// Return the success. + /// + /// * `ident`: The field name + /// * `value`: The value + fn declare(&mut self, ident: String, value: Value) -> bool { + let res = self.scopes.last_mut().unwrap().try_insert(ident, value); + res.is_ok() + } + + /// Look a for a field in all scopes. Return the value if found + /// + /// * `ident`: The field name + fn lookup(&self, ident: &str) -> Option<&Value> { + for scope in self.scopes.iter().rev() { + if let Some(v) = scope.get(ident) { + return Some(v); + } + } + + None + } +} macro_rules! bail { ($span:expr, $($fmts:expr),+) => { @@ -46,7 +114,60 @@ macro_rules! bail { impl Evaluator { pub fn new() -> Self { - Self {} + Self { + call_frames: vec![CallFrame { + scopes: vec![HashMap::new()], + }], + } + } + + fn frame(&self) -> &CallFrame { + self.call_frames.last().unwrap() + } + + fn frame_mut(&mut self) -> &mut CallFrame { + self.call_frames.last_mut().unwrap() + } + + fn enter_frame(&mut self) { + todo!() + } + + fn exit_frame(&mut self) { + todo!() + } + + fn enter_scope(&mut self) { + self.frame_mut().enter_scope(); + } + + fn exit_scope(&mut self) { + self.frame_mut().exit_scope(); + } + + fn scoped(&mut self, f: impl Fn(&mut Evaluator) -> T) -> T { + self.enter_scope(); + let res = f(self); + self.exit_scope(); + res + } + + pub fn exec_stmt(&mut self, stmt: &ast::Stmt, span: &Span) -> Res<()> { + match stmt { + ast::Stmt::Expr(expr) => { + self.eval_expr(&expr.v, &expr.s)?; + Ok(()) + } + ast::Stmt::Let { ident, expr } => { + let v = self.eval_expr(&expr.v, &expr.s)?; + let declared = self.declare(ident.v.clone(), v); + if declared { + Ok(()) + } else { + bail!(ident.s, "Failed to declare variable '{}', it has already been declared in this scope", ident.v); + } + } + } } pub fn eval_expr(&mut self, expr: &ast::Expr, span: &Span) -> Res { @@ -81,6 +202,17 @@ impl Evaluator { )) } }, + ast::Expr::Block(ast::TrailBlock(stmts, trail)) => self.scoped(|this| { + for stmt in stmts { + this.exec_stmt(&stmt.v, &stmt.s)?; + } + + if let Some(expr) = trail { + Ok::(this.eval_expr(&expr.v, &expr.s)?) + } else { + Ok(Value::Nil) + } + })?, ast::Expr::Call { expr, args } => todo!(), ast::Expr::Access { expr, field } => todo!(), ast::Expr::Add(lhs, rhs) => binop!(lhs, rhs, "+", |lhs, rhs| lhs + rhs), @@ -91,23 +223,23 @@ impl Evaluator { } fn lookup(&self, ident: &str) -> Option<&Value> { - todo!() + self.frame().lookup(ident) } - fn declare(&mut self, ident: String, value: Value) { - todo!() + fn declare(&mut self, ident: String, value: Value) -> bool { + self.frame_mut().declare(ident, value) } - fn assign(&mut self, ident: &str, value: Value) { - todo!() + fn assign(&mut self, ident: &str, value: Value) -> bool { + self.frame_mut().assign(ident, value) } } #[cfg(test)] mod tests { - use crate::{ast, span::Spanned}; - use super::{Evaluator, Value}; + use crate::{ast, span::Spanned}; + use indoc::indoc; fn parse_expr(source: &'static str) -> Spanned { crate::parser::SpannedExprParser::new() @@ -125,4 +257,18 @@ mod tests { fn binop() { assert_eq!(eval_expr("1 + 2 + 3"), Value::Int(6)); } + + #[test] + fn variables() { + assert_eq!( + eval_expr(indoc! {" + { + let x = 2; + let y = 3; + x + y + } + "}), + Value::Int(5) + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 0af76b1..b39c635 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#![allow(clippy::new_without_default)] +#![feature(map_try_insert)] + pub mod ast; pub mod eval; mod parser_test; diff --git a/src/parser.lalrpop b/src/parser.lalrpop index 5da59e5..73bbe2a 100644 --- a/src/parser.lalrpop +++ b/src/parser.lalrpop @@ -25,6 +25,15 @@ Stmt: ast::Stmt = { => ast::Stmt::Let { ident, expr }, } +TrailBlock: ast::TrailBlock + = "{" *> ?> "}" + => ast::TrailBlock( + stmts, + trail_expr.map(Box::new), + ); + +pub SpannedTrailBlock: Spanned = Spanned; + pub SpannedExpr: Spanned = Spanned; @@ -61,6 +70,8 @@ Term: ast::Expr = { "true" => ast::Expr::Bool(true), "false" => ast::Expr::Bool(false), + => ast::Expr::Block(block), + > => ast::Expr::Var(var), diff --git a/src/sexpr.rs b/src/sexpr.rs index 7f16a2a..e1fab64 100644 --- a/src/sexpr.rs +++ b/src/sexpr.rs @@ -94,6 +94,16 @@ impl From<&ast::Expr> for SExpr { &expr.v, SExpr::List(args.iter().map(|arg| (&arg.v).into()).collect()) ), + ast::Expr::Block(ast::TrailBlock(stmts, trail)) => { + let mut list_vec: Vec = vec![ + "block".into(), + SExpr::List(stmts.iter().map(|arg| (&arg.v).into()).collect()), + ]; + if let Some(trail) = trail { + list_vec.push((&trail.v).into()); + } + SExpr::List(list_vec) + } ast::Expr::Access { expr, field } => list!(".", &expr.v, &field.v), ast::Expr::Add(lhs, rhs) => list!("+", &lhs.v, &rhs.v), ast::Expr::Sub(lhs, rhs) => list!("-", &lhs.v, &rhs.v),