From 198dc5bdb3526991dfb6081cbc14be164a2f5a7d Mon Sep 17 00:00:00 2001 From: John Doty Date: Sat, 3 Feb 2024 09:38:08 -0800 Subject: [PATCH] [fine] Evaluate `is` expressions, SO MANY BUG FIXES --- fine/src/compiler.rs | 178 +++++++++++++++++++++++++---- fine/src/semantics.rs | 5 + fine/src/vm.rs | 47 ++++++-- fine/tests/expression/boolean.fine | 10 +- fine/tests/expression/class.fine | 14 ++- fine/tests/expression/is.fine | 3 +- 6 files changed, 215 insertions(+), 42 deletions(-) diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index fc817c65..0b0d0efe 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -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?"), diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index f104a7ea..2722463d 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -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); diff --git a/fine/src/vm.rs b/fine/src/vm.rs index 3a09687b..96895258 100644 --- a/fine/src/vm.rs +++ b/fine/src/vm.rs @@ -47,6 +47,7 @@ type Result = std::result::Result; #[derive(Clone, Debug)] pub struct Object { name: Rc, + class_id: i64, values: Box<[StackValue]>, } @@ -68,6 +69,7 @@ pub enum StackValue { Nothing, Bool(bool), Float(f64), + Int(i64), String(Rc), Function(Rc), ExternFunction(usize), @@ -146,6 +148,13 @@ impl Frame { } } + fn pop_int(&mut self) -> Result { + 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 { 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; diff --git a/fine/tests/expression/boolean.fine b/fine/tests/expression/boolean.fine index 98a991a1..5ae7741b 100644 --- a/fine/tests/expression/boolean.fine +++ b/fine/tests/expression/boolean.fine @@ -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 diff --git a/fine/tests/expression/class.fine b/fine/tests/expression/class.fine index e756d7b2..955aa879 100644 --- a/fine/tests/expression/class.fine +++ b/fine/tests/expression/class.fine @@ -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): diff --git a/fine/tests/expression/is.fine b/fine/tests/expression/is.fine index 0f645743..7003ab47 100644 --- a/fine/tests/expression/is.fine +++ b/fine/tests/expression/is.fine @@ -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) \ No newline at end of file