From 5bb36ef2e0552e5db8f93f5d5f571303f48d09f2 Mon Sep 17 00:00:00 2001 From: Enrico Granata Date: Mon, 22 Sep 2025 21:03:48 -0600 Subject: [PATCH] Implement if-case --- .../src/do_compile/nodes/for_statement.rs | 8 +++--- .../src/do_compile/nodes/if_cond_case.rs | 9 +++++++ .../src/do_compile/nodes/if_cond_expr.rs | 11 ++++++++ .../nodes/match_pattern_enum_case.rs | 16 +++++++----- compiler-lib/src/do_compile/nodes/mod.rs | 2 ++ docs/manual.md | 18 +++++++++++++ lib/aria/core/maybe.aria | 11 +++----- lib/aria/core/result.aria | 7 +++--- lib/aria/network/retry.aria | 4 +-- parser-lib/src/ast/mod.rs | 25 ++++++++++++++++++- parser-lib/src/ast/nodes/if_cond_case.rs | 21 ++++++++++++++++ parser-lib/src/ast/nodes/if_cond_expr.rs | 24 ++++++++++++++++++ parser-lib/src/ast/nodes/if_cond_piece.rs | 6 ++--- .../src/ast/nodes/match_pattern_enum_case.rs | 2 +- parser-lib/src/ast/nodes/mod.rs | 2 ++ parser-lib/src/grammar/grammar.pest | 4 ++- tests/if_case_elsif.aria | 16 ++++++++++++ tests/if_case_empty.aria | 13 ++++++++++ tests/if_case_some.aria | 14 +++++++++++ 19 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 compiler-lib/src/do_compile/nodes/if_cond_case.rs create mode 100644 compiler-lib/src/do_compile/nodes/if_cond_expr.rs create mode 100644 parser-lib/src/ast/nodes/if_cond_case.rs create mode 100644 parser-lib/src/ast/nodes/if_cond_expr.rs create mode 100644 tests/if_case_elsif.aria create mode 100644 tests/if_case_empty.aria create mode 100644 tests/if_case_some.aria diff --git a/compiler-lib/src/do_compile/nodes/for_statement.rs b/compiler-lib/src/do_compile/nodes/for_statement.rs index 131df6ac..32858ecc 100644 --- a/compiler-lib/src/do_compile/nodes/for_statement.rs +++ b/compiler-lib/src/do_compile/nodes/for_statement.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use aria_parser::ast::{ AssignStatement, BreakStatement, CodeBlock, DeclarationId, ElsePiece, Expression, Identifier, - IfCondPiece, IfPiece, IfStatement, ParenExpression, PostfixExpression, PostfixRvalue, Primary, - Statement, UnaryOperation, ValDeclStatement, WhileStatement, + IfCondExpr, IfCondPiece, IfPiece, IfStatement, ParenExpression, PostfixExpression, + PostfixRvalue, Primary, Statement, UnaryOperation, ValDeclStatement, WhileStatement, }; use crate::do_compile::{CompilationResult, CompileNode, CompileParams}; @@ -115,7 +115,7 @@ impl<'a> CompileNode<'a> for aria_parser::ast::ForStatement { // if !__for__any_hit { } let if_not_any_hit = IfCondPiece { loc: self.loc.clone(), - expression: Box::new(check_any_hit_expr), + expression: IfCondExpr::Expression(check_any_hit_expr), then: CodeBlock { loc: self.loc.clone(), entries: if let Some(els) = &self.els { @@ -183,7 +183,7 @@ impl<'a> CompileNode<'a> for aria_parser::ast::ForStatement { iff: IfPiece { content: IfCondPiece { loc: self.loc.clone(), - expression: Box::new(check_done_expr), + expression: IfCondExpr::Expression(check_done_expr), then: if_done_blk, }, }, diff --git a/compiler-lib/src/do_compile/nodes/if_cond_case.rs b/compiler-lib/src/do_compile/nodes/if_cond_case.rs new file mode 100644 index 00000000..0c9419ef --- /dev/null +++ b/compiler-lib/src/do_compile/nodes/if_cond_case.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +use crate::do_compile::{CompilationResult, CompileNode, CompileParams}; + +impl<'a> CompileNode<'a> for aria_parser::ast::IfCondCase { + fn do_compile(&self, params: &'a mut CompileParams) -> CompilationResult { + self.target.do_compile(params)?; + self.pattern.do_compile(params) + } +} diff --git a/compiler-lib/src/do_compile/nodes/if_cond_expr.rs b/compiler-lib/src/do_compile/nodes/if_cond_expr.rs new file mode 100644 index 00000000..72fe9137 --- /dev/null +++ b/compiler-lib/src/do_compile/nodes/if_cond_expr.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +use crate::do_compile::{CompilationResult, CompileNode, CompileParams}; + +impl<'a> CompileNode<'a> for aria_parser::ast::IfCondExpr { + fn do_compile(&self, params: &'a mut CompileParams) -> CompilationResult { + match self { + aria_parser::ast::IfCondExpr::IfCondCase(p) => p.do_compile(params), + aria_parser::ast::IfCondExpr::Expression(e) => e.do_compile(params), + } + } +} diff --git a/compiler-lib/src/do_compile/nodes/match_pattern_enum_case.rs b/compiler-lib/src/do_compile/nodes/match_pattern_enum_case.rs index 499f3ae5..3dc5ceef 100644 --- a/compiler-lib/src/do_compile/nodes/match_pattern_enum_case.rs +++ b/compiler-lib/src/do_compile/nodes/match_pattern_enum_case.rs @@ -5,6 +5,8 @@ use crate::{ func_builder::BasicBlockOpcode, }; +// in theory you may use __match_control_expr, which is defined by the match statement +// but if you do, this breaks if case matches, so prefer to dup/pop as necessary instead impl<'a> CompileNode<'a> for aria_parser::ast::MatchPatternEnumCase { fn do_compile(&self, params: &'a mut CompileParams) -> CompilationResult { let case_name_idx = self.insert_const_or_fail( @@ -12,6 +14,13 @@ impl<'a> CompileNode<'a> for aria_parser::ast::MatchPatternEnumCase { ConstantValue::String(self.case.value.clone()), &self.loc, )?; + let has_decl = self.payload.is_some(); + if has_decl { + params + .writer + .get_current_block() + .write_opcode_and_source_info(BasicBlockOpcode::Dup, self.loc.clone()); + } params .writer .get_current_block() @@ -48,6 +57,7 @@ impl<'a> CompileNode<'a> for aria_parser::ast::MatchPatternEnumCase { params .writer .get_current_block() + .write_opcode_and_source_info(BasicBlockOpcode::Pop, self.loc.clone()) .write_opcode_and_source_info(BasicBlockOpcode::PushFalse, self.loc.clone()); params .writer @@ -57,12 +67,6 @@ impl<'a> CompileNode<'a> for aria_parser::ast::MatchPatternEnumCase { self.loc.clone(), ); params.writer.set_current_block(if_true); - params.scope.emit_read( - "__match_control_expr", - &mut params.module.constants, - params.writer.get_current_block(), - p.loc.clone(), - )?; params .writer .get_current_block() diff --git a/compiler-lib/src/do_compile/nodes/mod.rs b/compiler-lib/src/do_compile/nodes/mod.rs index c207377b..738cfa2c 100644 --- a/compiler-lib/src/do_compile/nodes/mod.rs +++ b/compiler-lib/src/do_compile/nodes/mod.rs @@ -18,6 +18,8 @@ mod function_body; mod function_decl; mod guard_block; mod identifier; +mod if_cond_case; +mod if_cond_expr; mod if_statement; mod import_from_statement; mod import_statement; diff --git a/docs/manual.md b/docs/manual.md index 71776469..c39d9096 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -524,6 +524,24 @@ func main() { } ``` +For simple pattern matches, i.e. one enum, and matching to a case, with our without payload extraction, `if case` is also available: + +``` +func main() { + val may = Maybe::Some(3); + + if case Some(value) = may { + println(value); # prints 3 + } elsif case None = may { + println("None"); # does not print anything + } else { + println("not a Maybe"); # does not print anything + } +} +``` + +If the right-hand side of `if case` is not an enum, a runtime error occurs. + ## ⁉️ Maybe and Result `Maybe` is an enum that represents a potentially missing value. It is defined as diff --git a/lib/aria/core/maybe.aria b/lib/aria/core/maybe.aria index 85d138da..e5e1f297 100644 --- a/lib/aria/core/maybe.aria +++ b/lib/aria/core/maybe.aria @@ -3,13 +3,10 @@ flag: no_std; extension Maybe { func prettyprint() { - match this { - case None => { - return "None"; - }, - case Some(value) => { - return "Some({0})".format(value); - } + if case Some(value) = this { + return "Some({0})".format(value); + } else { + return "None"; } } } diff --git a/lib/aria/core/result.aria b/lib/aria/core/result.aria index e54c2aa5..bab89dd7 100644 --- a/lib/aria/core/result.aria +++ b/lib/aria/core/result.aria @@ -6,9 +6,10 @@ func err(e) { return Result::Err(e); } extension Result { type func new_with_maybe(m: Maybe) { - match m { - case Some(v) => { return ok(v); } - case None => { return err(Unit.new()); } + if case Some(v) = m { + return ok(v); + } else { + return err(Unit.new()); } } diff --git a/lib/aria/network/retry.aria b/lib/aria/network/retry.aria index 9ebdccd4..0bf17ba3 100644 --- a/lib/aria/network/retry.aria +++ b/lib/aria/network/retry.aria @@ -53,8 +53,8 @@ func retry(f, check, attempts_count = 3, delay_ms = 500) { } } - if last_exception.is_Some() { - return RetryResult::Exception(last_exception.unwrap_Some()); + if case Some(le) = last_exception { + return RetryResult::Exception(le); } else { return RetryResult::Fail(last_result); } diff --git a/parser-lib/src/ast/mod.rs b/parser-lib/src/ast/mod.rs index a6604e5a..b700e0c7 100644 --- a/parser-lib/src/ast/mod.rs +++ b/parser-lib/src/ast/mod.rs @@ -807,10 +807,33 @@ pub struct WriteOpEqStatement { pub val: Expression, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IfCondCase { + pub loc: SourcePointer, + pub pattern: MatchPatternEnumCase, + pub target: Expression, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IfCondExpr { + IfCondCase(IfCondCase), + Expression(Expression), +} + +impl IfCondExpr { + pub fn loc(&self) -> &SourcePointer { + match self { + Self::IfCondCase(c) => &c.loc, + Self::Expression(e) => e.loc(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct IfCondPiece { pub loc: SourcePointer, - pub expression: Box, + pub expression: IfCondExpr, pub then: CodeBlock, } diff --git a/parser-lib/src/ast/nodes/if_cond_case.rs b/parser-lib/src/ast/nodes/if_cond_case.rs new file mode 100644 index 00000000..20b5bf8c --- /dev/null +++ b/parser-lib/src/ast/nodes/if_cond_case.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +use crate::{ + ast::{ + IfCondCase, + derive::Derive, + prettyprint::{PrettyPrintable, printout_accumulator::PrintoutAccumulator}, + }, + gen_from_components, +}; + +use crate::ast::{Expression, MatchPatternEnumCase}; + +impl Derive for IfCondCase { + gen_from_components!(if_cond_case; pattern: MatchPatternEnumCase, target: Expression); +} + +impl PrettyPrintable for IfCondCase { + fn prettyprint(&self, buffer: PrintoutAccumulator) -> PrintoutAccumulator { + buffer << &self.pattern << " = " << &self.target + } +} diff --git a/parser-lib/src/ast/nodes/if_cond_expr.rs b/parser-lib/src/ast/nodes/if_cond_expr.rs new file mode 100644 index 00000000..d2806014 --- /dev/null +++ b/parser-lib/src/ast/nodes/if_cond_expr.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +use crate::{ + ast::{ + IfCondExpr, + derive::Derive, + prettyprint::{PrettyPrintable, printout_accumulator::PrintoutAccumulator}, + }, + gen_from_options, +}; + +use crate::ast::{Expression, IfCondCase}; + +impl Derive for IfCondExpr { + gen_from_options!(if_cond; (if_cond_case, IfCondCase), (expression, Expression)); +} + +impl PrettyPrintable for IfCondExpr { + fn prettyprint(&self, buffer: PrintoutAccumulator) -> PrintoutAccumulator { + match self { + Self::IfCondCase(c) => c.prettyprint(buffer), + Self::Expression(e) => e.prettyprint(buffer), + } + } +} diff --git a/parser-lib/src/ast/nodes/if_cond_piece.rs b/parser-lib/src/ast/nodes/if_cond_piece.rs index 67da1a3d..48c6a9e7 100644 --- a/parser-lib/src/ast/nodes/if_cond_piece.rs +++ b/parser-lib/src/ast/nodes/if_cond_piece.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ ast::{ - CodeBlock, Expression, IfCondPiece, SourceBuffer, + CodeBlock, IfCondExpr, IfCondPiece, SourceBuffer, derive::Derive, prettyprint::{PrettyPrintable, printout_accumulator::PrintoutAccumulator}, }, @@ -15,11 +15,11 @@ impl Derive for IfCondPiece { let mut inner = p.into_inner(); let expr = inner.next().expect("need expression"); let body = inner.next().expect("need body"); - let expression = Expression::from_parse_tree(expr, source); + let expression = IfCondExpr::from_parse_tree(expr, source); let then = CodeBlock::from_parse_tree(body, source); Self { loc: source.pointer(loc), - expression: Box::new(expression), + expression, then, } } diff --git a/parser-lib/src/ast/nodes/match_pattern_enum_case.rs b/parser-lib/src/ast/nodes/match_pattern_enum_case.rs index 7c5b63b7..43eae4f5 100644 --- a/parser-lib/src/ast/nodes/match_pattern_enum_case.rs +++ b/parser-lib/src/ast/nodes/match_pattern_enum_case.rs @@ -27,7 +27,7 @@ impl Derive for MatchPatternEnumCase { impl PrettyPrintable for MatchPatternEnumCase { fn prettyprint(&self, buffer: PrintoutAccumulator) -> PrintoutAccumulator { - let buffer = buffer << " case " << &self.case; + let buffer = buffer << "case " << &self.case; if let Some(p) = &self.payload { buffer << "(" << p << ")" } else { diff --git a/parser-lib/src/ast/nodes/mod.rs b/parser-lib/src/ast/nodes/mod.rs index aa81a986..f7f4a1d2 100644 --- a/parser-lib/src/ast/nodes/mod.rs +++ b/parser-lib/src/ast/nodes/mod.rs @@ -28,6 +28,8 @@ mod function_decl; mod guard_block; mod identifier; mod identifier_list; +mod if_cond_case; +mod if_cond_expr; mod if_cond_piece; mod if_piece; mod if_statement; diff --git a/parser-lib/src/grammar/grammar.pest b/parser-lib/src/grammar/grammar.pest index 5cd60a29..957bd67b 100644 --- a/parser-lib/src/grammar/grammar.pest +++ b/parser-lib/src/grammar/grammar.pest @@ -94,7 +94,9 @@ val_write_stmt = { postfix_lv ~ "=" ~ expression ~ ";" } add_op_eq = @{ "+=" | "-=" | "*=" | "/=" | "%=" } val_add_eq_write = { postfix_lv ~ add_op_eq ~ expression ~ ";" } -if_cond_piece = { expression ~ code_block } +if_cond_case = { match_pattern_enum_case ~ "=" ~ expression } +if_cond = { if_cond_case | expression } +if_cond_piece = { if_cond ~ code_block } if_piece = { "if" ~ if_cond_piece } elsif_piece = { "elsif" ~ if_cond_piece } else_piece = { "else" ~ code_block } diff --git a/tests/if_case_elsif.aria b/tests/if_case_elsif.aria new file mode 100644 index 00000000..a125e2cb --- /dev/null +++ b/tests/if_case_elsif.aria @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 +func get_payload_value(p) { + if case Some(n) = p { + return n; + } elsif case None = p { + return -1; + } else { + return 0; + } +} + +func main() { + assert get_payload_value(Maybe::Some(5)) == 5; + assert get_payload_value(Maybe::None) == -1; + assert get_payload_value(Result::Err("not a Maybe")) == 0; +} diff --git a/tests/if_case_empty.aria b/tests/if_case_empty.aria new file mode 100644 index 00000000..235f7d4e --- /dev/null +++ b/tests/if_case_empty.aria @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 +func main() { + val x = Maybe::None; + val hit = false; + + if case None = x { + hit = true; + } else { + assert false; + } + + assert hit; +} diff --git a/tests/if_case_some.aria b/tests/if_case_some.aria new file mode 100644 index 00000000..6b9b9fa2 --- /dev/null +++ b/tests/if_case_some.aria @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 +func main() { + val x = Maybe::Some(3); + val hit = false; + + if case Some(v) = x { + assert v == 3; + hit = true; + } else { + assert false; + } + + assert hit; +}