diff --git a/src/collections/scoped_buffer.rs b/src/collections/scoped_buffer.rs index 5c8fe0837176db8c1e6a6788f815df2db806a58f..1416a1634df2816c4b7127dcec1ded5ec63a6a64 100644 --- a/src/collections/scoped_buffer.rs +++ b/src/collections/scoped_buffer.rs @@ -77,6 +77,7 @@ impl ScopedSection { } #[inline] + #[allow(unused_mut)] // used in debug mode pub(crate) fn forget(mut self) { let vec = unsafe{&mut *self.inner}; #[cfg(debug_assertions)] { @@ -90,6 +91,7 @@ impl ScopedSection { } #[inline] + #[allow(unused_mut)] // used in debug mode pub(crate) fn into_vec(mut self) -> Vec { let vec = unsafe{&mut *self.inner}; #[cfg(debug_assertions)] { diff --git a/src/common.rs b/src/common.rs index 9ac1ddeb679f167a0242856a8f1ee093ba0669a3..2a6d8069b10b66f9aa9353131cae7d157c718c30 100644 --- a/src/common.rs +++ b/src/common.rs @@ -15,7 +15,6 @@ pub(crate) use mio::{ }; pub(crate) use std::{ collections::{BTreeMap, HashMap, HashSet}, - convert::TryInto, io::{Read, Write}, net::SocketAddr, sync::Arc, diff --git a/src/protocol/arena.rs b/src/protocol/arena.rs index 68dc8b5cd6f8ad83dbeef578b351f0d2055e557a..d85e21152016c3fc054927aa31a18220acaae87c 100644 --- a/src/protocol/arena.rs +++ b/src/protocol/arena.rs @@ -57,6 +57,15 @@ impl Arena { id } + // Compiler-internal direct retrieval + pub(crate) fn get(&self, idx: usize) -> &T { + return &self.store[idx] + } + pub(crate) fn get_id(&self, idx: usize) -> Id { + debug_assert!(idx < self.store.len()); + return Id::new(idx as i32); + } + pub fn iter(&self) -> impl Iterator { self.store.iter() } diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 47e5960bf8ccb2cbcb5c595aafe2d6ec02c81623..d252810ebdbe8cd7fc9266f18c237d2cb10fc5e3 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -1877,6 +1877,7 @@ pub struct VariableExpression { pub identifier: Identifier, // Validator/Linker pub declaration: Option, + pub used_as_binding_target: bool, pub parent: ExpressionParent, pub unique_id_in_definition: i32, } \ No newline at end of file diff --git a/src/protocol/ast_printer.rs b/src/protocol/ast_printer.rs index 68d1046f946d27bf2b21c38b5bdcf12c61504cea..e85620bd2d8bf499dc08b014c255c736bcccc044 100644 --- a/src/protocol/ast_printer.rs +++ b/src/protocol/ast_printer.rs @@ -51,6 +51,7 @@ const PREFIX_INDEXING_EXPR_ID: &'static str = "EIdx"; const PREFIX_SLICING_EXPR_ID: &'static str = "ESli"; const PREFIX_SELECT_EXPR_ID: &'static str = "ESel"; const PREFIX_LITERAL_EXPR_ID: &'static str = "ELit"; +const PREFIX_CAST_EXPR_ID: &'static str = "ECas"; const PREFIX_CALL_EXPR_ID: &'static str = "ECll"; const PREFIX_VARIABLE_EXPR_ID: &'static str = "EVar"; @@ -705,7 +706,14 @@ impl ASTWriter { .with_custom_val(|v| write_expression_parent(v, &expr.parent)); }, Expression::Cast(expr) => { - todo!("print casting expression") + self.kv(indent).with_id(PREFIX_CAST_EXPR_ID, expr.this.0.index) + .with_s_key("CallExpr"); + self.kv(indent2).with_s_key("ToType") + .with_custom_val(|t| write_parser_type(t, heap, &expr.to_type)); + self.kv(indent2).with_s_key("Subject"); + self.write_expr(heap, expr.subject, indent3); + self.kv(indent2).with_s_key("Parent") + .with_custom_val(|v| write_expression_parent(v, &expr.parent)); } Expression::Call(expr) => { self.kv(indent).with_id(PREFIX_CALL_EXPR_ID, expr.this.0.index) diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index 18de494a20c56c87ca3744076a9517f205d1676f..c50e075e365c569cdda3cb2c95e6e5995a128ae2 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -31,6 +31,7 @@ pub(crate) struct Frame { pub(crate) position: StatementId, pub(crate) expr_stack: VecDeque, // hack for expression evaluation, evaluated by popping from back pub(crate) expr_values: VecDeque, // hack for expression results, evaluated by popping from front/back + pub(crate) max_stack_size: u32, } impl Frame { @@ -43,12 +44,32 @@ impl Frame { _ => unreachable!("initializing frame with {:?} instead of a function/component", definition), }; + // Another not-so-pretty thing that has to be replaced somewhere in the + // future... + fn determine_max_stack_size(heap: &Heap, block_id: BlockStatementId, max_size: &mut u32) { + let block_stmt = &heap[block_id]; + debug_assert!(block_stmt.next_unique_id_in_scope >= 0); + + // Check current block + let cur_size = block_stmt.next_unique_id_in_scope as u32; + if cur_size > *max_size { *max_size = cur_size; } + + // And child blocks + for child_scope in &block_stmt.scope_node.nested { + determine_max_stack_size(heap, child_scope.to_block(), max_size); + } + } + + let mut max_stack_size = 0; + determine_max_stack_size(heap, first_statement, &mut max_stack_size); + Frame{ definition: definition_id, monomorph_idx, position: first_statement.upcast(), expr_stack: VecDeque::with_capacity(128), expr_values: VecDeque::with_capacity(128), + max_stack_size, } } @@ -89,7 +110,8 @@ impl Frame { self.serialize_expression(heap, expr.right); }, Expression::Binding(expr) => { - todo!("implement binding expression"); + self.serialize_expression(heap, expr.bound_to); + self.serialize_expression(heap, expr.bound_from); }, Expression::Conditional(expr) => { self.serialize_expression(heap, expr.test); @@ -199,8 +221,11 @@ impl Prompt { // Maybe do typechecking in the future? debug_assert!((monomorph_idx as usize) < _types.get_base_definition(&def).unwrap().definition.procedure_monomorphs().len()); - prompt.frames.push(Frame::new(heap, def, monomorph_idx)); + let new_frame = Frame::new(heap, def, monomorph_idx); + let max_stack_size = new_frame.max_stack_size; + prompt.frames.push(new_frame); args.into_store(&mut prompt.store); + prompt.store.reserve_stack(max_stack_size); prompt } @@ -279,7 +304,15 @@ impl Prompt { apply_assignment_operator(&mut self.store, to, expr.operation, rhs); }, Expression::Binding(_expr) => { - todo!("Binding expression"); + let bind_to = cur_frame.expr_values.pop_back().unwrap(); + let bind_from = cur_frame.expr_values.pop_back().unwrap(); + let bind_to_heap_pos = bind_to.get_heap_pos(); + let bind_from_heap_pos = bind_from.get_heap_pos(); + + let result = apply_binding_operator(&mut self.store, bind_to, bind_from); + self.store.drop_value(bind_to_heap_pos); + self.store.drop_value(bind_from_heap_pos); + cur_frame.expr_values.push_back(Value::Bool(result)); }, Expression::Conditional(expr) => { // Evaluate testing expression, then extend the @@ -509,46 +542,84 @@ impl Prompt { self.store.drop_value(subject.get_heap_pos()); } Expression::Call(expr) => { - // Push a new frame. Note that all expressions have - // been pushed to the front, so they're in the order - // of the definition. - let num_args = expr.arguments.len(); - - // Determine stack boundaries - let cur_stack_boundary = self.store.cur_stack_boundary; - 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 { - let argument = self.store.read_take_ownership(cur_frame.expr_values.pop_front().unwrap()); - self.store.stack.push(argument); - } + // If we're dealing with a builtin we don't do any + // fancy shenanigans at all, just push the result. + match expr.method { + Method::Length => { + let value = cur_frame.expr_values.pop_front().unwrap(); + let value_heap_pos = value.get_heap_pos(); + let value = self.store.maybe_read_ref(&value); + + let heap_pos = match value { + Value::Array(pos) => *pos, + Value::String(pos) => *pos, + _ => unreachable!("length(...) on {:?}", value), + }; + + let len = self.store.heap_regions[heap_pos as usize].values.len(); + + // TODO: @PtrInt + cur_frame.expr_values.push_back(Value::UInt32(len as u32)); + self.store.drop_value(value_heap_pos); + }, + Method::UserComponent => todo!("component creation"), + Method::UserFunction => { + // Push a new frame. Note that all expressions have + // been pushed to the front, so they're in the order + // of the definition. + let num_args = expr.arguments.len(); + + // Determine stack boundaries + let cur_stack_boundary = self.store.cur_stack_boundary; + 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 { + let argument = self.store.read_take_ownership(cur_frame.expr_values.pop_front().unwrap()); + self.store.stack.push(argument); + } - // Determine the monomorph index of the function we're calling - let mono_data = types.get_procedure_expression_data(&cur_frame.definition, cur_frame.monomorph_idx); - let call_data = &mono_data.expr_data[expr.unique_id_in_definition as usize]; + // Determine the monomorph index of the function we're calling + let mono_data = types.get_procedure_expression_data(&cur_frame.definition, cur_frame.monomorph_idx); + let call_data = &mono_data.expr_data[expr.unique_id_in_definition as usize]; - // Push the new frame - self.frames.push(Frame::new(heap, expr.definition, call_data.field_or_monomorph_idx)); - self.store.cur_stack_boundary = new_stack_boundary; + // Push the new frame and reserve its stack size + let new_frame = Frame::new(heap, expr.definition, call_data.field_or_monomorph_idx); + let new_stack_size = new_frame.max_stack_size; + self.frames.push(new_frame); + self.store.cur_stack_boundary = new_stack_boundary; + self.store.reserve_stack(new_stack_size); - // To simplify the logic a little bit we will now - // return and ask our caller to call us again - return Ok(EvalContinuation::Stepping); + // To simplify the logic a little bit we will now + // return and ask our caller to call us again + return Ok(EvalContinuation::Stepping); + }, + _ => todo!("other builtins"), + } }, Expression::Variable(expr) => { let variable = &heap[expr.declaration.unwrap()]; - cur_frame.expr_values.push_back(Value::Ref(ValueId::Stack(variable.unique_id_in_scope as StackPos))); + let ref_value = if expr.used_as_binding_target { + Value::Binding(variable.unique_id_in_scope as StackPos) + } else { + Value::Ref(ValueId::Stack(variable.unique_id_in_scope as StackPos)) + }; + cur_frame.expr_values.push_back(ref_value); } } } } } - debug_log!("Frame [{:?}] at {:?}, stack size = {}", cur_frame.definition, cur_frame.position, self.store.stack.len()); + debug_log!("Frame [{:?}] at {:?}", cur_frame.definition, cur_frame.position); if debug_enabled!() { - debug_log!("Stack:"); + debug_log!("Expression value stack (size = {}):", cur_frame.expr_values.len()); + for (stack_idx, stack_val) in cur_frame.expr_values.iter().enumerate() { + debug_log!(" [{:03}] {:?}", stack_idx, stack_val); + } + + debug_log!("Stack (size = {}):", self.store.stack.len()); for (stack_idx, stack_val) in self.store.stack.iter().enumerate() { debug_log!(" [{:03}] {:?}", stack_idx, stack_val); } @@ -564,12 +635,7 @@ impl Prompt { let stmt = &heap[cur_frame.position]; let return_value = match stmt { Statement::Block(stmt) => { - // Reserve space on stack, but also make sure excess stack space - // is cleared - self.store.clear_stack(stmt.first_unique_id_in_scope as usize); - self.store.reserve_stack(stmt.next_unique_id_in_scope as usize); cur_frame.position = stmt.statements[0]; - Ok(EvalContinuation::Stepping) }, Statement::EndBlock(stmt) => { @@ -676,6 +742,7 @@ impl Prompt { // Clean up our section of the stack self.store.clear_stack(0); + self.store.stack.truncate(self.store.cur_stack_boundary + 1); let prev_stack_idx = self.store.stack.pop().unwrap().as_stack_boundary(); // TODO: Temporary hack for testing, remove at some point diff --git a/src/protocol/eval/store.rs b/src/protocol/eval/store.rs index 468f98827091074a43fb43658ec71837aa7f68c9..3e8de99be086a6dbc2ca9c2f901e6dd359361570 100644 --- a/src/protocol/eval/store.rs +++ b/src/protocol/eval/store.rs @@ -39,8 +39,8 @@ impl Store { /// Resizes(!) the stack to fit the required number of values. Any /// unallocated slots are initialized to `Unassigned`. The specified stack /// index is exclusive. - pub(crate) fn reserve_stack(&mut self, unique_stack_idx: usize) { - let new_size = self.cur_stack_boundary + unique_stack_idx + 1; + pub(crate) fn reserve_stack(&mut self, unique_stack_idx: u32) { + let new_size = self.cur_stack_boundary + unique_stack_idx as usize + 1; if new_size > self.stack.len() { self.stack.resize(new_size, Value::Unassigned); } @@ -53,8 +53,8 @@ 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 diff --git a/src/protocol/eval/value.rs b/src/protocol/eval/value.rs index 1d36c208fabd2d8b60bce1a9fc46db00bbc128b5..3c25330e5ceae1b8ecdf5879ed2480ea3ad8eb59 100644 --- a/src/protocol/eval/value.rs +++ b/src/protocol/eval/value.rs @@ -29,6 +29,7 @@ pub enum Value { Unassigned, // Marker when variables are first declared, immediately followed by assignment PrevStackBoundary(isize), // Marker for stack frame beginning, so we can pop stack values Ref(ValueId), // Reference to a value, used by expressions producing references + Binding(StackPos), // Reference to a binding variable (reserved on the stack) // Builtin types Input(PortId), Output(PortId), @@ -531,7 +532,7 @@ pub(crate) fn apply_casting(store: &mut Store, output_type: &ConcreteType, subje _ => unreachable!() }) } - }; + } macro_rules! from_unsigned_cast { ($input:expr, $input_type:ty, $output_part:expr) => { @@ -706,6 +707,7 @@ pub(crate) fn apply_casting(store: &mut Store, output_type: &ConcreteType, subje } } +/// Recursively checks for equality. 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); @@ -759,6 +761,7 @@ pub(crate) fn apply_equality_operator(store: &Store, lhs: &Value, rhs: &Value) - } } +/// Recursively checks for inequality 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); @@ -809,4 +812,69 @@ pub(crate) fn apply_inequality_operator(store: &Store, lhs: &Value, rhs: &Value) Value::String(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_struct()), _ => unreachable!("apply_inequality_operator to lhs {:?}", lhs) } +} + +/// Recursively applies binding operator. Essentially an equality operator with +/// special handling if the LHS contains a binding reference to a stack +/// stack variable. +// Note: that there is a lot of `Value.clone()` going on here. As always: this +// is potentially cloning the references to heap values, not actually cloning +// those heap regions into a new heap region. +pub(crate) fn apply_binding_operator(store: &mut Store, lhs: Value, rhs: Value) -> bool { + let lhs = store.maybe_read_ref(&lhs).clone(); + let rhs = store.maybe_read_ref(&rhs).clone(); + + fn eval_binding_heap(store: &mut Store, lhs_pos: HeapPos, rhs_pos: HeapPos) -> bool { + let lhs_len = store.heap_regions[lhs_pos as usize].values.len(); + let rhs_len = store.heap_regions[rhs_pos as usize].values.len(); + if lhs_len != rhs_len { + return false; + } + + for idx in 0..lhs_len { + // More rust shenanigans... I'm going to calm myself by saying that + // this is just a temporary evaluator implementation. + let lhs_val = store.heap_regions[lhs_pos as usize].values[idx].clone(); + let rhs_val = store.heap_regions[rhs_pos as usize].values[idx].clone(); + if !apply_binding_operator(store, lhs_val, rhs_val) { + return false; + } + } + + return true; + } + + match lhs { + Value::Binding(var_pos) => { + let to_write = store.clone_value(rhs.clone()); + store.write(ValueId::Stack(var_pos), to_write); + return true; + }, + Value::Input(v) => v == rhs.as_input(), + Value::Output(v) => v == rhs.as_output(), + Value::Message(lhs_pos) => eval_binding_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_binding_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::Array(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_array()), + 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_binding_heap(store, lhs_pos, rhs_pos) + }, + Value::Struct(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_struct()), + _ => unreachable!("apply_binding_operator to lhs {:?}", lhs), + } } \ No newline at end of file diff --git a/src/protocol/parser/mod.rs b/src/protocol/parser/mod.rs index 3ca4bc1da522d162038b1a21aa4e5cfa66642190..eec169504fa74878f086f8d9c160003360749417 100644 --- a/src/protocol/parser/mod.rs +++ b/src/protocol/parser/mod.rs @@ -129,7 +129,7 @@ impl Parser { vec![ ("array", quick_type(&[PTV::ArrayLike, PTV::PolymorphicArgument(id.upcast(), 0)])) ], - quick_type(&[PTV::IntegerLike]) + quick_type(&[PTV::UInt32]) // TODO: @PtrInt )); insert_builtin_function(&mut parser, "assert", &[], |id| ( vec![ diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index f84453b973b4dc068e726bfe3301004b236b99a2..a7449130ff3b2e46ec03f93f4dbcbe5867e14476 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -349,7 +349,6 @@ impl PassDefinitions { self.consume_statement(module, iter, ctx, &mut statements)?; let wrap_end_pos = iter.last_valid_pos(); - debug_assert_eq!(statements.len(), 1); let statements = statements.into_vec(); let id = ctx.heap.alloc_block_statement(|this| BlockStatement{ @@ -835,6 +834,7 @@ impl PassDefinitions { this, identifier, declaration: None, + used_as_binding_target: false, parent: ExpressionParent::None, unique_id_in_definition: -1, }); @@ -1536,6 +1536,7 @@ impl PassDefinitions { this, identifier, declaration: None, + used_as_binding_target: false, parent: ExpressionParent::None, unique_id_in_definition: -1, }).upcast() diff --git a/src/protocol/parser/pass_typing.rs b/src/protocol/parser/pass_typing.rs index 6166b258fd36b0863b70a03bd8d409905af955a9..20bf5dab7e22922e63dfcf8624e8d0b7aa1254fe 100644 --- a/src/protocol/parser/pass_typing.rs +++ b/src/protocol/parser/pass_typing.rs @@ -485,9 +485,18 @@ impl InferenceType { /// Attempts to infer the first subtree based on the template. Like /// `infer_subtrees_for_both_types`, but now only applying inference to /// `to_infer` based on the type information in `template`. + /// + /// The `forced_template` flag controls whether `to_infer` is considered + /// valid if it is more specific then the template. When `forced_template` + /// is false, then as long as the `to_infer` and `template` types are + /// compatible the inference will succeed. If `forced_template` is true, + /// then `to_infer` MUST be less specific than `template` (e.g. + /// `IntegerLike` is less specific than `UInt32`. Likewise `Bool` is less + /// specific than `BindingBool`) fn infer_subtree_for_single_type( to_infer: &mut InferenceType, mut to_infer_idx: usize, template: &[InferenceTypePart], mut template_idx: usize, + forced_template: bool, ) -> SingleInferenceResult { let mut modified = false; let mut depth = 1; @@ -517,13 +526,15 @@ impl InferenceType { continue; } - // We cannot infer anything, but the template may still be - // compatible with the type we're inferring - if let Some(depth_change) = Self::check_part_for_single_type( - template, &mut template_idx, &to_infer.parts, &mut to_infer_idx - ) { - depth += depth_change; - continue; + if !forced_template { + // We cannot infer anything, but the template may still be + // compatible with the type we're inferring + if let Some(depth_change) = Self::check_part_for_single_type( + template, &mut template_idx, &to_infer.parts, &mut to_infer_idx + ) { + depth += depth_change; + continue; + } } return SingleInferenceResult::Incompatible @@ -1630,10 +1641,10 @@ impl PassTyping { AO::Set => false, AO::Multiplied | AO::Divided | AO::Added | AO::Subtracted => - self.apply_forced_constraint(ctx, arg1_expr_id, &NUMBERLIKE_TEMPLATE)?, + self.apply_template_constraint(ctx, arg1_expr_id, &NUMBERLIKE_TEMPLATE)?, AO::Remained | AO::ShiftedLeft | AO::ShiftedRight | AO::BitwiseAnded | AO::BitwiseXored | AO::BitwiseOred => - self.apply_forced_constraint(ctx, arg1_expr_id, &INTEGERLIKE_TEMPLATE)?, + self.apply_template_constraint(ctx, arg1_expr_id, &INTEGERLIKE_TEMPLATE)?, }; let (progress_arg1, progress_arg2) = self.apply_equal2_constraint( @@ -1720,9 +1731,9 @@ impl PassTyping { let (progress_expr, progress_arg1, progress_arg2) = match expr.operation { BO::Concatenate => { // Arguments may be arrays/slices, output is always an array - let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?; - let progress_arg1 = self.apply_forced_constraint(ctx, arg1_id, &ARRAYLIKE_TEMPLATE)?; - let progress_arg2 = self.apply_forced_constraint(ctx, arg2_id, &ARRAYLIKE_TEMPLATE)?; + let progress_expr = self.apply_template_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?; + let progress_arg1 = self.apply_template_constraint(ctx, arg1_id, &ARRAYLIKE_TEMPLATE)?; + let progress_arg2 = self.apply_template_constraint(ctx, arg2_id, &ARRAYLIKE_TEMPLATE)?; // If they're all arraylike, then we want the subtype to match let (subtype_expr, subtype_arg1, subtype_arg2) = @@ -1733,10 +1744,10 @@ impl PassTyping { BO::LogicalAnd => { // Logical AND may operate both on normal booleans and on // booleans that are the result of a binding expression. So we - // force the expression to bool-like, then apply an equal-3 + // force the expression to bool-like, then apply an equal_3 // constraint. Any BindingBool will promote all the other Bool // types. - let base_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?; + let base_expr = self.apply_template_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?; let (progress_expr, progress_arg1, progress_arg2) = self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 0)?; @@ -1752,7 +1763,7 @@ impl PassTyping { }, BO::BitwiseOr | BO::BitwiseXor | BO::BitwiseAnd | BO::Remainder | BO::ShiftLeft | BO::ShiftRight => { // All equal of integer type - let progress_base = self.apply_forced_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?; + let progress_base = self.apply_template_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?; let (progress_expr, progress_arg1, progress_arg2) = self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 0)?; @@ -1760,7 +1771,7 @@ impl PassTyping { }, BO::Equality | BO::Inequality => { // Equal2 on args, forced boolean output - let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)?; + let progress_expr = self.apply_template_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?; let (progress_arg1, progress_arg2) = self.apply_equal2_constraint(ctx, upcast_id, arg1_id, 0, arg2_id, 0)?; @@ -1768,8 +1779,8 @@ impl PassTyping { }, BO::LessThan | BO::GreaterThan | BO::LessThanEqual | BO::GreaterThanEqual => { // Equal2 on args with numberlike type, forced boolean output - let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)?; - let progress_arg_base = self.apply_forced_constraint(ctx, arg1_id, &NUMBERLIKE_TEMPLATE)?; + let progress_expr = self.apply_template_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?; + let progress_arg_base = self.apply_template_constraint(ctx, arg1_id, &NUMBERLIKE_TEMPLATE)?; let (progress_arg1, progress_arg2) = self.apply_equal2_constraint(ctx, upcast_id, arg1_id, 0, arg2_id, 0)?; @@ -1777,7 +1788,7 @@ impl PassTyping { }, BO::Add | BO::Subtract | BO::Multiply | BO::Divide => { // All equal of number type - let progress_base = self.apply_forced_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?; + let progress_base = self.apply_template_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?; let (progress_expr, progress_arg1, progress_arg2) = self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 0)?; @@ -1812,7 +1823,7 @@ impl PassTyping { let (progress_expr, progress_arg) = match expr.operation { UO::Positive | UO::Negative => { // Equal types of numeric class - let progress_base = self.apply_forced_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?; + let progress_base = self.apply_template_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?; let (progress_expr, progress_arg) = self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 0, arg_id, 0)?; @@ -1820,7 +1831,7 @@ impl PassTyping { }, UO::BitwiseNot | UO::PreIncrement | UO::PreDecrement | UO::PostIncrement | UO::PostDecrement => { // Equal types of integer class - let progress_base = self.apply_forced_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?; + let progress_base = self.apply_template_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?; let (progress_expr, progress_arg) = self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 0, arg_id, 0)?; @@ -1857,8 +1868,8 @@ impl PassTyping { debug_log!(" - Expr type: {}", self.temp_get_display_name(ctx, upcast_id)); // Make sure subject is arraylike and index is integerlike - let progress_subject_base = self.apply_forced_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?; - let progress_index = self.apply_forced_constraint(ctx, index_id, &INTEGERLIKE_TEMPLATE)?; + let progress_subject_base = self.apply_template_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?; + let progress_index = self.apply_template_constraint(ctx, index_id, &INTEGERLIKE_TEMPLATE)?; // Make sure if output is of T then subject is Array let (progress_expr, progress_subject) = @@ -1891,12 +1902,12 @@ impl PassTyping { debug_log!(" - Expr type: {}", self.temp_get_display_name(ctx, upcast_id)); // Make sure subject is arraylike and indices are of equal integerlike - let progress_subject_base = self.apply_forced_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?; - let progress_idx_base = self.apply_forced_constraint(ctx, from_id, &INTEGERLIKE_TEMPLATE)?; + let progress_subject_base = self.apply_template_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?; + let progress_idx_base = self.apply_template_constraint(ctx, from_id, &INTEGERLIKE_TEMPLATE)?; let (progress_from, progress_to) = self.apply_equal2_constraint(ctx, upcast_id, from_id, 0, to_id, 0)?; // Make sure if output is of Slice then subject is Array - let progress_expr_base = self.apply_forced_constraint(ctx, upcast_id, &SLICE_TEMPLATE)?; + let progress_expr_base = self.apply_template_constraint(ctx, upcast_id, &SLICE_TEMPLATE)?; let (progress_expr, progress_subject) = self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 1, subject_id, 1)?; @@ -2091,10 +2102,10 @@ impl PassTyping { let progress_expr = match &expr.value { Literal::Null => { - self.apply_forced_constraint(ctx, upcast_id, &MESSAGE_TEMPLATE)? + self.apply_template_constraint(ctx, upcast_id, &MESSAGE_TEMPLATE)? }, Literal::Integer(_) => { - self.apply_forced_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)? + self.apply_template_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)? }, Literal::True | Literal::False => { self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)? @@ -2344,7 +2355,7 @@ impl PassTyping { } // And the output should be an array of the element types - let mut progress_expr = self.apply_forced_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?; + let mut progress_expr = self.apply_template_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?; if !expr_elements.is_empty() { let first_arg_id = expr_elements[0]; let (inner_expr_progress, arg_progress) = self.apply_equal2_constraint( @@ -2369,10 +2380,8 @@ impl PassTyping { debug_log!(" * After:"); debug_log!(" - Expr type: {}", self.temp_get_display_name(ctx, upcast_id)); - // TODO: FIX!!!! if progress_expr { self.queue_expr_parent(ctx, upcast_id); } - Ok(()) } @@ -2381,6 +2390,11 @@ impl PassTyping { let expr = &ctx.heap[id]; let expr_idx = expr.unique_id_in_definition; + debug_log!("Casting expr: {}", upcast_id.index); + debug_log!(" * Before:"); + debug_log!(" - Expr type: {}", self.temp_get_display_name(ctx, upcast_id)); + debug_log!(" - Subject type: {}", self.temp_get_display_name(ctx, expr.subject)); + // The cast expression might have its output type fixed by the // programmer, so apply that type to the output. Apart from that casting // acts like a blocker for two-way inference. So we'll just have to wait @@ -2388,18 +2402,24 @@ impl PassTyping { // TODO: Another thing that has to be updated the moment the type // inferencer is fully index/job-based let infer_type = self.determine_inference_type_from_parser_type_elements(&expr.to_type.elements, true); - let expr_progress = self.apply_forced_constraint(ctx, upcast_id, &infer_type.parts)?; + let expr_progress = self.apply_template_constraint(ctx, upcast_id, &infer_type.parts)?; if expr_progress { self.queue_expr_parent(ctx, upcast_id); } // Check if the two types are compatible + debug_log!(" * After:"); + debug_log!(" - Expr type [{}]: {}", expr_progress, self.temp_get_display_name(ctx, upcast_id)); + debug_log!(" - Note that the subject type can never be inferred"); + debug_log!(" * Decision:"); + let subject_idx = ctx.heap[expr.subject].get_unique_id_in_definition(); let expr_type = &self.expr_types[expr_idx as usize].expr_type; let subject_type = &self.expr_types[subject_idx as usize].expr_type; if !expr_type.is_done || !subject_type.is_done { // Not yet done + debug_log!(" - Casting is valid: unknown as the types are not yet complete"); return Ok(()) } @@ -2421,6 +2441,8 @@ impl PassTyping { false }; + debug_log!(" - Casting is valid: {}", is_valid); + if !is_valid { let cast_expr = &ctx.heap[id]; let subject_expr = &ctx.heap[cast_expr.subject]; @@ -2615,7 +2637,7 @@ impl PassTyping { link_data.var_type.parts[0] == InferenceTypePart::Input || link_data.var_type.parts[0] == InferenceTypePart::Output ); - match InferenceType::infer_subtree_for_single_type(&mut link_data.var_type, 1, &unsafe{&*var_type}.parts, 1) { + match InferenceType::infer_subtree_for_single_type(&mut link_data.var_type, 1, &unsafe{&*var_type}.parts, 1, false) { SingleInferenceResult::Modified => { for other_expr in &link_data.used_at { let other_expr_idx = ctx.heap[*other_expr].get_unique_id_in_definition(); @@ -2666,16 +2688,17 @@ impl PassTyping { self.expr_queued.push_back(expr_idx); } - /// Applies a forced type constraint: the type associated with the supplied - /// expression will be molded into the provided "template". The template may - /// be fully specified (e.g. a bool) or contain "inference" variables (e.g. - /// an array of T) - fn apply_forced_constraint( + /// Applies a template type constraint: the type associated with the + /// supplied expression will be molded into the provided `template`. But + /// will be considered valid if the template could've been molded into the + /// expression type as well. Hence the template may be fully specified (e.g. + /// a bool) or contain "inference" variables (e.g. an array of T) + fn apply_template_constraint( &mut self, ctx: &Ctx, expr_id: ExpressionId, template: &[InferenceTypePart] ) -> Result { let expr_idx = ctx.heap[expr_id].get_unique_id_in_definition(); // TODO: @Temp let expr_type = &mut self.expr_types[expr_idx as usize].expr_type; - match InferenceType::infer_subtree_for_single_type(expr_type, 0, template, 0) { + match InferenceType::infer_subtree_for_single_type(expr_type, 0, template, 0, false) { SingleInferenceResult::Modified => Ok(true), SingleInferenceResult::Unmodified => Ok(false), SingleInferenceResult::Incompatible => Err( @@ -2684,13 +2707,13 @@ impl PassTyping { } } - fn apply_forced_constraint_types( + fn apply_template_constraint_to_types( to_infer: *mut InferenceType, to_infer_start_idx: usize, template: &[InferenceTypePart], template_start_idx: usize ) -> Result { match InferenceType::infer_subtree_for_single_type( unsafe{ &mut *to_infer }, to_infer_start_idx, - template, template_start_idx + template, template_start_idx, false ) { SingleInferenceResult::Modified => Ok(true), SingleInferenceResult::Unmodified => Ok(false), @@ -2698,6 +2721,22 @@ impl PassTyping { } } + /// Applies a forced constraint: the supplied expression's type MUST be + /// inferred from the template, the other way around is considered invalid. + fn apply_forced_constraint( + &mut self, ctx: &Ctx, expr_id: ExpressionId, template: &[InferenceTypePart] + ) -> Result { + let expr_idx = ctx.heap[expr_id].get_unique_id_in_definition(); + let expr_type = &mut self.expr_types[expr_idx as usize].expr_type; + match InferenceType::infer_subtree_for_single_type(expr_type, 0, template, 0, true) { + SingleInferenceResult::Modified => Ok(true), + SingleInferenceResult::Unmodified => Ok(false), + SingleInferenceResult::Incompatible => Err( + self.construct_template_type_error(ctx, expr_id, template) + ) + } + } + /// Applies a type constraint that expects the two provided types to be /// equal. We attempt to make progress in inferring the types. If the call /// is successful then the composition of all types are made equal. @@ -2783,7 +2822,7 @@ impl PassTyping { ); for (poly_idx, poly_section) in signature_type.marker_iter() { let polymorph_type = &mut polymorph_data.poly_vars[poly_idx as usize]; - match Self::apply_forced_constraint_types( + match Self::apply_template_constraint_to_types( polymorph_type, 0, poly_section, 0 ) { Ok(true) => { polymorph_progress.insert(poly_idx); }, @@ -2824,7 +2863,7 @@ impl PassTyping { // if polymorph_progress.contains(&poly_idx) { // Need to match subtrees let polymorph_type = &polymorph_data.poly_vars[poly_idx as usize]; - let modified_at_marker = Self::apply_forced_constraint_types( + let modified_at_marker = Self::apply_template_constraint_to_types( signature_type, start_idx, &polymorph_type.parts, 0 ).expect("no failure when applying polyvar constraints"); @@ -2839,7 +2878,7 @@ impl PassTyping { // apply it to the expression that is supposed to match the signature. if modified_sig { match InferenceType::infer_subtree_for_single_type( - expr_type, 0, &signature_type.parts, 0 + expr_type, 0, &signature_type.parts, 0, true ) { SingleInferenceResult::Modified => true, SingleInferenceResult::Unmodified => false, @@ -2978,10 +3017,9 @@ impl PassTyping { let expr = &ctx.heap[expr_id]; let inference_type = match expr.parent() { - EP::None => { + EP::None => // Should have been set by linker - println!("DEBUG: CRAP!\n{:?}", expr); - unreachable!() }, + unreachable!(), EP::ExpressionStmt(_) => // Determined during type inference InferenceType::new(false, false, vec![ITP::Unknown]), @@ -3044,7 +3082,7 @@ impl PassTyping { // We already have an entry debug_assert!(false, "does this ever happen?"); if let SingleInferenceResult::Incompatible = InferenceType::infer_subtree_for_single_type( - &mut infer_expr.expr_type, 0, &inference_type.parts, 0 + &mut infer_expr.expr_type, 0, &inference_type.parts, 0, false ) { return Err(self.construct_expr_type_error(ctx, expr_id, expr_id)); } @@ -3363,9 +3401,9 @@ impl PassTyping { match &element.variant { // Compiler-only types PTV::Void => { infer_type.push(ITP::Void); }, - PTV::InputOrOutput => { infer_type.push(ITP::PortLike); }, - PTV::ArrayLike => { infer_type.push(ITP::ArrayLike); }, - PTV::IntegerLike => { infer_type.push(ITP::IntegerLike); }, + PTV::InputOrOutput => { infer_type.push(ITP::PortLike); has_inferred = true }, + PTV::ArrayLike => { infer_type.push(ITP::ArrayLike); has_inferred = true }, + PTV::IntegerLike => { infer_type.push(ITP::IntegerLike); has_inferred = true }, // Builtins PTV::Message => { // TODO: @types Remove the Message -> Byte hack at some point... @@ -3711,7 +3749,7 @@ mod tests { let mut lhs_type = IT::new(false, false, vec![lhs.clone()]); let rhs_type = IT::new(false, true, vec![rhs.clone()]); let result = IT::infer_subtree_for_single_type( - &mut lhs_type, 0, &rhs_type.parts, 0 + &mut lhs_type, 0, &rhs_type.parts, 0, false ); assert_eq!(SingleInferenceResult::Modified, result); assert_eq!(lhs_type.parts, rhs_type.parts); @@ -3743,7 +3781,7 @@ mod tests { let mut lhs_type = IT::new(false, false, lhs.clone()); let rhs_type = IT::new(false, true, rhs.clone()); let result = IT::infer_subtree_for_single_type( - &mut lhs_type, 0, &rhs_type.parts, 0 + &mut lhs_type, 0, &rhs_type.parts, 0, false ); assert_eq!(SingleInferenceResult::Modified, result); assert_eq!(lhs_type.parts, rhs_type.parts) diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index 8f61de4147aa2470da3fbb36f3be8bcbcb311de1..72ade83a91fd22fd3c6069b522aafb867b586819 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -980,18 +980,16 @@ impl Visitor2 for PassValidationLinking { fn visit_variable_expr(&mut self, ctx: &mut Ctx, id: VariableExpressionId) -> VisitorResult { let var_expr = &ctx.heap[id]; - println!("DEBUG: Visiting:\nname: {}\nat: {:?}", var_expr.identifier.value.as_str(), var_expr.identifier.span); - let variable_id = match self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier) { + let (variable_id, is_binding_target) = match self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier) { Ok(variable_id) => { // Regular variable - variable_id + (variable_id, false) }, Err(()) => { // Couldn't find variable, but if we're in a binding expression, // then this may be the thing we're binding to. if self.in_binding_expr.is_invalid() || !self.in_binding_expr_lhs { - println!("DEBUG: INVAALLIIIIIIID ({})", var_expr.identifier.value.as_str()); return Err(ParseError::new_error_str_at_span( &ctx.module.source, var_expr.identifier.span, "unresolved variable" )); @@ -1067,12 +1065,13 @@ impl Visitor2 for PassValidationLinking { let body_scope = Scope::Regular(body_stmt_id); self.checked_at_single_scope_add_local(ctx, body_scope, 0, bound_variable_id)?; - bound_variable_id + (bound_variable_id, true) } }; let var_expr = &mut ctx.heap[id]; var_expr.declaration = Some(variable_id); + var_expr.used_as_binding_target = is_binding_target; var_expr.parent = self.expr_parent; var_expr.unique_id_in_definition = self.next_expr_index; self.next_expr_index += 1; diff --git a/src/protocol/parser/type_table.rs b/src/protocol/parser/type_table.rs index 597c4a116528ffda0d8e07b781d2897824e89f4c..f178a29928c532aefee9a3eec0892ea1bec350c3 100644 --- a/src/protocol/parser/type_table.rs +++ b/src/protocol/parser/type_table.rs @@ -319,15 +319,12 @@ impl TypeTable { let reserve_size = ctx.heap.definitions.len(); self.lookup.reserve(reserve_size); - for root_idx in 0..modules.len() { - let last_definition_idx = ctx.heap[modules[root_idx].root_id].definitions.len(); - for definition_idx in 0..last_definition_idx { - let definition_id = ctx.heap[modules[root_idx].root_id].definitions[definition_idx]; - self.resolve_base_definition(modules, ctx, definition_id)?; - } + for definition_idx in 0..ctx.heap.definitions.len() { + let definition_id = ctx.heap.definitions.get_id(definition_idx); + self.resolve_base_definition(modules, ctx, definition_id)?; } - debug_assert_eq!(self.lookup.len() + 6, reserve_size, "mismatch in reserved size of type table"); // NOTE: Temp fix for builtin functions + debug_assert_eq!(self.lookup.len(), reserve_size, "mismatch in reserved size of type table"); // NOTE: Temp fix for builtin functions for module in modules { module.phase = ModuleCompilationPhase::TypesAddedToTable; } @@ -543,7 +540,7 @@ impl TypeTable { UnionVariantValue::None => {}, UnionVariantValue::Embedded(embedded) => { for parser_type in embedded { - let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, parser_type)?; + let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, parser_type, false)?; if !self.ingest_resolve_result(modules, ctx, resolve_result)? { return Ok(false) } @@ -616,7 +613,7 @@ impl TypeTable { // Make sure all fields point to resolvable types for field_definition in &definition.fields { - let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &field_definition.parser_type)?; + let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &field_definition.parser_type, false)?; if !self.ingest_resolve_result(modules, ctx, resolve_result)? { return Ok(false) } @@ -670,7 +667,7 @@ impl TypeTable { // Check the return type debug_assert_eq!(definition.return_types.len(), 1, "not one return type"); // TODO: @ReturnValues - let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &definition.return_types[0])?; + let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &definition.return_types[0], definition.builtin)?; if !self.ingest_resolve_result(modules, ctx, resolve_result)? { return Ok(false) } @@ -678,7 +675,7 @@ impl TypeTable { // Check the argument types for param_id in &definition.parameters { let param = &ctx.heap[*param_id]; - let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, ¶m.parser_type)?; + let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, ¶m.parser_type, definition.builtin)?; if !self.ingest_resolve_result(modules, ctx, resolve_result)? { return Ok(false) } @@ -737,7 +734,7 @@ impl TypeTable { // Check argument types for param_id in &definition.parameters { let param = &ctx.heap[*param_id]; - let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, ¶m.parser_type)?; + let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, ¶m.parser_type, false)?; if !self.ingest_resolve_result(modules, ctx, resolve_result)? { return Ok(false) } @@ -833,7 +830,10 @@ impl TypeTable { /// Hence if one checks a particular parser type for being resolved, one may /// get back a result value indicating an embedded type (with a different /// DefinitionId) is unresolved. - fn resolve_base_parser_type(&mut self, modules: &[Module], ctx: &PassCtx, root_id: RootId, parser_type: &ParserType) -> Result { + fn resolve_base_parser_type( + &mut self, modules: &[Module], ctx: &PassCtx, root_id: RootId, + parser_type: &ParserType, is_builtin: bool + ) -> Result { // Note that as we iterate over the elements of a use ParserTypeVariant as PTV; @@ -847,7 +847,11 @@ impl TypeTable { for element in parser_type.elements.iter() { match element.variant { PTV::Void | PTV::InputOrOutput | PTV::ArrayLike | PTV::IntegerLike => { - unreachable!("compiler-only ParserTypeVariant within type definition"); + if is_builtin { + set_resolve_result(ResolveResult::Builtin); + } else { + unreachable!("compiler-only ParserTypeVariant within type definition"); + } }, PTV::Message | PTV::Bool | PTV::UInt8 | PTV::UInt16 | PTV::UInt32 | PTV::UInt64 | diff --git a/src/protocol/tests/eval_binding.rs b/src/protocol/tests/eval_binding.rs new file mode 100644 index 0000000000000000000000000000000000000000..4a44447ff65c2fef8dacaaa212a9bb63aaf71d88 --- /dev/null +++ b/src/protocol/tests/eval_binding.rs @@ -0,0 +1,146 @@ +use super::*; + +#[test] +fn test_binding_from_struct() { + Tester::new_single_source_expect_ok("top level", " + struct Foo{ u32 field } + func foo() -> u32 { + if (let Foo{ field: thing } = Foo{ field: 1337 }) { return thing; } + return 0; + } + ").for_function("foo", |f| { + f.call_ok(Some(Value::UInt32(1337))); + }); + + Tester::new_single_source_expect_ok("nested", " + struct Nested{ T inner } + struct Foo{ u32 field } + func make(T val) -> Nested { + return Nested{ inner: val }; + } + func foo() -> u32 { + if (let Nested{ inner: Nested { inner: to_get } } = make(make(Foo{ field: 42 }))) { + return to_get.field; + } + return 0; + } + ").for_function("foo", |f| { + f.call_ok(Some(Value::UInt32(42))); + }); +} + +#[test] +fn test_binding_from_array() { + Tester::new_single_source_expect_ok("top level", " + func foo() -> bool { + // Mismatches in length + bool failure1 = false; + u32[] vals = { 1, 2, 3 }; + if (let {1, a} = vals) { failure1 = true; } + + bool failure2 = false; + if (let {} = vals) { failure2 = true; } + + bool failure3 = false; + if (let {1, 2, 3, 4} = vals) { failure3 = true; } + + // Mismatches in known values + bool failure4 = false; + if (let {1, 3, a} = vals) { failure4 = true; } + + // Matching with known variables + auto constant_one = 1; + auto constant_two = 2; + auto constant_three = 3; + bool success1 = false; + if ( + let {binding_one, constant_two, constant_three} = vals && + let {constant_one, binding_two, constant_three} = vals && + let {constant_one, constant_two, binding_three} = vals + ) { + success1 = binding_one == 1 && binding_two == 2 && binding_three == 3; + } + + // Matching with literals + bool success2 = false; + if (let {binding_one, 2, 3} = vals && let {1, binding_two, 3} = vals && let {1, 2, binding_three} = vals) { + success2 = binding_one == 1 && binding_two == 2 && binding_three == 3; + } + + // Matching all + bool success3 = false; + if (let {binding_one, binding_two, binding_three} = vals && let {1, 2, 3} = vals) { + success3 = true; + } + + return + !failure1 && !failure2 && !failure3 && !failure4 && + success1 && success2 && success3; + } + ").for_function("foo", |f| { f + .call_ok(Some(Value::Bool(true))); + }); +} + +#[test] +fn test_binding_fizz_buzz() { + Tester::new_single_source_expect_ok("am I employable?", " + union Fizzable { Number(u32), FizzBuzz, Fizz, Buzz } + + func construct_fizz_buzz(u32 num) -> Fizzable[] { + u32 counter = 1; + auto result = {}; + while (counter <= num) { + auto value = Fizzable::Number(counter); + if (counter % 5 == 0) { + if (counter % 3 == 0) { + value = Fizzable::FizzBuzz; + } else { + value = Fizzable::Buzz; + } + } else if (counter % 3 == 0) { + value = Fizzable::Fizz; + } + + result = result @ { value }; // woohoo, no array builtins! + counter += 1; + } + + return result; + } + + func test_fizz_buzz(Fizzable[] fizzoid) -> bool { + u32 idx = 0; + bool valid = true; + while (idx < length(fizzoid)) { + auto number = idx + 1; + auto is_div3 = number % 3 == 0; + auto is_div5 = number % 5 == 0; + + if (let Fizzable::Number(got) = fizzoid[idx]) { + valid = valid && got == number && !is_div3 && !is_div5; + } else if (let Fizzable::FizzBuzz = fizzoid[idx]) { + valid = valid && is_div3 && is_div5; + } else if (let Fizzable::Fizz = fizzoid[idx]) { + valid = valid && is_div3 && !is_div5; + } else if (let Fizzable::Buzz = fizzoid[idx]) { + valid = valid && !is_div3 && is_div5; + } else { + // Impossibruuu! + valid = false; + } + + idx += 1; + } + + return valid; + } + + func fizz_buzz() -> bool { + auto fizz = construct_fizz_buzz(100); + return test_fizz_buzz(fizz); + } + ").for_function("fizz_buzz", |f| { f + .call_ok(Some(Value::Bool(true))); + }); +} \ No newline at end of file diff --git a/src/protocol/tests/eval_calls.rs b/src/protocol/tests/eval_calls.rs index 5a261115429234916d32645a65ec6986d80741c8..c3539167f93d2e02f0b126181fa688d26a8be9b6 100644 --- a/src/protocol/tests/eval_calls.rs +++ b/src/protocol/tests/eval_calls.rs @@ -13,8 +13,6 @@ fn test_function_call() { f.call_ok(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; diff --git a/src/protocol/tests/mod.rs b/src/protocol/tests/mod.rs index 65c9830f2db7508ee85ddaeed6dacdb72451ac35..eba79350ebe381f68cf7d8127ffb5f10b29f9f3d 100644 --- a/src/protocol/tests/mod.rs +++ b/src/protocol/tests/mod.rs @@ -6,6 +6,9 @@ * don't break existing functionality. * * In the future these should be replaced by proper testing protocols. + * + * If any of these tests fail, and you think they're not needed anymore, feel + * free to cast them out into oblivion, where dead code goes to die. */ mod utils; @@ -18,6 +21,7 @@ mod parser_binding; mod eval_operators; mod eval_calls; mod eval_casting; +mod eval_binding; mod eval_silly; pub(crate) use utils::{Tester}; // the testing harness diff --git a/src/protocol/tests/parser_binding.rs b/src/protocol/tests/parser_binding.rs index 44c6cbc44466eeb6b57d65756a9535f25a3a96e4..6fcab16a2354fa7b50c2021042976446f72051ff 100644 --- a/src/protocol/tests/parser_binding.rs +++ b/src/protocol/tests/parser_binding.rs @@ -32,52 +32,112 @@ fn test_correct_binding() { } #[test] -fn test_boolean_ops_on_binding() { - // Tester::new_single_source_expect_ok("apply && to binding result", " - // union TestUnion{ Two(u16), Four(u32), Eight(u64) } - // func foo() -> u32 { - // auto lit_2 = TestUnion::Two(2); - // auto lit_4 = TestUnion::Four(4); - // auto lit_8 = TestUnion::Eight(8); - // - // // Testing combined forms of bindings - // if ( - // let TestUnion::Two(test_2) = lit_2 && - // let TestUnion::Four(test_4) = lit_4 && - // let TestUnion::Eight(test_8) = lit_8 - // ) { - // auto valid_2 = test_2; - // auto valid_4 = test_4; - // auto valid_8 = test_8; - // } - // - // // Testing in combination with regular expressions, and to the correct - // // literals - // if (let TestUnion::Two(inter_a) = lit_2 && 5 + 2 == 7) { inter_a = 0; } - // if (5 + 2 == 7 && let TestUnion::Two(inter_b) = lit_2) { inter_b = 0; } - // if (2 + 2 == 4 && let TestUnion::Two(inter_c) = lit_2 && 3 + 3 == 8) { inter_c = 0; } - // - // // Testing with the 'incorrect' target union - // if (let TestUnion::Four(nope) = lit_2 && let TestUnion::Two(zilch) = lit_8) { } - // - // return 0; - // } - // ").for_function("foo", |f| { f - // .for_variable("valid_2", |v| { v.assert_concrete_type("u16"); }) - // .for_variable("valid_4", |v| { v.assert_concrete_type("u32"); }) - // .for_variable("valid_8", |v| { v.assert_concrete_type("u64"); }) - // .for_variable("inter_a", |v| { v.assert_concrete_type("u16"); }) - // .for_variable("inter_b", |v| { v.assert_concrete_type("u16"); }) - // .for_variable("inter_c", |v| { v.assert_concrete_type("u16"); }); - // }); +fn test_incorrect_binding() { + Tester::new_single_source_expect_err("binding at statement level", " + func foo() -> bool { + return let a = 5; + } + ").error(|e| { e + .assert_num(1) + .assert_occurs_at(0, "let") + .assert_msg_has(0, "only be used inside the testing expression"); + }); - Tester::new_single_source_expect_ok("apply || before binding", " -enum Test{ A, B } -func foo() -> u32 { - if (let a = Test::A || 5 + 2 == 7) { - auto mission_impossible = 5; - } - return 0; + Tester::new_single_source_expect_err("nested bindings", " + struct Struct{ bool field } + func foo() -> bool { + if (let Struct{ field: let a = Struct{ field: false } } = Struct{ field: true }) { + return test; + } + return false; + } + ").error(|e| { e + .assert_num(2) + .assert_occurs_at(0, "let a = ") + .assert_msg_has(0, "nested binding") + .assert_occurs_at(1, "let Struct") + .assert_msg_has(1, "outer binding"); + }); } - "); + +#[test] +fn test_boolean_ops_on_binding() { + Tester::new_single_source_expect_ok("apply && to binding result", " + union TestUnion{ Two(u16), Four(u32), Eight(u64) } + func foo() -> u32 { + auto lit_2 = TestUnion::Two(2); + auto lit_4 = TestUnion::Four(4); + auto lit_8 = TestUnion::Eight(8); + + // Testing combined forms of bindings + if ( + let TestUnion::Two(test_2) = lit_2 && + let TestUnion::Four(test_4) = lit_4 && + let TestUnion::Eight(test_8) = lit_8 + ) { + auto valid_2 = test_2; + auto valid_4 = test_4; + auto valid_8 = test_8; + } + + // Testing in combination with regular expressions, and to the correct + // literals + if (let TestUnion::Two(inter_a) = lit_2 && 5 + 2 == 7) { inter_a = 0; } + if (5 + 2 == 7 && let TestUnion::Two(inter_b) = lit_2) { inter_b = 0; } + if (2 + 2 == 4 && let TestUnion::Two(inter_c) = lit_2 && 3 + 3 == 8) { inter_c = 0; } + + // Testing with the 'incorrect' target union + if (let TestUnion::Four(nope) = lit_2 && let TestUnion::Two(zilch) = lit_8) { } + + return 0; + } + ").for_function("foo", |f| { f + .for_variable("valid_2", |v| { v.assert_concrete_type("u16"); }) + .for_variable("valid_4", |v| { v.assert_concrete_type("u32"); }) + .for_variable("valid_8", |v| { v.assert_concrete_type("u64"); }) + .for_variable("inter_a", |v| { v.assert_concrete_type("u16"); }) + .for_variable("inter_b", |v| { v.assert_concrete_type("u16"); }) + .for_variable("inter_c", |v| { v.assert_concrete_type("u16"); }); + }); + + Tester::new_single_source_expect_err("apply || before binding", " + enum Test{ A, B } + func foo() -> u32 { + if (let a = Test::A || 5 + 2 == 7) { + auto mission_impossible = 5; + } + return 0; + } + ").error(|e| { e + .assert_num(1) + .assert_occurs_at(0, "let") + .assert_msg_has(0, "expected a 'bool'"); + }); + + Tester::new_single_source_expect_err("apply || after binding", " + enum Test{ A, B } + func foo() -> u32 { + if (5 + 2 == 7 || let b = Test::B) { + auto magic_number = 7; + } + return 0; + } + ").error(|e| { e + .assert_num(1) + .assert_occurs_at(0, "let") + .assert_msg_has(0, "expected a 'bool'"); + }); + + Tester::new_single_source_expect_err("apply || before and after binding", " + enum Test{ A, B } + func foo() -> u32 { + if (1 + 2 == 3 || (let a = Test::A && let b = Test::B) || (2 + 2 == 4)) { + auto darth_vader_says = \"Noooooooooo\"; + } + return 0; + } + ").error(|e| { e + .assert_num(1) + .assert_msg_has(0, "expected a 'bool'"); + }); } \ No newline at end of file diff --git a/src/protocol/tests/utils.rs b/src/protocol/tests/utils.rs index 5aecec7cd791e6f53e4aa3400d5b62cbb3f614d7..a30ad950705bd70ef81abb8446908270ed6d7b8a 100644 --- a/src/protocol/tests/utils.rs +++ b/src/protocol/tests/utils.rs @@ -624,9 +624,6 @@ impl<'a> FunctionTester<'a> { // Keeping this simple for now, will likely change pub(crate) fn call_err(self, expected_result: &str) -> Self { - use crate::protocol::*; - use crate::runtime::*; - let (_, result) = self.eval_until_end(); match result { Ok(_) => { @@ -652,7 +649,6 @@ impl<'a> FunctionTester<'a> { fn eval_until_end(&self) -> (Prompt, Result) { use crate::protocol::*; - use crate::runtime::*; let mut prompt = Prompt::new(&self.ctx.types, &self.ctx.heap, self.def.this.upcast(), 0, ValueGroup::new_stack(Vec::new())); let mut call_context = EvalContext::None;