[fine] Evaluate is expressions, SO MANY BUG FIXES

This commit is contained in:
John Doty 2024-02-03 09:38:08 -08:00
parent b5b56b49a9
commit 198dc5bdb3
6 changed files with 215 additions and 42 deletions

View file

@ -26,7 +26,7 @@ pub enum Instruction {
FloatSubtract,
Jump(usize),
JumpFalse(usize),
JumpTrue(usize),
JumpTrue(usize), // TODO: Only one of these, and use BoolNot?
LoadArgument(usize),
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
LoadFunction(usize),
@ -44,6 +44,9 @@ pub enum Instruction {
StoreLocal(usize),
StoreModule(usize),
StringAdd,
IsClass(i64),
PushInt(i64),
}
pub enum Export {
@ -286,19 +289,22 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) {
let tree = &c.syntax[t];
let cr = match tree.kind {
TreeKind::Error => None,
TreeKind::LiteralExpression => compile_literal(c, t, tree),
TreeKind::GroupingExpression => compile_grouping(c, tree),
TreeKind::UnaryExpression => compile_unary_operator(c, t, tree),
TreeKind::ConditionalExpression => compile_condition_expression(c, tree),
TreeKind::BinaryExpression => compile_binary_expression(c, t, tree),
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
TreeKind::CallExpression => compile_call_expression(c, tree),
TreeKind::Block => compile_block_expression(c, tree),
TreeKind::Argument => compile_argument(c, tree),
TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree),
TreeKind::BinaryExpression => compile_binary_expression(c, t, tree),
TreeKind::Block => compile_block_expression(c, tree),
TreeKind::CallExpression => compile_call_expression(c, tree),
TreeKind::ConditionalExpression => compile_condition_expression(c, tree),
TreeKind::FieldValue => compile_field_value(c, t, tree),
TreeKind::GroupingExpression => compile_grouping(c, tree),
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
TreeKind::IsExpression => compile_is_expression(c, t, tree),
TreeKind::LiteralExpression => compile_literal(c, t, tree),
TreeKind::MemberAccess => compile_member_access(c, tree),
TreeKind::NewObjectExpression => compile_new_object_expression(c, t, tree),
TreeKind::SelfReference => compile_self_reference(c),
TreeKind::UnaryExpression => compile_unary_operator(c, t, tree),
_ => ice!(c, t, "{tree:?} is not an expression, cannot compile"),
};
if matches!(cr, None) {
@ -374,15 +380,16 @@ fn compile_condition_expression(c: &mut Compiler, t: &Tree) -> CR {
let then_branch = t.nth_tree(2)?;
compile_expression(c, then_branch);
if let Some(else_branch) = t.nth_tree(4) {
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_else_index, |i| Instruction::JumpFalse(i));
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_else_index, |i| Instruction::JumpFalse(i));
if let Some(else_branch) = t.nth_tree(4) {
compile_expression(c, else_branch);
c.patch(jump_end_index, |i| Instruction::Jump(i));
} else {
c.patch(jump_else_index, |i| Instruction::JumpFalse(i));
c.push(Instruction::PushNothing);
}
c.patch(jump_end_index, |i| Instruction::Jump(i));
OK
}
@ -420,30 +427,54 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatDivide)
}
TokenKind::And => {
// Compile the left hand side, it leaves a bool on the stack
compile_expression(c, tr.nth_tree(0)?);
let jump_false_index = c.push(Instruction::JumpFalse(0));
c.push(Instruction::PushTrue);
// If the first part is true (hooray!) then we need to evaluate
// the right hand side, so jump around the short circuit...
let jump_true_index = c.push(Instruction::JumpTrue(0));
// ...but if the first part is false then we stop here. We need
// to leave a value on the stack (it was consumed by jump above)
// so we push an extra False here, and jump to the end.
c.push(Instruction::PushFalse);
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_false_index, |i| Instruction::JumpFalse(i));
// Here we are, we consumed the `true` off the stack now time to
// do the right hand side.
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
// The right hand side leaves true or false on the stack, it's
// the result of the expression.
compile_expression(c, tr.nth_tree(2)?);
// (here's where you go after you leave the "false" on the stack.)
c.patch(jump_end_index, |i| Instruction::Jump(i));
OK
}
TokenKind::Or => {
// Compile the left hand side, it leaves a bool on the stack
compile_expression(c, tr.nth_tree(0)?);
let jump_true_index = c.push(Instruction::JumpTrue(0));
// If the first part is false (boo!) then we need to evaluate the
// right hand side, so jump around the short circuit...
let jump_false_index = c.push(Instruction::JumpFalse(0));
// ...but if the first part os true then we stop here. We need to
// leave a value on the stack (it was consumed by jump above) so
// we push an extra True here and jump to the end.
c.push(Instruction::PushTrue);
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
// Here we are, we consumed the `false` off the stack now time to
// do the right hand side.
c.patch(jump_false_index, |i| Instruction::JumpFalse(i));
// The right hand side leaves true or false on the stack, it's
// the result of the expression.
compile_expression(c, tr.nth_tree(2)?);
// (here's where you go after you leave "true" on the stack.)
c.patch(jump_end_index, |i| Instruction::Jump(i));
OK
}
@ -589,6 +620,112 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
OK
}
fn compile_is_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()> {
compile_expression(c, tree.nth_tree(0)?);
// If you have a binding, dup and store now, it is in scope.
let type_expr =
if let Some(binding) = tree.child_tree_of_kind(c.syntax, TreeKind::VariableBinding) {
if let Some(variable) = binding.nth_token(0) {
let environment = c.semantics.environment_of(t);
let declaration = environment.bind(variable)?;
let Declaration::Variable {
location: Location::Local,
index,
..
} = declaration
else {
ice!(c, t, "is cannot make a non-local, non-variable declaration")
};
c.push(Instruction::Dup);
c.push(Instruction::StoreLocal(*index));
}
binding.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?
} else {
tree.child_tree_of_kind(c.syntax, TreeKind::TypeExpression)?
};
compile_type_expr_eq(c, type_expr.nth_tree(0)?);
if let Some(tok) = tree.nth_token(3) {
if tok.kind == TokenKind::And {
let jump_true_index = c.push(Instruction::JumpTrue(0));
c.push(Instruction::PushFalse);
let jump_end_index = c.push(Instruction::Jump(0));
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
compile_expression(c, tree.nth_tree(4)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
}
}
OK
}
fn compile_type_expr_eq(c: &mut Compiler, t: TreeRef) {
let tree = &c.syntax[t];
let result = match tree.kind {
TreeKind::TypeIdentifier => compile_type_identifier_eq(c, t, tree),
TreeKind::AlternateType => compile_type_alternate_eq(c, tree),
_ => ice!(c, t, "tree is not a type expression"),
};
if result.is_none() {
c.push(inst_panic!("panic compiling type expression eq {:?}", tree));
}
}
fn compile_type_identifier_eq(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
let identifier = tree.nth_token(0)?;
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`
// expresion, we don't support RTTI for other types yet.
_ => return None,
}
OK
}
fn compile_type_alternate_eq(c: &mut Compiler, tree: &Tree) -> CR {
// Compile the left hand side, it leaves a bool on the stack
compile_type_expr_eq(c, tree.nth_tree(0)?);
// If the first part is false (boo!) then we need to evaluate the
// right hand side, so jump around the short circuit...
let jump_false_index = c.push(Instruction::JumpFalse(0));
// ...but if the first part is true then we stop here. We need to
// leave a value on the stack (it was consumed by jump above) so
// we push an extra True here and jump to the end.
c.push(Instruction::PushTrue);
let jump_end_index = c.push(Instruction::Jump(0));
// Here we are, we consumed the `false` off the stack now time to
// do the right hand side.
c.patch(jump_false_index, |i| Instruction::JumpFalse(i));
// The right hand side leaves true or false on the stack, it's
// the result of the expression.
compile_type_expr_eq(c, tree.nth_tree(2)?);
// (here's where you go after you leave "true" on the stack.)
c.patch(jump_end_index, |i| Instruction::Jump(i));
OK
}
fn compile_call_expression(c: &mut Compiler, tree: &Tree) -> CR {
let arg_list = tree.child_tree_of_kind(c.syntax, TreeKind::ArgumentList)?;
let mut args: Vec<_> = arg_list.child_trees().collect();
@ -883,6 +1020,7 @@ fn compile_function(c: &mut Compiler, t: TreeRef) -> CR {
let name = tree.nth_token(1)?.as_str();
let name_index = c.add_string(name.to_string());
c.push(Instruction::PushString(name_index));
c.push(Instruction::PushInt(t.index().try_into().unwrap()));
c.push(Instruction::NewObject(count));
}
_ => ice!(c, t, "what is this tree doing in compile_function?"),

View file

@ -123,6 +123,7 @@ pub enum Type {
// TODO: Numeric literals should be implicitly convertable, unlike other
// types. Maybe just "numeric literal" type?
F64,
I64,
String,
Bool,
@ -172,6 +173,7 @@ impl fmt::Display for Type {
Assignment(_) => write!(f, "assignment"),
Nothing => write!(f, "()"),
F64 => write!(f, "f64"),
I64 => write!(f, "i64"),
String => write!(f, "string"),
Bool => write!(f, "bool"),
Function(args, ret) => {
@ -831,6 +833,9 @@ impl<'a> Semantics<'a> {
return Environment::error();
};
// TODO: This binding should be un-assignable! Don't assign to this!
// (UNLESS VERY SPECIFIC CIRCUMSTANCES; WHICH ARE COMPLEX)
let mut env = Environment::new(Some(parent), Location::Local);
env.insert(variable, type_expr);
return EnvironmentRef::new(env);

View file

@ -47,6 +47,7 @@ type Result<T> = std::result::Result<T, VMErrorCode>;
#[derive(Clone, Debug)]
pub struct Object {
name: Rc<str>,
class_id: i64,
values: Box<[StackValue]>,
}
@ -68,6 +69,7 @@ pub enum StackValue {
Nothing,
Bool(bool),
Float(f64),
Int(i64),
String(Rc<str>),
Function(Rc<Function>),
ExternFunction(usize),
@ -146,6 +148,13 @@ impl Frame {
}
}
fn pop_int(&mut self) -> Result<i64> {
match self.pop_value()? {
StackValue::Int(v) => Ok(v),
v => Err(VMErrorCode::StackTypeMismatch(v, Type::I64)),
}
}
fn push_value(&mut self, v: StackValue) {
self.stack.push(v)
}
@ -178,6 +187,10 @@ impl Frame {
self.push_value(StackValue::Object(v));
}
fn push_int(&mut self, v: i64) {
self.push_value(StackValue::Int(v));
}
fn get_argument(&self, i: usize) -> Result<StackValue> {
self.args
.get(i)
@ -434,6 +447,7 @@ fn eval_one(
}
Instruction::NewObject(slots) => {
let class_id = f.pop_int()?;
let name = f.pop_string()?;
let mut values = Vec::with_capacity(slots);
for _ in 0..slots {
@ -442,6 +456,7 @@ fn eval_one(
let object = Object {
name,
class_id,
values: values.into(),
};
@ -451,6 +466,18 @@ fn eval_one(
let obj = f.pop_object()?;
f.push_value(obj.get_slot(slot)?);
}
Instruction::IsClass(id) => {
let value = f.pop_value()?;
match value {
StackValue::Object(o) => {
f.push_bool(o.class_id == id);
}
_ => f.push_bool(false),
}
}
Instruction::PushInt(v) => {
f.push_int(v);
}
}
Ok(Flow::Continue)
@ -469,16 +496,16 @@ pub fn eval(
let instructions = f.func.instructions();
let instruction = instructions[index];
// {
// eprint!("{index}: {instruction:?} [");
// for val in f.stack.iter().rev().take(3) {
// eprint!("{val:?} ");
// }
// if f.stack.len() > 3 {
// eprint!("...");
// }
// eprintln!("]");
// }
{
eprint!("{index}: {instruction:?} [");
for val in f.stack.iter().rev().take(3) {
eprint!("{val:?} ");
}
if f.stack.len() > 3 {
eprint!("...");
}
eprintln!("]");
}
index += 1;

View file

@ -13,16 +13,16 @@ fun test() -> bool {
// | strings (0):
// | code (15):
// | 0: PushTrue
// | 1: JumpFalse(4)
// | 2: PushTrue
// | 1: JumpTrue(4)
// | 2: PushFalse
// | 3: Jump(5)
// | 4: PushFalse
// | 5: JumpTrue(8)
// | 5: JumpFalse(8)
// | 6: PushTrue
// | 7: Jump(14)
// | 8: PushFalse
// | 9: JumpFalse(12)
// | 10: PushTrue
// | 9: JumpTrue(12)
// | 10: PushFalse
// | 11: Jump(14)
// | 12: PushTrue
// | 13: BoolNot

View file

@ -38,21 +38,23 @@ fun test() -> f64 {
// | function Point (6 args, 0 locals):
// | strings (1):
// | 0: Point
// | code (5):
// | code (6):
// | 0: LoadArgument(1)
// | 1: LoadArgument(0)
// | 2: PushString(0)
// | 3: NewObject(2)
// | 4: Return
// | 3: PushInt(37)
// | 4: NewObject(2)
// | 5: Return
// | function Line (4 args, 0 locals):
// | strings (1):
// | 0: Line
// | code (5):
// | code (6):
// | 0: LoadArgument(1)
// | 1: LoadArgument(0)
// | 2: PushString(0)
// | 3: NewObject(2)
// | 4: Return
// | 3: PushInt(44)
// | 4: NewObject(2)
// | 5: Return
// | function test (0 args, 3 locals):
// | strings (0):
// | code (27):

View file

@ -10,9 +10,10 @@ fun test() -> f64 {
result = result + 1;
}
if b is c:Foo and c.a == 24 {
result = result + 1;
result = result + 10;
}
result
}
// @no-errors
// @eval: Float(1.0)