Changeset - fe0efc81bd4e
[Not reviewed]
src/collections/scoped_buffer.rs
Show inline comments
 
@@ -77,6 +77,7 @@ impl<T: Sized> ScopedSection<T> {
 
    }
 

	
 
    #[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<T: Sized> ScopedSection<T> {
 
    }
 

	
 
    #[inline]
 
    #[allow(unused_mut)] // used in debug mode
 
    pub(crate) fn into_vec(mut self) -> Vec<T> {
 
        let vec = unsafe{&mut *self.inner};
 
        #[cfg(debug_assertions)]  {
src/common.rs
Show inline comments
 
@@ -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,
src/protocol/arena.rs
Show inline comments
 
@@ -57,6 +57,15 @@ impl<T> Arena<T> {
 
        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<T> {
 
        debug_assert!(idx < self.store.len());
 
        return Id::new(idx as i32);
 
    }
 

	
 
    pub fn iter(&self) -> impl Iterator<Item = &T> {
 
        self.store.iter()
 
    }
src/protocol/ast.rs
Show inline comments
 
@@ -1877,6 +1877,7 @@ pub struct VariableExpression {
 
    pub identifier: Identifier,
 
    // Validator/Linker
 
    pub declaration: Option<VariableId>,
 
    pub used_as_binding_target: bool,
 
    pub parent: ExpressionParent,
 
    pub unique_id_in_definition: i32,
 
}
 
\ No newline at end of file
src/protocol/ast_printer.rs
Show inline comments
 
@@ -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)
src/protocol/eval/executor.rs
Show inline comments
 
@@ -31,6 +31,7 @@ pub(crate) struct Frame {
 
    pub(crate) position: StatementId,
 
    pub(crate) expr_stack: VecDeque<ExprInstruction>, // hack for expression evaluation, evaluated by popping from back
 
    pub(crate) expr_values: VecDeque<Value>, // 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
src/protocol/eval/store.rs
Show inline comments
 
@@ -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
src/protocol/eval/value.rs
Show inline comments
 
@@ -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
src/protocol/parser/mod.rs
Show inline comments
 
@@ -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![
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -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()
src/protocol/parser/pass_typing.rs
Show inline comments
 
@@ -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<T>
 
        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<T> then subject is Array<T>
 
        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<bool, ParseError> {
 
        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<bool, ()> {
 
        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<bool, ParseError> {
 
        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)
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -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;
src/protocol/parser/type_table.rs
Show inline comments
 
@@ -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, &param.parser_type)?;
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &param.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, &param.parser_type)?;
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &param.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<ResolveResult, ParseError> {
 
    fn resolve_base_parser_type(
 
        &mut self, modules: &[Module], ctx: &PassCtx, root_id: RootId,
 
        parser_type: &ParserType, is_builtin: bool
 
    ) -> Result<ResolveResult, ParseError> {
 
        // 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 |
src/protocol/tests/eval_binding.rs
Show inline comments
 
new file 100644
 
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>{ T inner }
 
        struct Foo{ u32 field }
 
        func make<T>(T val) -> Nested<T> {
 
            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
src/protocol/tests/eval_calls.rs
Show inline comments
 
@@ -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;
src/protocol/tests/mod.rs
Show inline comments
 
@@ -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
src/protocol/tests/parser_binding.rs
Show inline comments
 
@@ -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
src/protocol/tests/utils.rs
Show inline comments
 
@@ -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<EvalContinuation, EvalError>) {
 
        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;
0 comments (0 inline, 0 general)