[fine] WIP: Classes
This commit is contained in:
parent
e6c96fde38
commit
0ee89bf26b
7 changed files with 370 additions and 59 deletions
|
|
@ -42,6 +42,7 @@ pub enum Instruction {
|
|||
StoreModule(usize),
|
||||
StringAdd,
|
||||
Dup,
|
||||
NewObject(usize),
|
||||
}
|
||||
|
||||
pub enum Export {
|
||||
|
|
@ -488,6 +489,7 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
|
|||
|
||||
Declaration::ExternFunction { .. } => Instruction::Panic,
|
||||
Declaration::Function { .. } => Instruction::Panic,
|
||||
Declaration::Class { .. } => Instruction::Panic,
|
||||
};
|
||||
c.push(instruction);
|
||||
}
|
||||
|
|
@ -545,6 +547,23 @@ fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> O
|
|||
Instruction::LoadFunction(index)
|
||||
}
|
||||
Declaration::ExternFunction { id, .. } => Instruction::LoadExternFunction(id.id()),
|
||||
Declaration::Class { declaration, .. } => {
|
||||
let key = FunctionKey { tree: *declaration };
|
||||
let index = match c.function_bindings.get(&key) {
|
||||
Some(index) => *index,
|
||||
None => {
|
||||
let tree = &c.syntax[*declaration];
|
||||
compiler_assert_eq!(c, t, tree.kind, TreeKind::ClassDecl);
|
||||
|
||||
compile_class_declaration(c, t, tree, false)?;
|
||||
|
||||
*c.function_bindings
|
||||
.get(&key)
|
||||
.expect("did not compile the class constructor!")
|
||||
}
|
||||
};
|
||||
Instruction::LoadFunction(index)
|
||||
}
|
||||
};
|
||||
|
||||
c.push(instruction);
|
||||
|
|
@ -705,13 +724,58 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v
|
|||
OK
|
||||
}
|
||||
|
||||
fn compile_class_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
|
||||
// Only compile a given function once.
|
||||
// Classes get compiled as constructor functions which get called.
|
||||
let fk = FunctionKey { tree: t };
|
||||
if !c.function_bindings.contains_key(&fk) {
|
||||
let name = tree.nth_token(1)?;
|
||||
|
||||
let field_count = tree.children.len() - 2;
|
||||
|
||||
let function_index = c.temp_functions.len();
|
||||
c.temp_functions.push(None);
|
||||
|
||||
c.pending_functions.push((
|
||||
fk.clone(),
|
||||
function_index,
|
||||
Function::new(name.as_str(), field_count),
|
||||
));
|
||||
c.function_bindings.insert(fk, function_index);
|
||||
c.module
|
||||
.exports
|
||||
.insert(name.to_string(), Export::Function(function_index));
|
||||
}
|
||||
|
||||
if gen_value {
|
||||
c.push(Instruction::PushNothing);
|
||||
}
|
||||
|
||||
OK
|
||||
}
|
||||
|
||||
fn compile_function(c: &mut Compiler, t: TreeRef) -> CR {
|
||||
let tree = &c.syntax[t];
|
||||
let block = tree.child_of_kind(c.syntax, TreeKind::Block)?;
|
||||
match tree.kind {
|
||||
TreeKind::FunctionDecl => {
|
||||
let block = tree.child_of_kind(c.syntax, TreeKind::Block)?;
|
||||
compile_expression(c, block);
|
||||
}
|
||||
TreeKind::ClassDecl => {
|
||||
let count = tree.children_of_kind(c.syntax, TreeKind::FieldDecl).count();
|
||||
for i in 0..count {
|
||||
c.push(Instruction::LoadArgument(count - 1 - i));
|
||||
}
|
||||
|
||||
let name = tree.nth_token(1)?.as_str();
|
||||
let name_index = c.add_string(name.to_string());
|
||||
c.push(Instruction::PushString(name_index));
|
||||
c.push(Instruction::NewObject(count));
|
||||
}
|
||||
_ => ice!(c, t, "what is this tree doing in compile_function?"),
|
||||
}
|
||||
|
||||
compile_expression(c, block);
|
||||
c.push(Instruction::Return);
|
||||
|
||||
OK
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,8 +125,10 @@ pub enum TreeKind {
|
|||
BinaryExpression,
|
||||
Block,
|
||||
CallExpression,
|
||||
ClassDecl,
|
||||
ConditionalExpression,
|
||||
ExpressionStatement,
|
||||
FieldDecl,
|
||||
File,
|
||||
ForStatement,
|
||||
FunctionDecl,
|
||||
|
|
@ -145,6 +147,9 @@ pub enum TreeKind {
|
|||
TypeParameter,
|
||||
TypeParameterList,
|
||||
UnaryExpression,
|
||||
FieldList,
|
||||
NewObjectExpression,
|
||||
FieldValue,
|
||||
}
|
||||
|
||||
pub struct Tree<'a> {
|
||||
|
|
@ -183,14 +188,17 @@ impl<'a> Tree<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn children_of_kind<'b>(
|
||||
&'b self,
|
||||
s: &'b SyntaxTree,
|
||||
kind: TreeKind,
|
||||
) -> impl Iterator<Item = TreeRef> + 'b {
|
||||
self.child_trees()
|
||||
.filter_map(move |t| if s[t].kind == kind { Some(t) } else { None })
|
||||
}
|
||||
|
||||
pub fn child_of_kind(&self, s: &SyntaxTree, kind: TreeKind) -> Option<TreeRef> {
|
||||
self.children
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
Child::Tree(t) => Some(*t),
|
||||
_ => None,
|
||||
})
|
||||
.find(|c| s[*c].kind == kind)
|
||||
self.children_of_kind(&s, kind).next()
|
||||
}
|
||||
|
||||
pub fn child_tree_of_kind<'b>(
|
||||
|
|
@ -400,6 +408,10 @@ impl<'a> CParser<'a> {
|
|||
self.error(error);
|
||||
}
|
||||
|
||||
fn expect_start(&mut self, kind: TokenKind) {
|
||||
assert!(self.eat(kind));
|
||||
}
|
||||
|
||||
fn advance_with_error<T>(&mut self, error: T) -> MarkClosed
|
||||
where
|
||||
T: Into<String>,
|
||||
|
|
@ -498,6 +510,7 @@ fn file(p: &mut CParser) {
|
|||
while !p.eof() {
|
||||
match p.peek() {
|
||||
TokenKind::Fun => function(p),
|
||||
TokenKind::Class => class(p),
|
||||
_ => statement(p),
|
||||
}
|
||||
}
|
||||
|
|
@ -505,10 +518,9 @@ fn file(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn function(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Fun));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::Fun, "expected a function to start with 'fun'");
|
||||
p.expect_start(TokenKind::Fun);
|
||||
p.expect(TokenKind::Identifier, "expected a function name");
|
||||
if p.at(TokenKind::LeftParen) {
|
||||
param_list(p);
|
||||
|
|
@ -523,11 +535,40 @@ fn function(p: &mut CParser) {
|
|||
p.end(m, TreeKind::FunctionDecl);
|
||||
}
|
||||
|
||||
fn param_list(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::LeftParen));
|
||||
fn class(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::LeftParen, "expect '(' to start a parameter list");
|
||||
p.expect_start(TokenKind::Class);
|
||||
p.expect(TokenKind::Identifier, "expected a class name");
|
||||
if p.eat(TokenKind::LeftBrace) {
|
||||
while !p.at(TokenKind::RightBrace) && !p.eof() {
|
||||
field_decl(p);
|
||||
}
|
||||
}
|
||||
p.expect(TokenKind::RightBrace, "expected a class to end with a '}'");
|
||||
|
||||
p.end(m, TreeKind::ClassDecl);
|
||||
}
|
||||
|
||||
fn field_decl(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::Identifier, "expected a field name");
|
||||
if p.eat(TokenKind::Colon) {
|
||||
type_expr(p);
|
||||
}
|
||||
p.expect(
|
||||
TokenKind::Semicolon,
|
||||
"expect a ';' after field declarations",
|
||||
);
|
||||
|
||||
p.end(m, TreeKind::FieldDecl);
|
||||
}
|
||||
|
||||
fn param_list(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
p.expect_start(TokenKind::LeftParen);
|
||||
while !p.at(TokenKind::RightParen) && !p.eof() {
|
||||
if p.at(TokenKind::Identifier) {
|
||||
parameter(p);
|
||||
|
|
@ -541,12 +582,9 @@ fn param_list(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn parameter(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Identifier));
|
||||
let m = p.start();
|
||||
p.expect(
|
||||
TokenKind::Identifier,
|
||||
"expected an identifier for a parameter name",
|
||||
);
|
||||
|
||||
p.expect_start(TokenKind::Identifier);
|
||||
if p.eat(TokenKind::Colon) {
|
||||
type_expr(p);
|
||||
}
|
||||
|
|
@ -558,13 +596,9 @@ fn parameter(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn return_type(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Arrow));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(
|
||||
TokenKind::Arrow,
|
||||
"function return type starts with an arrow",
|
||||
);
|
||||
p.expect_start(TokenKind::Arrow);
|
||||
type_expr(p);
|
||||
|
||||
p.end(m, TreeKind::ReturnType);
|
||||
|
|
@ -584,10 +618,9 @@ fn type_expr(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn type_parameter_list(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Less));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::Less, "expected < to start type parameter list");
|
||||
p.expect_start(TokenKind::Less);
|
||||
while !p.at(TokenKind::Greater) && !p.eof() {
|
||||
if p.at(TokenKind::Identifier) {
|
||||
type_parameter(p);
|
||||
|
|
@ -613,10 +646,9 @@ fn type_parameter(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn block(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::LeftBrace));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::LeftBrace, "expect '{' to start a block");
|
||||
p.expect_start(TokenKind::LeftBrace);
|
||||
while !p.at(TokenKind::RightBrace) && !p.eof() {
|
||||
statement(p);
|
||||
}
|
||||
|
|
@ -650,10 +682,9 @@ fn statement_if(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn statement_let(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Let));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::Let, "expect 'let' to start a let statement");
|
||||
p.expect_start(TokenKind::Let);
|
||||
p.expect(TokenKind::Identifier, "expected a name for the variable");
|
||||
p.expect(TokenKind::Equal, "expected a '=' after the variable name");
|
||||
expression(p);
|
||||
|
|
@ -665,13 +696,9 @@ fn statement_let(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn statement_return(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Return));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(
|
||||
TokenKind::Return,
|
||||
"expect 'return' to start a return statement",
|
||||
);
|
||||
p.expect_start(TokenKind::Return);
|
||||
expression(p);
|
||||
if !p.at(TokenKind::RightBrace) {
|
||||
p.expect(TokenKind::Semicolon, "expect ';' to end a return statement");
|
||||
|
|
@ -681,10 +708,9 @@ fn statement_return(p: &mut CParser) {
|
|||
}
|
||||
|
||||
fn statement_for(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::For));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::For, "expect a for to start a for loop");
|
||||
p.expect_start(TokenKind::For);
|
||||
p.expect(
|
||||
TokenKind::Identifier,
|
||||
"expected an identifier for the loop variable",
|
||||
|
|
@ -771,13 +797,9 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
|||
}
|
||||
|
||||
fn argument_list(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::LeftParen));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(
|
||||
TokenKind::LeftParen,
|
||||
"expect an argument list to start with '('",
|
||||
);
|
||||
p.expect_start(TokenKind::LeftParen);
|
||||
while !p.at(TokenKind::RightParen) && !p.eof() {
|
||||
argument(p);
|
||||
}
|
||||
|
|
@ -818,6 +840,8 @@ fn prefix_expression(p: &mut CParser) -> MarkClosed {
|
|||
|
||||
TokenKind::LeftBracket => list_constructor(p),
|
||||
|
||||
TokenKind::New => object_constructor(p),
|
||||
|
||||
_ => p.advance_with_error("expected an expression"),
|
||||
}
|
||||
}
|
||||
|
|
@ -829,10 +853,9 @@ fn literal(p: &mut CParser) -> MarkClosed {
|
|||
}
|
||||
|
||||
fn grouping(p: &mut CParser) -> MarkClosed {
|
||||
assert!(p.at(TokenKind::LeftParen));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::LeftParen, "expected '(' to start grouping");
|
||||
p.expect_start(TokenKind::LeftParen);
|
||||
expression(p);
|
||||
p.expect(TokenKind::RightParen, "unmatched parentheses in expression");
|
||||
|
||||
|
|
@ -849,10 +872,9 @@ fn unary(p: &mut CParser) -> MarkClosed {
|
|||
}
|
||||
|
||||
fn conditional(p: &mut CParser) -> MarkClosed {
|
||||
assert!(p.at(TokenKind::If));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::If, "expected conditional to start with 'if'");
|
||||
p.expect_start(TokenKind::If);
|
||||
expression(p);
|
||||
block(p);
|
||||
if p.eat(TokenKind::Else) {
|
||||
|
|
@ -877,13 +899,9 @@ fn identifier(p: &mut CParser) -> MarkClosed {
|
|||
}
|
||||
|
||||
fn list_constructor(p: &mut CParser) -> MarkClosed {
|
||||
assert!(p.at(TokenKind::LeftBracket));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(
|
||||
TokenKind::LeftBracket,
|
||||
"expect a list constructor to start with [",
|
||||
);
|
||||
p.expect_start(TokenKind::LeftBracket);
|
||||
while !p.at(TokenKind::RightBracket) && !p.eof() {
|
||||
list_constructor_element(p);
|
||||
}
|
||||
|
|
@ -909,6 +927,49 @@ fn list_constructor_element(p: &mut CParser) {
|
|||
p.end(m, TreeKind::ListConstructorElement);
|
||||
}
|
||||
|
||||
fn object_constructor(p: &mut CParser) -> MarkClosed {
|
||||
let m = p.start();
|
||||
|
||||
p.expect_start(TokenKind::New);
|
||||
type_expr(p);
|
||||
if p.at(TokenKind::LeftBrace) {
|
||||
field_list(p);
|
||||
} else {
|
||||
p.error("expected a '{' to start the field list after the class type");
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::NewObjectExpression)
|
||||
}
|
||||
|
||||
fn field_list(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
p.expect_start(TokenKind::LeftBrace);
|
||||
while !p.at(TokenKind::RightBrace) && !p.eof() {
|
||||
field_value(p);
|
||||
}
|
||||
p.expect(
|
||||
TokenKind::RightBrace,
|
||||
"expected the field list to end with '}'",
|
||||
);
|
||||
|
||||
p.end(m, TreeKind::FieldList);
|
||||
}
|
||||
|
||||
fn field_value(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::Identifier, "expected a field name");
|
||||
if p.eat(TokenKind::Colon) {
|
||||
expression(p);
|
||||
}
|
||||
if !p.at(TokenKind::RightBrace) {
|
||||
p.expect(TokenKind::Comma, "expect a ',' between fields");
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::FieldValue);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,17 @@ impl fmt::Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FieldDecl {
|
||||
pub name: Rc<str>,
|
||||
pub field_type: Type,
|
||||
}
|
||||
|
||||
pub struct ClassDecl {
|
||||
pub name: Rc<str>,
|
||||
pub fields: Vec<FieldDecl>,
|
||||
pub decl_tree: TreeRef,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Type {
|
||||
// Signals a type error. If you receive this then you know that an error
|
||||
|
|
@ -91,6 +102,7 @@ pub enum Type {
|
|||
|
||||
Function(Vec<Box<Type>>, Box<Type>),
|
||||
List(Box<Type>),
|
||||
Class(Rc<ClassDecl>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
|
|
@ -136,6 +148,7 @@ impl fmt::Display for Type {
|
|||
// TODO: Better names
|
||||
TypeVariable(_) => write!(f, "$_"),
|
||||
List(t) => write!(f, "list<{t}>"),
|
||||
Class(c) => write!(f, "class {}", c.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -173,6 +186,10 @@ pub enum Declaration {
|
|||
declaration_type: Type,
|
||||
id: ExternalFunctionId,
|
||||
},
|
||||
Class {
|
||||
declaration_type: Type,
|
||||
declaration: TreeRef, //?
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Environment {
|
||||
|
|
@ -404,7 +421,9 @@ impl<'a> Semantics<'a> {
|
|||
}
|
||||
|
||||
pub fn snapshot_errors(&self) -> Vec<Error> {
|
||||
(*self.errors.borrow()).clone()
|
||||
let mut result = (*self.errors.borrow()).clone();
|
||||
result.sort_by(|a, b| a.start.0.cmp(&b.start.0));
|
||||
result
|
||||
}
|
||||
|
||||
pub fn logical_parent(&self, tr: TreeRef) -> Option<TreeRef> {
|
||||
|
|
@ -681,6 +700,16 @@ impl<'a> Semantics<'a> {
|
|||
.all(|(a, b)| self.type_compat(a, b))
|
||||
}
|
||||
|
||||
(Type::Class(ca), Type::Class(cb)) => {
|
||||
// TODO: If we were doing structural comparisons here...
|
||||
// maybe? MAYBE?
|
||||
//
|
||||
// Like if this is directional we can look for field
|
||||
// subsets { ..:int, ..:int } can be { ..:int } etc.
|
||||
//
|
||||
ca.decl_tree == cb.decl_tree
|
||||
}
|
||||
|
||||
// Avoid introducing more errors
|
||||
(Type::Error, _) => true,
|
||||
(_, Type::Error) => true,
|
||||
|
|
@ -736,6 +765,8 @@ impl<'a> Semantics<'a> {
|
|||
TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree),
|
||||
TreeKind::ListConstructor => self.type_of_list_constructor(t, tree),
|
||||
|
||||
TreeKind::NewObjectExpression => self.type_of_new_object_expression(tree),
|
||||
|
||||
_ => self.internal_compiler_error(Some(t), "asking for a nonsense type"),
|
||||
};
|
||||
|
||||
|
|
@ -865,6 +896,13 @@ impl<'a> Semantics<'a> {
|
|||
);
|
||||
return Some(Type::Error);
|
||||
}
|
||||
Declaration::Class { .. } => {
|
||||
self.report_error_tree_ref(
|
||||
left_tree,
|
||||
"cannot assign a new value to a class declaration",
|
||||
);
|
||||
return Some(Type::Error);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.report_error_tree_ref(
|
||||
|
|
@ -1041,6 +1079,13 @@ impl<'a> Semantics<'a> {
|
|||
fn type_of_call(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::CallExpression);
|
||||
|
||||
// TODO: Move the vast majority of error checking out of this
|
||||
// function: once you know that the 0th tree (the function
|
||||
// expression) yields a function type, assume the type of the
|
||||
// call is the type of the function return. Don't bother
|
||||
// matching argument types &c; do that in an explicit
|
||||
// check_call_expression function below.
|
||||
|
||||
let f_ref = tree.nth_tree(0)?;
|
||||
let f = self.type_of(f_ref);
|
||||
|
||||
|
|
@ -1151,6 +1196,9 @@ impl<'a> Semantics<'a> {
|
|||
Declaration::ExternFunction {
|
||||
declaration_type, ..
|
||||
} => declaration_type.clone(),
|
||||
Declaration::Class {
|
||||
declaration_type, ..
|
||||
} => declaration_type.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1211,6 +1259,13 @@ impl<'a> Semantics<'a> {
|
|||
Some(Type::List(Box::new(element_type)))
|
||||
}
|
||||
|
||||
fn type_of_new_object_expression(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::NewObjectExpression);
|
||||
|
||||
// NOTE: Matching fields is done in the check function.
|
||||
Some(self.type_of(tree.nth_tree(1)?))
|
||||
}
|
||||
|
||||
fn type_of_list_constructor_element(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::ListConstructorElement);
|
||||
Some(self.type_of(tree.nth_tree(0)?))
|
||||
|
|
@ -1280,6 +1335,12 @@ impl<'a> Semantics<'a> {
|
|||
} => {
|
||||
eprintln!("{declaration_type:?} (extern {id:?})");
|
||||
}
|
||||
Declaration::Class {
|
||||
declaration_type,
|
||||
declaration,
|
||||
} => {
|
||||
eprintln!("{declaration_type:?} (class {declaration:?})");
|
||||
}
|
||||
};
|
||||
}
|
||||
environment = env.parent.clone();
|
||||
|
|
@ -1321,10 +1382,13 @@ pub fn check(s: &Semantics) {
|
|||
| TreeKind::GroupingExpression
|
||||
| TreeKind::UnaryExpression
|
||||
| TreeKind::ConditionalExpression
|
||||
| TreeKind::CallExpression
|
||||
| TreeKind::BinaryExpression => {
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::CallExpression => {
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
|
||||
TreeKind::ArgumentList => {}
|
||||
TreeKind::Argument => {
|
||||
let _ = s.type_of(t);
|
||||
|
|
@ -1343,6 +1407,12 @@ pub fn check(s: &Semantics) {
|
|||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::ForStatement => check_for_statement(s, t),
|
||||
|
||||
TreeKind::ClassDecl => {}
|
||||
TreeKind::FieldDecl => {}
|
||||
TreeKind::FieldList => {}
|
||||
TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
|
||||
TreeKind::FieldValue => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1426,6 +1496,72 @@ fn check_for_statement(s: &Semantics, t: TreeRef) {
|
|||
let _ = s.environment_of(t);
|
||||
}
|
||||
|
||||
// TODO: TEST: Check mutual recursion with function calls
|
||||
// TODO: TEST: Missing fields
|
||||
// TODO: TEST: Extra fields
|
||||
// TODO: TEST: Existing and type mismatch
|
||||
|
||||
fn check_new_object_expression(s: &Semantics, tree: &Tree) {
|
||||
let Some(type_expression) = tree.nth_tree(1) else {
|
||||
return;
|
||||
};
|
||||
let Some(field_list) = tree.child_tree_of_kind(s.syntax_tree, TreeKind::FieldList) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let class_type = s.type_of(type_expression);
|
||||
match &class_type {
|
||||
Type::Class(c) => {
|
||||
let mut any_errors = false;
|
||||
let mut field_bindings = HashMap::new();
|
||||
for field in field_list.children_of_kind(s.syntax_tree, TreeKind::FieldValue) {
|
||||
let f = &s.syntax_tree[field];
|
||||
if let Some(name) = f.nth_token(0) {
|
||||
let field_type = s.type_of(field);
|
||||
field_bindings.insert(name.as_str(), (field, field_type));
|
||||
} else {
|
||||
any_errors = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check individual bindings...
|
||||
for f in c.fields.iter() {
|
||||
if let Some((field_tree, expr_type)) = field_bindings.get(&*f.name) {
|
||||
if !s.type_compat(&f.field_type, expr_type) {
|
||||
s.report_error_tree_ref(
|
||||
*field_tree,
|
||||
format!(
|
||||
"field {} has is of type {}, but this expression generates a {}",
|
||||
f.name, f.field_type, expr_type,
|
||||
),
|
||||
);
|
||||
}
|
||||
field_bindings.remove(&*f.name);
|
||||
} else if !any_errors {
|
||||
s.report_error_tree(
|
||||
tree,
|
||||
format!("missing an initializer for field {}", f.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !any_errors {
|
||||
for (n, (field_tree, _)) in field_bindings.iter() {
|
||||
s.report_error_tree_ref(
|
||||
*field_tree,
|
||||
format!("{} does not have a field named {}", class_type, n),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::Error => (),
|
||||
ct => s.report_error_tree_ref(
|
||||
type_expression,
|
||||
format!("expected this to be a class type, but it is {ct}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ pub enum TokenKind {
|
|||
Import,
|
||||
In,
|
||||
Let,
|
||||
New,
|
||||
Or,
|
||||
Return,
|
||||
Select,
|
||||
|
|
@ -302,6 +303,11 @@ impl<'a> Tokens<'a> {
|
|||
return TokenKind::Let;
|
||||
}
|
||||
}
|
||||
'n' => {
|
||||
if ident == "new" {
|
||||
return TokenKind::New;
|
||||
}
|
||||
}
|
||||
'o' => {
|
||||
if ident == "or" {
|
||||
return TokenKind::Or;
|
||||
|
|
@ -573,7 +579,7 @@ mod tests {
|
|||
|
||||
test_tokens!(
|
||||
more_keywords,
|
||||
"fun if import let return select this true while truewhile",
|
||||
"fun if import let return select this true while truewhile new",
|
||||
(0, Fun, "fun"),
|
||||
(4, If, "if"),
|
||||
(7, Import, "import"),
|
||||
|
|
@ -583,7 +589,8 @@ mod tests {
|
|||
(32, This, "this"),
|
||||
(37, True, "true"),
|
||||
(42, While, "while"),
|
||||
(48, Identifier, "truewhile")
|
||||
(48, Identifier, "truewhile"),
|
||||
(58, New, "new")
|
||||
);
|
||||
|
||||
test_tokens!(
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ pub struct VMError {
|
|||
|
||||
type Result<T> = std::result::Result<T, VMErrorCode>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Object {
|
||||
name: Rc<str>,
|
||||
values: Box<[StackValue]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum StackValue {
|
||||
Nothing,
|
||||
|
|
@ -47,6 +53,7 @@ pub enum StackValue {
|
|||
String(Rc<str>),
|
||||
Function(Rc<Function>),
|
||||
ExternFunction(usize),
|
||||
Object(Rc<Object>),
|
||||
}
|
||||
|
||||
enum FuncValue {
|
||||
|
|
@ -130,6 +137,10 @@ impl Frame {
|
|||
self.push_value(StackValue::ExternFunction(v));
|
||||
}
|
||||
|
||||
fn push_object(&mut self, v: Rc<Object>) {
|
||||
self.push_value(StackValue::Object(v));
|
||||
}
|
||||
|
||||
fn get_argument(&self, i: usize) -> Result<StackValue> {
|
||||
self.args
|
||||
.get(i)
|
||||
|
|
@ -384,6 +395,21 @@ fn eval_one(
|
|||
let y = f.pop_string()?;
|
||||
f.push_bool(x == y);
|
||||
}
|
||||
|
||||
Instruction::NewObject(slots) => {
|
||||
let name = f.pop_string()?;
|
||||
let mut values = Vec::with_capacity(slots);
|
||||
for _ in 0..slots {
|
||||
values.push(f.pop_value()?);
|
||||
}
|
||||
|
||||
let object = Object {
|
||||
name,
|
||||
values: values.into(),
|
||||
};
|
||||
|
||||
f.push_object(object.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Flow::Continue)
|
||||
|
|
|
|||
13
fine/tests/expression/class.fine
Normal file
13
fine/tests/expression/class.fine
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class Point {
|
||||
x: f64;
|
||||
y: f64;
|
||||
}
|
||||
|
||||
fun test() -> f64 {
|
||||
let pt = new Point { x: 7, y: 23 };
|
||||
let z = pt.x;
|
||||
z
|
||||
}
|
||||
|
||||
// @ignore
|
||||
// @no-errors
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
fun sum(x: list<f64>) -> f64 {
|
||||
75 // lol
|
||||
let result = 0;
|
||||
for v in x {
|
||||
result = result + v;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fun test() {
|
||||
fun test() -> f64 {
|
||||
let val = [1, 2, 3];
|
||||
sum(val);
|
||||
sum(val)
|
||||
}
|
||||
|
||||
// @no-errors
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue