From 9108f7ae988d0782be784fa2ad072a5d109fbc3c Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Tue, 13 Feb 2024 10:20:46 -0800 Subject: [PATCH] Let else --- Cargo.toml | 6 ++-- src/compiler.rs | 66 +++++++++++++++++++++++++++++++++++++++++-- src/syntax.rs | 10 ++++++- src/vm.rs | 4 +-- tests/cases/match.rsn | 10 +++++++ tests/harness.rs | 2 +- 6 files changed, 89 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5426bf1..1ad320a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,6 @@ unsafe_code = "forbid" name = "harness" harness = false -[profile.release] -debug = true -lto = true +# [profile.release] +# debug = true +# lto = true diff --git a/src/compiler.rs b/src/compiler.rs index 526f0ce..791b752 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1081,9 +1081,19 @@ impl<'a> Scope<'a> { self.compiler.code.label(pattern_mismatch); if let Some(r#else) = &decl.r#else { + self.compile_expression(r#else, OpDestination::Void); match refutable { - Refutability::Refutable => todo!("ensure all branches in else exit"), - Refutability::Irrefutable => {} + Refutability::Refutable => { + if let Err(range) = Self::check_all_branches_diverge(r#else) { + self.compiler + .errors + .push(Ranged::new(range, Error::LetElseMustDiverge)); + } + } + Refutability::Irrefutable => self + .compiler + .errors + .push(Ranged::new(r#else.1, Error::ElseOnIrrefutablePattern)), } } else { self.compiler.code.throw(FaultKind::PatternMismatch); @@ -1093,6 +1103,56 @@ impl<'a> Scope<'a> { self.compiler.code.copy(true, dest); } + fn check_all_branches_diverge(expr: &Ranged) -> Result<(), SourceRange> { + match &expr.0 { + Expression::Break(_) | Expression::Return(_) | Expression::Continue(_) => Ok(()), + Expression::If(if_expr) => { + let Some(when_false) = &if_expr.when_false else { + return Err(expr.1); + }; + Self::check_all_branches_diverge(&if_expr.when_true)?; + + Self::check_all_branches_diverge(when_false) + } + Expression::Block(block) => Self::check_all_branches_diverge(&block.body), + Expression::Binary(binary) if binary.kind == syntax::BinaryKind::Chain => { + Self::check_all_branches_diverge(&binary.left) + .or_else(|_| Self::check_all_branches_diverge(&binary.right)) + } + Expression::Try(_) => { + todo!("try isn't implemented yet") + } + Expression::Loop(loop_expr) => { + let conditional = match &loop_expr.kind { + LoopKind::Infinite => false, + LoopKind::While(_) | LoopKind::TailWhile(_) | LoopKind::For { .. } => true, + }; + + if !conditional { + return Err(expr.1); + } + + Self::check_all_branches_diverge(&loop_expr.block.body) + } + + Expression::Literal(_) + | Expression::Lookup(_) + | Expression::Match(_) + | Expression::TryOrNil(_) + | Expression::Map(_) + | Expression::List(_) + | Expression::Tuple(_) + | Expression::Call(_) + | Expression::Index(_) + | Expression::Assign(_) + | Expression::Unary(_) + | Expression::Binary(_) + | Expression::Module(_) + | Expression::Function(_) + | Expression::Variable(_) => Err(expr.1), + } + } + fn declare_local(&mut self, name: Symbol, mutable: bool, value: Stack, dest: OpDestination) { self.compiler.code.copy(value, dest); let previous_declaration = self @@ -1453,6 +1513,8 @@ pub enum Error { ExpectedBlock, NameAlreadyBound, OrPatternBindingsMismatch, + ElseOnIrrefutablePattern, + LetElseMustDiverge, Syntax(syntax::Error), } diff --git a/src/syntax.rs b/src/syntax.rs index ba2931e..cac3bc6 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -2071,14 +2071,22 @@ fn parse_variable( Expression::Literal(Literal::Nil), ) }; + + let r#else = if tokens.peek_token() == Some(Token::Identifier(Symbol::else_symbol().clone())) { + tokens.next()?; + Some(config.parse_expression(tokens)?) + } else { + None + }; + Ok(tokens.ranged( start.., Expression::Variable(Box::new(Variable { publish, mutable, pattern, - r#else: None, value, + r#else, })), )) } diff --git a/src/vm.rs b/src/vm.rs index ec76a9f..0310089 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -619,7 +619,7 @@ impl Vm { Ordering::Equal => return Ok(StepResult::Complete), Ordering::Greater => return Err(Fault::InvalidInstructionAddress), }; - println!("Executing {instruction:?}"); + // println!("Executing {instruction:?}"); let next_instruction = StepResult::from(address.checked_add(1)); let result = match instruction { LoadedOp::Return => return Ok(StepResult::Complete), @@ -754,7 +754,7 @@ impl Vm { loaded.op1, loaded.op2, loaded.dest, - |vm, lhs, rhs| dbg!(lhs.matches(vm, &rhs).map(Value::Bool)), + |vm, lhs, rhs| lhs.matches(vm, &rhs).map(Value::Bool), ), LoadedOp::BitwiseAnd(loaded) => self.op_binop( code_index, diff --git a/tests/cases/match.rsn b/tests/cases/match.rsn index bd75ca0..819df64 100644 --- a/tests/cases/match.rsn +++ b/tests/cases/match.rsn @@ -69,4 +69,14 @@ try_mismatch: { false => 0, }?"#, output: Nil, +} + +let_irrefutable: { + src: r#"let foo = 1 else { return }"#, + output: Error([Ranged(ElseOnIrrefutablePattern, SourceRange { start: 17, length: 10 })]), +} + +let_else_doesnt_diverge: { + src: r#"let 1 = true else { true }"#, + output: Error([Ranged(LetElseMustDiverge, SourceRange { start: 20, length: 4 })]), } \ No newline at end of file diff --git a/tests/harness.rs b/tests/harness.rs index dc87296..0e38913 100644 --- a/tests/harness.rs +++ b/tests/harness.rs @@ -16,7 +16,7 @@ use serde::Deserialize; fn main() { let filter = std::env::args().nth(1).unwrap_or_default(); - // let filter = String::from("chain_range_short_circuit"); + // let filter = String::from("let_irrefutable"); for entry in std::fs::read_dir("tests/cases").unwrap() { let entry = entry.unwrap().path(); if entry.extension().map_or(false, |ext| ext == "rsn") {