From c53dae21bce9d1a7f8f8febe05f6bc7ff8c1830d 2021-01-20 21:43:54 From: mh Date: 2021-01-20 21:43:54 Subject: [PATCH] first reimplementation of visitors --- diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index d21be4e16a41167642e8cf8485ddf5b590e01f51..2cf4fc0e434bc25cf1a4035461e4854d9bc90739 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -1533,10 +1533,10 @@ pub enum ScopeVariant { Synchronous((SynchronousStatementId, BlockStatementId)), } +// TODO: Cleanup #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Scope { - pub variant: ScopeVariant, - pub parent: Option, + pub variant: ScopeVariant } impl Scope { @@ -2235,7 +2235,7 @@ impl SyntaxElement for BlockStatement { impl VariableScope for BlockStatement { fn parent_scope(&self, _h: &Heap) -> Option { - self.parent_scope + self.parent_scope.clone() } fn get_variable(&self, h: &Heap, id: &Identifier) -> Option { for local_id in self.locals.iter() { @@ -2461,9 +2461,10 @@ pub struct SynchronousStatement { pub this: SynchronousStatementId, // Phase 1: parser pub position: InputPosition, - pub parameters: Vec, + // pub parameters: Vec, pub body: StatementId, // Phase 2: linker + pub end_sync: Option, pub parent_scope: Option, } @@ -2475,7 +2476,7 @@ impl SyntaxElement for SynchronousStatement { impl VariableScope for SynchronousStatement { fn parent_scope(&self, _h: &Heap) -> Option { - self.parent_scope + self.parent_scope.clone() } fn get_variable(&self, h: &Heap, id: &Identifier) -> Option { for parameter_id in self.parameters.iter() { @@ -2493,6 +2494,7 @@ pub struct EndSynchronousStatement { pub this: EndSynchronousStatementId, // Phase 2: linker pub position: InputPosition, // of corresponding sync statement + pub start_sync: SynchronousStatementId, pub next: Option, } @@ -2891,8 +2893,6 @@ pub struct CallExpression { pub position: InputPosition, pub method: Method, pub arguments: Vec, - // Phase 2: linker - pub declaration: Option, } impl SyntaxElement for CallExpression { @@ -2920,7 +2920,7 @@ pub struct VariableExpression { pub this: VariableExpressionId, // Phase 1: parser pub position: InputPosition, - pub identifier: Identifier, + pub identifier: NamespacedIdentifier, // Phase 2: linker pub declaration: Option, } diff --git a/src/protocol/lexer.rs b/src/protocol/lexer.rs index 4ae30955efb5e6734fd6cd28403ed5d316bc9bb2..54a81de9c944626a8b34cc4dacff50302b24f458 100644 --- a/src/protocol/lexer.rs +++ b/src/protocol/lexer.rs @@ -1182,7 +1182,7 @@ impl Lexer<'_> { h: &mut Heap, ) -> Result { let position = self.source.pos(); - let identifier = self.consume_identifier()?; + let identifier = self.consume_namespaced_identifier()?; Ok(h.alloc_variable_expression(|this| VariableExpression { this, position, @@ -1470,18 +1470,18 @@ impl Lexer<'_> { let position = self.source.pos(); self.consume_keyword(b"synchronous")?; self.consume_whitespace(false)?; - let mut parameters = Vec::new(); - if self.has_string(b"(") { - self.consume_parameters(h, &mut parameters)?; - self.consume_whitespace(false)?; - } else if !self.has_keyword(b"skip") && !self.has_string(b"{") { - return Err(self.error_at_pos("Expected block statement")); - } + // TODO: What was the purpose of this? Seems superfluous and confusing? + // let mut parameters = Vec::new(); + // if self.has_string(b"(") { + // self.consume_parameters(h, &mut parameters)?; + // self.consume_whitespace(false)?; + // } else if !self.has_keyword(b"skip") && !self.has_string(b"{") { + // return Err(self.error_at_pos("Expected block statement")); + // } let body = self.consume_statement(h)?; Ok(h.alloc_synchronous_statement(|this| SynchronousStatement { this, position, - parameters, body, parent_scope: None, })) diff --git a/src/protocol/parser/visitor.rs b/src/protocol/parser/visitor.rs index a21bfa7d27bf16fc9d7eef1429f12d138e21721c..f9b11e208d13e90a7f03f02d73335b3b6b35d52f 100644 --- a/src/protocol/parser/visitor.rs +++ b/src/protocol/parser/visitor.rs @@ -13,6 +13,10 @@ pub(crate) struct Ctx<'p> { types: &'p mut TypeTable, } +/// Visitor is a generic trait that will fully walk the AST. The default +/// implementation of the visitors is to not recurse. The exception is the +/// top-level `visit_definition`, `visit_stmt` and `visit_expr` methods, which +/// call the appropriate visitor function. pub(crate) trait Visitor2 { // Entry point fn visit_module(&mut self, ctx: &mut Ctx) -> VisitorResult { @@ -135,9 +139,37 @@ enum DefinitionType { Function } -struct NoNameYet { +/// This particular visitor will go through the entire AST in a recursive manner +/// and check if all statements and expressions are legal (e.g. no "return" +/// statements in component definitions), and will link certain AST nodes to +/// their appropriate targets (e.g. goto statements, or function calls). +/// +/// This visitor will not perform control-flow analysis (e.g. making sure that +/// each function actually returns) and will also not perform type checking. So +/// the linking of function calls and component instantiations will be checked +/// and linked to the appropriate definitions, but the return types and/or +/// arguments will not be checked for validity. +/// +/// The visitor visits each statement in a block in a breadth-first manner +/// first. We are thereby sure that we have found all variables/labels in a +/// particular block. In this phase nodes may queue statements for insertion +/// (e.g. the insertion of an `EndIf` statement for a particular `If` +/// statement). These will be inserted after visiting every node, after which +/// the visitor recurses into each statement in a block. +/// +/// Because of this scheme expressions will not be visited in the breadth-first +/// pass. +struct ValidityAndLinkerVisitor { + /// `in_sync` is `Some(id)` if the visitor is visiting the children of a + /// synchronous statement. A single value is sufficient as nested + /// synchronous statements are not allowed in_sync: Option, + /// `in_while` contains the last encountered `While` statement. This is used + /// to resolve unlabeled `Continue`/`Break` statements. in_while: Option, + // Traversal state: current scope (which can be used to find the parent + // scope), the definition variant we are considering, and whether the + // visitor is performing breadthwise block statement traversal. cur_scope: Option, def_type: DefinitionType, performing_breadth_pass: bool, @@ -153,7 +185,7 @@ struct NoNameYet { insert_buffer: Vec<(u32, StatementId)>, } -impl NoNameYet { +impl ValidityAndLinkerVisitor { fn new() -> Self { Self{ in_sync: None, @@ -179,7 +211,7 @@ impl NoNameYet { } } -impl Visitor2 for NoNameYet { +impl Visitor2 for ValidityAndLinkerVisitor { //-------------------------------------------------------------------------- // Definition visitors //-------------------------------------------------------------------------- @@ -203,7 +235,6 @@ impl Visitor2 for NoNameYet { self.cur_scope = Some(Scope { variant: ScopeVariant::Definition(id.upcast()), - parent: None, }); self.performing_breadth_pass = true; @@ -228,7 +259,6 @@ impl Visitor2 for NoNameYet { self.cur_scope = Some(Scope { variant: ScopeVariant::Definition(id.upcast()), - parent: None, }); self.performing_breadth_pass = true; @@ -242,53 +272,7 @@ impl Visitor2 for NoNameYet { //-------------------------------------------------------------------------- fn visit_block_stmt(&mut self, ctx: &mut Ctx, id: BlockStatementId) -> VisitorResult { - if self.performing_breadth_pass { - // Our parent is performing a breadth-pass. We do this simple stuff - // here - let body = &mut ctx.heap[id]; - body.parent_scope = self.cur_scope.clone(); - body.relative_pos_in_parent = self.relative_pos_in_block; - return Ok(()) - } - - // We may descend into children of this block. However, this is - // where we first perform a breadth-first pass - self.performing_breadth_pass = true; - self.cur_scope = Some(Scope::Block(id)); - let first_statement_index = self.statement_buffer.len(); - - { - let body = &ctx.heap[id]; - self.statement_buffer.extend_from_slice(&body.statements); - } - - let mut stmt_index = first_statement_index; - while stmt_index < self.statement_buffer.len() { - self.relative_pos_in_block = (stmt_index - first_statement_index) as u32; - self.visit_stmt(ctx, self.statement_buffer[stmt_index])?; - stmt_index += 1; - } - - if !self.insert_buffer.is_empty() { - let body = &mut ctx.heap[id]; - for (pos, stmt) in self.insert_buffer.drain(..) { - body.statements.insert(pos as usize, stmt); - } - } - - // And the depth pass - self.performing_breadth_pass = false; - stmt_index = first_statement_index; - while stmt_index < self.statement_buffer.len() { - self.visit_stmt(ctx, self.statement_buffer[stmt_index])?; - stmt_index += 1; - } - - // Pop statement buffer - debug_assert!(self.insert_buffer.is_empty(), "insert buffer not empty after depth pass"); - self.statement_buffer.truncate(first_statement_index); - - Ok(()) + self.visit_block_stmt_with_hint(ctx, id, None) } fn visit_local_memory_stmt(&mut self, ctx: &mut Ctx, id: MemoryStatementId) -> VisitorResult { @@ -296,10 +280,10 @@ impl Visitor2 for NoNameYet { let stmt = &ctx.heap[id]; stmt.relative_pos_in_block = self.relative_pos_in_block; self.checked_local_add(ctx, stmt.variable)?; + } else { + self.visit_expr(ctx, ctx.heap[id].initial)?; } - self.visit_expr(ctx, ctx.heap[id].initial)?; - Ok(()) } @@ -347,17 +331,17 @@ impl Visitor2 for NoNameYet { let stmt = &mut ctx.heap[id]; stmt.end_if = Some(end_if_id); self.insert_buffer.push((self.relative_pos_in_block + 1, end_if_id.upcast())); + } else { + // Traverse expression and bodies + let (test_id, true_id, false_id) = { + let stmt = &ctx.heap[id]; + (stmt.test, stmt.true_body, stmt.false_body) + }; + self.visit_expr(ctx, test_id)?; + self.visit_stmt(ctx, true_id)?; + self.visit_stmt(ctx, false_id)?; } - // Traverse expression and bodies - let (test_id, true_id, false_id) = { - let stmt = &ctx.heap[id]; - (stmt.test, stmt.true_body, stmt.false_body) - }; - self.visit_expr(ctx, test_id)?; - self.visit_stmt(ctx, true_id)?; - self.visit_stmt(ctx, false_id)?; - Ok(()) } @@ -383,17 +367,17 @@ impl Visitor2 for NoNameYet { stmt.in_sync = self.in_sync.clone(); self.insert_buffer.push((self.relative_pos_in_block + 1, end_while_id.upcast())); + } else { + let (test_id, body_id) = { + let stmt = &ctx.heap[id]; + (stmt.test, stmt.body) + }; + let old_while = self.in_while.replace(id); + self.visit_expr(ctx, test_id)?; + self.visit_stmt(ctx, body_id)?; + self.in_while = old_while; } - let (test_id, body_id) = { - let stmt = &ctx.heap[id]; - (stmt.test, stmt.body) - }; - let old_while = self.in_while.replace(id); - self.visit_expr(ctx, test_id)?; - self.visit_stmt(ctx, body_id)?; - self.in_while = old_while; - Ok(()) } @@ -431,12 +415,435 @@ impl Visitor2 for NoNameYet { } fn visit_synchronous_stmt(&mut self, ctx: &mut Ctx, id: SynchronousStatementId) -> VisitorResult { + if self.performing_breadth_pass { + // Check for validity of synchronous statement + let cur_sync = &ctx.heap[id]; + if self.in_sync.is_some() { + // Nested synchronous statement + let old_sync = &ctx.heap[self.in_sync.unwrap()]; + return Err( + ParseError2::new_error(&ctx.module.source, cur_sync.position, "Illegal nested synchronous statement") + .with_postfixed_info(&ctx.module.source, old_sync.position, "It is nested in this synchronous statement") + ); + } + + if self.def_type != DefinitionType::Primitive { + return Err(ParseError2::new_error( + &ctx.module.source, cur_sync.position, + "Synchronous statements may only be used in primitive components" + )); + } + + // Append SynchronousEnd pseudo-statement + let sync_end_id = ctx.heap.alloc_end_synchronous_statement(|this| EndSynchronousStatement{ + this, + position: stmt.position, + start_sync: id, + next: None, + }); + let sync_start = &mut ctx.heap[id]; + sync_start.end_sync = Some(sync_end_id); + self.insert_buffer.push((self.relative_pos_in_block + 1, sync_end_id.upcast())); + } else { + let old = self.in_sync.replace(id); + self.visit_stmt_with_hint(ctx, stmt.body, Some(id))?; + self.in_sync = old; + } + + Ok(()) + } + + fn visit_return_stmt(&mut self, ctx: &mut Ctx, id: ReturnStatementId) -> VisitorResult { + if self.performing_breadth_pass { + let stmt = &ctx.heap[id]; + if self.def_type != DefinitionType::Function { + return Err( + ParseError2::new_error(&ctx.module.source, stmt.position, "Return statements may only appear in function bodies") + ); + } + } else { + // If here then we are within a function + self.visit_expr(ctx, ctx.heap[id].expression)?; + } + + Ok(()) + } + + fn visit_assert_stmt(&mut self, ctx: &mut Ctx, id: AssertStatementId) -> VisitorResult { let stmt = &ctx.heap[id]; - stmt. + if self.performing_breadth_pass { + if self.def_type == DefinitionType::Function { + // TODO: We probably want to allow this. Mark the function as + // using asserts, and then only allow calls to these functions + // within components. Such a marker will cascade through any + // functions that then call an asserting function + return Err( + ParseError2::new_error(&ctx.module.source, stmt.position, "Illegal assert statement in a function") + ); + } + + // We are in a component of some sort, but we also need to be within a + // synchronous statement + if self.in_sync.is_none() { + return Err( + ParseError2::new_error(&ctx.module.source, stmt.position, "Illegal assert statement outside of a synchronous block") + ); + } + } else { + self.visit_expr(ctx, stmt.expression)?; + } + + Ok(()) + } + + fn visit_goto_stmt(&mut self, ctx: &mut Ctx, id: GotoStatementId) -> VisitorResult { + if !self.performing_breadth_pass { + // Must perform goto label resolving after the breadth pass, this + // way we are able to find all the labels in current and outer + // scopes. + let goto_stmt = &mut ctx.heap[id]; + let target_id = self.find_label(ctx, &goto_stmt.label)?; + goto_stmt.target = Some(target_id); + + let target = &ctx.heap[target_id]; + if self.in_sync != target.in_sync { + // We can only goto the current scope or outer scopes. Because + // nested sync statements are not allowed so if the value does + // not match, then we must be inside a sync scope + debug_assert!(self.in_sync.is_some()); + let sync_stmt = &ctx.heap[self.in_sync.unwrap()]; + return Err( + ParseError2::new_error(&ctx.module.source, goto_stmt.position, "Goto may not escape the surrounding synchronous block") + .with_postfixed_info(&ctx.module.source, target.position, "This is the target of the goto statement") + .with_postfixed_info(&ctx.module.source, sync_stmt.position, "Which will jump past this statement") + ); + } + } + + Ok(()) + } + + fn visit_new_stmt(&mut self, ctx: &mut Ctx, id: NewStatementId) -> VisitorResult { + if self.performing_breadth_pass { + // TODO: Cleanup error messages, can be done cleaner + // Make sure new statement occurs within a composite component + let new_stmt = &ctx.heap[id]; + if self.def_type != DefinitionType::Composite { + return Err( + ParseError2::new_error(&ctx.module.source, new_stmt.position, "Instantiating components may only be done in composite components") + ); + } + + // No fancy recursive parsing, must be followed by a call expression + let definition_id = { + let call_expr = &ctx.heap[new_stmt.expression]; + if let Expression::Call(call_expr) = call_expr { + if let Method::Symbolic(symbolic) = &call_expr.method { + // Resolve method + let (symbol, iter) = ctx.symbols.resolve_namespaced_symbol(ctx.module.root_id, &symbolic.identifier)?; + if iter.num_remaining() != 0 { + return Err( + ParseError2::new_error(&ctx.module.source, symbolic.identifier.position, "Unknown component") + ) + } + + match symbol.symbol { + Symbol::Namespace(_) => return Err( + ParseError2::new_error(&ctx.module.source, symbolic.identifier.position, "Unknown component") + .with_postfixed_info(&ctx.module.source, symbol.position, "The identifier points to this import") + ), + Symbol::Definition((target_root_id, target_definition_id)) => { + match &ctx.heap[target_definition_id] { + Definition::Component(_) => target_definition_id, + _ => return Err( + ParseError2::new_error(&ctx.module.source, symbolic.identifier.position, "Must instantiate a component") + ) + } + } + } + } else { + return Err( + ParseError2::new_error(&ctx.module.source, call_expr.position, "Must instantiate a component") + ); + } + } else { + return Err( + ParseError2::new_error(&ctx.module.source, call_expr.position, "Must instantiate a component") + ); + } + }; + + // Modify new statement's symbolic call to point to the appropriate + // definition. + let call_expr = &mut ctx.heap[new_stmt.expression]; + match &mut call_expr.method { + Method::Symbolic(method) => method.definition = Some(definition_id), + _ => unreachable!() + } + } + + Ok(()) + } + + fn visit_put_stmt(&mut self, ctx: &mut Ctx, id: PutStatementId) -> VisitorResult { + // TODO: Make `put` an expression. Perhaps silly, but much easier to + // perform typechecking + if self.performing_breadth_pass { + let put_stmt = &ctx.heap[id]; + if self.in_sync.is_none() { + return Err(ParseError2::new_error( + &ctx.module.source, put_stmt.position, "Put must be called in a synchronous block" + )); + } + } else { + let put_stmt = &ctx.heap[id]; + self.visit_expr(ctx, put_stmt.port)?; + self.visit_expr(ctx, put_stmt.message)?; + } + + Ok(()) + } + + fn visit_expr_stmt(&mut self, ctx: &mut Ctx, id: ExpressionStatementId) -> VisitorResult { + if !self.performing_breadth_pass { + let expr = &ctx.heap[id]; + self.visit_expr(ctx, expr.expression)?; + } + + Ok(()) + } + + + //-------------------------------------------------------------------------- + // Expression visitors + //-------------------------------------------------------------------------- + + fn visit_assignment_expr(&mut self, ctx: &mut Ctx, id: AssignmentExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + Ok(()) + } + + fn visit_conditional_expr(&mut self, ctx: &mut Ctx, id: ConditionalExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + let conditional_expr = &ctx.heap[id]; + self.visit_expr(ctx, conditional_expr.test)?; + self.visit_expr(ctx, conditional_expr.true_expression)?; + self.visit_expr(ctx, conditional_expr.false_expression)?; + Ok(()) + } + + fn visit_binary_expr(&mut self, ctx: &mut Ctx, id: BinaryExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + let binary_expr = &ctx.heap[id]; + self.visit_expr(ctx, binary_expr.left)?; + self.visit_expr(ctx, binary_expr.right)?; + Ok(()) + } + + fn visit_unary_expr(&mut self, ctx: &mut Ctx, id: UnaryExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + let unary_expr = &ctx.heap[id]; + self.visit_expr(ctx, unary_expr.expression)?; + Ok(()) + } + + fn visit_indexing_expr(&mut self, ctx: &mut Ctx, id: IndexingExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + let indexing_expr = &ctx.heap[id]; + self.visit_expr(ctx, indexing_expr.subject)?; + self.visit_expr(ctx, indexing_expr.index)?; + Ok(()) + } + + fn visit_slicing_expr(&mut self, ctx: &mut Ctx, id: SlicingExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + // TODO: Same as the select expression: slicing depends on the type of + // the thing that is being sliced. + let slicing_expr = &ctx.heap[id]; + self.visit_expr(ctx, slicing_expr.subject)?; + self.visit_expr(ctx, slicing_expr.from_index)?; + self.visit_expr(ctx, slicing_expr.to_index)?; + Ok(()) + } + + fn visit_select_expr(&mut self, ctx: &mut Ctx, id: SelectExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + // TODO: Is it true that this always depends on the return value? I + // mean: the following should be a valid expression: + // int i = some_call_that_returns_a_struct(5, 2).field_of_struct + // We could rule out some things (of which we're sure what the type is) + // but it seems better to do this later + let select_expr = &ctx.heap[id]; + self.visit_expr(ctx, select_expr.subject)?; + + Ok(()) + } + + fn visit_array_expr(&mut self, ctx: &mut Ctx, id: ArrayExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + let array_expr = &ctx.heap[id]; + for field in &array_expr.elements { + self.visit_expr(ctx, *field)?; + } + Ok(()) + } + + fn visit_constant_expr(&mut self, ctx: &mut Ctx, id: ConstantExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + Ok(()) + } + + fn visit_call_expr(&mut self, ctx: &mut Ctx, id: CallExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + + let call_expr = &mut ctx.heap[id]; + match &mut call_expr.method { + Method::Create => {}, + Method::Fires => { + if self.def_type != DefinitionType::Primitive { + return Err(ParseError2::new_error( + &ctx.module.source, call_expr.position, + "A call to 'fires' may only occur in primitive component definitions" + )); + } + }, + Method::Get => { + if self.def_type != DefinitionType::Primitive { + return Err(ParseError2::new_error( + &ctx.module.source, call_expr.position, + "A call to 'get' may only occur in primitive component definitions" + )); + } + }, + Method::Symbolic(symbolic) => { + // Find symbolic method + let symbol = ctx.symbols.resolve_namespaced_symbol(ctx.module.root_id, &symbolic.identifier); + if symbol.is_none() { + return Err(ParseError2::new_error( + &ctx.module.source, symbolic.identifier.position, + "Could not find definition of function call" + )); + } + let (symbol, iter) = symbol.unwrap(); + if iter.num_remaining() != 0 { + return Err( + ParseError2::new_error(&ctx.module.source, symbolic.identifier.position,"Could not find definition of function call") + .with_postfixed_info(&ctx.module.source, symbol.position, "Could resolve part of the identifier to this symbol") + ); + } + let definition_id = match &symbol.symbol { + Symbol::Definition((_, definition_id)) => { + let definition = ctx.types.get_definition(definition_id); + debug_assert!(definition.is_some(), "Symbol resolved to definition, but not present in type table"); + let definition = definition.unwrap(); + match definition { + DefinedType::Function(_) => Some(*definition_id), + _ => None, + } + }, + Symbol::Namespace(_) => None, + }; + if definition_id.is_none() { + return Err( + ParseError2::new_error(&ctx.module.source, symbolic.identifier.position, "Could not find definition of function call") + ); + } + + symbolic.definition = Some(definition_id.unwrap()); + } + } + + Ok(()) + } + + fn visit_variable_expr(&mut self, ctx: &mut Ctx, id: VariableExpressionId) -> VisitorResult { + debug_assert!(!self.performing_breadth_pass); + + let var_expr = &ctx.heap[id]; + let variable_id = self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier)?; + let var_expr = &mut ctx.heap[id]; + var_expr.declaration = Some(variable_id); + + Ok(()) } } -impl NoNameYet { +impl ValidityAndLinkerVisitor { + //-------------------------------------------------------------------------- + // Special traversal + //-------------------------------------------------------------------------- + + fn visit_stmt_with_hint(&mut self, ctx: &mut Ctx, id: StatementId, hint: Option) -> VisitorResult { + if let Statement::Block(block) = &ctx.heap[id] { + self.visit_block_stmt_with_hint(ctx, block.this, hint) + } else { + self.visit_stmt(ctx, id) + } + } + + fn visit_block_stmt_with_hint(&mut self, ctx: &mut Ctx, id: BlockStatementId, hint: Option) -> VisitorResult { + if self.performing_breadth_pass { + // Our parent is performing a breadth-pass. We do this simple stuff + // here + let body = &mut ctx.heap[id]; + body.parent_scope = self.cur_scope.clone(); + body.relative_pos_in_parent = self.relative_pos_in_block; + + return Ok(()) + } + + // We may descend into children of this block. However, this is + // where we first perform a breadth-first pass + self.performing_breadth_pass = true; + let old_scope = self.cur_scope.replace(match hint { + Some(sync_id) => Scope{ + variant: ScopeVariant::Synchronous((sync_id, id)), + }, + None => Scope{ + variant: ScopeVariant::Regular(id) + } + }); + let first_statement_index = self.statement_buffer.len(); + + { + let body = &ctx.heap[id]; + self.statement_buffer.extend_from_slice(&body.statements); + } + + let mut stmt_index = first_statement_index; + while stmt_index < self.statement_buffer.len() { + self.relative_pos_in_block = (stmt_index - first_statement_index) as u32; + self.visit_stmt(ctx, self.statement_buffer[stmt_index])?; + stmt_index += 1; + } + + if !self.insert_buffer.is_empty() { + let body = &mut ctx.heap[id]; + for (pos, stmt) in self.insert_buffer.drain(..) { + body.statements.insert(pos as usize, stmt); + } + } + + // And the depth pass. Because we're not actually visiting any inserted + // nodes because we're using the statement buffer, we may safely use the + // relative_pos_in_block counter. + self.performing_breadth_pass = false; + stmt_index = first_statement_index; + while stmt_index < self.statement_buffer.len() { + self.relative_pos_in_block = (stmt_index - first_statement_index) as u32; + self.visit_stmt(ctx, self.statement_buffer[stmt_index])?; + stmt_index += 1; + } + + self.cur_scope = old_scope; + + // Pop statement buffer + debug_assert!(self.insert_buffer.is_empty(), "insert buffer not empty after depth pass"); + self.statement_buffer.truncate(first_statement_index); + + Ok(()) + } + //-------------------------------------------------------------------------- // Utilities //-------------------------------------------------------------------------- @@ -507,6 +914,58 @@ impl NoNameYet { Ok(()) } + /// 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: &NamespacedIdentifier) -> Result { + debug_assert!(self.cur_scope.is_some()); + debug_assert!(identifier.num_namespaces > 0); + + // TODO: Update once globals are possible as well + if identifier.num_namespaces > 1 { + todo!("Implement namespaced constant seeking") + } + + // TODO: May still refer to an alias of a global symbol using a single + // identifier in the namespace. + // No need to use iterator over namespaces if here + let mut scope = self.cur_scope.as_ref().unwrap(); + loop { + debug_assert!(scope.is_block()); + let block = &ctx.heap[scope.to_block()]; + for local_id in &block.locals { + let local = &ctx.heap[*local_id]; + if local.relative_pos_in_block < relative_pos && local.identifier.value == identifier.value { + return Ok(local_id.upcast()); + } + } + + debug_assert!(block.parent_scope.is_some()); + scope = block.parent_scope.as_ref().unwrap(); + if !scope.is_block() { + // Definition scope, need to check arguments to definition + match scope.variant { + ScopeVariant::Definition(definition_id) => { + let definition = &ctx.heap[definition_id]; + for parameter_id in definition.parameters() { + let parameter = &ctx.heap[*parameter_id]; + if parameter.identifier.value == identifier.value { + return Ok(parameter_id.upcast()); + } + } + }, + _ => unreachable!(), + } + + // Variable could not be found + return Err(ParseError2::new_error( + &ctx.module.source, identifier.position, "This variable is not declared" + )); + } else { + relative_pos = block.relative_pos_in_parent; + } + } + } + fn checked_label_add(&mut self, ctx: &mut Ctx, id: LabeledStatementId) -> Result<(), ParseError2> { debug_assert!(self.cur_scope.is_some()); @@ -529,8 +988,8 @@ impl NoNameYet { } } - debug_assert!(scope.parent.is_some(), "block scope does not have a parent"); - scope = scope.parent.as_ref().unwrap(); + debug_assert!(block.parent_scope.is_some(), "block scope does not have a parent"); + scope = block.parent_scope.as_ref().unwrap(); if !scope.is_block() { break; } @@ -543,16 +1002,35 @@ impl NoNameYet { Ok(()) } + /// Finds a particular labeled statement by its identifier. Once found it + /// will make sure that the target label does not skip over any variable + /// declarations within the scope in which the label was found. fn find_label(&self, ctx: &Ctx, identifier: &Identifier) -> Result { debug_assert!(self.cur_scope.is_some()); let mut scope = self.cur_scope.as_ref().unwrap(); loop { debug_assert!(scope.is_block(), "scope is not a block"); + let relative_scope_pos = ctx.heap[scope.to_block()].relative_pos_in_parent; + let block = &ctx.heap[scope.to_block()]; for label_id in &block.labels { let label = &ctx.heap[*label_id]; if label.label.value == identifier.value { + for local_id in &block.locals { + // TODO: Better to do this in control flow analysis, it + // is legal to skip over a variable declaration if it + // is not actually being used. I might be missing + // something here when laying out the bytecode... + let local = &ctx.heap[*local_id]; + if local.relative_pos_in_block > relative_scope_pos && local.relative_pos_in_block < label.relative_pos_in_block { + return Err( + ParseError2::new_error(&ctx.module.source, identifier.position, "This target label skips over a variable declaration") + .with_postfixed_info(&ctx.module.source, label.position, "Because it jumps to this label") + .with_postfixed_info(&ctx.module.source, local.position, "Which skips over this variable") + ); + } + } return Ok(*label_id); } } @@ -562,6 +1040,7 @@ impl NoNameYet { if !scope.is_block() { return Err(ParseError2::new_error(&ctx.module.source, identifier.position, "Could not find this label")); } + } }