diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index 357193c6e57e241286b52eb9658e678a33e2484d..9834caa60c2f4e8b6362040612f79769e78e4dee 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -6,6 +6,16 @@ use super::store::*; use crate::protocol::*; use crate::protocol::ast::*; +macro_rules! debug_enabled { () => { true }; } +macro_rules! debug_log { + ($format:literal) => { + enabled_debug_print!(true, "exec", $format); + }; + ($format:literal, $($args:expr),*) => { + enabled_debug_print!(true, "exec", $format, $($args),*); + }; +} + #[derive(Debug, Clone)] pub(crate) enum ExprInstruction { EvalExpr(ExpressionId), @@ -175,6 +185,28 @@ impl Prompt { } pub(crate) fn step(&mut self, heap: &Heap, ctx: &mut EvalContext) -> EvalResult { + // Helper function to transfer multiple values from the expression value + // array into a heap region (e.g. constructing arrays or structs). + fn transfer_expression_values_front_into_heap(cur_frame: &mut Frame, store: &mut Store, num_values: usize) -> HeapPos { + let heap_pos = store.alloc_heap(); + + // Do the transformation first (because Rust...) + for val_idx in 0..num_values { + cur_frame.expr_values[val_idx] = store.read_take_ownership(cur_frame.expr_values[val_idx].clone()); + } + + // And now transfer to the heap region + let values = &mut store.heap_regions[heap_pos as usize].values; + debug_assert!(values.is_empty()); + values.reserve(num_values); + for _ in 0..num_values { + values.push(cur_frame.expr_values.pop_front().unwrap()); + } + + heap_pos + } + + // Checking if we're at the end of execution let cur_frame = self.frames.last_mut().unwrap(); if cur_frame.position.is_invalid() { if heap[cur_frame.definition].is_function() { @@ -183,8 +215,12 @@ impl Prompt { return Ok(EvalContinuation::Terminal); } + debug_log!("Taking step in '{}'", heap[cur_frame.definition].identifier().value.as_str()); + + // Execute all pending expressions while !cur_frame.expr_stack.is_empty() { let next = cur_frame.expr_stack.pop_back().unwrap(); + debug_log!("Expr stack: {:?}", next); match next { ExprInstruction::PushValToFront => { cur_frame.expr_values.rotate_right(1); @@ -193,12 +229,15 @@ impl Prompt { let expr = &heap[expr_id]; match expr { Expression::Assignment(expr) => { + // TODO: Stuff goes wrong here, either make these + // utilities do the alloc/dealloc, or let it all be + // done here. let to = cur_frame.expr_values.pop_back().unwrap().as_ref(); let rhs = cur_frame.expr_values.pop_back().unwrap(); - let rhs_heap_pos = rhs.get_heap_pos(); + // let rhs_heap_pos = rhs.get_heap_pos(); apply_assignment_operator(&mut self.store, to, expr.operation, rhs); cur_frame.expr_values.push_back(self.store.read_copy(to)); - self.store.drop_value(rhs_heap_pos); + // self.store.drop_value(rhs_heap_pos); }, Expression::Binding(_expr) => { todo!("Binding expression"); @@ -245,8 +284,14 @@ impl Prompt { // heap while refering to an element... let subject = cur_frame.expr_values.pop_back().unwrap(); let subject_heap_pos = subject.get_heap_pos(); + let heap_pos = match subject { - Value::Ref(value_ref) => self.store.read_ref(value_ref).as_array(), + Value::Ref(value_ref) => { + println!("DEBUG: Called with {:?}", subject); + let result = self.store.read_ref(value_ref); + println!("DEBUG: And got {:?}", result); + result.as_array() + }, val => val.as_array(), }; @@ -301,39 +346,24 @@ impl Prompt { } } Literal::Struct(lit_value) => { - let heap_pos = self.store.alloc_heap(); - let num_fields = lit_value.fields.len(); - let values = &mut self.store.heap_regions[heap_pos as usize].values; - debug_assert!(values.is_empty()); - values.reserve(num_fields); - for _ in 0..num_fields { - values.push(cur_frame.expr_values.pop_front().unwrap()); - } + let heap_pos = transfer_expression_values_front_into_heap( + cur_frame, &mut self.store, lit_value.fields.len() + ); Value::Struct(heap_pos) } Literal::Enum(lit_value) => { Value::Enum(lit_value.variant_idx as i64) } Literal::Union(lit_value) => { - let heap_pos = self.store.alloc_heap(); - let num_values = lit_value.values.len(); - let values = &mut self.store.heap_regions[heap_pos as usize].values; - debug_assert!(values.is_empty()); - values.reserve(num_values); - for _ in 0..num_values { - values.push(cur_frame.expr_values.pop_front().unwrap()); - } + let heap_pos = transfer_expression_values_front_into_heap( + cur_frame, &mut self.store, lit_value.values.len() + ); Value::Union(lit_value.variant_idx as i64, heap_pos) } Literal::Array(lit_value) => { - let heap_pos = self.store.alloc_heap(); - let num_values = lit_value.len(); - let values = &mut self.store.heap_regions[heap_pos as usize].values; - debug_assert!(values.is_empty()); - values.reserve(num_values); - for _ in 0..num_values { - values.push(cur_frame.expr_values.pop_front().unwrap()) - } + let heap_pos = transfer_expression_values_front_into_heap( + cur_frame, &mut self.store, lit_value.len() + ); Value::Array(heap_pos) } }; @@ -346,16 +376,20 @@ impl Prompt { // of the definition. let num_args = expr.arguments.len(); - // Prepare stack for a new frame + // Determine stack boundaries let cur_stack_boundary = self.store.cur_stack_boundary; - self.store.cur_stack_boundary = self.store.stack.len(); + let new_stack_boundary = self.store.stack.len(); + + // Push new boundary and function arguments for new frame self.store.stack.push(Value::PrevStackBoundary(cur_stack_boundary as isize)); for _ in 0..num_args { - self.store.stack.push(cur_frame.expr_values.pop_front().unwrap()); + let argument = self.store.read_take_ownership(cur_frame.expr_values.pop_front().unwrap()); + self.store.stack.push(argument); } // Push the new frame self.frames.push(Frame::new(heap, expr.definition)); + self.store.cur_stack_boundary = new_stack_boundary; // To simplify the logic a little bit we will now // return and ask our caller to call us again @@ -370,6 +404,19 @@ impl Prompt { } } + debug_log!("Frame [{:?}] at {:?}, stack size = {}", cur_frame.definition, cur_frame.position, self.store.stack.len()); + if debug_enabled!() { + debug_log!("Stack:"); + for (stack_idx, stack_val) in self.store.stack.iter().enumerate() { + debug_log!(" [{:03}] {:?}", stack_idx, stack_val); + } + + debug_log!("Heap:"); + for (heap_idx, heap_region) in self.store.heap_regions.iter().enumerate() { + let is_free = self.store.free_regions.iter().any(|idx| *idx as usize == heap_idx); + debug_log!(" [{:03}] in_use: {}, len: {}, vals: {:?}", heap_idx, !is_free, heap_region.values.len(), &heap_region.values); + } + } // No (more) expressions to evaluate. So evaluate statement (that may // depend on the result on the last evaluated expression(s)) let stmt = &heap[cur_frame.position]; @@ -472,29 +519,35 @@ impl Prompt { debug_assert!(heap[cur_frame.definition].is_function()); debug_assert_eq!(cur_frame.expr_values.len(), 1, "expected one expr value for return statement"); - // The preceding frame has executed a call, so is expecting the // return expression on its expression value stack. Note that // we may be returning a reference to something on our stack, // so we need to read that value and clone it. let return_value = cur_frame.expr_values.pop_back().unwrap(); + println!("DEBUG: Pre-ret val {:?}", &return_value); let return_value = match return_value { Value::Ref(value_id) => self.store.read_copy(value_id), _ => return_value, }; + println!("DEBUG: Pos-ret val {:?}", &return_value); - let prev_stack_idx = self.store.stack[self.store.cur_stack_boundary].as_stack_boundary(); + // Pre-emptively pop our stack frame self.frames.pop(); + + // Clean up our section of the stack self.store.clear_stack(0); + let prev_stack_idx = self.store.stack.pop().unwrap().as_stack_boundary(); // TODO: Temporary hack for testing, remove at some point if self.frames.is_empty() { debug_assert!(prev_stack_idx == -1); - self.store.stack[0] = return_value; + debug_assert!(self.store.stack.len() == 0); + self.store.stack.push(return_value); return Ok(EvalContinuation::Terminal); } debug_assert!(prev_stack_idx >= 0); + // Return to original state of stack frame self.store.cur_stack_boundary = prev_stack_idx as usize; let cur_frame = self.frames.last_mut().unwrap(); cur_frame.expr_values.push_back(return_value); diff --git a/src/protocol/eval/mod.rs b/src/protocol/eval/mod.rs index d8dd58ddadf2283671e42215f56c632ac76c98d1..5c7b066b229f28ca73911ecd278d4d73e4fbf5f6 100644 --- a/src/protocol/eval/mod.rs +++ b/src/protocol/eval/mod.rs @@ -20,9 +20,9 @@ /// implementation would fully fill out the type table with alignment/size/ /// offset information and lay out bytecode. -mod value; -mod store; -mod executor; +pub(crate) mod value; +pub(crate) mod store; +pub(crate) mod executor; pub use value::{Value, ValueGroup}; pub(crate) use store::{Store}; diff --git a/src/protocol/eval/store.rs b/src/protocol/eval/store.rs index e0bed260e87ce5062e3b6cd498b005585946f7b8..b2c17b8273c6af9d5dda4368f3b074d8cce7f392 100644 --- a/src/protocol/eval/store.rs +++ b/src/protocol/eval/store.rs @@ -53,7 +53,25 @@ impl Store { let new_size = self.cur_stack_boundary + unique_stack_idx + 1; for idx in new_size..self.stack.len() { self.drop_value(self.stack[idx].get_heap_pos()); - self.stack[idx] = Value::Unassigned; + } + self.stack.truncate(new_size); + } + + /// Reads a value and takes ownership. This is different from a move because + /// the value might indirectly reference stack/heap values. For these kinds + /// values we will actually return a cloned value. + pub(crate) fn read_take_ownership(&mut self, value: Value) -> Value { + match value { + Value::Ref(ValueId::Stack(pos)) => { + let abs_pos = self.cur_stack_boundary + 1 + pos as usize; + return self.clone_value(self.stack[abs_pos].clone()); + }, + Value::Ref(ValueId::Heap(heap_pos, value_idx)) => { + let heap_pos = heap_pos as usize; + let value_idx = value_idx as usize; + return self.clone_value(self.heap_regions[heap_pos].values[value_idx].clone()); + }, + _ => value } } @@ -133,9 +151,20 @@ impl Store { fn clone_value(&mut self, value: Value) -> Value { // Quickly check if the value is not on the heap let source_heap_pos = value.get_heap_pos(); + println!("DEBUG: Cloning {:?}", value); if source_heap_pos.is_none() { - // We can do a trivial copy - return value; + // We can do a trivial copy, unless we're dealing with a value + // reference + return match value { + Value::Ref(ValueId::Stack(stack_pos)) => { + let abs_stack_pos = self.cur_stack_boundary + stack_pos as usize + 1; + self.clone_value(self.stack[abs_stack_pos].clone()) + }, + Value::Ref(ValueId::Heap(heap_pos, val_idx)) => { + self.clone_value(self.heap_regions[heap_pos as usize].values[val_idx as usize].clone()) + }, + _ => value, + }; } // Value does live on heap, copy it diff --git a/src/protocol/eval/value.rs b/src/protocol/eval/value.rs index ee08ad1323fe931b22e1adef58449793c1eab082..27c8a7129c6bd33ba070a856018bb4490ec71a01 100644 --- a/src/protocol/eval/value.rs +++ b/src/protocol/eval/value.rs @@ -408,15 +408,8 @@ pub(crate) fn apply_binary_operator(store: &mut Store, lhs: &Value, op: BinaryOp // If any of the values are references, retrieve the thing they're referring // to. - let lhs = match lhs { - Value::Ref(value_id) => store.read_ref(*value_id), - _ => lhs, - }; - - let rhs = match rhs { - Value::Ref(value_id) => store.read_ref(*value_id), - _ => rhs, - }; + let lhs = store.maybe_read_ref(lhs); + let rhs = store.maybe_read_ref(rhs); match op { BO::Concatenate => unreachable!(), @@ -429,8 +422,8 @@ pub(crate) fn apply_binary_operator(store: &mut Store, lhs: &Value, op: BinaryOp BO::BitwiseOr => { apply_int_op_and_return_self!(lhs, |, op, rhs); }, BO::BitwiseXor => { apply_int_op_and_return_self!(lhs, ^, op, rhs); }, BO::BitwiseAnd => { apply_int_op_and_return_self!(lhs, &, op, rhs); }, - BO::Equality => { todo!("implement") }, - BO::Inequality => { todo!("implement") }, + BO::Equality => { Value::Bool(apply_equality_operator(store, lhs, rhs)) }, + BO::Inequality => { Value::Bool(apply_inequality_operator(store, lhs, rhs)) }, BO::LessThan => { apply_int_op_and_return_bool!(lhs, <, op, rhs); }, BO::GreaterThan => { apply_int_op_and_return_bool!(lhs, >, op, rhs); }, BO::LessThanEqual => { apply_int_op_and_return_bool!(lhs, <=, op, rhs); }, @@ -492,5 +485,105 @@ pub(crate) fn apply_unary_operator(store: &mut Store, op: UnaryOperator, value: } pub(crate) fn apply_equality_operator(store: &Store, lhs: &Value, rhs: &Value) -> bool { + let lhs = store.maybe_read_ref(lhs); + let rhs = store.maybe_read_ref(rhs); + + fn eval_equality_heap(store: &Store, lhs_pos: HeapPos, rhs_pos: HeapPos) -> bool { + let lhs_vals = &store.heap_regions[lhs_pos as usize].values; + let rhs_vals = &store.heap_regions[rhs_pos as usize].values; + let lhs_len = lhs_vals.len(); + if lhs_len != rhs_vals.len() { + return false; + } + + for idx in 0..lhs_len { + let lhs_val = &lhs_vals[idx]; + let rhs_val = &rhs_vals[idx]; + if !apply_equality_operator(store, lhs_val, rhs_val) { + return false; + } + } + + return true; + } + + match lhs { + Value::Input(v) => *v == rhs.as_input(), + Value::Output(v) => *v == rhs.as_output(), + Value::Message(lhs_pos) => eval_equality_heap(store, *lhs_pos, rhs.as_message()), + Value::Null => todo!("remove null"), + Value::Bool(v) => *v == rhs.as_bool(), + Value::Char(v) => *v == rhs.as_char(), + Value::String(lhs_pos) => eval_equality_heap(store, *lhs_pos, rhs.as_string()), + Value::UInt8(v) => *v == rhs.as_uint8(), + Value::UInt16(v) => *v == rhs.as_uint16(), + Value::UInt32(v) => *v == rhs.as_uint32(), + Value::UInt64(v) => *v == rhs.as_uint64(), + Value::SInt8(v) => *v == rhs.as_sint8(), + Value::SInt16(v) => *v == rhs.as_sint16(), + Value::SInt32(v) => *v == rhs.as_sint32(), + Value::SInt64(v) => *v == rhs.as_sint64(), + Value::Enum(v) => *v == rhs.as_enum(), + Value::Union(lhs_tag, lhs_pos) => { + let (rhs_tag, rhs_pos) = rhs.as_union(); + if *lhs_tag != rhs_tag { + return false; + } + eval_equality_heap(store, *lhs_pos, rhs_pos) + }, + Value::Struct(lhs_pos) => eval_equality_heap(store, *lhs_pos, rhs.as_struct()), + _ => unreachable!("apply_equality_operator to lhs {:?}", lhs), + } +} +pub(crate) fn apply_inequality_operator(store: &Store, lhs: &Value, rhs: &Value) -> bool { + let lhs = store.maybe_read_ref(lhs); + let rhs = store.maybe_read_ref(rhs); + + fn eval_inequality_heap(store: &Store, lhs_pos: HeapPos, rhs_pos: HeapPos) -> bool { + let lhs_vals = &store.heap_regions[lhs_pos as usize].values; + let rhs_vals = &store.heap_regions[rhs_pos as usize].values; + let lhs_len = lhs_vals.len(); + if lhs_len != rhs_vals.len() { + return true; + } + + for idx in 0..lhs_len { + let lhs_val = &lhs_vals[idx]; + let rhs_val = &rhs_vals[idx]; + if apply_inequality_operator(store, lhs_val, rhs_val) { + return true; + } + } + + return false; + } + + match lhs { + Value::Input(v) => *v != rhs.as_input(), + Value::Output(v) => *v != rhs.as_output(), + Value::Message(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_message()), + Value::Null => todo!("remove null"), + Value::Bool(v) => *v != rhs.as_bool(), + Value::Char(v) => *v != rhs.as_char(), + Value::String(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_string()), + Value::UInt8(v) => *v != rhs.as_uint8(), + Value::UInt16(v) => *v != rhs.as_uint16(), + Value::UInt32(v) => *v != rhs.as_uint32(), + Value::UInt64(v) => *v != rhs.as_uint64(), + Value::SInt8(v) => *v != rhs.as_sint8(), + Value::SInt16(v) => *v != rhs.as_sint16(), + Value::SInt32(v) => *v != rhs.as_sint32(), + Value::SInt64(v) => *v != rhs.as_sint64(), + Value::Enum(v) => *v != rhs.as_enum(), + Value::Union(lhs_tag, lhs_pos) => { + let (rhs_tag, rhs_pos) = rhs.as_union(); + if *lhs_tag != rhs_tag { + return true; + } + eval_inequality_heap(store, *lhs_pos, rhs_pos) + }, + Value::String(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_struct()), + _ => unreachable!("apply_inequality_operator to lhs {:?}", lhs) + } } \ No newline at end of file diff --git a/src/protocol/tests/eval_calls.rs b/src/protocol/tests/eval_calls.rs new file mode 100644 index 0000000000000000000000000000000000000000..46a5cfe377b50fb58dbd2ea76dbe31e8f631fb4d --- /dev/null +++ b/src/protocol/tests/eval_calls.rs @@ -0,0 +1,31 @@ +use super::*; +use crate::protocol::eval::*; + +#[test] +fn test_function_call() { + Tester::new_single_source_expect_ok("with literal arg", " + func add_two(u32 value) -> u32 { + return value + 2; + } + func foo() -> u32 { + return add_two(5); + } + ").for_function("foo", |f| { + f.call(Some(Value::UInt32(7))); + }); + + println!("\n\n\n\n\n\n\n"); + + Tester::new_single_source_expect_ok("with variable arg", " + func add_two(u32 value) -> u32 { + value += 1; + return value + 1; + } + func foo() -> bool { + auto initial = 5; + auto result = add_two(initial); + return initial == 5 && result == 7; + }").for_function("foo", |f| { + f.call(Some(Value::Bool(true))); + }); +} \ No newline at end of file diff --git a/src/protocol/tests/eval_operators.rs b/src/protocol/tests/eval_operators.rs index c0607d3a1523913f9529582e9ec576e37ce03413..37082ccd680e5cb35f24f01548193745e74f32fb 100644 --- a/src/protocol/tests/eval_operators.rs +++ b/src/protocol/tests/eval_operators.rs @@ -1,7 +1,8 @@ use super::*; +use crate::protocol::eval::*; #[test] -fn test_assignment() { +fn test_assignment_operators() { fn construct_source(value_type: &str, value_initial: &str, value_op: &str) -> String { return format!( "func foo() -> {} {{ @@ -12,59 +13,184 @@ fn test_assignment() { value_type, value_type, value_initial, value_op ); } - Tester::new_single_source_expect_ok( - "set", construct_source("u32", "1", "= 5") - ).for_function("foo", |f| { f.call(); }); - Tester::new_single_source_expect_ok( - "multiplied", construct_source("u32", "2", "*= 4") - ).for_function("foo", |f| { f.call(); }); + fn perform_test(name: &str, source: String, expected_value: Value) { + Tester::new_single_source_expect_ok(name, source) + .for_function("foo", move |f| { + f.call(Some(expected_value)); + }); + } - Tester::new_single_source_expect_ok( - "divided", construct_source("u32", "8", "/= 4") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "set", + construct_source("u32", "1", "= 5"), + Value::UInt32(5) + ); - Tester::new_single_source_expect_ok( - "remained", construct_source("u32", "8", "%= 3") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "multiplied", + construct_source("u32", "2", "*= 4"), + Value::UInt32(8) + ); - Tester::new_single_source_expect_ok( - "added", construct_source("u32", "2", "+= 4") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "divided", + construct_source("u32", "8", "/= 4"), + Value::UInt32(2) + ); - Tester::new_single_source_expect_ok( - "subtracted", construct_source("u32", "6", "-= 4") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "remained", + construct_source("u32", "8", "%= 3"), + Value::UInt32(2) + ); - Tester::new_single_source_expect_ok( - "shifted left", construct_source("u32", "2", "<<= 2") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "added", + construct_source("u32", "2", "+= 4"), + Value::UInt32(6) + ); - Tester::new_single_source_expect_ok( - "shifted right", construct_source("u32", "8", ">>= 2") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "subtracted", + construct_source("u32", "6", "-= 4"), + Value::UInt32(2) + ); - Tester::new_single_source_expect_ok( - "bitwise and", construct_source("u32", "3", "&= 2") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "shifted left", + construct_source("u32", "2", "<<= 2"), + Value::UInt32(8) + ); - Tester::new_single_source_expect_ok( - "bitwise xor", construct_source("u32", "3", "^= 7") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "shifted right", + construct_source("u32", "8", ">>= 2"), + Value::UInt32(2) + ); - Tester::new_single_source_expect_ok( - "bitwise or", construct_source("u32", "12", "|= 3") - ).for_function("foo", |f| { f.call(); }); + perform_test( + "bitwise and", + construct_source("u32", "15", "&= 35"), + Value::UInt32(3) + ); + + perform_test( + "bitwise xor", + construct_source("u32", "3", "^= 7"), + Value::UInt32(4) + ); + + perform_test( + "bitwise or", + construct_source("u32", "12", "|= 3"), + Value::UInt32(15) + ); } #[test] -fn test_function_call() { - Tester::new_single_source_expect_ok("calling", " - func add_two(u32 value) -> u32 { - return value + 2; +fn test_concatenate_operator() { + Tester::new_single_source_expect_ok( + "concatenate and check pairs", + " + func check_pair(T[] arr, u32 idx) -> bool { + return arr[idx] == arr[idx + 1]; + } + + struct Point2D { + u32 x, + u32 y, + } + + func create_point(u32 x, u32 y) -> Point2D { + return Point2D{ x: x, y: y }; + } + + func create_array() -> Point2D[] { + return { + create_point(1, 2), + create_point(1, 2), + create_point(3, 4), + create_point(3, 4) + }; + } + + func foo() -> bool { + auto lhs = create_array(); + auto rhs = create_array(); + auto total = lhs @ rhs; + auto is_equal = + check_pair(total, 0) && + check_pair(total, 2) && + check_pair(total, 4) && + check_pair(total, 6); + auto is_not_equal = + !check_pair(total, 0) || + !check_pair(total, 2) || + !check_pair(total, 4) || + !check_pair(total, 6); + return is_equal && !is_not_equal; + } + " + ).for_function("foo", |f| { + f.call(Some(Value::Bool(true))); + }); +} +#[test] +fn test_binary_integer_operators() { + fn construct_source(value_type: &str, code: &str) -> String { + format!(" + func foo() -> {} {{ + {} + }} + ", value_type, code) } - func foo() -> u32 { - return add_two(5); + + fn perform_test(test_name: &str, value_type: &str, code: &str, expected_value: Value) { + Tester::new_single_source_expect_ok(test_name, construct_source(value_type, code)) + .for_function("foo", move |f| { + f.call(Some(expected_value)); + }); } - ").for_function("foo", |f| { f.call(); }); + + perform_test( + "bitwise_or", "u16", + "auto a = 3; return a | 4;", Value::UInt16(7) + ); + perform_test( + "bitwise_xor", "u16", + "auto a = 3; return a ^ 7;", Value::UInt16(4) + ); + perform_test( + "bitwise and", "u16", + "auto a = 0b110011; return a & 0b011110;", Value::UInt16(0b010010) + ); + perform_test( + "shift left", "u16", + "auto a = 0x0F; return a << 4;", Value::UInt16(0xF0) + ); + perform_test( + "shift right", "u64", + "auto a = 0xF0; return a >> 4;", Value::UInt64(0x0F) + ); + perform_test( + "add", "u32", + "auto a = 5; return a + 5;", Value::UInt32(10) + ); + perform_test( + "subtract", "u32", + "auto a = 3; return a - 3;", Value::UInt32(0) + ); + perform_test( + "multiply", "u8", + "auto a = 2 * 2; return a * 2 * 2;", Value::UInt8(16) + ); + perform_test( + "divide", "u8", + "auto a = 32 / 2; return a / 2 / 2;", Value::UInt8(4) + ); + perform_test( + "remainder", "u16", + "auto a = 29; return a % 3;", Value::UInt16(2) + ); } \ No newline at end of file diff --git a/src/protocol/tests/mod.rs b/src/protocol/tests/mod.rs index ac50bdb9a135321bc7ad893e9071c99b7051fb09..c326c008d60559ce528ad882e1869a116e00583e 100644 --- a/src/protocol/tests/mod.rs +++ b/src/protocol/tests/mod.rs @@ -1,3 +1,13 @@ +/** + * protocol/tests.rs + * + * Contains tests for various parts of the lexer/parser and the evaluator of the + * code. These are intended to be temporary tests such that we're sure that we + * don't break existing functionality. + * + * In the future these should be replaced by proper testing protocols. + */ + mod utils; mod lexer; mod parser_validation; @@ -5,5 +15,6 @@ mod parser_inference; mod parser_monomorphs; mod parser_imports; mod eval_operators; +mod eval_calls; pub(crate) use utils::{Tester}; \ No newline at end of file diff --git a/src/protocol/tests/utils.rs b/src/protocol/tests/utils.rs index d81dd664fcd85b39b51c893af83aa53649e2300c..b510760722f0cb48d5502cd2e5c25691a9a0d367 100644 --- a/src/protocol/tests/utils.rs +++ b/src/protocol/tests/utils.rs @@ -209,7 +209,7 @@ impl AstOkTester { self } - pub(crate) fn for_function(self, name: &str, f: F) -> Self { + pub(crate) fn for_function(self, name: &str, f: F) -> Self { let mut found = false; for definition in self.heap.definitions.iter() { if let Definition::Function(definition) = definition { @@ -587,6 +587,20 @@ impl<'a> FunctionTester<'a> { } } + assert!( + prompt.store.stack.len() > 0, // note: stack never shrinks + "[{}] No value on stack after calling function for {}", + self.ctx.test_name, self.assert_postfix() + ); + + if let Some(expected_result) = expected_result { + debug_assert!(expected_result.get_heap_pos().is_none(), "comparing against heap thingamajigs is not yet implemented"); + assert!( + value::apply_equality_operator(&prompt.store, &prompt.store.stack[0], &expected_result), + "[{}] Result from call was {:?}, but expected {:?} for {}", + self.ctx.test_name, &prompt.store.stack[0], &expected_result, self.assert_postfix() + ) + } self }