From 65ec2d4cca40a572733cad53a3fcafd7b365d811 Mon Sep 17 00:00:00 2001 From: John Doty Date: Sun, 4 Feb 2024 19:36:47 -0800 Subject: [PATCH] [fine] Implement the wildcard pattern --- fine/src/compiler.rs | 62 ++++++++++++++++++++++++++--------- fine/src/parser.rs | 13 +++++++- fine/src/semantics.rs | 38 +++++++++++++++++---- fine/src/tokens.rs | 11 +++++-- fine/tests/expression/is.fine | 5 ++- 5 files changed, 104 insertions(+), 25 deletions(-) diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 0a48a5f2..ba4300a8 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -629,6 +629,18 @@ fn compile_is_expression(c: &mut Compiler, tree: &Tree) -> Option<()> { fn compile_pattern(c: &mut Compiler, t: TreeRef) -> Option<()> { let tree = &c.syntax[t]; + // Let's *try* to generate good code in the presence of a wildcard pattern.... + let is_wildcard = tree + .child_tree_of_kind(c.syntax, TreeKind::WildcardPattern) + .is_some(); + + let type_expr = tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression); + + let and_index = tree.children.iter().position(|c| match c { + Child::Token(t) => t.kind == TokenKind::And, + _ => false, + }); + // If you have a binding, dup and store now, it is in scope. if let Some(binding) = tree.child_tree_of_kind(c.syntax, TreeKind::VariableBinding) { if let Some(variable) = binding.nth_token(0) { @@ -644,31 +656,51 @@ fn compile_pattern(c: &mut Compiler, t: TreeRef) -> Option<()> { ice!(c, t, "is cannot make a non-local, non-variable declaration") }; - c.push(Instruction::Dup); + // If we aren't a wildcard or we have an attached predicate then + // we will need the value on the stack, otherwise we can discard + // it. + if and_index.is_some() || !is_wildcard { + c.push(Instruction::Dup); + } c.push(Instruction::StoreLocal(*index)); } } - let type_expr = tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?; - compile_type_expr_eq(c, type_expr.nth_tree(0)?); - - let and_index = tree.children.iter().position(|c| match c { - Child::Token(t) => t.kind == TokenKind::And, - _ => false, - }); + if !is_wildcard { + let type_expr = type_expr?; + compile_type_expr_eq(c, type_expr.nth_tree(0)?); + } if let Some(and_index) = and_index { - // This is the tail end of an "AND" expression. - let jump_true_index = c.push(Instruction::JumpTrue(0)); + let jump_end_index = if is_wildcard { + // If the pattern was a wildcard then don't bother with this jump + // nonsense; we know the pattern matched, all we need to do is + // evaluate the predicate. + None + } else { + // Otherwise test the pattern to see if it passed; if it did then + // we need to run the predicate. (This is the back half of an AND + // expression.) + let jump_true_index = c.push(Instruction::JumpTrue(0)); - c.push(Instruction::PushFalse); - let jump_end_index = c.push(Instruction::Jump(0)); + c.push(Instruction::PushFalse); + let jump_end_index = c.push(Instruction::Jump(0)); - c.patch(jump_true_index, |i| Instruction::JumpTrue(i)); + c.patch(jump_true_index, |i| Instruction::JumpTrue(i)); + Some(jump_end_index) + }; compile_expression(c, tree.nth_tree(and_index + 1)?); - c.patch(jump_end_index, |i| Instruction::Jump(i)); - }; + + // If we wound up with a jump what needs patching, patch it. + if let Some(jump_end_index) = jump_end_index { + c.patch(jump_end_index, |i| Instruction::Jump(i)); + } + } else if is_wildcard { + // If there was no predicate *and* the pattern was a wildcard then + // I'll just need to push true here. + c.push(Instruction::PushTrue); + } OK } diff --git a/fine/src/parser.rs b/fine/src/parser.rs index e5c0ea2c..8a7ca568 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -163,6 +163,7 @@ pub enum TreeKind { MatchExpression, MatchBody, MatchArm, + WildcardPattern, } pub struct Tree<'a> { @@ -998,7 +999,11 @@ fn pattern(p: &mut CParser, right_power: u8) { variable_binding(p); } - type_expr(p); + if p.peek() == TokenKind::Underscore { + wildcard_pattern(p); + } else { + type_expr(p); + } if p.eat(TokenKind::And) { expression_with_power(p, right_power); @@ -1016,6 +1021,12 @@ fn variable_binding(p: &mut CParser) { p.end(m, TreeKind::VariableBinding); } +fn wildcard_pattern(p: &mut CParser) { + let m = p.start(); + p.expect_start(TokenKind::Underscore); + p.end(m, TreeKind::WildcardPattern); +} + fn argument_list(p: &mut CParser) { let m = p.start(); diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index b8a0b274..5a72fada 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -857,11 +857,21 @@ impl<'a> Semantics<'a> { // parse error, don't make more trouble. return Environment::error(); }; - self.environment_of_pattern(parent, pattern) + + // The left hand side of the `is` expression is used for wildcard types. + let Some(lhs) = tree.nth_tree(0) else { + return Environment::error(); + }; + self.environment_of_pattern(parent, pattern, lhs) } // NOTE: THIS IS CALLED DIRECTLY, NOT VIA `environment_of` TO AVOID CYCLES. - fn environment_of_pattern(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef { + fn environment_of_pattern( + &self, + parent: EnvironmentRef, + tree: &Tree, + value_expr: TreeRef, + ) -> EnvironmentRef { assert_eq!(tree.kind, TreeKind::Pattern); let Some(binding) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::VariableBinding) @@ -872,15 +882,30 @@ impl<'a> Semantics<'a> { let Some(variable) = binding.nth_token(0) else { return Environment::error(); }; - let Some(type_expr) = tree.child_of_kind(self.syntax_tree, TreeKind::TypeExpression) else { - return Environment::error(); + + let is_wildcard = tree + .child_of_kind(self.syntax_tree, TreeKind::WildcardPattern) + .is_some(); + + let variable_decl = if is_wildcard { + // If the variable is bound to a wildcard then treat the value + // expression as the declaration for the purpose of determining + // type. + value_expr + } else { + // Otherwise the binding is to the type expression which must + // match for the variable to have a value. + let Some(type_expr) = tree.child_of_kind(self.syntax_tree, TreeKind::TypeExpression) + else { + return Environment::error(); + }; + type_expr }; // TODO: This binding should be un-assignable! Don't assign to this! - // (UNLESS VERY SPECIFIC CIRCUMSTANCES; WHICH ARE COMPLEX) let mut env = Environment::new(Some(parent), Location::Local); - env.insert(variable, type_expr); + env.insert(variable, variable_decl); EnvironmentRef::new(env) } @@ -1927,6 +1952,7 @@ pub fn check(s: &Semantics) { TreeKind::IsExpression => check_is_expression(s, tree), TreeKind::VariableBinding => check_variable_binding(s, tree), TreeKind::Pattern => check_pattern(s, tree), + TreeKind::WildcardPattern => {} TreeKind::MatchArm => {} TreeKind::MatchBody => {} diff --git a/fine/src/tokens.rs b/fine/src/tokens.rs index de87f659..87271bf0 100644 --- a/fine/src/tokens.rs +++ b/fine/src/tokens.rs @@ -57,6 +57,7 @@ pub enum TokenKind { Select, Selff, True, + Underscore, While, Yield, } @@ -351,6 +352,11 @@ impl<'a> Tokens<'a> { return TokenKind::Yield; } } + '_' => { + if ident == "_" { + return TokenKind::Underscore; + } + } _ => (), } @@ -605,10 +611,11 @@ mod tests { test_tokens!( more_more_keywords, - "in is match", + "in is match _", (0, In, "in"), (3, Is, "is"), - (6, Match, "match") + (6, Match, "match"), + (12, Underscore, "_") ); test_tokens!( diff --git a/fine/tests/expression/is.fine b/fine/tests/expression/is.fine index ade0ecd6..50a61abc 100644 --- a/fine/tests/expression/is.fine +++ b/fine/tests/expression/is.fine @@ -12,6 +12,9 @@ fun test() -> f64 { if b is c:Foo and c.a == 24 { result = result + 10; } + if b is c:_ { + result = result + 100; // underscore should always match! + } if b is c:Foo { result = result + c.a; // c should still be in scope! } @@ -19,4 +22,4 @@ fun test() -> f64 { } // @no-errors -// @eval: Float(1001.0) \ No newline at end of file +// @eval: Float(1101.0) \ No newline at end of file