Compare commits
2 commits
afa4812074
...
b5b56b49a9
| Author | SHA1 | Date | |
|---|---|---|---|
| b5b56b49a9 | |||
| ba5b37f5ff |
6 changed files with 157 additions and 30 deletions
|
|
@ -155,6 +155,9 @@ pub enum TreeKind {
|
||||||
TypeParameter,
|
TypeParameter,
|
||||||
TypeParameterList,
|
TypeParameterList,
|
||||||
UnaryExpression,
|
UnaryExpression,
|
||||||
|
|
||||||
|
IsExpression,
|
||||||
|
VariableBinding,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tree<'a> {
|
pub struct Tree<'a> {
|
||||||
|
|
@ -318,6 +321,7 @@ struct MarkClosed {
|
||||||
struct CParser<'a> {
|
struct CParser<'a> {
|
||||||
tokens: Tokens<'a>,
|
tokens: Tokens<'a>,
|
||||||
current: Token<'a>,
|
current: Token<'a>,
|
||||||
|
next: Token<'a>,
|
||||||
fuel: Cell<u32>,
|
fuel: Cell<u32>,
|
||||||
events: Vec<ParseEvent<'a>>,
|
events: Vec<ParseEvent<'a>>,
|
||||||
panic: bool,
|
panic: bool,
|
||||||
|
|
@ -328,11 +332,13 @@ impl<'a> CParser<'a> {
|
||||||
let mut parser = CParser {
|
let mut parser = CParser {
|
||||||
tokens,
|
tokens,
|
||||||
current: Token::new(TokenKind::EOF, 0, ""),
|
current: Token::new(TokenKind::EOF, 0, ""),
|
||||||
|
next: Token::new(TokenKind::EOF, 0, ""),
|
||||||
fuel: Cell::new(256),
|
fuel: Cell::new(256),
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
panic: false,
|
panic: false,
|
||||||
};
|
};
|
||||||
parser.current = parser.tokens.next();
|
parser.current = parser.tokens.next();
|
||||||
|
parser.next = parser.tokens.next();
|
||||||
parser.skip_ephemera();
|
parser.skip_ephemera();
|
||||||
parser
|
parser
|
||||||
}
|
}
|
||||||
|
|
@ -371,14 +377,18 @@ impl<'a> CParser<'a> {
|
||||||
self.events.push(ParseEvent::Advance {
|
self.events.push(ParseEvent::Advance {
|
||||||
token: self.current.clone(),
|
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();
|
self.skip_ephemera();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_ephemera(&mut self) {
|
fn skip_ephemera(&mut self) {
|
||||||
while self.current.kind == TokenKind::Whitespace || self.current.kind == TokenKind::Comment
|
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
|
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) {
|
// fn trace(&self, msg: &str) {
|
||||||
// eprintln!("{}: {}: {}", self.current.start, self.current, msg);
|
// eprintln!("{}: {}: {}", self.current.start, self.current, msg);
|
||||||
// }
|
// }
|
||||||
|
|
@ -887,7 +908,7 @@ fn expression(p: &mut CParser) {
|
||||||
expression_with_power(p, 0)
|
expression_with_power(p, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNARY_POWER: u8 = 14;
|
const UNARY_POWER: u8 = 16;
|
||||||
|
|
||||||
fn infix_power(token: TokenKind) -> Option<(u8, u8)> {
|
fn infix_power(token: TokenKind) -> Option<(u8, u8)> {
|
||||||
// A dumb thing: the pair controls associativity.
|
// A dumb thing: the pair controls associativity.
|
||||||
|
|
@ -897,17 +918,18 @@ fn infix_power(token: TokenKind) -> Option<(u8, u8)> {
|
||||||
match token {
|
match token {
|
||||||
TokenKind::Equal => Some((1, 0)),
|
TokenKind::Equal => Some((1, 0)),
|
||||||
TokenKind::Or => Some((2, 3)),
|
TokenKind::Or => Some((2, 3)),
|
||||||
TokenKind::And => Some((4, 5)),
|
TokenKind::Is => Some((4, 5)),
|
||||||
TokenKind::EqualEqual | TokenKind::BangEqual => Some((6, 7)),
|
TokenKind::And => Some((6, 7)),
|
||||||
|
TokenKind::EqualEqual | TokenKind::BangEqual => Some((8, 9)),
|
||||||
TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => {
|
TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => {
|
||||||
Some((8, 9))
|
Some((10, 11))
|
||||||
}
|
}
|
||||||
TokenKind::Plus | TokenKind::Minus => Some((10, 11)),
|
TokenKind::Plus | TokenKind::Minus => Some((12, 13)),
|
||||||
TokenKind::Star | TokenKind::Slash => Some((12, 13)),
|
TokenKind::Star | TokenKind::Slash => Some((14, 15)),
|
||||||
//
|
//
|
||||||
// UNARY_POWER goes here.
|
// UNARY_POWER goes here.
|
||||||
//
|
//
|
||||||
TokenKind::Dot => Some((16, 17)),
|
TokenKind::Dot => Some((18, 19)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -931,22 +953,58 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: I don't think this works for other "infix" types, but we'll
|
expr = match token {
|
||||||
// see won't we.
|
TokenKind::Dot => member_access(p, expr, rp),
|
||||||
let m = p.start_before(expr);
|
TokenKind::Is => is_expression(p, expr, rp),
|
||||||
p.advance(); // Consume the operator
|
_ => binary_expression(p, expr, rp),
|
||||||
expression_with_power(p, rp);
|
};
|
||||||
expr = p.end(
|
|
||||||
m,
|
|
||||||
if token == TokenKind::Dot {
|
|
||||||
TreeKind::MemberAccess
|
|
||||||
} else {
|
|
||||||
TreeKind::BinaryExpression
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
fn argument_list(p: &mut CParser) {
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
|
|
||||||
|
|
@ -1125,7 +1183,14 @@ fn field_list(p: &mut CParser) {
|
||||||
|
|
||||||
p.expect_start(TokenKind::LeftBrace);
|
p.expect_start(TokenKind::LeftBrace);
|
||||||
while !p.at(TokenKind::RightBrace) && !p.eof() {
|
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(
|
p.expect(
|
||||||
TokenKind::RightBrace,
|
TokenKind::RightBrace,
|
||||||
|
|
@ -1138,7 +1203,7 @@ fn field_list(p: &mut CParser) {
|
||||||
fn field_value(p: &mut CParser) {
|
fn field_value(p: &mut CParser) {
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
|
|
||||||
p.expect(TokenKind::Identifier, "expected a field name");
|
p.expect_start(TokenKind::Identifier);
|
||||||
if p.eat(TokenKind::Colon) {
|
if p.eat(TokenKind::Colon) {
|
||||||
expression(p);
|
expression(p);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -604,14 +604,13 @@ impl<'a> Semantics<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = match tree.kind {
|
let result = match tree.kind {
|
||||||
TreeKind::LetStatement => self.environment_of_let(parent, tree),
|
TreeKind::IsExpression => self.environment_of_is_expression(parent, tree),
|
||||||
TreeKind::ParamList => self.environment_of_paramlist(parent, tree),
|
|
||||||
TreeKind::File => self.environment_of_file(parent, tree),
|
|
||||||
TreeKind::Block => self.environment_of_block(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::ForStatement => self.environment_of_for(parent, tree),
|
||||||
|
TreeKind::LetStatement => self.environment_of_let(parent, tree),
|
||||||
TreeKind::MemberAccess => self.environment_of_member_access(tree),
|
TreeKind::MemberAccess => self.environment_of_member_access(tree),
|
||||||
|
TreeKind::ParamList => self.environment_of_paramlist(parent, tree),
|
||||||
|
|
||||||
_ => parent,
|
_ => 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 {
|
pub fn class_of(&self, t: TreeRef) -> ClassRef {
|
||||||
{
|
{
|
||||||
// I want to make sure that this borrow is dropped after this block.
|
// 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,
|
||||||
(_, Type::Error) => true,
|
(_, Type::Error) => true,
|
||||||
|
|
||||||
|
// Can... I... convert unreachable always? Is this sound?
|
||||||
|
(Type::Unreachable, _) => true,
|
||||||
|
|
||||||
// TODO: Unification on type variables! :D
|
// TODO: Unification on type variables! :D
|
||||||
(_, _) => false,
|
(_, _) => false,
|
||||||
}
|
}
|
||||||
|
|
@ -1010,6 +1030,7 @@ impl<'a> Semantics<'a> {
|
||||||
TreeKind::GroupingExpression => self.type_of_grouping(tree),
|
TreeKind::GroupingExpression => self.type_of_grouping(tree),
|
||||||
TreeKind::Identifier => self.type_of_identifier(t, tree),
|
TreeKind::Identifier => self.type_of_identifier(t, tree),
|
||||||
TreeKind::IfStatement => self.type_of_if_statement(tree),
|
TreeKind::IfStatement => self.type_of_if_statement(tree),
|
||||||
|
TreeKind::IsExpression => Some(Type::Bool),
|
||||||
TreeKind::LetStatement => Some(Type::Nothing),
|
TreeKind::LetStatement => Some(Type::Nothing),
|
||||||
TreeKind::ListConstructor => self.type_of_list_constructor(t, tree),
|
TreeKind::ListConstructor => self.type_of_list_constructor(t, tree),
|
||||||
TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree),
|
TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree),
|
||||||
|
|
@ -1854,6 +1875,9 @@ pub fn check(s: &Semantics) {
|
||||||
TreeKind::FieldValue => {}
|
TreeKind::FieldValue => {}
|
||||||
TreeKind::SelfParameter => {}
|
TreeKind::SelfParameter => {}
|
||||||
TreeKind::SelfReference => {}
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ pub enum TokenKind {
|
||||||
If,
|
If,
|
||||||
Import,
|
Import,
|
||||||
In,
|
In,
|
||||||
|
Is,
|
||||||
Let,
|
Let,
|
||||||
New,
|
New,
|
||||||
Or,
|
Or,
|
||||||
|
|
@ -297,6 +298,9 @@ impl<'a> Tokens<'a> {
|
||||||
if ident == "in" {
|
if ident == "in" {
|
||||||
return TokenKind::In;
|
return TokenKind::In;
|
||||||
}
|
}
|
||||||
|
if ident == "is" {
|
||||||
|
return TokenKind::Is;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'l' => {
|
'l' => {
|
||||||
if ident == "let" {
|
if ident == "let" {
|
||||||
|
|
@ -593,6 +597,8 @@ mod tests {
|
||||||
(58, New, "new")
|
(58, New, "new")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_tokens!(more_more_keywords, "in is", (0, In, "in"), (3, Is, "is"));
|
||||||
|
|
||||||
test_tokens!(
|
test_tokens!(
|
||||||
strings,
|
strings,
|
||||||
r#"'this is a string that\'s great!\r\n' "foo's" 'bar"s' "#,
|
r#"'this is a string that\'s great!\r\n' "foo's" 'bar"s' "#,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class Monster {
|
||||||
fun print(x:string) {}
|
fun print(x:string) {}
|
||||||
|
|
||||||
fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) {
|
fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) {
|
||||||
weapon match {
|
match weapon {
|
||||||
w:RangedWeapon -> distance >= w.minRange and distance <= w.maxRange,
|
w:RangedWeapon -> distance >= w.minRange and distance <= w.maxRange,
|
||||||
_ -> distance == 1
|
_ -> distance == 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
fine/tests/expression/is.fine
Normal file
18
fine/tests/expression/is.fine
Normal file
|
|
@ -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
|
||||||
6
fine/tests/expression/return_expression.fine
Normal file
6
fine/tests/expression/return_expression.fine
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
fun explicit_return() -> f64 {
|
||||||
|
return 10.0;
|
||||||
|
// No error: after this point code is unreachable.
|
||||||
|
}
|
||||||
|
|
||||||
|
// @no-errors
|
||||||
Loading…
Add table
Add a link
Reference in a new issue