diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 0b0d0efe..0a48a5f2 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -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 } diff --git a/fine/src/parser.rs b/fine/src/parser.rs index d6bc089a..e5c0ea2c 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -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 { 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::*; diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index 0c3e726f..b8a0b274 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -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 } diff --git a/fine/src/tokens.rs b/fine/src/tokens.rs index 36a9ec65..de87f659 100644 --- a/fine/src/tokens.rs +++ b/fine/src/tokens.rs @@ -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,