[fine] Implement the wildcard pattern

This commit is contained in:
John Doty 2024-02-04 19:36:47 -08:00
parent e0721d09fc
commit 65ec2d4cca
5 changed files with 104 additions and 25 deletions

View file

@ -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
}

View file

@ -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();

View file

@ -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 => {}

View file

@ -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!(

View file

@ -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)
// @eval: Float(1101.0)