Skip to content

Commit

Permalink
Evaluate blocks and statements
Browse files Browse the repository at this point in the history
  • Loading branch information
Quaqqer committed Jun 21, 2024
1 parent c54541e commit a489c14
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 9 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 4 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ pub enum Stmt {
},
}

#[derive(Debug)]
pub struct TrailBlock(pub Vec<Spanned<Stmt>>, pub Option<Box<Spanned<Expr>>>);

#[derive(Debug)]
pub enum Expr {
Int(i32),
Bool(bool),
Var(Spanned<String>),
Block(TrailBlock),
Call {
expr: Box<Spanned<Expr>>,
args: Vec<Spanned<Expr>>,
Expand Down
164 changes: 155 additions & 9 deletions src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
use std::collections::HashMap;

use crate::{ast, span::Span};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
Int(i32),
Bool(bool),
Nil,
}

impl Value {
pub fn ty(&self) -> ValueType {
match self {
Value::Int(_) => ValueType::Int,
Value::Bool(_) => ValueType::Bool,
Value::Nil => ValueType::Nil,
}
}
}

pub enum ValueType {
Int,
Bool,
Nil,
}

impl std::fmt::Display for ValueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValueType::Int => write!(f, "int"),
ValueType::Bool => write!(f, "bool"),
ValueType::Nil => write!(f, "nil"),
}
}
}
Expand All @@ -36,7 +42,69 @@ pub enum EvaluatorError {
Text(String, Span),
}

pub struct Evaluator {}
pub struct Evaluator {
call_frames: Vec<CallFrame>,
}

struct CallFrame {
scopes: Vec<HashMap<String, Value>>,
}

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),+) => {
Expand All @@ -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<T>(&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<Value> {
Expand Down Expand Up @@ -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::<Value, EvaluatorError>(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),
Expand All @@ -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<ast::Expr> {
crate::parser::SpannedExprParser::new()
Expand All @@ -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)
);
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#![allow(clippy::new_without_default)]
#![feature(map_try_insert)]

pub mod ast;
pub mod eval;
mod parser_test;
Expand Down
11 changes: 11 additions & 0 deletions src/parser.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ Stmt: ast::Stmt = {
=> ast::Stmt::Let { ident, expr },
}

TrailBlock: ast::TrailBlock
= "{" <stmts:Spanned<Stmt>*> <trail_expr:Spanned<Expr>?> "}"
=> ast::TrailBlock(
stmts,
trail_expr.map(Box::new),
);

pub SpannedTrailBlock: Spanned<ast::TrailBlock> = Spanned<TrailBlock>;

pub SpannedExpr: Spanned<ast::Expr>
= Spanned<Expr>;

Expand Down Expand Up @@ -61,6 +70,8 @@ Term: ast::Expr = {
"true" => ast::Expr::Bool(true),
"false" => ast::Expr::Bool(false),

<block:TrailBlock> => ast::Expr::Block(block),

<var:Spanned<Ident>>
=> ast::Expr::Var(var),

Expand Down
10 changes: 10 additions & 0 deletions src/sexpr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SExpr> = 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),
Expand Down

0 comments on commit a489c14

Please sign in to comment.