[fine] Starting on pattern matching, make patterns explicit

This commit is contained in:
John Doty 2024-02-04 07:47:16 -08:00
parent f00b9e22e7
commit deed9d9a45
4 changed files with 178 additions and 50 deletions

View file

@ -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,50 +620,56 @@ 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);
let declaration = environment.bind(variable)?;
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);
let declaration = environment.bind(variable)?;
let Declaration::Variable {
location: Location::Local,
index,
..
} = declaration
else {
ice!(c, t, "is cannot make a non-local, non-variable declaration")
};
let Declaration::Variable {
location: Location::Local,
index,
..
} = declaration
else {
ice!(c, t, "is cannot make a non-local, non-variable declaration")
};
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)?
};
compile_type_expr_eq(c, type_expr.nth_tree(0)?);
if let Some(tok) = tree.nth_token(3) {
if tok.kind == TokenKind::And {
let jump_true_index = c.push(Instruction::JumpTrue(0));
c.push(Instruction::PushFalse);
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
compile_expression(c, tree.nth_tree(4)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
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 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);
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
compile_expression(c, tree.nth_tree(and_index + 1)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
};
OK
}

View file

@ -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::*;

View file

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

View file

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