[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:
parent
0d48bfb113
commit
2839b43f6d
5 changed files with 286 additions and 123 deletions
|
|
@ -487,6 +487,9 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
|
||||||
compiler_assert!(c, t, index < c.module.globals);
|
compiler_assert!(c, t, index < c.module.globals);
|
||||||
Instruction::StoreModule(index)
|
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);
|
c.push(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Member
|
||||||
|
// TODO: List element
|
||||||
_ => ice!(c, t, "Unsupported lvalue type"),
|
_ => ice!(c, t, "Unsupported lvalue type"),
|
||||||
}
|
}
|
||||||
OK
|
OK
|
||||||
|
|
@ -533,6 +539,10 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
|
||||||
compiler_assert!(c, t, index < c.module.globals);
|
compiler_assert!(c, t, index < c.module.globals);
|
||||||
Instruction::LoadModule(index)
|
Instruction::LoadModule(index)
|
||||||
}
|
}
|
||||||
|
Location::Slot => {
|
||||||
|
// TODO: Assert slot is in field range?
|
||||||
|
Instruction::LoadSlot(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Declaration::Function { declaration, .. } => {
|
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 {
|
fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
||||||
// We pass in the arguments.... by... field order?
|
// 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);
|
c.push(Instruction::Panic);
|
||||||
return OK;
|
return OK;
|
||||||
};
|
};
|
||||||
|
let class = c.semantics.class_of(ct);
|
||||||
|
|
||||||
let field_list = tree.child_tree_of_kind(c.syntax, TreeKind::FieldList)?;
|
let field_list = tree.child_tree_of_kind(c.syntax, TreeKind::FieldList)?;
|
||||||
let mut field_bindings = HashMap::new();
|
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 {
|
fn compile_member_access(c: &mut Compiler, tree: &Tree) -> CR {
|
||||||
let lhs = tree.nth_tree(0)?;
|
// In member access; the lhs sets up the object and in theory the rhs
|
||||||
compile_expression(c, lhs);
|
// binds against it. ::shrug::
|
||||||
|
compile_expression(c, tree.nth_tree(0)?);
|
||||||
let id = tree.nth_token(2)?;
|
compile_expression(c, tree.nth_tree(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));
|
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,8 @@ pub enum TreeKind {
|
||||||
ConditionalExpression,
|
ConditionalExpression,
|
||||||
ExpressionStatement,
|
ExpressionStatement,
|
||||||
FieldDecl,
|
FieldDecl,
|
||||||
|
FieldList,
|
||||||
|
FieldValue,
|
||||||
File,
|
File,
|
||||||
ForStatement,
|
ForStatement,
|
||||||
FunctionDecl,
|
FunctionDecl,
|
||||||
|
|
@ -139,18 +141,18 @@ pub enum TreeKind {
|
||||||
ListConstructor,
|
ListConstructor,
|
||||||
ListConstructorElement,
|
ListConstructorElement,
|
||||||
LiteralExpression,
|
LiteralExpression,
|
||||||
|
MemberAccess,
|
||||||
|
NewObjectExpression,
|
||||||
ParamList,
|
ParamList,
|
||||||
Parameter,
|
Parameter,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
ReturnType,
|
ReturnType,
|
||||||
|
SelfParameter,
|
||||||
|
SelfReference,
|
||||||
TypeExpression,
|
TypeExpression,
|
||||||
TypeParameter,
|
TypeParameter,
|
||||||
TypeParameterList,
|
TypeParameterList,
|
||||||
UnaryExpression,
|
UnaryExpression,
|
||||||
FieldList,
|
|
||||||
NewObjectExpression,
|
|
||||||
FieldValue,
|
|
||||||
MemberAccess,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tree<'a> {
|
pub struct Tree<'a> {
|
||||||
|
|
@ -606,6 +608,8 @@ fn param_list(p: &mut CParser) {
|
||||||
while !p.at(TokenKind::RightParen) && !p.eof() {
|
while !p.at(TokenKind::RightParen) && !p.eof() {
|
||||||
if p.at(TokenKind::Identifier) {
|
if p.at(TokenKind::Identifier) {
|
||||||
parameter(p);
|
parameter(p);
|
||||||
|
} else if p.at(TokenKind::Selff) {
|
||||||
|
self_parameter(p);
|
||||||
} else {
|
} else {
|
||||||
if p.at_any(PARAM_LIST_RECOVERY) {
|
if p.at_any(PARAM_LIST_RECOVERY) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -632,6 +636,21 @@ fn parameter(p: &mut CParser) {
|
||||||
p.end(m, TreeKind::Parameter);
|
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) {
|
fn return_type(p: &mut CParser) {
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
|
|
||||||
|
|
@ -780,31 +799,27 @@ fn expression(p: &mut CParser) {
|
||||||
expression_with_power(p, 0)
|
expression_with_power(p, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BINDING POWERS. When parsing expressions we only accept expressions that
|
const UNARY_POWER: u8 = 14;
|
||||||
// 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 PRIMARY_POWER: u8 = 9;
|
fn infix_power(token: TokenKind) -> Option<(u8, u8)> {
|
||||||
|
// A dumb thing: the pair controls associativity.
|
||||||
fn token_power<'a>(token: TokenKind) -> Option<u8> {
|
//
|
||||||
|
// If lhs < rhs then it's left-associative, otherwise it's
|
||||||
|
// right-associative.
|
||||||
match token {
|
match token {
|
||||||
TokenKind::Equal => Some(ASSIGNMENT_POWER),
|
TokenKind::Equal => Some((1, 0)),
|
||||||
TokenKind::Or => Some(OR_POWER),
|
TokenKind::Or => Some((2, 3)),
|
||||||
TokenKind::And => Some(AND_POWER),
|
TokenKind::And => Some((4, 5)),
|
||||||
TokenKind::EqualEqual | TokenKind::BangEqual => Some(EQUALITY_POWER),
|
TokenKind::EqualEqual | TokenKind::BangEqual => Some((6, 7)),
|
||||||
TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => {
|
TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => {
|
||||||
Some(COMPARISON_POWER)
|
Some((8, 9))
|
||||||
}
|
}
|
||||||
TokenKind::Plus | TokenKind::Minus => Some(TERM_POWER),
|
TokenKind::Plus | TokenKind::Minus => Some((10, 11)),
|
||||||
TokenKind::Star | TokenKind::Slash => Some(FACTOR_POWER),
|
TokenKind::Star | TokenKind::Slash => Some((12, 13)),
|
||||||
|
//
|
||||||
|
// UNARY_POWER goes here.
|
||||||
|
//
|
||||||
|
TokenKind::Dot => Some((16, 17)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -818,10 +833,11 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Some(power) = token_power(p.peek()) else {
|
let token = p.peek();
|
||||||
|
let Some((lp, rp)) = infix_power(token) else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
if power < minimum_power {
|
if lp < minimum_power {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -829,18 +845,15 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
||||||
// see won't we.
|
// see won't we.
|
||||||
let m = p.start_before(expr);
|
let m = p.start_before(expr);
|
||||||
p.advance(); // Consume the operator
|
p.advance(); // Consume the operator
|
||||||
expression_with_power(p, power);
|
expression_with_power(p, rp);
|
||||||
expr = p.end(m, TreeKind::BinaryExpression);
|
expr = p.end(
|
||||||
}
|
m,
|
||||||
|
if token == TokenKind::Dot {
|
||||||
while p.at(TokenKind::Dot) {
|
TreeKind::MemberAccess
|
||||||
let m = p.start_before(expr);
|
} else {
|
||||||
p.advance(); // Consume the dot
|
TreeKind::BinaryExpression
|
||||||
p.expect(
|
},
|
||||||
TokenKind::Identifier,
|
|
||||||
"expected an identifier for member access",
|
|
||||||
);
|
);
|
||||||
expr = p.end(m, TreeKind::MemberAccess);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -885,6 +898,7 @@ fn prefix_expression(p: &mut CParser) -> MarkClosed {
|
||||||
TokenKind::If => conditional(p),
|
TokenKind::If => conditional(p),
|
||||||
|
|
||||||
TokenKind::Identifier => identifier(p),
|
TokenKind::Identifier => identifier(p),
|
||||||
|
TokenKind::Selff => self_reference(p),
|
||||||
|
|
||||||
TokenKind::LeftBracket => list_constructor(p),
|
TokenKind::LeftBracket => list_constructor(p),
|
||||||
|
|
||||||
|
|
@ -946,6 +960,15 @@ fn identifier(p: &mut CParser) -> MarkClosed {
|
||||||
p.end(m, TreeKind::Identifier)
|
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 {
|
fn list_constructor(p: &mut CParser) -> MarkClosed {
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,23 @@ pub struct ClassDecl {
|
||||||
pub decl_tree: TreeRef,
|
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)]
|
#[derive(Clone)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
// Signals a type error. If you receive this then you know that an error
|
// 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>),
|
Function(Vec<Box<Type>>, Box<Type>),
|
||||||
List(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 {
|
impl Type {
|
||||||
|
|
@ -144,7 +164,7 @@ impl fmt::Display for Type {
|
||||||
// TODO: Better names
|
// TODO: Better names
|
||||||
TypeVariable(_) => write!(f, "$_"),
|
TypeVariable(_) => write!(f, "$_"),
|
||||||
List(t) => write!(f, "list<{t}>"),
|
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,
|
Argument,
|
||||||
Local,
|
Local,
|
||||||
Module,
|
Module,
|
||||||
// TODO: Member
|
Slot,
|
||||||
// TODO: ArrayIndex
|
// TODO: ArrayIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,6 +224,7 @@ impl Environment {
|
||||||
let base = parent.as_ref().map(|p| p.next_index).unwrap_or(0);
|
let base = parent.as_ref().map(|p| p.next_index).unwrap_or(0);
|
||||||
let next_index = match (parent_location, location) {
|
let next_index = match (parent_location, location) {
|
||||||
(_, Location::Argument) => 0,
|
(_, Location::Argument) => 0,
|
||||||
|
(_, Location::Slot) => 0,
|
||||||
|
|
||||||
(Location::Local, Location::Local) => base,
|
(Location::Local, Location::Local) => base,
|
||||||
(_, Location::Local) => 0,
|
(_, Location::Local) => 0,
|
||||||
|
|
@ -221,8 +242,12 @@ impl Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, token: &Token, t: Type) -> Option<Declaration> {
|
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(
|
let result = self.declarations.insert(
|
||||||
token.as_str().into(),
|
name,
|
||||||
Declaration::Variable {
|
Declaration::Variable {
|
||||||
declaration_type: t,
|
declaration_type: t,
|
||||||
location: self.location,
|
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.
|
// By default, the parent for each child is current tree.
|
||||||
for child in &tree.children {
|
for child in &tree.children {
|
||||||
|
|
@ -375,6 +411,7 @@ pub struct Semantics<'a> {
|
||||||
types: RefCell<Vec<Incremental<Type>>>,
|
types: RefCell<Vec<Incremental<Type>>>,
|
||||||
environments: RefCell<Vec<Incremental<EnvironmentRef>>>,
|
environments: RefCell<Vec<Incremental<EnvironmentRef>>>,
|
||||||
root_environment: EnvironmentRef,
|
root_environment: EnvironmentRef,
|
||||||
|
classes: RefCell<Vec<Incremental<ClassRef>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Semantics<'a> {
|
impl<'a> Semantics<'a> {
|
||||||
|
|
@ -394,6 +431,7 @@ impl<'a> Semantics<'a> {
|
||||||
types: RefCell::new(vec![Incremental::None; tree.len()]),
|
types: RefCell::new(vec![Incremental::None; tree.len()]),
|
||||||
environments: RefCell::new(vec![Incremental::None; tree.len()]),
|
environments: RefCell::new(vec![Incremental::None; tree.len()]),
|
||||||
root_environment: EnvironmentRef::new(root_environment),
|
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
|
// 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::ForStatement => self.environment_of_for(parent, tree),
|
||||||
|
|
||||||
|
TreeKind::MemberAccess => self.environment_of_member_access(parent, tree),
|
||||||
|
|
||||||
_ => parent,
|
_ => parent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -629,6 +669,13 @@ impl<'a> Semantics<'a> {
|
||||||
Location::Local => Location::Local,
|
Location::Local => Location::Local,
|
||||||
Location::Module => Location::Module,
|
Location::Module => Location::Module,
|
||||||
Location::Argument => Location::Local,
|
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);
|
let mut environment = Environment::new(Some(parent), location);
|
||||||
|
|
@ -689,6 +736,91 @@ impl<'a> Semantics<'a> {
|
||||||
EnvironmentRef::new(environment)
|
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 {
|
pub fn type_compat(&self, a: &Type, b: &Type) -> bool {
|
||||||
// TODO: Convert this into "can become" or something.
|
// TODO: Convert this into "can become" or something.
|
||||||
// TODO: This is wrong; we because of numeric literals etc.
|
// 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))
|
.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...
|
// TODO: If we were doing structural comparisons here...
|
||||||
// maybe? MAYBE?
|
// maybe? MAYBE?
|
||||||
//
|
//
|
||||||
// Like if this is directional we can look for field
|
// Like if this is directional we can look for field
|
||||||
// subsets { ..:int, ..:int } can be { ..:int } etc.
|
// subsets { ..:int, ..:int } can be { ..:int } etc.
|
||||||
//
|
//
|
||||||
ca.decl_tree == cb.decl_tree
|
ca == cb
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid introducing more errors
|
// Avoid introducing more errors
|
||||||
|
|
@ -1182,29 +1314,8 @@ impl<'a> Semantics<'a> {
|
||||||
fn type_of_member_access(&self, tree: &Tree) -> Option<Type> {
|
fn type_of_member_access(&self, tree: &Tree) -> Option<Type> {
|
||||||
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
||||||
|
|
||||||
let op = tree.nth_token(1)?;
|
// Type of member access is the type of the RHS.
|
||||||
let member = tree.nth_token(2)?;
|
Some(self.type_of(tree.nth_tree(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> {
|
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> {
|
fn type_of_class_decl(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
||||||
assert_eq!(tree.kind, TreeKind::ClassDecl);
|
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 name = tree.nth_token(1)?;
|
||||||
let mut fields = Vec::new();
|
Some(Type::Class(t, name.as_str().into()))
|
||||||
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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_of_field_value(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
fn type_of_field_value(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
||||||
|
|
@ -1533,6 +1627,8 @@ pub fn check(s: &Semantics) {
|
||||||
TreeKind::FieldList => {}
|
TreeKind::FieldList => {}
|
||||||
TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
|
TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
|
||||||
TreeKind::FieldValue => {}
|
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);
|
let class_type = s.type_of(type_expression);
|
||||||
match &class_type {
|
match &class_type {
|
||||||
Type::Class(c) => {
|
Type::Class(c, _) => {
|
||||||
|
let class = s.class_of(*c);
|
||||||
|
|
||||||
let mut any_errors = false;
|
let mut any_errors = false;
|
||||||
let mut field_bindings = HashMap::new();
|
let mut field_bindings = HashMap::new();
|
||||||
for field in field_list.children_of_kind(s.syntax_tree, TreeKind::FieldValue) {
|
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...
|
// 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 let Some((field_tree, expr_type)) = field_bindings.get(&*f.name) {
|
||||||
if !s.type_compat(&f.field_type, expr_type) {
|
if !s.type_compat(&f.field_type, expr_type) {
|
||||||
s.report_error_tree_ref(
|
s.report_error_tree_ref(
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,31 @@ class Point {
|
||||||
x: f64;
|
x: f64;
|
||||||
y: f64;
|
y: f64;
|
||||||
|
|
||||||
fun something_static() -> f64 {
|
// fun something_static() -> f64 {
|
||||||
12
|
// 12
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fun square_length(self) -> f64 {
|
||||||
|
// self.x * self.x + self.y * self.y
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun square_length(self) -> f64 {
|
class Line {
|
||||||
self.x * self.x + self.y * self.y
|
start: Point;
|
||||||
}
|
end: Point;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test() -> f64 {
|
fun test() -> f64 {
|
||||||
let pt = new Point { x: 7, y: 23 };
|
let line = new Line {
|
||||||
let z = pt.x + pt.square_length() + Point::something_static();
|
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
|
z
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ignore WIP: Methods
|
/// @ignore WIP: Methods
|
||||||
// @no-errors
|
// @no-errors
|
||||||
// @eval: Float(7.0)
|
// @eval: Float(7.0)
|
||||||
// @compiles-to:
|
// @compiles-to:
|
||||||
|
|
@ -35,17 +44,33 @@ fun test() -> f64 {
|
||||||
// | 2: PushString(0)
|
// | 2: PushString(0)
|
||||||
// | 3: NewObject(2)
|
// | 3: NewObject(2)
|
||||||
// | 4: Return
|
// | 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):
|
// | function test (0 args, 2 locals):
|
||||||
// | strings (0):
|
// | strings (0):
|
||||||
// | code (10):
|
// | code (17):
|
||||||
// | 0: PushFloat(23.0)
|
// | 0: PushFloat(99.0)
|
||||||
// | 1: PushFloat(7.0)
|
// | 1: PushFloat(999.0)
|
||||||
// | 2: LoadFunction(1)
|
// | 2: LoadFunction(1)
|
||||||
// | 3: Call(2)
|
// | 3: Call(2)
|
||||||
// | 4: StoreLocal(0)
|
// | 4: PushFloat(23.0)
|
||||||
// | 5: LoadLocal(0)
|
// | 5: PushFloat(7.0)
|
||||||
// | 6: LoadSlot(0)
|
// | 6: LoadFunction(1)
|
||||||
// | 7: StoreLocal(1)
|
// | 7: Call(2)
|
||||||
// | 8: LoadLocal(1)
|
// | 8: LoadFunction(2)
|
||||||
// | 9: Return
|
// | 9: Call(2)
|
||||||
|
// | 10: StoreLocal(0)
|
||||||
|
// | 11: LoadLocal(0)
|
||||||
|
// | 12: LoadSlot(0)
|
||||||
|
// | 13: LoadSlot(0)
|
||||||
|
// | 14: StoreLocal(1)
|
||||||
|
// | 15: LoadLocal(1)
|
||||||
|
// | 16: Return
|
||||||
// |
|
// |
|
||||||
|
|
|
||||||
18
fine/tests/expression/errors/wild_member_access.fine
Normal file
18
fine/tests/expression/errors/wild_member_access.fine
Normal 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 '}'
|
||||||
Loading…
Add table
Add a link
Reference in a new issue