[fine] Improvements to classes

- Classes are defined lazily
- Member access is via environment
- Member access is just a binary expression with a weird environment
- Slot loads look like variable loads now
- Associativity in the parser (ugh)
This commit is contained in:
John Doty 2024-01-22 23:17:02 -08:00
parent 0d48bfb113
commit 2839b43f6d
5 changed files with 286 additions and 123 deletions

View file

@ -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
}

View file

@ -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<u8> {
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();

View file

@ -68,6 +68,23 @@ pub struct ClassDecl {
pub decl_tree: TreeRef,
}
#[derive(Clone)]
pub struct ClassRef(Rc<ClassDecl>);
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<Type>>, Box<Type>),
List(Box<Type>),
Class(Rc<ClassDecl>),
// Classes need to be fetched explicitly from the semantics; they are
// computed lazily.
Class(TreeRef, Rc<str>),
}
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<Declaration> {
self.insert_name(token.as_str().into(), t)
}
pub fn insert_name(&mut self, name: Box<str>, t: Type) -> Option<Declaration> {
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<Vec<Incremental<Type>>>,
environments: RefCell<Vec<Incremental<EnvironmentRef>>>,
root_environment: EnvironmentRef,
classes: RefCell<Vec<Incremental<ClassRef>>>,
}
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<Type> {
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<Type> {
@ -1320,26 +1431,9 @@ impl<'a> Semantics<'a> {
fn type_of_class_decl(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
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<Type> {
@ -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(

View file

@ -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
// |

View file

@ -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 '}'