[fine] Starting on pattern matching, make patterns explicit
This commit is contained in:
parent
f00b9e22e7
commit
deed9d9a45
4 changed files with 178 additions and 50 deletions
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::{SyntaxTree, Tree, TreeKind, TreeRef},
|
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
|
||||||
semantics::{Declaration, Location, Semantics, Type},
|
semantics::{Declaration, Location, Semantics, Type},
|
||||||
tokens::TokenKind,
|
tokens::TokenKind,
|
||||||
};
|
};
|
||||||
|
|
@ -298,7 +298,7 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) {
|
||||||
TreeKind::FieldValue => compile_field_value(c, t, tree),
|
TreeKind::FieldValue => compile_field_value(c, t, tree),
|
||||||
TreeKind::GroupingExpression => compile_grouping(c, tree),
|
TreeKind::GroupingExpression => compile_grouping(c, tree),
|
||||||
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
|
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
|
||||||
TreeKind::IsExpression => compile_is_expression(c, t, tree),
|
TreeKind::IsExpression => compile_is_expression(c, tree),
|
||||||
TreeKind::LiteralExpression => compile_literal(c, t, tree),
|
TreeKind::LiteralExpression => compile_literal(c, t, tree),
|
||||||
TreeKind::MemberAccess => compile_member_access(c, tree),
|
TreeKind::MemberAccess => compile_member_access(c, tree),
|
||||||
TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree),
|
TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree),
|
||||||
|
|
@ -620,11 +620,16 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_is_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()> {
|
fn compile_is_expression(c: &mut Compiler, tree: &Tree) -> Option<()> {
|
||||||
compile_expression(c, tree.nth_tree(0)?);
|
compile_expression(c, tree.nth_tree(0)?);
|
||||||
|
|
||||||
|
compile_pattern(c, tree.nth_tree(2)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_pattern(c: &mut Compiler, t: TreeRef) -> Option<()> {
|
||||||
|
let tree = &c.syntax[t];
|
||||||
|
|
||||||
// If you have a binding, dup and store now, it is in scope.
|
// If you have a binding, dup and store now, it is in scope.
|
||||||
let type_expr =
|
|
||||||
if let Some(binding) = tree.child_tree_of_kind(c.syntax, TreeKind::VariableBinding) {
|
if let Some(binding) = tree.child_tree_of_kind(c.syntax, TreeKind::VariableBinding) {
|
||||||
if let Some(variable) = binding.nth_token(0) {
|
if let Some(variable) = binding.nth_token(0) {
|
||||||
let environment = c.semantics.environment_of(t);
|
let environment = c.semantics.environment_of(t);
|
||||||
|
|
@ -642,16 +647,18 @@ fn compile_is_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()
|
||||||
c.push(Instruction::Dup);
|
c.push(Instruction::Dup);
|
||||||
c.push(Instruction::StoreLocal(*index));
|
c.push(Instruction::StoreLocal(*index));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?
|
let type_expr = tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?;
|
||||||
} else {
|
|
||||||
tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?
|
|
||||||
};
|
|
||||||
|
|
||||||
compile_type_expr_eq(c, type_expr.nth_tree(0)?);
|
compile_type_expr_eq(c, type_expr.nth_tree(0)?);
|
||||||
|
|
||||||
if let Some(tok) = tree.nth_token(3) {
|
let and_index = tree.children.iter().position(|c| match c {
|
||||||
if tok.kind == TokenKind::And {
|
Child::Token(t) => t.kind == TokenKind::And,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
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_true_index = c.push(Instruction::JumpTrue(0));
|
||||||
|
|
||||||
c.push(Instruction::PushFalse);
|
c.push(Instruction::PushFalse);
|
||||||
|
|
@ -659,10 +666,9 @@ fn compile_is_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()
|
||||||
|
|
||||||
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
|
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
|
||||||
|
|
||||||
compile_expression(c, tree.nth_tree(4)?);
|
compile_expression(c, tree.nth_tree(and_index + 1)?);
|
||||||
c.patch(jump_end_index, |i| Instruction::Jump(i));
|
c.patch(jump_end_index, |i| Instruction::Jump(i));
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ pub enum TreeKind {
|
||||||
GroupingExpression,
|
GroupingExpression,
|
||||||
Identifier,
|
Identifier,
|
||||||
IfStatement,
|
IfStatement,
|
||||||
|
IsExpression,
|
||||||
LetStatement,
|
LetStatement,
|
||||||
ListConstructor,
|
ListConstructor,
|
||||||
ListConstructorElement,
|
ListConstructorElement,
|
||||||
|
|
@ -156,8 +157,12 @@ pub enum TreeKind {
|
||||||
TypeParameterList,
|
TypeParameterList,
|
||||||
UnaryExpression,
|
UnaryExpression,
|
||||||
|
|
||||||
IsExpression,
|
Pattern,
|
||||||
VariableBinding,
|
VariableBinding,
|
||||||
|
|
||||||
|
MatchExpression,
|
||||||
|
MatchBody,
|
||||||
|
MatchArm,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tree<'a> {
|
pub struct Tree<'a> {
|
||||||
|
|
@ -902,6 +907,7 @@ const EXPRESSION_FIRST: &[TokenKind] = &[
|
||||||
TokenKind::Selff,
|
TokenKind::Selff,
|
||||||
TokenKind::LeftBracket,
|
TokenKind::LeftBracket,
|
||||||
TokenKind::New,
|
TokenKind::New,
|
||||||
|
TokenKind::Match,
|
||||||
];
|
];
|
||||||
|
|
||||||
fn expression(p: &mut CParser) {
|
fn expression(p: &mut CParser) {
|
||||||
|
|
@ -979,20 +985,26 @@ fn is_expression(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClos
|
||||||
let m = p.start_before(left);
|
let m = p.start_before(left);
|
||||||
p.advance(); // Consume the operator
|
p.advance(); // Consume the operator
|
||||||
|
|
||||||
// This is hard to do with just, like, no lookahead.
|
pattern(p, right_power);
|
||||||
if p.peek() == TokenKind::Identifier && p.peek_next() == TokenKind::Colon {
|
|
||||||
// This is a variable binding.
|
p.end(m, TreeKind::IsExpression)
|
||||||
variable_binding(p);
|
|
||||||
} else {
|
|
||||||
type_expr(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional predicates go into the right-hand-side.
|
fn pattern(p: &mut CParser, right_power: u8) {
|
||||||
|
let m = p.start();
|
||||||
|
|
||||||
|
// patterns are very simple.
|
||||||
|
if p.peek() == TokenKind::Identifier && p.peek_next() == TokenKind::Colon {
|
||||||
|
variable_binding(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_expr(p);
|
||||||
|
|
||||||
if p.eat(TokenKind::And) {
|
if p.eat(TokenKind::And) {
|
||||||
expression_with_power(p, right_power);
|
expression_with_power(p, right_power);
|
||||||
}
|
}
|
||||||
|
|
||||||
p.end(m, TreeKind::IsExpression)
|
p.end(m, TreeKind::Pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn variable_binding(p: &mut CParser) {
|
fn variable_binding(p: &mut CParser) {
|
||||||
|
|
@ -1000,7 +1012,6 @@ fn variable_binding(p: &mut CParser) {
|
||||||
|
|
||||||
p.expect_start(TokenKind::Identifier);
|
p.expect_start(TokenKind::Identifier);
|
||||||
p.expect_start(TokenKind::Colon);
|
p.expect_start(TokenKind::Colon);
|
||||||
type_expr(p);
|
|
||||||
|
|
||||||
p.end(m, TreeKind::VariableBinding);
|
p.end(m, TreeKind::VariableBinding);
|
||||||
}
|
}
|
||||||
|
|
@ -1055,9 +1066,14 @@ fn prefix_expression(p: &mut CParser) -> Option<MarkClosed> {
|
||||||
TokenKind::LeftBracket => list_constructor(p),
|
TokenKind::LeftBracket => list_constructor(p),
|
||||||
|
|
||||||
TokenKind::New => object_constructor(p),
|
TokenKind::New => object_constructor(p),
|
||||||
|
TokenKind::Match => match_expression(p),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
assert!(!p.at_any(EXPRESSION_FIRST));
|
assert!(
|
||||||
|
!p.at_any(EXPRESSION_FIRST),
|
||||||
|
"TokenKind::{:?} is in EXPRESSION_FIRST but not handled; is this a new kind of prefix?",
|
||||||
|
p.peek()
|
||||||
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1214,6 +1230,60 @@ fn field_value(p: &mut CParser) {
|
||||||
p.end(m, TreeKind::FieldValue);
|
p.end(m, TreeKind::FieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn match_expression(p: &mut CParser) -> MarkClosed {
|
||||||
|
let m = p.start();
|
||||||
|
|
||||||
|
p.expect_start(TokenKind::Match);
|
||||||
|
expression(p); // ?
|
||||||
|
if p.at(TokenKind::LeftBrace) {
|
||||||
|
match_body(p);
|
||||||
|
} else {
|
||||||
|
p.error("expected a '{' to start the alternatives after `match`");
|
||||||
|
}
|
||||||
|
|
||||||
|
p.end(m, TreeKind::MatchExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_body(p: &mut CParser) {
|
||||||
|
let m = p.start();
|
||||||
|
|
||||||
|
p.expect_start(TokenKind::LeftBrace);
|
||||||
|
while !p.at(TokenKind::RightBrace) && !p.eof() {
|
||||||
|
if p.at(TokenKind::Identifier) {
|
||||||
|
// TODO: type_expr_first ?
|
||||||
|
match_arm(p);
|
||||||
|
} else {
|
||||||
|
if p.at_any(STATEMENT_RECOVERY) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.advance_with_error("expected a type expression to start a match arm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.expect(
|
||||||
|
TokenKind::RightBrace,
|
||||||
|
"expected a '}' to end the alternatives in a match",
|
||||||
|
);
|
||||||
|
|
||||||
|
p.end(m, TreeKind::MatchBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_arm(p: &mut CParser) {
|
||||||
|
let m = p.start();
|
||||||
|
|
||||||
|
pattern(p, 0);
|
||||||
|
|
||||||
|
if p.eat(TokenKind::Arrow) {
|
||||||
|
expression(p);
|
||||||
|
} else {
|
||||||
|
p.error("expected an arrow after the pattern in a match arm");
|
||||||
|
}
|
||||||
|
if !p.at(TokenKind::RightBrace) {
|
||||||
|
p.expect(TokenKind::Comma, "expected a comma between match arms");
|
||||||
|
}
|
||||||
|
|
||||||
|
p.end(m, TreeKind::MatchArm);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -850,14 +850,29 @@ impl<'a> Semantics<'a> {
|
||||||
|
|
||||||
fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
||||||
assert_eq!(tree.kind, TreeKind::IsExpression);
|
assert_eq!(tree.kind, TreeKind::IsExpression);
|
||||||
|
|
||||||
|
// The environment of an `is` expression is the environment produced by the pattern.
|
||||||
|
let Some(pattern) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::Pattern) else {
|
||||||
|
// Should really have a pattern in there; otherwise there was a
|
||||||
|
// parse error, don't make more trouble.
|
||||||
|
return Environment::error();
|
||||||
|
};
|
||||||
|
self.environment_of_pattern(parent, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: THIS IS CALLED DIRECTLY, NOT VIA `environment_of` TO AVOID CYCLES.
|
||||||
|
fn environment_of_pattern(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
||||||
|
assert_eq!(tree.kind, TreeKind::Pattern);
|
||||||
|
|
||||||
let Some(binding) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::VariableBinding)
|
let Some(binding) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::VariableBinding)
|
||||||
else {
|
else {
|
||||||
|
// No binding, no new environment.
|
||||||
return parent;
|
return parent;
|
||||||
};
|
};
|
||||||
let Some(variable) = binding.nth_token(0) else {
|
let Some(variable) = binding.nth_token(0) else {
|
||||||
return Environment::error();
|
return Environment::error();
|
||||||
};
|
};
|
||||||
let Some(type_expr) = binding.nth_tree(2) else {
|
let Some(type_expr) = tree.child_of_kind(self.syntax_tree, TreeKind::TypeExpression) else {
|
||||||
return Environment::error();
|
return Environment::error();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -866,7 +881,7 @@ impl<'a> Semantics<'a> {
|
||||||
|
|
||||||
let mut env = Environment::new(Some(parent), Location::Local);
|
let mut env = Environment::new(Some(parent), Location::Local);
|
||||||
env.insert(variable, type_expr);
|
env.insert(variable, type_expr);
|
||||||
return EnvironmentRef::new(env);
|
EnvironmentRef::new(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn class_of(&self, t: TreeRef) -> ClassRef {
|
pub fn class_of(&self, t: TreeRef) -> ClassRef {
|
||||||
|
|
@ -1911,6 +1926,11 @@ pub fn check(s: &Semantics) {
|
||||||
|
|
||||||
TreeKind::IsExpression => check_is_expression(s, tree),
|
TreeKind::IsExpression => check_is_expression(s, tree),
|
||||||
TreeKind::VariableBinding => check_variable_binding(s, tree),
|
TreeKind::VariableBinding => check_variable_binding(s, tree),
|
||||||
|
TreeKind::Pattern => check_pattern(s, tree),
|
||||||
|
|
||||||
|
TreeKind::MatchArm => {}
|
||||||
|
TreeKind::MatchBody => {}
|
||||||
|
TreeKind::MatchExpression => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2090,6 +2110,26 @@ fn check_is_expression(_s: &Semantics, _tree: &Tree) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_pattern(s: &Semantics, tree: &Tree) {
|
||||||
|
// If there's an AND then it must produce a boolean.
|
||||||
|
let and_index = tree.children.iter().position(|c| match c {
|
||||||
|
Child::Token(t) => t.kind == TokenKind::And,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
if let Some(and_index) = and_index {
|
||||||
|
if let Some(pred) = tree.nth_tree(and_index + 1) {
|
||||||
|
let predicate_type = s.type_of(pred);
|
||||||
|
if !s.can_convert(&predicate_type, &Type::Bool) {
|
||||||
|
// TODO: TEST
|
||||||
|
s.report_error_tree_ref(
|
||||||
|
pred,
|
||||||
|
format!("this predicate produces {predicate_type}, but must produce a boolean"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_variable_binding(_s: &Semantics, _tree: &Tree) {
|
fn check_variable_binding(_s: &Semantics, _tree: &Tree) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ pub enum TokenKind {
|
||||||
In,
|
In,
|
||||||
Is,
|
Is,
|
||||||
Let,
|
Let,
|
||||||
|
Match,
|
||||||
New,
|
New,
|
||||||
Or,
|
Or,
|
||||||
Return,
|
Return,
|
||||||
|
|
@ -307,6 +308,11 @@ impl<'a> Tokens<'a> {
|
||||||
return TokenKind::Let;
|
return TokenKind::Let;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'm' => {
|
||||||
|
if ident == "match" {
|
||||||
|
return TokenKind::Match;
|
||||||
|
}
|
||||||
|
}
|
||||||
'n' => {
|
'n' => {
|
||||||
if ident == "new" {
|
if ident == "new" {
|
||||||
return TokenKind::New;
|
return TokenKind::New;
|
||||||
|
|
@ -597,7 +603,13 @@ mod tests {
|
||||||
(58, New, "new")
|
(58, New, "new")
|
||||||
);
|
);
|
||||||
|
|
||||||
test_tokens!(more_more_keywords, "in is", (0, In, "in"), (3, Is, "is"));
|
test_tokens!(
|
||||||
|
more_more_keywords,
|
||||||
|
"in is match",
|
||||||
|
(0, In, "in"),
|
||||||
|
(3, Is, "is"),
|
||||||
|
(6, Match, "match")
|
||||||
|
);
|
||||||
|
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
strings,
|
strings,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue