diff --git a/fine/src/parser.rs b/fine/src/parser.rs index 8a906de0..d6bc089a 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -155,6 +155,9 @@ pub enum TreeKind { TypeParameter, TypeParameterList, UnaryExpression, + + IsExpression, + VariableBinding, } pub struct Tree<'a> { @@ -318,6 +321,7 @@ struct MarkClosed { struct CParser<'a> { tokens: Tokens<'a>, current: Token<'a>, + next: Token<'a>, fuel: Cell, events: Vec>, panic: bool, @@ -328,11 +332,13 @@ impl<'a> CParser<'a> { let mut parser = CParser { tokens, current: Token::new(TokenKind::EOF, 0, ""), + next: Token::new(TokenKind::EOF, 0, ""), fuel: Cell::new(256), events: Vec::new(), panic: false, }; parser.current = parser.tokens.next(); + parser.next = parser.tokens.next(); parser.skip_ephemera(); parser } @@ -371,14 +377,18 @@ impl<'a> CParser<'a> { self.events.push(ParseEvent::Advance { token: self.current.clone(), }); - self.current = self.tokens.next(); + // Move next into current (and current into next but who cares, thanks rust.) + std::mem::swap(&mut self.current, &mut self.next); + self.next = self.tokens.next(); self.skip_ephemera(); } fn skip_ephemera(&mut self) { while self.current.kind == TokenKind::Whitespace || self.current.kind == TokenKind::Comment { - self.current = self.tokens.next(); + // Move next into current (and current into next but who cares, thanks rust.) + std::mem::swap(&mut self.current, &mut self.next); + self.next = self.tokens.next(); } } @@ -397,6 +407,17 @@ impl<'a> CParser<'a> { self.current.kind } + fn peek_next(&self) -> TokenKind { + if self.fuel.get() == 0 { + panic!( + "parser is stuck at '{}' ({})!", + self.current, self.current.start + ); + } + self.fuel.set(self.fuel.get() - 1); + self.next.kind + } + // fn trace(&self, msg: &str) { // eprintln!("{}: {}: {}", self.current.start, self.current, msg); // } @@ -887,7 +908,7 @@ fn expression(p: &mut CParser) { expression_with_power(p, 0) } -const UNARY_POWER: u8 = 14; +const UNARY_POWER: u8 = 16; fn infix_power(token: TokenKind) -> Option<(u8, u8)> { // A dumb thing: the pair controls associativity. @@ -897,17 +918,18 @@ fn infix_power(token: TokenKind) -> Option<(u8, u8)> { match token { TokenKind::Equal => Some((1, 0)), TokenKind::Or => Some((2, 3)), - TokenKind::And => Some((4, 5)), - TokenKind::EqualEqual | TokenKind::BangEqual => Some((6, 7)), + TokenKind::Is => Some((4, 5)), + TokenKind::And => Some((6, 7)), + TokenKind::EqualEqual | TokenKind::BangEqual => Some((8, 9)), TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => { - Some((8, 9)) + Some((10, 11)) } - TokenKind::Plus | TokenKind::Minus => Some((10, 11)), - TokenKind::Star | TokenKind::Slash => Some((12, 13)), + TokenKind::Plus | TokenKind::Minus => Some((12, 13)), + TokenKind::Star | TokenKind::Slash => Some((14, 15)), // // UNARY_POWER goes here. // - TokenKind::Dot => Some((16, 17)), + TokenKind::Dot => Some((18, 19)), _ => None, } } @@ -931,22 +953,58 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) { break; } - // TODO: I don't think this works for other "infix" types, but we'll - // see won't we. - let m = p.start_before(expr); - p.advance(); // Consume the operator - expression_with_power(p, rp); - expr = p.end( - m, - if token == TokenKind::Dot { - TreeKind::MemberAccess - } else { - TreeKind::BinaryExpression - }, - ); + expr = match token { + TokenKind::Dot => member_access(p, expr, rp), + TokenKind::Is => is_expression(p, expr, rp), + _ => binary_expression(p, expr, rp), + }; } } +fn member_access(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClosed { + let m = p.start_before(left); + p.advance(); // Consume the operator + expression_with_power(p, right_power); + p.end(m, TreeKind::MemberAccess) +} + +fn binary_expression(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClosed { + let m = p.start_before(left); + p.advance(); // Consume the operator + expression_with_power(p, right_power); + p.end(m, TreeKind::BinaryExpression) +} + +fn is_expression(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClosed { + let m = p.start_before(left); + p.advance(); // Consume the operator + + // This is hard to do with just, like, no lookahead. + 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. + if p.eat(TokenKind::And) { + expression_with_power(p, right_power); + } + + p.end(m, TreeKind::IsExpression) +} + +fn variable_binding(p: &mut CParser) { + let m = p.start(); + + p.expect_start(TokenKind::Identifier); + p.expect_start(TokenKind::Colon); + type_expr(p); + + p.end(m, TreeKind::VariableBinding); +} + fn argument_list(p: &mut CParser) { let m = p.start(); @@ -1125,7 +1183,14 @@ fn field_list(p: &mut CParser) { p.expect_start(TokenKind::LeftBrace); while !p.at(TokenKind::RightBrace) && !p.eof() { - field_value(p); + if p.at(TokenKind::Identifier) { + field_value(p); + } else { + if p.at_any(STATEMENT_RECOVERY) { + break; + } + p.advance_with_error("expected an identifier in a field list"); + } } p.expect( TokenKind::RightBrace, @@ -1138,7 +1203,7 @@ fn field_list(p: &mut CParser) { fn field_value(p: &mut CParser) { let m = p.start(); - p.expect(TokenKind::Identifier, "expected a field name"); + p.expect_start(TokenKind::Identifier); if p.eat(TokenKind::Colon) { expression(p); } diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index f09d1521..f104a7ea 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -604,14 +604,13 @@ impl<'a> Semantics<'a> { }; let result = match tree.kind { - TreeKind::LetStatement => self.environment_of_let(parent, tree), - TreeKind::ParamList => self.environment_of_paramlist(parent, tree), - TreeKind::File => self.environment_of_file(parent, tree), + TreeKind::IsExpression => self.environment_of_is_expression(parent, tree), TreeKind::Block => self.environment_of_block(parent, tree), - + TreeKind::File => self.environment_of_file(parent, tree), TreeKind::ForStatement => self.environment_of_for(parent, tree), - + TreeKind::LetStatement => self.environment_of_let(parent, tree), TreeKind::MemberAccess => self.environment_of_member_access(tree), + TreeKind::ParamList => self.environment_of_paramlist(parent, tree), _ => parent, }; @@ -819,6 +818,24 @@ impl<'a> Semantics<'a> { } } + fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef { + assert_eq!(tree.kind, TreeKind::IsExpression); + let Some(binding) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::VariableBinding) + else { + return parent; + }; + let Some(variable) = binding.nth_token(0) else { + return Environment::error(); + }; + let Some(type_expr) = binding.nth_tree(2) else { + return Environment::error(); + }; + + let mut env = Environment::new(Some(parent), Location::Local); + env.insert(variable, type_expr); + return EnvironmentRef::new(env); + } + pub fn class_of(&self, t: TreeRef) -> ClassRef { { // I want to make sure that this borrow is dropped after this block. @@ -968,6 +985,9 @@ impl<'a> Semantics<'a> { (Type::Error, _) => true, (_, Type::Error) => true, + // Can... I... convert unreachable always? Is this sound? + (Type::Unreachable, _) => true, + // TODO: Unification on type variables! :D (_, _) => false, } @@ -1010,6 +1030,7 @@ impl<'a> Semantics<'a> { TreeKind::GroupingExpression => self.type_of_grouping(tree), TreeKind::Identifier => self.type_of_identifier(t, tree), TreeKind::IfStatement => self.type_of_if_statement(tree), + TreeKind::IsExpression => Some(Type::Bool), TreeKind::LetStatement => Some(Type::Nothing), TreeKind::ListConstructor => self.type_of_list_constructor(t, tree), TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree), @@ -1854,6 +1875,9 @@ pub fn check(s: &Semantics) { TreeKind::FieldValue => {} TreeKind::SelfParameter => {} TreeKind::SelfReference => {} + + TreeKind::IsExpression => check_is_expression(s, tree), + TreeKind::VariableBinding => check_variable_binding(s, tree), } } } @@ -2029,6 +2053,14 @@ fn check_class_declaration(s: &Semantics, tree: &Tree) { } } +fn check_is_expression(_s: &Semantics, _tree: &Tree) { + // TODO +} + +fn check_variable_binding(_s: &Semantics, _tree: &Tree) { + // TODO +} + #[cfg(test)] mod tests { use super::*; diff --git a/fine/src/tokens.rs b/fine/src/tokens.rs index 67140c88..36a9ec65 100644 --- a/fine/src/tokens.rs +++ b/fine/src/tokens.rs @@ -48,6 +48,7 @@ pub enum TokenKind { If, Import, In, + Is, Let, New, Or, @@ -297,6 +298,9 @@ impl<'a> Tokens<'a> { if ident == "in" { return TokenKind::In; } + if ident == "is" { + return TokenKind::Is; + } } 'l' => { if ident == "let" { @@ -593,6 +597,8 @@ mod tests { (58, New, "new") ); + test_tokens!(more_more_keywords, "in is", (0, In, "in"), (3, Is, "is")); + test_tokens!( strings, r#"'this is a string that\'s great!\r\n' "foo's" 'bar"s' "#, diff --git a/fine/tests/expression/alternates.fine b/fine/tests/expression/alternates.fine index 45dcb868..97bb3007 100644 --- a/fine/tests/expression/alternates.fine +++ b/fine/tests/expression/alternates.fine @@ -33,7 +33,7 @@ class Monster { fun print(x:string) {} fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) { - weapon match { + match weapon { w:RangedWeapon -> distance >= w.minRange and distance <= w.maxRange, _ -> distance == 1 } diff --git a/fine/tests/expression/is.fine b/fine/tests/expression/is.fine new file mode 100644 index 00000000..0f645743 --- /dev/null +++ b/fine/tests/expression/is.fine @@ -0,0 +1,18 @@ +class Foo { + a: f64; +} + +fun test() -> f64 { + let b = new Foo { a : 23 }; + + let result = 0; + if b is c:Foo and c.a == 23 { + result = result + 1; + } + if b is c:Foo and c.a == 24 { + result = result + 1; + } + result +} + +// @no-errors diff --git a/fine/tests/expression/return_expression.fine b/fine/tests/expression/return_expression.fine new file mode 100644 index 00000000..8d559820 --- /dev/null +++ b/fine/tests/expression/return_expression.fine @@ -0,0 +1,6 @@ +fun explicit_return() -> f64 { + return 10.0; + // No error: after this point code is unreachable. +} + +// @no-errors \ No newline at end of file