[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 crate::{
|
||||
parser::{SyntaxTree, Tree, TreeKind, TreeRef},
|
||||
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
|
||||
semantics::{Declaration, Location, Semantics, Type},
|
||||
tokens::TokenKind,
|
||||
};
|
||||
|
|
@ -298,7 +298,7 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) {
|
|||
TreeKind::FieldValue => compile_field_value(c, t, tree),
|
||||
TreeKind::GroupingExpression => compile_grouping(c, 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::MemberAccess => compile_member_access(c, 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
|
||||
}
|
||||
|
||||
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_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.
|
||||
let type_expr =
|
||||
if let Some(binding) = tree.child_tree_of_kind(c.syntax, TreeKind::VariableBinding) {
|
||||
if let Some(variable) = binding.nth_token(0) {
|
||||
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::StoreLocal(*index));
|
||||
}
|
||||
}
|
||||
|
||||
binding.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?
|
||||
} else {
|
||||
tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?
|
||||
};
|
||||
|
||||
let type_expr = tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?;
|
||||
compile_type_expr_eq(c, type_expr.nth_tree(0)?);
|
||||
|
||||
if let Some(tok) = tree.nth_token(3) {
|
||||
if tok.kind == TokenKind::And {
|
||||
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 {
|
||||
// This is the tail end of an "AND" expression.
|
||||
let jump_true_index = c.push(Instruction::JumpTrue(0));
|
||||
|
||||
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));
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OK
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ pub enum TreeKind {
|
|||
GroupingExpression,
|
||||
Identifier,
|
||||
IfStatement,
|
||||
IsExpression,
|
||||
LetStatement,
|
||||
ListConstructor,
|
||||
ListConstructorElement,
|
||||
|
|
@ -156,8 +157,12 @@ pub enum TreeKind {
|
|||
TypeParameterList,
|
||||
UnaryExpression,
|
||||
|
||||
IsExpression,
|
||||
Pattern,
|
||||
VariableBinding,
|
||||
|
||||
MatchExpression,
|
||||
MatchBody,
|
||||
MatchArm,
|
||||
}
|
||||
|
||||
pub struct Tree<'a> {
|
||||
|
|
@ -902,6 +907,7 @@ const EXPRESSION_FIRST: &[TokenKind] = &[
|
|||
TokenKind::Selff,
|
||||
TokenKind::LeftBracket,
|
||||
TokenKind::New,
|
||||
TokenKind::Match,
|
||||
];
|
||||
|
||||
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);
|
||||
p.advance(); // Consume the operator
|
||||
|
||||
// This is hard to do with just, like, no lookahead.
|
||||
pattern(p, right_power);
|
||||
|
||||
p.end(m, TreeKind::IsExpression)
|
||||
}
|
||||
|
||||
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 {
|
||||
// This is a variable binding.
|
||||
variable_binding(p);
|
||||
} else {
|
||||
type_expr(p);
|
||||
}
|
||||
|
||||
// Additional predicates go into the right-hand-side.
|
||||
type_expr(p);
|
||||
|
||||
if p.eat(TokenKind::And) {
|
||||
expression_with_power(p, right_power);
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::IsExpression)
|
||||
p.end(m, TreeKind::Pattern);
|
||||
}
|
||||
|
||||
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::Colon);
|
||||
type_expr(p);
|
||||
|
||||
p.end(m, TreeKind::VariableBinding);
|
||||
}
|
||||
|
|
@ -1055,9 +1066,14 @@ fn prefix_expression(p: &mut CParser) -> Option<MarkClosed> {
|
|||
TokenKind::LeftBracket => list_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;
|
||||
}
|
||||
};
|
||||
|
|
@ -1214,6 +1230,60 @@ fn field_value(p: &mut CParser) {
|
|||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -850,14 +850,29 @@ impl<'a> Semantics<'a> {
|
|||
|
||||
fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
||||
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)
|
||||
else {
|
||||
// No binding, no new environment.
|
||||
return parent;
|
||||
};
|
||||
let Some(variable) = binding.nth_token(0) else {
|
||||
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();
|
||||
};
|
||||
|
||||
|
|
@ -866,7 +881,7 @@ impl<'a> Semantics<'a> {
|
|||
|
||||
let mut env = Environment::new(Some(parent), Location::Local);
|
||||
env.insert(variable, type_expr);
|
||||
return EnvironmentRef::new(env);
|
||||
EnvironmentRef::new(env)
|
||||
}
|
||||
|
||||
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::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
|
||||
}
|
||||
|
||||
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) {
|
||||
// TODO
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ pub enum TokenKind {
|
|||
In,
|
||||
Is,
|
||||
Let,
|
||||
Match,
|
||||
New,
|
||||
Or,
|
||||
Return,
|
||||
|
|
@ -307,6 +308,11 @@ impl<'a> Tokens<'a> {
|
|||
return TokenKind::Let;
|
||||
}
|
||||
}
|
||||
'm' => {
|
||||
if ident == "match" {
|
||||
return TokenKind::Match;
|
||||
}
|
||||
}
|
||||
'n' => {
|
||||
if ident == "new" {
|
||||
return TokenKind::New;
|
||||
|
|
@ -597,7 +603,13 @@ mod tests {
|
|||
(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!(
|
||||
strings,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue