diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 7657a9390ebffa00927a9cdc1bc6ae71225e0df2..b92a35cc3ebf01a92e04de55bbc52babd6d429a8 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -752,7 +752,7 @@ pub struct Variable { pub parser_type: ParserType, pub identifier: Identifier, // Validator/linker - pub relative_pos_in_block: u32, + pub relative_pos_in_block: i32, pub unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated } @@ -1170,7 +1170,7 @@ pub struct BlockStatement { pub scope_node: ScopeNode, pub first_unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated pub next_unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated - pub relative_pos_in_parent: u32, + pub relative_pos_in_parent: i32, pub locals: Vec, pub labels: Vec, pub next: StatementId, @@ -1224,6 +1224,7 @@ pub struct MemoryStatement { // Phase 1: parser pub span: InputSpan, pub variable: VariableId, + pub initial_expr: AssignmentExpressionId, // Phase 2: linker pub next: StatementId, } @@ -1241,7 +1242,7 @@ pub struct ChannelStatement { pub from: VariableId, // output pub to: VariableId, // input // Phase 2: linker - pub relative_pos_in_block: u32, + pub relative_pos_in_block: i32, pub next: StatementId, } @@ -1252,7 +1253,7 @@ pub struct LabeledStatement { pub label: Identifier, pub body: StatementId, // Phase 2: linker - pub relative_pos_in_block: u32, + pub relative_pos_in_block: i32, pub in_sync: SynchronousStatementId, // may be invalid } @@ -1357,8 +1358,9 @@ pub struct SelectStatement { #[derive(Debug, Clone)] pub struct SelectCase { - pub guard_var: MemoryStatementId, // invalid ID if there is no declaration of a variable - pub guard_expr: ExpressionStatementId, // if `guard_var.is_some()`, then always assignment expression + // The guard statement of a `select` is either a MemoryStatement or an + // ExpressionStatement. Nothing else is allowed by the initial parsing + pub guard: StatementId, pub block: BlockStatementId, } @@ -1410,6 +1412,7 @@ pub struct ExpressionStatement { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ExpressionParent { None, // only set during initial parsing + Memory(MemoryStatementId), If(IfStatementId), While(WhileStatementId), Return(ReturnStatementId), diff --git a/src/protocol/ast_printer.rs b/src/protocol/ast_printer.rs index 1075d78961a11fdf554c641ea1d37715f0fc50bb..83cbb7af3b8022fc8614ea521b8ca594bb66d4d3 100644 --- a/src/protocol/ast_printer.rs +++ b/src/protocol/ast_printer.rs @@ -433,6 +433,8 @@ impl ASTWriter { self.kv(indent2).with_s_key("Variable"); self.write_variable(heap, stmt.variable, indent3); + self.kv(indent2).with_s_key("InitialValue"); + self.write_expr(heap, stmt.initial_expr.upcast(), indent3); self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index); } } @@ -541,15 +543,8 @@ impl ASTWriter { let indent3 = indent2 + 1; let indent4 = indent3 + 1; for case in &stmt.cases { - if !case.guard_var.is_invalid() { - self.kv(indent3).with_s_key("GuardStatement"); - self.write_stmt(heap, case.guard_var.upcast().upcast(), indent4); - } else { - self.kv(indent3).with_s_key("GuardStatement").with_s_val("None"); - } - - self.kv(indent3).with_s_key("GuardExpression"); - self.write_stmt(heap, case.guard_expr.upcast(), indent4); + self.kv(indent3).with_s_key("Guard"); + self.write_stmt(heap, case.guard, indent4); self.kv(indent3).with_s_key("Block"); self.write_stmt(heap, case.block.upcast(), indent4); @@ -1021,6 +1016,7 @@ fn write_expression_parent(target: &mut String, parent: &ExpressionParent) { *target = match parent { EP::None => String::from("None"), + EP::Memory(id) => format!("MemStmt({})", id.0.0.index), EP::If(id) => format!("IfStmt({})", id.0.index), EP::While(id) => format!("WhileStmt({})", id.0.index), EP::Return(id) => format!("ReturnStmt({})", id.0.index), diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index 75c41c08a68920931792d388ed51c4c5cdb16cb6..fe43edfd8207faf5e777a0eb40d607093c617812 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -825,8 +825,13 @@ impl Prompt { Statement::Local(stmt) => { match stmt { LocalStatement::Memory(stmt) => { - let variable = &heap[stmt.variable]; - self.store.write(ValueId::Stack(variable.unique_id_in_scope as u32), Value::Unassigned); + if cfg!(debug_assertions) { + let variable = &heap[stmt.variable]; + debug_assert!(match self.store.read_ref(ValueId::Stack(variable.unique_id_in_scope as u32)) { + Value::Unassigned => false, + _ => true, + }); + } cur_frame.position = stmt.next; Ok(EvalContinuation::Stepping) @@ -1049,6 +1054,16 @@ impl Prompt { let stmt = &heap[cur_frame.position]; match stmt { + Statement::Local(stmt) => { + if let LocalStatement::Memory(stmt) = stmt { + // Setup as unassigned, when we execute the memory + // statement (after evaluating expression), it should no + // longer be `Unassigned`. + let variable = &heap[stmt.variable]; + self.store.write(ValueId::Stack(variable.unique_id_in_scope as u32), Value::Unassigned); + cur_frame.prepare_single_expression(heap, stmt.initial_expr.upcast()); + } + }, Statement::If(stmt) => cur_frame.prepare_single_expression(heap, stmt.test), Statement::While(stmt) => cur_frame.prepare_single_expression(heap, stmt.test), Statement::Return(stmt) => { diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index 885c5aa4a83626d35fed98fa687fed9be50e92ff..94de9499c7f17bf17a73332551296924f9d7953d 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -487,10 +487,9 @@ impl PassDefinitions { // Two fallback possibilities: the first one is a memory // declaration, the other one is to parse it as a normal // expression. This is a bit ugly. - if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { + if let Some(memory_stmt_id) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { consume_token(&module.source, iter, TokenKind::SemiColon)?; section.push(memory_stmt_id.upcast().upcast()); - section.push(assignment_stmt_id.upcast()); } else { let id = self.consume_expression_statement(module, iter, ctx)?; section.push(id.upcast()); @@ -498,10 +497,9 @@ impl PassDefinitions { } } else if next == TokenKind::OpenParen { // Same as above: memory statement or normal expression - if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { + if let Some(memory_stmt_id) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { consume_token(&module.source, iter, TokenKind::SemiColon); section.push(memory_stmt_id.upcast().upcast()); - section.push(assignment_stmt_id.upcast()); } else { let id = self.consume_expression_statement(module, iter, ctx)?; section.push(id.upcast()); @@ -696,26 +694,26 @@ impl PassDefinitions { let mut next = iter.next(); while Some(TokenKind::CloseCurly) != next { - let (guard_var, guard_expr) = match self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { - Some(guard_var_and_expr) => guard_var_and_expr, + let guard = match self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { + Some(guard_mem_stmt) => guard_mem_stmt.upcast().upcast(), None => { let start_pos = iter.last_valid_pos(); let expr = self.consume_expression(module, iter, ctx)?; let end_pos = iter.last_valid_pos(); - let guard_expr = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{ + let guard_expr_stmt = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{ this, span: InputSpan::from_positions(start_pos, end_pos), expression: expr, next: StatementId::new_invalid(), }); - (MemoryStatementId::new_invalid(), guard_expr) + guard_expr_stmt.upcast() }, }; consume_token(&module.source, iter, TokenKind::ArrowRight)?; let block = self.consume_block_or_wrapped_statement(module, iter, ctx)?; - cases.push(SelectCase{ guard_var, guard_expr, block }); + cases.push(SelectCase{ guard, block }); next = iter.next(); } @@ -925,9 +923,14 @@ impl PassDefinitions { Ok(()) } + /// Attempts to consume a memory statement (a statement along the lines of + /// `type var_name = initial_expr`). Will return `Ok(None)` if it didn't + /// seem like there was a memory statement, `Ok(Some(...))` if there was + /// one, and `Err(...)` if its reasonable to assume that there was a memory + /// statement, but we failed to parse it. fn maybe_consume_memory_statement_without_semicolon( &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx - ) -> Result, ParseError> { + ) -> Result, ParseError> { // This is a bit ugly. It would be nicer if we could somehow // consume the expression with a type hint if we do get a valid // type, but we don't get an identifier following it @@ -947,11 +950,10 @@ impl PassDefinitions { let memory_span = InputSpan::from_positions(parser_type.full_span.begin, identifier.span.end); let assign_span = consume_token(&module.source, iter, TokenKind::Equal)?; - let initial_expr_begin_pos = iter.last_valid_pos(); let initial_expr_id = self.consume_expression(module, iter, ctx)?; let initial_expr_end_pos = iter.last_valid_pos(); - // Allocate the memory statement with the variable + // Create the AST variable let local_id = ctx.heap.alloc_variable(|this| Variable{ this, kind: VariableKind::Local, @@ -960,18 +962,13 @@ impl PassDefinitions { relative_pos_in_block: 0, unique_id_in_scope: -1, }); - let memory_stmt_id = ctx.heap.alloc_memory_statement(|this| MemoryStatement{ - this, - span: memory_span, - variable: local_id, - next: StatementId::new_invalid() - }); - // Allocate the initial assignment + // Create the initial assignment expression + // Note: we set the initial variable declaration here let variable_expr_id = ctx.heap.alloc_variable_expression(|this| VariableExpression{ this, identifier, - declaration: None, + declaration: Some(local_id), used_as_binding_target: false, parent: ExpressionParent::None, unique_id_in_definition: -1, @@ -986,14 +983,17 @@ impl PassDefinitions { parent: ExpressionParent::None, unique_id_in_definition: -1, }); - let assignment_stmt_id = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{ + + // Put both together in the memory statement + let memory_stmt_id = ctx.heap.alloc_memory_statement(|this| MemoryStatement{ this, - span: InputSpan::from_positions(initial_expr_begin_pos, initial_expr_end_pos), - expression: assignment_expr_id.upcast(), - next: StatementId::new_invalid(), + span: memory_span, + variable: local_id, + initial_expr: assignment_expr_id, + next: StatementId::new_invalid() }); - return Ok(Some((memory_stmt_id, assignment_stmt_id))) + return Ok(Some(memory_stmt_id)); } } diff --git a/src/protocol/parser/pass_typing.rs b/src/protocol/parser/pass_typing.rs index 4eb0f85958973c5846df37d892465fb89e1b5321..6bc1fc4c18fd33b15b8772cf7e10ab17fff706ad 100644 --- a/src/protocol/parser/pass_typing.rs +++ b/src/protocol/parser/pass_typing.rs @@ -1094,11 +1094,16 @@ impl Visitor for PassTyping { fn visit_local_memory_stmt(&mut self, ctx: &mut Ctx, id: MemoryStatementId) -> VisitorResult { let memory_stmt = &ctx.heap[id]; + let initial_expr_id = memory_stmt.initial_expr; + // Setup memory statement inference let local = &ctx.heap[memory_stmt.variable]; let var_type = self.determine_inference_type_from_parser_type_elements(&local.parser_type.elements, true); self.var_types.insert(memory_stmt.variable, VarData::new_local(var_type)); + // Process the initial value + self.visit_assignment_expr(ctx, initial_expr_id)?; + Ok(()) } @@ -3276,7 +3281,7 @@ impl PassTyping { EP::None => // Should have been set by linker unreachable!(), - EP::ExpressionStmt(_) => + EP::Memory(_) | EP::ExpressionStmt(_) => // Determined during type inference InferenceType::new(false, false, vec![ITP::Unknown]), EP::Expression(parent_id, idx_in_parent) => { diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index 0be50a5a640db03eed0ff2175564a511314ab7fc..c299a3b8e364e5e64cafb9cf106ee3f4ce6ecf38 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -99,7 +99,7 @@ pub(crate) struct PassValidationLinking { // used for the error's position must_be_assignable: Option, // Keeping track of relative positions and unique IDs. - relative_pos_in_block: u32, // of statements: to determine when variables are visible + relative_pos_in_block: i32, // of statements: to determine when variables are visible next_expr_index: i32, // to arrive at a unique ID for all expressions within a definition // Various temporary buffers for traversal. Essentially working around // Rust's borrowing rules since it cannot understand we're modifying AST @@ -255,7 +255,15 @@ impl Visitor for PassValidationLinking { } fn visit_local_memory_stmt(&mut self, ctx: &mut Ctx, id: MemoryStatementId) -> VisitorResult { + let stmt = &ctx.heap[id]; + let expr_id = stmt.initial_expr; + assign_and_replace_next_stmt!(self, ctx, id.upcast().upcast()); + debug_assert_eq!(self.expr_parent, ExpressionParent::None); + self.expr_parent = ExpressionParent::Memory(id); + self.visit_assignment_expr(ctx, expr_id)?; + self.expr_parent = ExpressionParent::None; + Ok(()) } @@ -456,12 +464,12 @@ impl Visitor for PassValidationLinking { } // Visit the various arms in the select block - // note: three statements per case, so we lookup as `3 * index + offset` let mut case_stmt_ids = self.statement_buffer.start_section(); let num_cases = select_stmt.cases.len(); for case in &select_stmt.cases { - case_stmt_ids.push(case.guard_var.upcast().upcast()); - case_stmt_ids.push(case.guard_expr.upcast()); + // Note: we add both to the buffer, retrieve them later in indexed + // fashion + case_stmt_ids.push(case.guard); case_stmt_ids.push(case.block.upcast()); } @@ -470,16 +478,11 @@ impl Visitor for PassValidationLinking { // Link up the "Select" with the "EndSelect". If there are no cases then // runtime will pick the "EndSelect" immediately. for idx in 0..num_cases { - let base_idx = 3 * idx; - let guard_var_id = case_stmt_ids[base_idx ]; - let guard_expr_id = case_stmt_ids[base_idx + 1]; - let arm_block_id = case_stmt_ids[base_idx + 2]; + let base_idx = 2 * idx; + let guard_id = case_stmt_ids[base_idx ]; + let arm_block_id = case_stmt_ids[base_idx + 1]; - if !guard_var_id.is_invalid() { - self.visit_stmt(ctx, guard_var_id)?; - } - - self.visit_stmt(ctx, guard_expr_id)?; + self.visit_stmt(ctx, guard_id)?; self.visit_stmt(ctx, arm_block_id)?; assign_then_erase_next_stmt!(self, ctx, end_select_id.upcast()); @@ -583,6 +586,7 @@ impl Visitor for PassValidationLinking { // code (mainly typechecking), we disallow nested use in expressions match self.expr_parent { // Look at us: lying through our teeth while providing error messages. + ExpressionParent::Memory(_) => {}, ExpressionParent::ExpressionStmt(_) => {}, _ => { let assignment_span = assignment_expr.full_span; @@ -1244,96 +1248,103 @@ impl Visitor for PassValidationLinking { fn visit_variable_expr(&mut self, ctx: &mut Ctx, id: VariableExpressionId) -> VisitorResult { let var_expr = &ctx.heap[id]; - 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, 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 { - return Err(ParseError::new_error_str_at_span( - &ctx.module().source, var_expr.identifier.span, "unresolved variable" - )); - } + // Check if declaration was already resolved (this occurs for the + // variable expr that is on the LHS of the assignment expr that is + // associated with a variable declaration) + let mut variable_id = var_expr.declaration; + let mut is_binding_target = false; - // This is a binding variable, but it may only appear in very - // specific locations. - let is_valid_binding = match self.expr_parent { - ExpressionParent::Expression(expr_id, idx) => { - match &ctx.heap[expr_id] { - Expression::Binding(_binding_expr) => { - // Nested binding is disallowed, and because of - // the check above we know we're directly at the - // LHS of the binding expression - debug_assert_eq!(_binding_expr.this, self.in_binding_expr); - debug_assert_eq!(idx, 0); - true - } - Expression::Literal(lit_expr) => { - // Only struct, unions, tuples and arrays can - // have subexpressions, so we're always fine - if cfg!(debug_assertions) { - match lit_expr.value { - Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) | Literal::Tuple(_) => {}, - _ => unreachable!(), - } - } + // Otherwise try to find it + if variable_id.is_none() { + variable_id = self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier); + } - true - }, - _ => false, + // Otherwise try to see if is a variable introduced by a binding expr + let variable_id = if let Some(variable_id) = variable_id { + variable_id + } else { + if self.in_binding_expr.is_invalid() || !self.in_binding_expr_lhs { + return Err(ParseError::new_error_str_at_span( + &ctx.module().source, var_expr.identifier.span, "unresolved variable" + )); + } + + // This is a binding variable, but it may only appear in very + // specific locations. + let is_valid_binding = match self.expr_parent { + ExpressionParent::Expression(expr_id, idx) => { + match &ctx.heap[expr_id] { + Expression::Binding(_binding_expr) => { + // Nested binding is disallowed, and because of + // the check above we know we're directly at the + // LHS of the binding expression + debug_assert_eq!(_binding_expr.this, self.in_binding_expr); + debug_assert_eq!(idx, 0); + true } - }, - _ => { - false - } - }; + Expression::Literal(lit_expr) => { + // Only struct, unions, tuples and arrays can + // have subexpressions, so we're always fine + if cfg!(debug_assertions) { + match lit_expr.value { + Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) | Literal::Tuple(_) => {}, + _ => unreachable!(), + } + } - if !is_valid_binding { - let binding_expr = &ctx.heap[self.in_binding_expr]; - return Err(ParseError::new_error_str_at_span( - &ctx.module().source, var_expr.identifier.span, - "illegal location for binding variable: binding variables may only be nested under a binding expression, or a struct, union or array literal" - ).with_info_at_span( - &ctx.module().source, binding_expr.operator_span, format!( - "'{}' was interpreted as a binding variable because the variable is not declared and it is nested under this binding expression", - var_expr.identifier.value.as_str() - ) - )); + true + }, + _ => false, + } + }, + _ => { + false } + }; - // By now we know that this is a valid binding expression. Given - // that a binding expression must be nested under an if/while - // statement, we now add the variable to the (implicit) block - // statement following the if/while statement. - let bound_identifier = var_expr.identifier.clone(); - let bound_variable_id = ctx.heap.alloc_variable(|this| Variable{ - this, - kind: VariableKind::Binding, - parser_type: ParserType{ - elements: vec![ParserTypeElement{ - element_span: bound_identifier.span, - variant: ParserTypeVariant::Inferred - }], - full_span: bound_identifier.span - }, - identifier: bound_identifier, - relative_pos_in_block: 0, - unique_id_in_scope: -1, - }); + if !is_valid_binding { + let binding_expr = &ctx.heap[self.in_binding_expr]; + return Err(ParseError::new_error_str_at_span( + &ctx.module().source, var_expr.identifier.span, + "illegal location for binding variable: binding variables may only be nested under a binding expression, or a struct, union or array literal" + ).with_info_at_span( + &ctx.module().source, binding_expr.operator_span, format!( + "'{}' was interpreted as a binding variable because the variable is not declared and it is nested under this binding expression", + var_expr.identifier.value.as_str() + ) + )); + } - let body_stmt_id = match &ctx.heap[self.in_test_expr] { - Statement::If(stmt) => stmt.true_body, - Statement::While(stmt) => stmt.body, - _ => unreachable!(), - }; - let body_scope = Scope::Regular(body_stmt_id); - self.checked_at_single_scope_add_local(ctx, body_scope, 0, bound_variable_id)?; + // By now we know that this is a valid binding expression. Given + // that a binding expression must be nested under an if/while + // statement, we now add the variable to the (implicit) block + // statement following the if/while statement. + let bound_identifier = var_expr.identifier.clone(); + let bound_variable_id = ctx.heap.alloc_variable(|this| Variable { + this, + kind: VariableKind::Binding, + parser_type: ParserType { + elements: vec![ParserTypeElement { + element_span: bound_identifier.span, + variant: ParserTypeVariant::Inferred + }], + full_span: bound_identifier.span + }, + identifier: bound_identifier, + relative_pos_in_block: 0, + unique_id_in_scope: -1, + }); + + let body_stmt_id = match &ctx.heap[self.in_test_expr] { + Statement::If(stmt) => stmt.true_body, + Statement::While(stmt) => stmt.body, + _ => unreachable!(), + }; + let body_scope = Scope::Regular(body_stmt_id); + self.checked_at_single_scope_add_local(ctx, body_scope, -1, bound_variable_id)?; // add at -1 such that first statement can access - (bound_variable_id, true) - } + is_binding_target = true; + bound_variable_id }; let var_expr = &mut ctx.heap[id]; @@ -1396,14 +1407,14 @@ impl PassValidationLinking { // statements such that we can find the `goto`-targets immediately when // performing the depth pass for stmt_idx in 0..statement_section.len() { - self.relative_pos_in_block = stmt_idx as u32; + self.relative_pos_in_block = stmt_idx as i32; self.visit_statement_for_locals_labels_and_in_sync(ctx, self.relative_pos_in_block, statement_section[stmt_idx])?; } // Perform the depth-first traversal assign_and_replace_next_stmt!(self, ctx, id.upcast()); for stmt_idx in 0..statement_section.len() { - self.relative_pos_in_block = stmt_idx as u32; + self.relative_pos_in_block = stmt_idx as i32; self.visit_stmt(ctx, statement_section[stmt_idx])?; } assign_and_replace_next_stmt!(self, ctx, end_block_id.upcast()); @@ -1415,7 +1426,7 @@ impl PassValidationLinking { Ok(()) } - fn visit_statement_for_locals_labels_and_in_sync(&mut self, ctx: &mut Ctx, relative_pos: u32, id: StatementId) -> VisitorResult { + fn visit_statement_for_locals_labels_and_in_sync(&mut self, ctx: &mut Ctx, relative_pos: i32, id: StatementId) -> VisitorResult { let statement = &mut ctx.heap[id]; match statement { Statement::Local(stmt) => { @@ -1492,16 +1503,16 @@ impl PassValidationLinking { let relative_var_pos = if var_idx < var_section.len() { ctx.heap[var_section[var_idx]].relative_pos_in_block } else { - u32::MAX + i32::MAX }; let relative_scope_pos = if scope_idx < scope_section.len() { ctx.heap[scope_section[scope_idx]].as_block().relative_pos_in_parent } else { - u32::MAX + i32::MAX }; - debug_assert!(!(relative_var_pos == u32::MAX && relative_scope_pos == u32::MAX)); + debug_assert!(!(relative_var_pos == i32::MAX && relative_scope_pos == i32::MAX)); // In certain cases the relative variable position is the same as // the scope position (insertion of binding variables). In that case @@ -1533,10 +1544,11 @@ impl PassValidationLinking { /// Adds a local variable to the current scope. It will also annotate the /// `Local` in the AST with its relative position in the block. - fn checked_add_local(&mut self, ctx: &mut Ctx, relative_pos: u32, id: VariableId) -> Result<(), ParseError> { + fn checked_add_local(&mut self, ctx: &mut Ctx, relative_pos: i32, id: VariableId) -> Result<(), ParseError> { debug_assert!(self.cur_scope.is_block()); let local = &ctx.heap[id]; let mut scope = &self.cur_scope; + println!("DEBUG: Adding local '{}' at relative_pos {} in scope {:?}", local.identifier.value.as_str(), relative_pos, self.cur_scope); loop { // We immediately go to the parent scope. We check the current scope @@ -1594,7 +1606,7 @@ impl PassValidationLinking { /// scope for variable conflicts and the symbol table for global conflicts. /// Will NOT check parent scopes of the specified scope. fn checked_at_single_scope_add_local( - &mut self, ctx: &mut Ctx, scope: Scope, relative_pos: u32, id: VariableId + &mut self, ctx: &mut Ctx, scope: Scope, relative_pos: i32, id: VariableId ) -> Result<(), ParseError> { // Check the symbol table for conflicts { @@ -1643,7 +1655,8 @@ impl PassValidationLinking { /// Finds a variable in the visitor's scope that must appear before the /// specified relative position within that block. - fn find_variable(&self, ctx: &Ctx, mut relative_pos: u32, identifier: &Identifier) -> Result { + fn find_variable(&self, ctx: &Ctx, mut relative_pos: i32, identifier: &Identifier) -> Option { + println!("DEBUG: Calling find_variable for '{}' at relative_pos {}", identifier.value.as_str(), relative_pos); debug_assert!(self.cur_scope.is_block()); // No need to use iterator over namespaces if here @@ -1652,12 +1665,14 @@ impl PassValidationLinking { loop { debug_assert!(scope.is_block()); let block = &ctx.heap[scope.to_block()]; + println!("DEBUG: > Looking in block {:?} at relative_pos {}", scope.to_block().0, relative_pos); for local_id in &block.locals { let local = &ctx.heap[*local_id]; - if local.relative_pos_in_block <= relative_pos && identifier == &local.identifier { - return Ok(*local_id); + if local.relative_pos_in_block < relative_pos && identifier == &local.identifier { + println!("DEBUG: > Matched at local with relative_pos {}", local.relative_pos_in_block); + return Some(*local_id); } } @@ -1670,7 +1685,7 @@ impl PassValidationLinking { for parameter_id in definition.parameters() { let parameter = &ctx.heap[*parameter_id]; if identifier == ¶meter.identifier { - return Ok(*parameter_id); + return Some(*parameter_id); } } }, @@ -1678,7 +1693,7 @@ impl PassValidationLinking { } // Variable could not be found - return Err(()) + return None } else { relative_pos = block.relative_pos_in_parent; } @@ -1687,7 +1702,7 @@ impl PassValidationLinking { /// Adds a particular label to the current scope. Will return an error if /// there is another label with the same name visible in the current scope. - fn checked_add_label(&mut self, ctx: &mut Ctx, relative_pos: u32, in_sync: SynchronousStatementId, id: LabeledStatementId) -> Result<(), ParseError> { + fn checked_add_label(&mut self, ctx: &mut Ctx, relative_pos: i32, in_sync: SynchronousStatementId, id: LabeledStatementId) -> Result<(), ParseError> { debug_assert!(self.cur_scope.is_block()); // Make sure label is not defined within the current scope or any of the diff --git a/src/protocol/tests/parser_validation.rs b/src/protocol/tests/parser_validation.rs index 7d516ce640715f9c74acc159ac56a7ed8307bef9..e80f6a90615d914fc5882c72c00d9534292360c8 100644 --- a/src/protocol/tests/parser_validation.rs +++ b/src/protocol/tests/parser_validation.rs @@ -565,6 +565,29 @@ fn test_incorrect_modifying_operators() { ).error(|e| { e.assert_msg_has(0, "assignments are statements"); }); } +#[test] +fn test_variable_introduction_in_scope() { + Tester::new_single_source_expect_err( + "variable use before declaration", + "func f() -> u8 { return thing; auto thing = 5; }" + ).error(|e| { e.assert_msg_has(0, "unresolved variable"); }); + + Tester::new_single_source_expect_err( + "variable use in declaration", + "func f() -> u8 { auto thing = 5 + thing; return thing; }" + ).error(|e| { e.assert_msg_has(0, "unresolved variable"); }); + + Tester::new_single_source_expect_ok( + "variable use after declaration", + "func f() -> u8 { auto thing = 5; return thing; }" + ); + + Tester::new_single_source_expect_err( + "variable use of closed scope", + "func f() -> u8 { { auto thing = 5; } return thing; }" + ).error(|e| { e.assert_msg_has(0, "unresolved variable"); }); +} + #[test] fn test_correct_select_statement() { Tester::new_single_source_expect_ok( @@ -575,7 +598,6 @@ fn test_correct_select_statement() { sync select { outer_value = get(input) -> outer_value = 0; auto new_value = get(input) -> { - f(); outer_value = new_value; } get(input) + get(input) -> diff --git a/src/protocol/tests/utils.rs b/src/protocol/tests/utils.rs index fa7153214d697f2f57231e79511d844c581a5442..63ff0f95929262c11c44f701f0f31015589658dd 100644 --- a/src/protocol/tests/utils.rs +++ b/src/protocol/tests/utils.rs @@ -535,6 +535,7 @@ impl<'a> FunctionTester<'a> { let mut found_local_id = None; if let Some(block_id) = wrapping_block_id { + // Found the right block, find the variable inside the block again let block_stmt = self.ctx.heap[block_id].as_block(); for local_id in &block_stmt.locals { let var = &self.ctx.heap[*local_id]; @@ -1194,6 +1195,12 @@ fn seek_expr_in_stmt bool>(heap: &Heap, start: StatementId let stmt = &heap[start]; match stmt { + Statement::Local(stmt) => { + match stmt { + LocalStatement::Memory(stmt) => seek_expr_in_expr(heap, stmt.initial_expr.upcast(), f), + LocalStatement::Channel(_) => None + } + } Statement::Block(stmt) => { for stmt_id in &stmt.statements { if let Some(id) = seek_expr_in_stmt(heap, *stmt_id, f) {