[fine] Support assignment to member, loops with iterators
Hmm it's starting to look like something.
This commit is contained in:
parent
3415b1a3f6
commit
239e859eaf
7 changed files with 360 additions and 171 deletions
|
|
@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
|
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
|
||||||
semantics::{Declaration, Location, Semantics, Type},
|
semantics::{Declaration, Environment, Location, Semantics, Type},
|
||||||
tokens::TokenKind,
|
tokens::TokenKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -26,7 +26,11 @@ pub enum Instruction {
|
||||||
FloatSubtract,
|
FloatSubtract,
|
||||||
GreaterFloat,
|
GreaterFloat,
|
||||||
GreaterString,
|
GreaterString,
|
||||||
|
IsBool,
|
||||||
IsClass(i64),
|
IsClass(i64),
|
||||||
|
IsFloat,
|
||||||
|
IsNothing,
|
||||||
|
IsString,
|
||||||
Jump(usize),
|
Jump(usize),
|
||||||
JumpFalse(usize),
|
JumpFalse(usize),
|
||||||
JumpTrue(usize), // TODO: Only one of these, and use BoolNot?
|
JumpTrue(usize), // TODO: Only one of these, and use BoolNot?
|
||||||
|
|
@ -49,6 +53,7 @@ pub enum Instruction {
|
||||||
StoreArgument(usize),
|
StoreArgument(usize),
|
||||||
StoreLocal(usize),
|
StoreLocal(usize),
|
||||||
StoreModule(usize),
|
StoreModule(usize),
|
||||||
|
StoreSlot(usize),
|
||||||
StringAdd,
|
StringAdd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +308,7 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) {
|
||||||
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
|
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
|
||||||
TreeKind::IsExpression => compile_is_expression(c, tree),
|
TreeKind::IsExpression => compile_is_expression(c, tree),
|
||||||
TreeKind::LiteralExpression => compile_literal(c, t, tree),
|
TreeKind::LiteralExpression => compile_literal(c, t, tree),
|
||||||
TreeKind::MemberAccess => compile_member_access(c, tree),
|
TreeKind::MemberAccess => compile_member_access(c, t, tree),
|
||||||
TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree),
|
TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree),
|
||||||
TreeKind::SelfReference => compile_self_reference(c),
|
TreeKind::SelfReference => compile_self_reference(c),
|
||||||
TreeKind::UnaryExpression => compile_unary_operator(c, t, tree),
|
TreeKind::UnaryExpression => compile_unary_operator(c, t, tree),
|
||||||
|
|
@ -534,49 +539,68 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
|
||||||
|
|
||||||
let lvalue = tr.nth_tree(0)?;
|
let lvalue = tr.nth_tree(0)?;
|
||||||
let ltree = &c.syntax[lvalue];
|
let ltree = &c.syntax[lvalue];
|
||||||
match ltree.kind {
|
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
let mut environment = Environment::error();
|
||||||
|
|
||||||
|
let declaration = match ltree.kind {
|
||||||
|
// TODO: Assign to list access
|
||||||
TreeKind::Identifier => {
|
TreeKind::Identifier => {
|
||||||
let ident = ltree.nth_token(0)?;
|
let id = ltree.nth_token(0)?;
|
||||||
let environment = c.semantics.environment_of(lvalue);
|
environment = c.semantics.environment_of(lvalue);
|
||||||
let declaration = environment.bind(ident)?;
|
environment.bind(id)?
|
||||||
|
}
|
||||||
let instruction = match declaration {
|
TreeKind::MemberAccess => {
|
||||||
Declaration::Variable {
|
let id = ltree.nth_token(2)?;
|
||||||
location, index, ..
|
let typ = c.semantics.type_of(ltree.nth_tree(0)?);
|
||||||
} => {
|
environment = match &typ {
|
||||||
let index = *index;
|
Type::Object(ct, _) => {
|
||||||
match location {
|
let class = c.semantics.class_of(*ct);
|
||||||
Location::Argument => {
|
class.env.clone()
|
||||||
compiler_assert!(c, t, index < c.function.args);
|
|
||||||
Instruction::StoreArgument(index)
|
|
||||||
}
|
|
||||||
Location::Local => {
|
|
||||||
if index >= c.function.locals {
|
|
||||||
c.function.locals = index + 1;
|
|
||||||
}
|
|
||||||
Instruction::StoreLocal(index)
|
|
||||||
}
|
|
||||||
Location::Module => {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Type::Class(ct, _) => {
|
||||||
Declaration::ExternFunction { .. } => inst_panic!("store ext"),
|
let class = c.semantics.class_of(*ct);
|
||||||
Declaration::Function { .. } => inst_panic!("store func"),
|
class.static_env.clone()
|
||||||
Declaration::Class { .. } => inst_panic!("store class"),
|
}
|
||||||
|
_ => return None,
|
||||||
};
|
};
|
||||||
c.push(instruction);
|
environment.bind(id)?
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let instruction = match declaration {
|
||||||
|
Declaration::Variable {
|
||||||
|
location, index, ..
|
||||||
|
} => {
|
||||||
|
let index = *index;
|
||||||
|
match location {
|
||||||
|
Location::Argument => {
|
||||||
|
compiler_assert!(c, t, index < c.function.args);
|
||||||
|
Instruction::StoreArgument(index)
|
||||||
|
}
|
||||||
|
Location::Local => {
|
||||||
|
if index >= c.function.locals {
|
||||||
|
c.function.locals = index + 1;
|
||||||
|
}
|
||||||
|
Instruction::StoreLocal(index)
|
||||||
|
}
|
||||||
|
Location::Module => {
|
||||||
|
compiler_assert!(c, t, index < c.module.globals);
|
||||||
|
Instruction::StoreModule(index)
|
||||||
|
}
|
||||||
|
Location::Slot => {
|
||||||
|
compile_expression(c, ltree.nth_tree(0)?);
|
||||||
|
Instruction::StoreSlot(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Member
|
Declaration::ExternFunction { .. } => inst_panic!("store ext"),
|
||||||
// TODO: List element
|
Declaration::Function { .. } => inst_panic!("store func"),
|
||||||
_ => ice!(c, t, "Unsupported lvalue type"),
|
Declaration::Class { .. } => inst_panic!("store class"),
|
||||||
}
|
};
|
||||||
|
c.push(instruction);
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
_ => ice!(c, t, "Unsupported binary expression '{op}'"),
|
_ => ice!(c, t, "Unsupported binary expression '{op}'"),
|
||||||
|
|
@ -754,19 +778,33 @@ fn compile_type_expr_eq(c: &mut Compiler, t: TreeRef) {
|
||||||
|
|
||||||
fn compile_type_identifier_eq(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
fn compile_type_identifier_eq(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
||||||
let identifier = tree.nth_token(0)?;
|
let identifier = tree.nth_token(0)?;
|
||||||
let environment = c.semantics.environment_of(t);
|
match identifier.as_str() {
|
||||||
match environment.bind(identifier)? {
|
"f64" => {
|
||||||
Declaration::Class { declaration, .. } => {
|
c.push(Instruction::IsFloat);
|
||||||
// The runtime identifier of the class is the tree index of the
|
|
||||||
// class declaration sure why not.
|
|
||||||
let index = declaration.index();
|
|
||||||
c.push(Instruction::IsClass(index.try_into().unwrap()));
|
|
||||||
}
|
}
|
||||||
|
"string" => {
|
||||||
|
c.push(Instruction::IsString);
|
||||||
|
}
|
||||||
|
"bool" => {
|
||||||
|
c.push(Instruction::IsBool);
|
||||||
|
}
|
||||||
|
"nothing" => {
|
||||||
|
c.push(Instruction::IsNothing);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let environment = c.semantics.environment_of(t);
|
||||||
|
match environment.bind(identifier)? {
|
||||||
|
Declaration::Class { declaration, .. } => {
|
||||||
|
// The runtime identifier of the class is the tree index of the
|
||||||
|
// class declaration sure why not.
|
||||||
|
let index = declaration.index();
|
||||||
|
c.push(Instruction::IsClass(index.try_into().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: enforce that type identifier binds to class in `is`
|
_ => return None,
|
||||||
// expresion, we don't support RTTI for other types yet.
|
}
|
||||||
_ => return None,
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
@ -911,16 +949,35 @@ fn compile_field_value(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
||||||
compile_load_declaration(c, t, declaration)
|
compile_load_declaration(c, t, declaration)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_member_access(c: &mut Compiler, tree: &Tree) -> CR {
|
fn compile_member_access(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
||||||
// In member access; the lhs sets up the object and in theory the rhs
|
// In member access; the lhs sets up the object and in theory the rhs
|
||||||
// binds against it. ::shrug::
|
// binds against it. ::shrug::
|
||||||
//
|
//
|
||||||
compile_expression(c, tree.nth_tree(0)?);
|
compile_expression(c, tree.nth_tree(0)?);
|
||||||
|
|
||||||
|
let typ = c.semantics.type_of(tree.nth_tree(0)?);
|
||||||
|
let ident = tree.nth_token(2)?;
|
||||||
|
|
||||||
|
let environment = match &typ {
|
||||||
|
Type::Object(ct, _) => {
|
||||||
|
let class = c.semantics.class_of(*ct);
|
||||||
|
class.env.clone()
|
||||||
|
}
|
||||||
|
Type::Class(ct, _) => {
|
||||||
|
let class = c.semantics.class_of(*ct);
|
||||||
|
class.static_env.clone()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
c.push(inst_panic!("cannot get environment of {typ}"));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let declaration = environment.bind(ident)?;
|
||||||
|
|
||||||
// NOTE: If this is a method call we still don't have to do anything
|
// NOTE: If this is a method call we still don't have to do anything
|
||||||
// special here, since the load of the member function will *not*
|
// special here, since the load of the member function will *not*
|
||||||
// consume the self pointer from the stack.
|
// consume the self pointer from the stack.
|
||||||
compile_expression(c, tree.nth_tree(2)?);
|
compile_load_declaration(c, t, declaration);
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -938,6 +995,7 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
|
||||||
TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value),
|
TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value),
|
||||||
TreeKind::IfStatement => compile_if_statement(c, tree, gen_value),
|
TreeKind::IfStatement => compile_if_statement(c, tree, gen_value),
|
||||||
TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value),
|
TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value),
|
||||||
|
TreeKind::ReturnStatement => compile_return_statement(c, tree),
|
||||||
TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value),
|
TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value),
|
||||||
|
|
||||||
_ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind),
|
_ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind),
|
||||||
|
|
@ -1129,3 +1187,13 @@ fn compile_while_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR
|
||||||
|
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_return_statement(c: &mut Compiler, tree: &Tree) -> CR {
|
||||||
|
if let Some(expr) = tree.nth_tree(1) {
|
||||||
|
compile_expression(c, expr);
|
||||||
|
} else {
|
||||||
|
c.push(Instruction::PushNothing);
|
||||||
|
}
|
||||||
|
c.push(Instruction::Return);
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -961,7 +961,8 @@ fn infix_power(token: TokenKind) -> Option<(u8, u8)> {
|
||||||
//
|
//
|
||||||
// UNARY_POWER goes here.
|
// UNARY_POWER goes here.
|
||||||
//
|
//
|
||||||
TokenKind::Dot => Some((18, 19)),
|
TokenKind::LeftParen => Some((18, 19)),
|
||||||
|
TokenKind::Dot => Some((20, 21)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -970,11 +971,6 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
||||||
let Some(mut expr) = prefix_expression(p) else {
|
let Some(mut expr) = prefix_expression(p) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
while p.at(TokenKind::LeftParen) {
|
|
||||||
let m = p.start_before(expr);
|
|
||||||
argument_list(p);
|
|
||||||
expr = p.end(m, TreeKind::CallExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let token = p.peek();
|
let token = p.peek();
|
||||||
|
|
@ -986,17 +982,23 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expr = match token {
|
expr = match token {
|
||||||
TokenKind::Dot => member_access(p, expr, rp),
|
TokenKind::Dot => member_access(p, expr),
|
||||||
TokenKind::Is => is_expression(p, expr, rp),
|
TokenKind::Is => is_expression(p, expr, rp),
|
||||||
|
TokenKind::LeftParen => call(p, expr),
|
||||||
_ => binary_expression(p, expr, rp),
|
_ => binary_expression(p, expr, rp),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn member_access(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClosed {
|
fn member_access(p: &mut CParser, left: MarkClosed) -> MarkClosed {
|
||||||
let m = p.start_before(left);
|
let m = p.start_before(left);
|
||||||
p.advance(); // Consume the operator
|
p.advance(); // Consume the operator
|
||||||
expression_with_power(p, right_power);
|
|
||||||
|
p.expect(
|
||||||
|
TokenKind::Identifier,
|
||||||
|
"expected an identifier after a '.' in member access",
|
||||||
|
);
|
||||||
|
|
||||||
p.end(m, TreeKind::MemberAccess)
|
p.end(m, TreeKind::MemberAccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1016,6 +1018,12 @@ fn is_expression(p: &mut CParser, left: MarkClosed, right_power: u8) -> MarkClos
|
||||||
p.end(m, TreeKind::IsExpression)
|
p.end(m, TreeKind::IsExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call(p: &mut CParser, left: MarkClosed) -> MarkClosed {
|
||||||
|
let m = p.start_before(left);
|
||||||
|
argument_list(p);
|
||||||
|
p.end(m, TreeKind::CallExpression)
|
||||||
|
}
|
||||||
|
|
||||||
const PATTERN_START: &[TokenKind] = &[TokenKind::Identifier, TokenKind::Underscore];
|
const PATTERN_START: &[TokenKind] = &[TokenKind::Identifier, TokenKind::Underscore];
|
||||||
|
|
||||||
fn pattern(p: &mut CParser, right_power: u8) {
|
fn pattern(p: &mut CParser, right_power: u8) {
|
||||||
|
|
|
||||||
|
|
@ -422,26 +422,34 @@ fn set_logical_parents(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TreeKind::MemberAccess => {
|
TreeKind::ConditionalExpression => {
|
||||||
// The LHS has the environment of my parent, because I set up an
|
// Special case! The parent of the `then` clause is the
|
||||||
// environment containing only members of the type of the LHS. The
|
// condition, so any variable bound by the condition is valid in
|
||||||
// RHS has that one.
|
// the `then` clause. The `else` clause and the condition itself
|
||||||
if let Some(lhs) = tree.nth_tree(0) {
|
// do not have the bindings in scope, obviously.
|
||||||
set_logical_parents(parents, syntax_tree, lhs, parent);
|
let body_parent = if let Some(is_condition) = tree.nth_tree(1) {
|
||||||
}
|
Some(is_condition)
|
||||||
if let Some(rhs) = tree.nth_tree(2) {
|
} else {
|
||||||
set_logical_parents(parents, syntax_tree, rhs, Some(t));
|
Some(t)
|
||||||
|
};
|
||||||
|
|
||||||
|
let then_body = tree.nth_tree(2);
|
||||||
|
for child in &tree.children {
|
||||||
|
match child {
|
||||||
|
Child::Token(_) => (),
|
||||||
|
Child::Tree(ct) => {
|
||||||
|
if Some(*ct) == then_body {
|
||||||
|
set_logical_parents(parents, syntax_tree, *ct, body_parent);
|
||||||
|
} else {
|
||||||
|
set_logical_parents(parents, syntax_tree, *ct, Some(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TreeKind::ConditionalExpression => {
|
TreeKind::WhileStatement => {
|
||||||
// Special case! If the condition expression is a `is` expression
|
// Just like `if`, bindings in the condition are valid in the body.
|
||||||
// then it is the parent for the body of the `then` clause so
|
let body_parent = if let Some(is_condition) = tree.nth_tree(1) {
|
||||||
// that any variable bindings it makes remain in scope. The
|
|
||||||
// `else` clause and the condition itself do not have the
|
|
||||||
// bindings in scope, obviously.
|
|
||||||
let body_parent = if let Some(is_condition) =
|
|
||||||
tree.child_of_kind(syntax_tree, TreeKind::IsExpression)
|
|
||||||
{
|
|
||||||
Some(is_condition)
|
Some(is_condition)
|
||||||
} else {
|
} else {
|
||||||
Some(t)
|
Some(t)
|
||||||
|
|
@ -640,7 +648,6 @@ impl<'a> Semantics<'a> {
|
||||||
TreeKind::IsExpression => self.environment_of_is_expression(parent, tree),
|
TreeKind::IsExpression => self.environment_of_is_expression(parent, tree),
|
||||||
TreeKind::LetStatement => self.environment_of_let(parent, tree),
|
TreeKind::LetStatement => self.environment_of_let(parent, tree),
|
||||||
TreeKind::MatchArm => self.environment_of_match_arm(parent, t, tree),
|
TreeKind::MatchArm => self.environment_of_match_arm(parent, t, tree),
|
||||||
TreeKind::MemberAccess => self.environment_of_member_access(tree),
|
|
||||||
TreeKind::ParamList => self.environment_of_paramlist(parent, tree),
|
TreeKind::ParamList => self.environment_of_paramlist(parent, tree),
|
||||||
|
|
||||||
_ => parent,
|
_ => parent,
|
||||||
|
|
@ -820,35 +827,6 @@ impl<'a> Semantics<'a> {
|
||||||
parent
|
parent
|
||||||
}
|
}
|
||||||
|
|
||||||
fn environment_of_member_access(&self, tree: &Tree) -> EnvironmentRef {
|
|
||||||
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
|
||||||
|
|
||||||
// Build environment out of members of type of lhs
|
|
||||||
let Some(lhs) = tree.nth_tree(0) else {
|
|
||||||
return Environment::error();
|
|
||||||
};
|
|
||||||
let Some(op) = tree.nth_token(1) else {
|
|
||||||
return Environment::error();
|
|
||||||
};
|
|
||||||
let typ = self.type_of(lhs);
|
|
||||||
match &typ {
|
|
||||||
Type::Object(ct, _) => {
|
|
||||||
let class = self.class_of(*ct);
|
|
||||||
class.env.clone()
|
|
||||||
}
|
|
||||||
Type::Class(ct, _) => {
|
|
||||||
let class = self.class_of(*ct);
|
|
||||||
class.static_env.clone()
|
|
||||||
}
|
|
||||||
Type::Error => Environment::error(),
|
|
||||||
_ => {
|
|
||||||
// TODO: This is probably wrong, yeah?
|
|
||||||
self.report_error(op.start, format!("cannot access members of '{typ}'"));
|
|
||||||
Environment::error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
||||||
assert_eq!(tree.kind, TreeKind::IsExpression);
|
assert_eq!(tree.kind, TreeKind::IsExpression);
|
||||||
|
|
||||||
|
|
@ -1156,7 +1134,7 @@ impl<'a> Semantics<'a> {
|
||||||
TreeKind::MatchArm => self.type_of_match_arm(tree),
|
TreeKind::MatchArm => self.type_of_match_arm(tree),
|
||||||
TreeKind::MatchBody => self.type_of_match_body(tree),
|
TreeKind::MatchBody => self.type_of_match_body(tree),
|
||||||
TreeKind::MatchExpression => self.type_of_match_expression(tree),
|
TreeKind::MatchExpression => self.type_of_match_expression(tree),
|
||||||
TreeKind::MemberAccess => self.type_of_member_access(tree),
|
TreeKind::MemberAccess => self.type_of_member_access(t, tree),
|
||||||
TreeKind::NewObjectExpression => self.type_of_new_object_expression(tree),
|
TreeKind::NewObjectExpression => self.type_of_new_object_expression(tree),
|
||||||
TreeKind::Parameter => self.type_of_parameter(tree),
|
TreeKind::Parameter => self.type_of_parameter(tree),
|
||||||
TreeKind::ReturnStatement => Some(Type::Unreachable),
|
TreeKind::ReturnStatement => Some(Type::Unreachable),
|
||||||
|
|
@ -1293,40 +1271,69 @@ impl<'a> Semantics<'a> {
|
||||||
op: &Token,
|
op: &Token,
|
||||||
) -> Option<Type> {
|
) -> Option<Type> {
|
||||||
// Ensure the left tree is an lvalue
|
// Ensure the left tree is an lvalue
|
||||||
let environment = self.environment_of(left_tree);
|
|
||||||
let tree = &self.syntax_tree[left_tree];
|
let tree = &self.syntax_tree[left_tree];
|
||||||
|
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
let mut environment = Environment::error();
|
||||||
|
|
||||||
let declaration = match tree.kind {
|
let declaration = match tree.kind {
|
||||||
// TODO: Assign to member access or list access
|
// TODO: Assign to list access
|
||||||
TreeKind::Identifier => environment.bind(tree.nth_token(0)?),
|
TreeKind::Identifier => {
|
||||||
_ => None,
|
let id = tree.nth_token(0)?;
|
||||||
};
|
environment = self.environment_of(left_tree);
|
||||||
match declaration {
|
match environment.bind(id) {
|
||||||
Some(d) => match d {
|
Some(decl) => decl,
|
||||||
Declaration::Variable { .. } => (),
|
None => {
|
||||||
Declaration::ExternFunction { .. } | Declaration::Function { .. } => {
|
if !environment.is_error {
|
||||||
self.report_error_tree_ref(
|
self.report_error_tree(tree, format!("cannot find value {id} here"));
|
||||||
left_tree,
|
}
|
||||||
"cannot assign a new value to a function declaration",
|
return Some(Type::Error);
|
||||||
);
|
}
|
||||||
return Some(Type::Error);
|
|
||||||
}
|
}
|
||||||
Declaration::Class { .. } => {
|
}
|
||||||
self.report_error_tree_ref(
|
TreeKind::MemberAccess => {
|
||||||
left_tree,
|
let id = tree.nth_token(2)?;
|
||||||
"cannot assign a new value to a class declaration",
|
let typ = self.type_of(tree.nth_tree(0)?);
|
||||||
);
|
environment = self.member_environment(left_tree, &typ);
|
||||||
return Some(Type::Error);
|
match environment.bind(id) {
|
||||||
|
Some(decl) => decl,
|
||||||
|
None => {
|
||||||
|
if !environment.is_error {
|
||||||
|
self.report_error_tree(tree, format!("'{typ}' has no member {id}"));
|
||||||
|
}
|
||||||
|
return Some(Type::Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
_ => {
|
||||||
self.report_error_tree_ref(
|
self.report_error_tree_ref(
|
||||||
left_tree,
|
left_tree,
|
||||||
"cannot assign a value to this expression, it is not a place you can store things",
|
"cannot assign a value to this expression, it is not a place you can store things",
|
||||||
);
|
);
|
||||||
return Some(Type::Error);
|
return Some(Type::Error);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match declaration {
|
||||||
|
Declaration::Variable { .. } => (),
|
||||||
|
Declaration::ExternFunction { .. } | Declaration::Function { .. } => {
|
||||||
|
self.report_error_tree_ref(
|
||||||
|
left_tree,
|
||||||
|
"cannot assign a new value to a function declaration",
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = environment;
|
||||||
|
|
||||||
let left_type = match left_type {
|
let left_type = match left_type {
|
||||||
Type::Assignment(x) => *x,
|
Type::Assignment(x) => *x,
|
||||||
t => t,
|
t => t,
|
||||||
|
|
@ -1629,11 +1636,43 @@ impl<'a> Semantics<'a> {
|
||||||
Some(result)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_of_member_access(&self, tree: &Tree) -> Option<Type> {
|
fn type_of_member_access(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
||||||
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
||||||
|
|
||||||
// Type of member access is the type of the RHS.
|
let lhs = tree.nth_tree(0)?;
|
||||||
Some(self.type_of(tree.nth_tree(2)?))
|
let typ = self.type_of(lhs);
|
||||||
|
let env = self.member_environment(lhs, &typ);
|
||||||
|
let id = tree.nth_token(2)?;
|
||||||
|
if id.kind != TokenKind::Identifier {
|
||||||
|
return Some(Type::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(declaration) = env.bind(id) else {
|
||||||
|
if !env.is_error {
|
||||||
|
self.report_error(id.start, format!("'{typ}' has no member {id}"));
|
||||||
|
}
|
||||||
|
return Some(Type::Error);
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(self.type_of_declaration(t, declaration))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn member_environment(&self, t: TreeRef, typ: &Type) -> EnvironmentRef {
|
||||||
|
match &typ {
|
||||||
|
Type::Object(ct, _) => {
|
||||||
|
let class = self.class_of(*ct);
|
||||||
|
class.env.clone()
|
||||||
|
}
|
||||||
|
Type::Class(ct, _) => {
|
||||||
|
let class = self.class_of(*ct);
|
||||||
|
class.static_env.clone()
|
||||||
|
}
|
||||||
|
Type::Error => return Environment::error(),
|
||||||
|
_ => {
|
||||||
|
self.report_error_tree_ref(t, format!("cannot access members of '{typ}'"));
|
||||||
|
return Environment::error();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> {
|
fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> {
|
||||||
|
|
@ -1665,17 +1704,7 @@ impl<'a> Semantics<'a> {
|
||||||
let id = tree.nth_token(0)?;
|
let id = tree.nth_token(0)?;
|
||||||
let environment = self.environment_of(t);
|
let environment = self.environment_of(t);
|
||||||
if let Some(declaration) = environment.bind(id) {
|
if let Some(declaration) = environment.bind(id) {
|
||||||
return Some(match declaration {
|
return Some(self.type_of_declaration(t, declaration));
|
||||||
Declaration::Variable { declaration, .. } => self.type_of(*declaration),
|
|
||||||
Declaration::Function { declaration, .. } => self.type_of(*declaration),
|
|
||||||
Declaration::ExternFunction {
|
|
||||||
declaration_type, ..
|
|
||||||
} => declaration_type.clone(),
|
|
||||||
Declaration::Class { declaration, .. } => match self.type_of(*declaration) {
|
|
||||||
Type::Object(cd, name) => Type::Class(cd, name.clone()),
|
|
||||||
_ => self.internal_compiler_error(Some(t), "bound to a class not understood"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !environment.is_error {
|
if !environment.is_error {
|
||||||
|
|
@ -1684,6 +1713,20 @@ impl<'a> Semantics<'a> {
|
||||||
Some(Type::Error)
|
Some(Type::Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_of_declaration(&self, t: TreeRef, declaration: &Declaration) -> Type {
|
||||||
|
match declaration {
|
||||||
|
Declaration::Variable { declaration, .. } => self.type_of(*declaration),
|
||||||
|
Declaration::Function { declaration, .. } => self.type_of(*declaration),
|
||||||
|
Declaration::ExternFunction {
|
||||||
|
declaration_type, ..
|
||||||
|
} => declaration_type.clone(),
|
||||||
|
Declaration::Class { declaration, .. } => match self.type_of(*declaration) {
|
||||||
|
Type::Object(cd, name) => Type::Class(cd, name.clone()),
|
||||||
|
_ => self.internal_compiler_error(Some(t), "bound to a class not understood"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn type_of_self_parameter(&self, tree: &Tree) -> Option<Type> {
|
fn type_of_self_parameter(&self, tree: &Tree) -> Option<Type> {
|
||||||
let pl = tree.parent?;
|
let pl = tree.parent?;
|
||||||
let param_list = &self.syntax_tree[pl];
|
let param_list = &self.syntax_tree[pl];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::compiler::{Export, Function, Instruction, Module};
|
use crate::compiler::{Export, Function, Instruction, Module};
|
||||||
|
|
@ -48,17 +49,17 @@ type Result<T> = std::result::Result<T, VMErrorCode>;
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
name: Rc<str>,
|
name: Rc<str>,
|
||||||
class_id: i64,
|
class_id: i64,
|
||||||
values: Box<[StackValue]>,
|
values: RefCell<Box<[StackValue]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
pub fn get_slot(&self, index: usize) -> Result<StackValue> {
|
pub fn get_slot(&self, index: usize) -> Result<StackValue> {
|
||||||
match self.values.get(index) {
|
match self.values.borrow().get(index) {
|
||||||
Some(v) => Ok(v.clone()),
|
Some(v) => Ok(v.clone()),
|
||||||
None => Err(VMErrorCode::SlotOutOfRange(
|
None => Err(VMErrorCode::SlotOutOfRange(
|
||||||
index,
|
index,
|
||||||
self.name.clone(),
|
self.name.clone(),
|
||||||
self.values.len(),
|
self.values.borrow().len(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +77,43 @@ pub enum StackValue {
|
||||||
Object(Rc<Object>),
|
Object(Rc<Object>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StackValue {
|
||||||
|
pub fn is_object(&self, id: i64) -> bool {
|
||||||
|
match self {
|
||||||
|
StackValue::Object(o) => o.class_id == id,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_float(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
StackValue::Float(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_nothing(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
StackValue::Nothing => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_bool(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
StackValue::Bool(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_string(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
StackValue::String(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum FuncValue {
|
enum FuncValue {
|
||||||
Function(Rc<Function>),
|
Function(Rc<Function>),
|
||||||
ExternFunction(usize),
|
ExternFunction(usize),
|
||||||
|
|
@ -387,6 +425,11 @@ fn eval_one(
|
||||||
let v = f.pop_value()?;
|
let v = f.pop_value()?;
|
||||||
c.set_global(i, v)?;
|
c.set_global(i, v)?;
|
||||||
}
|
}
|
||||||
|
Instruction::StoreSlot(i) => {
|
||||||
|
let o = f.pop_object()?;
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
o.values.borrow_mut()[i] = v;
|
||||||
|
}
|
||||||
Instruction::LoadFunction(i) => {
|
Instruction::LoadFunction(i) => {
|
||||||
let v = c.get_function(i)?;
|
let v = c.get_function(i)?;
|
||||||
f.push_function(v);
|
f.push_function(v);
|
||||||
|
|
@ -477,7 +520,7 @@ fn eval_one(
|
||||||
let object = Object {
|
let object = Object {
|
||||||
name,
|
name,
|
||||||
class_id,
|
class_id,
|
||||||
values: values.into(),
|
values: RefCell::new(values.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
f.push_object(object.into());
|
f.push_object(object.into());
|
||||||
|
|
@ -488,16 +531,27 @@ fn eval_one(
|
||||||
}
|
}
|
||||||
Instruction::IsClass(id) => {
|
Instruction::IsClass(id) => {
|
||||||
let value = f.pop_value()?;
|
let value = f.pop_value()?;
|
||||||
match value {
|
f.push_bool(value.is_object(id));
|
||||||
StackValue::Object(o) => {
|
|
||||||
f.push_bool(o.class_id == id);
|
|
||||||
}
|
|
||||||
_ => f.push_bool(false),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Instruction::PushInt(v) => {
|
Instruction::PushInt(v) => {
|
||||||
f.push_int(v);
|
f.push_int(v);
|
||||||
}
|
}
|
||||||
|
Instruction::IsBool => {
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
f.push_bool(v.is_bool());
|
||||||
|
}
|
||||||
|
Instruction::IsFloat => {
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
f.push_bool(v.is_float());
|
||||||
|
}
|
||||||
|
Instruction::IsString => {
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
f.push_bool(v.is_string());
|
||||||
|
}
|
||||||
|
Instruction::IsNothing => {
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
f.push_bool(v.is_nothing());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Flow::Continue)
|
Ok(Flow::Continue)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fun test() -> f64 {
|
||||||
// | 0: LoadArgument(1)
|
// | 0: LoadArgument(1)
|
||||||
// | 1: LoadArgument(0)
|
// | 1: LoadArgument(0)
|
||||||
// | 2: PushString(0)
|
// | 2: PushString(0)
|
||||||
// | 3: PushInt(37)
|
// | 3: PushInt(33)
|
||||||
// | 4: NewObject(2)
|
// | 4: NewObject(2)
|
||||||
// | 5: Return
|
// | 5: Return
|
||||||
// | function Line (4 args, 0 locals):
|
// | function Line (4 args, 0 locals):
|
||||||
|
|
@ -52,7 +52,7 @@ fun test() -> f64 {
|
||||||
// | 0: LoadArgument(1)
|
// | 0: LoadArgument(1)
|
||||||
// | 1: LoadArgument(0)
|
// | 1: LoadArgument(0)
|
||||||
// | 2: PushString(0)
|
// | 2: PushString(0)
|
||||||
// | 3: PushInt(44)
|
// | 3: PushInt(40)
|
||||||
// | 4: NewObject(2)
|
// | 4: NewObject(2)
|
||||||
// | 5: Return
|
// | 5: Return
|
||||||
// | function test (0 args, 3 locals):
|
// | function test (0 args, 3 locals):
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,6 @@ fun test() {
|
||||||
let z = f.{let y = 222; foo };
|
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.
|
|
||||||
//
|
|
||||||
// TODO: This is parsed wrong; the `{` is consumed after the '.' and it
|
|
||||||
// should instead be ignored. This is the "greedy" expression parsing that
|
|
||||||
// matklad talks about in his resilient parser article.
|
|
||||||
//
|
|
||||||
// @expect-errors:
|
// @expect-errors:
|
||||||
// | 7:12: Error at '{': expect ';' to end a let statement
|
// | 7:12: Error at '{': expected an identifier after a '.' in member access
|
||||||
// | 7:26: cannot find value foo here
|
// | 7:26: cannot find value foo here
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
|
class Finished {}
|
||||||
|
let FINISHED = new Finished {};
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
current: f64;
|
||||||
|
|
||||||
|
fun next(self) -> f64 or Finished {
|
||||||
|
if self.current < 10 {
|
||||||
|
let result = self.current;
|
||||||
|
self.current = self.current + 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FINISHED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun test() -> f64 {
|
fun test() -> f64 {
|
||||||
let result = 1;
|
let result = 1;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
@ -5,8 +23,15 @@ fun test() -> f64 {
|
||||||
result = result * 2;
|
result = result * 2;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
result
|
|
||||||
|
let sum = 0;
|
||||||
|
let it = new Iterator { current: 0 };
|
||||||
|
while it.next() is v: f64 {
|
||||||
|
sum = sum + v;
|
||||||
|
}
|
||||||
|
|
||||||
|
result + sum
|
||||||
}
|
}
|
||||||
|
|
||||||
// @no-errors
|
// @no-errors
|
||||||
// @eval: Float(1024.0)
|
// @eval: Float(1069.0)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue