From 57522b2871e873cc0ebab61d0d0ad4006add5c2c Mon Sep 17 00:00:00 2001 From: Empa Date: Fri, 5 Jan 2024 12:09:41 +0100 Subject: [PATCH] Block expressions --- crates/saft-ast/src/lib.rs | 13 ++++- crates/saft-eval/src/interpreter.rs | 12 +++- crates/saft-eval/src/value.rs | 2 + crates/saft-lexer/src/lex.rs | 2 +- crates/saft-parser/src/lib.rs | 88 +++++++++++++++++++++++++---- 5 files changed, 103 insertions(+), 14 deletions(-) diff --git a/crates/saft-ast/src/lib.rs b/crates/saft-ast/src/lib.rs index 0fd431a..6d03967 100644 --- a/crates/saft-ast/src/lib.rs +++ b/crates/saft-ast/src/lib.rs @@ -18,6 +18,12 @@ pub enum Statement { Item(Item), } +#[derive(Debug, Clone, PartialEq)] +pub struct Block { + pub stmts: Vec>, + pub tail: Option>>, +} + #[derive(Debug, Clone, PartialEq)] pub enum Expr { Nil, @@ -27,9 +33,11 @@ pub enum Expr { String(String), Var(Spanned), - Grouping(Box>), Vec(Vec>), + Grouping(Box>), + Block(Block), + Neg(Box>), Not(Box>), Assign(Box>, Box>), @@ -84,7 +92,8 @@ impl Expr { Eq(..) => "equal", Ne(..) => "not equal", IDiv(..) => "integer division", - Not(_) => "not", + Not(..) => "not", + Block(..) => "block", } } } diff --git a/crates/saft-eval/src/interpreter.rs b/crates/saft-eval/src/interpreter.rs index e902d3f..b8ae904 100644 --- a/crates/saft-eval/src/interpreter.rs +++ b/crates/saft-eval/src/interpreter.rs @@ -1,7 +1,7 @@ use crate::natives::add_natives; use crate::value::{Cast, Function, NativeFuncData, Num, SaftFunction, Value}; use codespan_reporting::diagnostic::{Diagnostic, Label}; -use saft_ast::{Expr, Ident, Item, Module, Statement}; +use saft_ast::{Block, Expr, Ident, Item, Module, Statement}; use saft_common::span::{Span, Spanned}; use std::borrow::Borrow; use std::collections::HashMap; @@ -586,6 +586,16 @@ impl Interpreter { .ok_or::(cast_error!(v, "numeric"))?), )) } + Expr::Block(Block { stmts, tail }) => self.scoped(|interpreter| { + for stmt in stmts { + interpreter.exec_statement(stmt)?; + } + + match tail { + Some(box expr) => Ok::<_, ControlFlow>(interpreter.eval_expr(expr)?.v), + None => Ok(Value::Nil), + } + })?, })) } } diff --git a/crates/saft-eval/src/value.rs b/crates/saft-eval/src/value.rs index 777d761..c1de9ae 100644 --- a/crates/saft-eval/src/value.rs +++ b/crates/saft-eval/src/value.rs @@ -193,6 +193,7 @@ impl Num { } } + #[allow(clippy::should_implement_trait)] pub fn eq(&self, rhs: impl Borrow) -> bool { match bin_promote(self, rhs.borrow()) { (Num::Int(a), Num::Int(b)) => a == b, @@ -244,6 +245,7 @@ pub struct SaftFunction { #[derive(Clone, Debug)] pub struct NativeFuncData { pub name: &'static str, + #[allow(clippy::type_complexity)] pub f: fn(&mut Interpreter, &Span, Vec>) -> Result, } diff --git a/crates/saft-lexer/src/lex.rs b/crates/saft-lexer/src/lex.rs index 7c15909..4903dd0 100644 --- a/crates/saft-lexer/src/lex.rs +++ b/crates/saft-lexer/src/lex.rs @@ -193,7 +193,7 @@ mod test { assert_eq!(Lexer::new(src).all_tokens(), spanned_tokens); } - fn spanned<'a>(t: T, r: Range) -> Spanned { + fn spanned(t: T, r: Range) -> Spanned { Spanned::new(t, span(r)) } diff --git a/crates/saft-parser/src/lib.rs b/crates/saft-parser/src/lib.rs index 24a3bb2..cbea9c1 100644 --- a/crates/saft-parser/src/lib.rs +++ b/crates/saft-parser/src/lib.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, collections::VecDeque}; -use ast::{Expr, Item, Module, Statement}; +use ast::{Block, Expr, Item, Module, Statement}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use saft_ast as ast; use saft_common::span::{Span, Spanned}; @@ -21,6 +21,16 @@ fn unexpected(got: impl Borrow>, expected: impl Into) -> } } +macro_rules! exotic { + ($span:expr, $msg:expr) => { + return Err(exotic($span, $msg)) + }; +} + +fn exotic(span: impl Borrow, expected: impl Into) -> Error { + Error::Exotic(span.borrow().spanned(expected.into())) +} + mod prec { pub const NONE: i32 = 0; pub const ASSIGN: i32 = 1; @@ -42,16 +52,20 @@ pub enum Error { got: Spanned, expected: String, }, + Exotic(Spanned), } impl Error { - pub fn diagnostic(&self, file_id: FileId) -> Diagnostic { + pub fn diagnostic(self, file_id: FileId) -> Diagnostic { match self { Error::UnexpectedToken { got, expected } => Diagnostic::error() .with_message("Got an unexpected token") - .with_labels(vec![Label::primary(file_id, got.s.r.clone()).with_message( + .with_labels(vec![Label::primary(file_id, got.s.r).with_message( format!("Got {} but expected {}", got.v.describe(), expected), )]), + Error::Exotic(sm) => Diagnostic::error() + .with_message(sm.v) + .with_labels(vec![Label::primary(file_id, sm.s.r)]), } } } @@ -93,6 +107,18 @@ impl<'a> Parser<'a> { } } + fn eat_msg(&mut self, t: Token, msg: impl Into) -> Result { + let st = self.peek(); + + match st.v { + v if v == t => { + self.advance(); + Ok(st.s) + } + _ => exotic!(st.s, msg), + } + } + fn eat_ident(&mut self) -> Result, Error> { let st = self.peek(); @@ -105,15 +131,15 @@ impl<'a> Parser<'a> { } } - fn try_eat(&mut self, t: Token) -> bool { + fn try_eat(&mut self, t: Token) -> Option { let st = self.peek(); match st.v { v if v == t => { self.eat(t).expect("Was peeked, should not happen"); - true + Some(st.s) } - _ => false, + _ => None, } } @@ -164,6 +190,44 @@ impl<'a> Parser<'a> { Ok(stmts) } + pub fn parse_block(&mut self, start: Option) -> Result, Error> { + let start = match start { + Some(s) => s, + None => self.eat(Token::LBrace)?, + }; + + let mut stmts = Vec::new(); + + while self.peek().v != Token::RBrace { + let stmt = self.parse_statement()?; + + match &stmt.v { + Statement::Expr(e) => { + if let Some(end) = self.try_eat(Token::RBrace) { + let s = start.join(end); + + return Ok(s.spanned(Expr::Block(Block { + stmts, + tail: Some(Box::new(e.clone())), + }))); + } + } + Statement::Item(_) => {} + _ => { + self.eat_msg(Token::Semicolon, "Expected a ';' after a statement")?; + } + } + + stmts.push(stmt); + } + + let end = self.eat(Token::RBrace)?; + + let s = start.join(end); + + Ok(s.spanned(Expr::Block(Block { stmts, tail: None }))) + } + pub fn parse_statement(&mut self) -> Result, Error> { let st = self.peek(); @@ -194,7 +258,8 @@ impl<'a> Parser<'a> { | Token::LBracket | Token::True | Token::False - | Token::Bang => { + | Token::Bang + | Token::LBrace => { let expr = self.parse_expr()?; Ok(expr.s.clone().spanned(Statement::Expr(expr))) } @@ -250,7 +315,7 @@ impl<'a> Parser<'a> { exprs.push(self.parse_expr()?); - if !self.try_eat(T::Comma) { + if self.try_eat(T::Comma).is_none() { break; } } @@ -258,6 +323,9 @@ impl<'a> Parser<'a> { let s = start.join(end); Ok(s.spanned(Expr::Vec(exprs))) } + T::LBrace => { + self.parse_block(Some(start)) + } T::True => Ok(st.s.spanned(Expr::Bool(true))), T::False => Ok(st.s.spanned(Expr::Bool(false))), @@ -335,7 +403,7 @@ impl<'a> Parser<'a> { let arg = parser.parse_expr()?; arguments.push(arg); - let ate_comma = parser.try_eat(Token::Comma); + let ate_comma = parser.try_eat(Token::Comma).is_some(); if !ate_comma { break; } @@ -375,7 +443,7 @@ impl<'a> Parser<'a> { let param = self.eat_ident()?; params.push(param); - if !self.try_eat(Token::Comma) { + if self.try_eat(Token::Comma).is_none() { break; } }