From 318a842241fccef0184c69e31a7d08306799c6c6 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sat, 4 May 2024 13:45:43 +0200 Subject: rebel-parse, rebel-lang: implement if-else expressions --- crates/rebel-lang/src/typing.rs | 28 ++++++++++++++++++++++++++++ crates/rebel-lang/src/value.rs | 21 +++++++++++++++++++++ crates/rebel-parse/src/ast/expr.rs | 17 +++++++++++++++++ crates/rebel-parse/src/grammar/recipe.rs | 15 ++++++++++++--- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/crates/rebel-lang/src/typing.rs b/crates/rebel-lang/src/typing.rs index 5a0ca0a..6c5ba81 100644 --- a/crates/rebel-lang/src/typing.rs +++ b/crates/rebel-lang/src/typing.rs @@ -200,6 +200,10 @@ impl<'scope> Context<'scope> { Index { base, index } => self.type_index(base, index), Field { base, field } => self.type_field(base, field), Block(block) => self.type_block(block), + IfElse { + if_blocks, + else_block, + } => self.type_ifelse(if_blocks, else_block), Paren(subexpr) => self.type_expr(subexpr), Path(path) => self.type_path(path), Literal(lit) => self.type_literal(lit), @@ -379,6 +383,30 @@ impl<'scope> Context<'scope> { Ok(ret) } + fn type_ifelse( + &mut self, + if_blocks: &[(expr::Expr, ast::Block)], + else_block: &Option>, + ) -> Result { + let (mut ret, mut common_upvalues) = if let Some(block) = else_block { + self.type_scope(&block.0)? + } else { + (Type::Unit, HashSet::new()) + }; + + for (cond, block) in if_blocks { + Type::Bool.unify(self.type_expr(cond)?, Coerce::Assign)?; + + let (typ, upvalues) = self.type_scope(&block.0)?; + ret = ret.unify(typ, Coerce::Common)?; + common_upvalues = &common_upvalues & &upvalues; + } + + self.0.initialize_all(common_upvalues); + + Ok(ret) + } + fn type_path(&self, path: &ast::Path<'_>) -> Result { let var = self.0.lookup_value(path)?; if !self.0.is_initialized(path) { diff --git a/crates/rebel-lang/src/value.rs b/crates/rebel-lang/src/value.rs index 2b07803..97a6f8b 100644 --- a/crates/rebel-lang/src/value.rs +++ b/crates/rebel-lang/src/value.rs @@ -107,6 +107,10 @@ impl<'scope> Context<'scope> { Index { base, index } => self.eval_index(base, index), Field { base, field } => self.eval_field(base, field), Block(block) => self.eval_block(block), + IfElse { + if_blocks, + else_block, + } => self.eval_ifelse(if_blocks, else_block), Paren(subexpr) => self.eval_expr(subexpr), Path(path) => self.eval_path(path), Literal(lit) => self.eval_literal(lit), @@ -323,6 +327,23 @@ impl<'scope> Context<'scope> { Ok(ret) } + fn eval_ifelse( + &mut self, + if_blocks: &[(expr::Expr, ast::Block)], + else_block: &Option>, + ) -> Result { + for (cond, block) in if_blocks { + if self.eval_expr(cond)? == Value::Bool(true) { + return self.eval_block(block); + } + } + if let Some(block) = else_block { + self.eval_block(block) + } else { + Ok(Value::Unit) + } + } + fn eval_path(&self, path: &ast::Path<'_>) -> Result { Ok(self.0.lookup_value(path)?.clone()) } diff --git a/crates/rebel-parse/src/ast/expr.rs b/crates/rebel-parse/src/ast/expr.rs index 2e8d750..3994296 100644 --- a/crates/rebel-parse/src/ast/expr.rs +++ b/crates/rebel-parse/src/ast/expr.rs @@ -34,6 +34,10 @@ pub enum Expr<'a> { field: Ident<'a>, }, Block(Block<'a>), + IfElse { + if_blocks: Vec<(Expr<'a>, Block<'a>)>, + else_block: Option>>, + }, Paren(Box>), Path(Path<'a>), Literal(Literal<'a>), @@ -129,6 +133,19 @@ impl<'a> Expr<'a> { } Ok(()) } + Expr::IfElse { + if_blocks, + else_block, + } => { + for (cond, block) in if_blocks { + cond.validate()?; + block.validate()?; + } + if let Some(block) = else_block { + block.validate()?; + } + Ok(()) + } Expr::Paren(expr) => expr.validate(), Expr::Path(_) => Ok(()), Expr::Literal(lit) => lit.validate(), diff --git a/crates/rebel-parse/src/grammar/recipe.rs b/crates/rebel-parse/src/grammar/recipe.rs index 11f1ce6..1ada80c 100644 --- a/crates/rebel-parse/src/grammar/recipe.rs +++ b/crates/rebel-parse/src/grammar/recipe.rs @@ -133,15 +133,24 @@ peg::parser! { } base:@ p('.') field:field() { Expr::field(base, field) } -- - p('(') e:expr() p(')') { Expr::paren(e) } - p('{') block:block() p('}') { Expr::Block(block) } e:atom() { e } } rule atom() -> Expr<'a> - = lit:literal() { Expr::Literal(lit) } + = p('(') e:expr() p(')') { Expr::paren(e) } + / [Token::Keyword(Keyword::If)] + if_blocks:(cond_block() ++ ([Token::Keyword(Keyword::Else)] [Token::Keyword(Keyword::If)])) + else_block:([Token::Keyword(Keyword::Else)] p('{') block:block() p('}') { Box::new(block) })? + { + Expr::IfElse { if_blocks, else_block } + } + / p('{') block:block() p('}') { Expr::Block(block) } + / lit:literal() { Expr::Literal(lit) } / path:path() { Expr::Path(path) } + rule cond_block() -> (Expr<'a>, ast::Block<'a>) + = cond:expr() p('{') block:block() p('}') { (cond, block) } + rule call_params() -> Vec> = args:delimited(, ) { args } -- cgit v1.2.3