diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 6f6645a4..731d8aee 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -487,6 +487,9 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { compiler_assert!(c, t, index < c.module.globals); Instruction::StoreModule(index) } + Location::Slot => { + ice!(c, t, "cannot have an identifier lvalue bind to a slot"); + } } } @@ -496,6 +499,9 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { }; c.push(instruction); } + + // TODO: Member + // TODO: List element _ => ice!(c, t, "Unsupported lvalue type"), } OK @@ -533,6 +539,10 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat compiler_assert!(c, t, index < c.module.globals); Instruction::LoadModule(index) } + Location::Slot => { + // TODO: Assert slot is in field range? + Instruction::LoadSlot(index) + } } } Declaration::Function { declaration, .. } => { @@ -603,10 +613,11 @@ fn compile_argument(c: &mut Compiler, tree: &Tree) -> CR { fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { // We pass in the arguments.... by... field order? - let Type::Class(class) = c.semantics.type_of(t) else { + let Type::Class(ct, _) = c.semantics.type_of(t) else { c.push(Instruction::Panic); return OK; }; + let class = c.semantics.class_of(ct); let field_list = tree.child_tree_of_kind(c.syntax, TreeKind::FieldList)?; let mut field_bindings = HashMap::new(); @@ -669,22 +680,10 @@ fn compile_field_value(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR { } fn compile_member_access(c: &mut Compiler, tree: &Tree) -> CR { - let lhs = tree.nth_tree(0)?; - compile_expression(c, lhs); - - let id = tree.nth_token(2)?; - let Type::Class(cr) = c.semantics.type_of(lhs) else { - return None; - }; - let Some((index, _fld)) = cr - .fields - .iter() - .enumerate() - .find(|(_, f)| &*f.name == id.as_str()) - else { - return None; - }; - c.push(Instruction::LoadSlot(index)); + // In member access; the lhs sets up the object and in theory the rhs + // binds against it. ::shrug:: + compile_expression(c, tree.nth_tree(0)?); + compile_expression(c, tree.nth_tree(2)?); OK } diff --git a/fine/src/parser.rs b/fine/src/parser.rs index 50a2c244..1e0101c5 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -129,6 +129,8 @@ pub enum TreeKind { ConditionalExpression, ExpressionStatement, FieldDecl, + FieldList, + FieldValue, File, ForStatement, FunctionDecl, @@ -139,18 +141,18 @@ pub enum TreeKind { ListConstructor, ListConstructorElement, LiteralExpression, + MemberAccess, + NewObjectExpression, ParamList, Parameter, ReturnStatement, ReturnType, + SelfParameter, + SelfReference, TypeExpression, TypeParameter, TypeParameterList, UnaryExpression, - FieldList, - NewObjectExpression, - FieldValue, - MemberAccess, } pub struct Tree<'a> { @@ -606,6 +608,8 @@ fn param_list(p: &mut CParser) { while !p.at(TokenKind::RightParen) && !p.eof() { if p.at(TokenKind::Identifier) { parameter(p); + } else if p.at(TokenKind::Selff) { + self_parameter(p); } else { if p.at_any(PARAM_LIST_RECOVERY) { break; @@ -632,6 +636,21 @@ fn parameter(p: &mut CParser) { p.end(m, TreeKind::Parameter); } +fn self_parameter(p: &mut CParser) { + let m = p.start(); + + p.expect_start(TokenKind::Selff); + if p.eat(TokenKind::Colon) { + p.error("self parameters cannot have explicit types"); + type_expr(p); + } + if !p.at(TokenKind::RightParen) { + p.expect(TokenKind::Comma, "expected a comma between parameters"); + } + + p.end(m, TreeKind::SelfParameter); +} + fn return_type(p: &mut CParser) { let m = p.start(); @@ -780,31 +799,27 @@ fn expression(p: &mut CParser) { expression_with_power(p, 0) } -// BINDING POWERS. When parsing expressions we only accept expressions that -// meet a minimum binding power. (This is like "precedence" but I just super -// don't like that terminology.) -const ASSIGNMENT_POWER: u8 = 0; // = -const OR_POWER: u8 = 1; // or -const AND_POWER: u8 = 2; // and -const EQUALITY_POWER: u8 = 3; // == != -const COMPARISON_POWER: u8 = 4; // < > <= >= -const TERM_POWER: u8 = 5; // + - -const FACTOR_POWER: u8 = 6; // * / -const UNARY_POWER: u8 = 7; // ! - +const UNARY_POWER: u8 = 14; -// const PRIMARY_POWER: u8 = 9; - -fn token_power<'a>(token: TokenKind) -> Option { +fn infix_power(token: TokenKind) -> Option<(u8, u8)> { + // A dumb thing: the pair controls associativity. + // + // If lhs < rhs then it's left-associative, otherwise it's + // right-associative. match token { - TokenKind::Equal => Some(ASSIGNMENT_POWER), - TokenKind::Or => Some(OR_POWER), - TokenKind::And => Some(AND_POWER), - TokenKind::EqualEqual | TokenKind::BangEqual => Some(EQUALITY_POWER), + TokenKind::Equal => Some((1, 0)), + TokenKind::Or => Some((2, 3)), + TokenKind::And => Some((4, 5)), + TokenKind::EqualEqual | TokenKind::BangEqual => Some((6, 7)), TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => { - Some(COMPARISON_POWER) + Some((8, 9)) } - TokenKind::Plus | TokenKind::Minus => Some(TERM_POWER), - TokenKind::Star | TokenKind::Slash => Some(FACTOR_POWER), + TokenKind::Plus | TokenKind::Minus => Some((10, 11)), + TokenKind::Star | TokenKind::Slash => Some((12, 13)), + // + // UNARY_POWER goes here. + // + TokenKind::Dot => Some((16, 17)), _ => None, } } @@ -818,10 +833,11 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) { } loop { - let Some(power) = token_power(p.peek()) else { + let token = p.peek(); + let Some((lp, rp)) = infix_power(token) else { break; }; - if power < minimum_power { + if lp < minimum_power { break; } @@ -829,18 +845,15 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) { // see won't we. let m = p.start_before(expr); p.advance(); // Consume the operator - expression_with_power(p, power); - expr = p.end(m, TreeKind::BinaryExpression); - } - - while p.at(TokenKind::Dot) { - let m = p.start_before(expr); - p.advance(); // Consume the dot - p.expect( - TokenKind::Identifier, - "expected an identifier for member access", + expression_with_power(p, rp); + expr = p.end( + m, + if token == TokenKind::Dot { + TreeKind::MemberAccess + } else { + TreeKind::BinaryExpression + }, ); - expr = p.end(m, TreeKind::MemberAccess); } } @@ -885,6 +898,7 @@ fn prefix_expression(p: &mut CParser) -> MarkClosed { TokenKind::If => conditional(p), TokenKind::Identifier => identifier(p), + TokenKind::Selff => self_reference(p), TokenKind::LeftBracket => list_constructor(p), @@ -946,6 +960,15 @@ fn identifier(p: &mut CParser) -> MarkClosed { p.end(m, TreeKind::Identifier) } +fn self_reference(p: &mut CParser) -> MarkClosed { + assert!(p.at(TokenKind::Selff)); + let m = p.start(); + + p.advance(); + + p.end(m, TreeKind::SelfReference) +} + fn list_constructor(p: &mut CParser) -> MarkClosed { let m = p.start(); diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index 0ceeafae..dd9837c3 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -68,6 +68,23 @@ pub struct ClassDecl { pub decl_tree: TreeRef, } +#[derive(Clone)] +pub struct ClassRef(Rc); + +impl ClassRef { + pub fn new(class: ClassDecl) -> Self { + ClassRef(Rc::new(class)) + } +} + +impl std::ops::Deref for ClassRef { + type Target = ClassDecl; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Clone)] pub enum Type { // Signals a type error. If you receive this then you know that an error @@ -99,7 +116,10 @@ pub enum Type { Function(Vec>, Box), List(Box), - Class(Rc), + + // Classes need to be fetched explicitly from the semantics; they are + // computed lazily. + Class(TreeRef, Rc), } impl Type { @@ -144,7 +164,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), + Class(_, name) => write!(f, "class {}", name), } } } @@ -154,7 +174,7 @@ pub enum Location { Argument, Local, Module, - // TODO: Member + Slot, // TODO: ArrayIndex } @@ -204,6 +224,7 @@ impl Environment { let base = parent.as_ref().map(|p| p.next_index).unwrap_or(0); let next_index = match (parent_location, location) { (_, Location::Argument) => 0, + (_, Location::Slot) => 0, (Location::Local, Location::Local) => base, (_, Location::Local) => 0, @@ -221,8 +242,12 @@ impl Environment { } pub fn insert(&mut self, token: &Token, t: Type) -> Option { + self.insert_name(token.as_str().into(), t) + } + + pub fn insert_name(&mut self, name: Box, t: Type) -> Option { let result = self.declarations.insert( - token.as_str().into(), + name, Declaration::Variable { declaration_type: t, location: self.location, @@ -341,6 +366,17 @@ fn set_logical_parents( } } } + TreeKind::MemberAccess => { + // The LHS has the environment of my parent, because I set up an + // environment containing only members of the type of the LHS. The + // RHS has that one. + if let Some(lhs) = tree.nth_tree(0) { + set_logical_parents(parents, syntax_tree, lhs, parent); + } + if let Some(rhs) = tree.nth_tree(2) { + set_logical_parents(parents, syntax_tree, rhs, Some(t)); + } + } _ => { // By default, the parent for each child is current tree. for child in &tree.children { @@ -375,6 +411,7 @@ pub struct Semantics<'a> { types: RefCell>>, environments: RefCell>>, root_environment: EnvironmentRef, + classes: RefCell>>, } impl<'a> Semantics<'a> { @@ -394,6 +431,7 @@ impl<'a> Semantics<'a> { types: RefCell::new(vec![Incremental::None; tree.len()]), environments: RefCell::new(vec![Incremental::None; tree.len()]), root_environment: EnvironmentRef::new(root_environment), + classes: RefCell::new(vec![Incremental::None; tree.len()]), }; // NOTE: We ensure all the known errors are reported before we move @@ -519,6 +557,8 @@ impl<'a> Semantics<'a> { TreeKind::ForStatement => self.environment_of_for(parent, tree), + TreeKind::MemberAccess => self.environment_of_member_access(parent, tree), + _ => parent, }; @@ -629,6 +669,13 @@ impl<'a> Semantics<'a> { Location::Local => Location::Local, Location::Module => Location::Module, Location::Argument => Location::Local, + + // TODO: Wait can I... do wild stuff? e.g.: + // + // foo.{let y = 23; bar} + // + // ??? + Location::Slot => Location::Local, }; let mut environment = Environment::new(Some(parent), location); @@ -689,6 +736,91 @@ impl<'a> Semantics<'a> { EnvironmentRef::new(environment) } + fn environment_of_member_access(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef { + // TODO: Build an "error environment" and then anything that tried to + // bind in it would know to give up and not report a new error. + + assert_eq!(tree.kind, TreeKind::MemberAccess); + + // Build environment out of members of type of lhs + let Some(lhs) = tree.nth_tree(0) else { + return parent; + }; + let Some(op) = tree.nth_token(1) else { + return parent; + }; + let typ = self.type_of(lhs); + match &typ { + Type::Class(ct, _) => { + // NOTE: The parent here is None! environment search after a dot + // is constrained to the scope of the type, obviously! + let class = self.class_of(*ct); + let mut env = Environment::new(None, Location::Slot); + + // TODO: Cache environment of type. + // NOTE: It is important that this go in order! The location + // index is the slot index! + for field in class.fields.iter() { + env.insert_name((&*field.name).into(), field.field_type.clone()); + } + + EnvironmentRef::new(env) + } + Type::Error => parent, // TODO: WRONG + _ => { + // TODO: This is probably wrong, yeah? + self.report_error(op.start, format!("cannot access members of '{typ}'")); + parent + } + } + } + + pub fn class_of(&self, t: TreeRef) -> ClassRef { + { + // I want to make sure that this borrow is dropped after this block. + let mut borrow = self.classes.borrow_mut(); + let state = &mut borrow[t.index()]; + match state { + Incremental::None => (), + Incremental::Complete(e) => return e.clone(), + Incremental::InProgress => { + drop(borrow); + self.internal_compiler_error(Some(t), "circular class dependency"); + } + } + *state = Incremental::InProgress; + } + + // TODO: Right now there's only one way to make a class decl. :P + let tree = &self.syntax_tree[t]; + assert_eq!(tree.kind, TreeKind::ClassDecl); + + let name = tree.nth_token(1).map(|t| t.as_str()).unwrap_or(""); + let mut fields = Vec::new(); + for field in tree.children_of_kind(self.syntax_tree, TreeKind::FieldDecl) { + let f = &self.syntax_tree[field]; + if let Some(field_name) = f.nth_token(0) { + let field_type = f + .nth_tree(2) + .map(|t| self.type_of(t)) + .unwrap_or(Type::Error); + fields.push(FieldDecl { + name: field_name.as_str().into(), + field_type, + }); + } + } + + let result = ClassRef::new(ClassDecl { + name: name.into(), + fields: fields.into(), + decl_tree: t, + }); + + self.classes.borrow_mut()[t.index()] = Incremental::Complete(result.clone()); + result + } + pub fn type_compat(&self, a: &Type, b: &Type) -> bool { // TODO: Convert this into "can become" or something. // TODO: This is wrong; we because of numeric literals etc. @@ -709,14 +841,14 @@ impl<'a> Semantics<'a> { .all(|(a, b)| self.type_compat(a, b)) } - (Type::Class(ca), Type::Class(cb)) => { + (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 + ca == cb } // Avoid introducing more errors @@ -1182,29 +1314,8 @@ impl<'a> Semantics<'a> { fn type_of_member_access(&self, tree: &Tree) -> Option { assert_eq!(tree.kind, TreeKind::MemberAccess); - let op = tree.nth_token(1)?; - let member = tree.nth_token(2)?; - let typ = self.type_of(tree.nth_tree(0)?); - match &typ { - Type::Class(c) => { - // TODO: Accelerate? - if let Some(field) = c.fields.iter().find(|f| &*f.name == member.as_str()) { - Some(field.field_type.clone()) - } else { - self.report_error( - op.start, - format!("'{typ}' does not have a member named '{member}'"), - ); - Some(Type::Error) - } - } - Type::Error => Some(Type::Error), - _ => { - // TODO: This is probably wrong, yeah? - self.report_error(op.start, format!("cannot access members of '{typ}'")); - Some(Type::Error) - } - } + // Type of member access is the type of the RHS. + Some(self.type_of(tree.nth_tree(2)?)) } fn type_of_expression_statement(&self, tree: &Tree) -> Option { @@ -1320,26 +1431,9 @@ impl<'a> Semantics<'a> { fn type_of_class_decl(&self, t: TreeRef, tree: &Tree) -> Option { assert_eq!(tree.kind, TreeKind::ClassDecl); - // TODO: Field types need to be lazy because of recursion. - + // The type of a class is computed lazily. let name = tree.nth_token(1)?; - let mut fields = Vec::new(); - for field in tree.children_of_kind(self.syntax_tree, TreeKind::FieldDecl) { - let f = &self.syntax_tree[field]; - let field_name = f.nth_token(0)?; - fields.push(FieldDecl { - name: field_name.as_str().into(), - field_type: self.type_of(f.nth_tree(2)?), - }); - } - - let cd = ClassDecl { - name: name.as_str().into(), - fields: fields.into(), - decl_tree: t, - }; - - Some(Type::Class(cd.into())) + Some(Type::Class(t, name.as_str().into())) } fn type_of_field_value(&self, t: TreeRef, tree: &Tree) -> Option { @@ -1533,6 +1627,8 @@ pub fn check(s: &Semantics) { TreeKind::FieldList => {} TreeKind::NewObjectExpression => check_new_object_expression(s, tree), TreeKind::FieldValue => {} + TreeKind::SelfParameter => {} + TreeKind::SelfReference => {} } } } @@ -1626,7 +1722,9 @@ fn check_new_object_expression(s: &Semantics, tree: &Tree) { let class_type = s.type_of(type_expression); match &class_type { - Type::Class(c) => { + Type::Class(c, _) => { + let class = s.class_of(*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) { @@ -1640,7 +1738,7 @@ fn check_new_object_expression(s: &Semantics, tree: &Tree) { } // Check individual bindings... - for f in c.fields.iter() { + for f in class.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( diff --git a/fine/tests/expression/class.fine b/fine/tests/expression/class.fine index 07a60799..e9c7d7b9 100644 --- a/fine/tests/expression/class.fine +++ b/fine/tests/expression/class.fine @@ -2,22 +2,31 @@ class Point { x: f64; y: f64; - fun something_static() -> f64 { - 12 - } + // fun something_static() -> f64 { + // 12 + // } - fun square_length(self) -> f64 { - self.x * self.x + self.y * self.y - } + // fun square_length(self) -> f64 { + // self.x * self.x + self.y * self.y + // } +} + +class Line { + start: Point; + end: Point; } fun test() -> f64 { - let pt = new Point { x: 7, y: 23 }; - let z = pt.x + pt.square_length() + Point::something_static(); + let line = new Line { + start: new Point { x: 7, y: 23 }, + end: new Point { x: 999, y: 99 }, + }; + + let z = line.start.x;// + pt.square_length() + Point::something_static(); z } -// @ignore WIP: Methods +/// @ignore WIP: Methods // @no-errors // @eval: Float(7.0) // @compiles-to: @@ -35,17 +44,33 @@ fun test() -> f64 { // | 2: PushString(0) // | 3: NewObject(2) // | 4: Return +// | function Line (4 args, 0 locals): +// | strings (1): +// | 0: Line +// | code (5): +// | 0: LoadArgument(1) +// | 1: LoadArgument(0) +// | 2: PushString(0) +// | 3: NewObject(2) +// | 4: Return // | function test (0 args, 2 locals): // | strings (0): -// | code (10): -// | 0: PushFloat(23.0) -// | 1: PushFloat(7.0) +// | code (17): +// | 0: PushFloat(99.0) +// | 1: PushFloat(999.0) // | 2: LoadFunction(1) // | 3: Call(2) -// | 4: StoreLocal(0) -// | 5: LoadLocal(0) -// | 6: LoadSlot(0) -// | 7: StoreLocal(1) -// | 8: LoadLocal(1) -// | 9: Return +// | 4: PushFloat(23.0) +// | 5: PushFloat(7.0) +// | 6: LoadFunction(1) +// | 7: Call(2) +// | 8: LoadFunction(2) +// | 9: Call(2) +// | 10: StoreLocal(0) +// | 11: LoadLocal(0) +// | 12: LoadSlot(0) +// | 13: LoadSlot(0) +// | 14: StoreLocal(1) +// | 15: LoadLocal(1) +// | 16: Return // | diff --git a/fine/tests/expression/errors/wild_member_access.fine b/fine/tests/expression/errors/wild_member_access.fine new file mode 100644 index 00000000..649b6f2d --- /dev/null +++ b/fine/tests/expression/errors/wild_member_access.fine @@ -0,0 +1,18 @@ +class Foo { + foo: f64; +} + +fun test() { + let f = new Foo { foo: 12 }; + let z = f.{let y = 222; foo }; +} + +// NOTE: The AST allows for generic expressions to the right of the dot. +// We need to make sure things are parsed correctly. +// +// TODO: Better parser recovery will improve the specifics of the errors. +// @expect-errors: +// | 7:12: Error at '{': expected an expression +// | 7:13: Error at 'let': expect ';' to end a let statement +// | 7:26: cannot find value foo here +// | 8:0: Error at '}': unbalanced '}'