diff --git a/compiler/plc_ast/src/visitor.rs b/compiler/plc_ast/src/visitor.rs index 811e74e629..27d9ae0efb 100644 --- a/compiler/plc_ast/src/visitor.rs +++ b/compiler/plc_ast/src/visitor.rs @@ -1,11 +1,12 @@ -/// This module defines the `AstVisitor` trait and its associated macros. -/// The `AstVisitor` trait provides a set of methods for traversing and visiting ASTs +//! This module defines the `AstVisitor` trait and its associated macros. +//! The `AstVisitor` trait provides a set of methods for traversing and visiting ASTs + use crate::ast::AstNode; use crate::ast::*; use crate::control_statements::{AstControlStatement, ConditionalBlock, ReturnStatement}; use crate::literals::AstLiteral; -/// Macro that walks through all nodes in an iterator and applies the visitor's `walk` method to each node. +/// Macro that calls the visitor's `visit` method for every AstNode in the passed iterator `iter`. macro_rules! visit_all_nodes { ($visitor:expr, $iter:expr) => { for node in $iter { @@ -14,7 +15,7 @@ macro_rules! visit_all_nodes { }; } -/// Macro that walks through a list of nodes and applies the visitor's `walk` method to each node. +/// Macro that calls the visitor's `visit` method for every AstNode in the passed sequence of nodes. macro_rules! visit_nodes { ($visitor:expr, $($node:expr),*) => { $( @@ -23,16 +24,46 @@ macro_rules! visit_nodes { }; } +/// The `Walker` implements the traversal of the AST nodes and Ast-related objects (e.g. CompilationUnit). +/// The `walk` method is called on the object to visit its children. +/// If the object passed to a `AstVisitor`'s `visit` method implements the `Walker` trait, +/// a call to the it's walk function continues the visiting process on its children. +/// +/// Spliting the traversal logic into a separate trait allows to call the default traversal logic +/// from the visitor while overriding the visitor's `visit` method for specific nodes. +/// +/// # Example +/// ``` +/// use plc_ast::{ +/// ast::{Assignment, AstNode}, +/// visitor::{AstVisitor, Walker}, +/// }; +/// +/// impl Walker for Assignment { +/// fn walk(&self, visitor: &mut V) +/// where V: AstVisitor { +/// visitor.visit(&self.right); +/// visitor.visit(&self.left); +/// } +/// ``` +/// pub trait Walker { fn walk(&self, visitor: &mut V) where V: AstVisitor; } -/// The `AstVisitor` trait provides a set of methods for traversing and visiting different types of AST nodes. +/// The `AstVisitor` trait provides a set of methods for visiting different types of AST nodes. /// Implementors can individually override the methods they are interested in. When overriding a method, /// make sure to call `walk` on the visited statement to visit its children. DO NOT call walk on -/// the node itself (last parameter). +/// the node itself to avoid a recursion (last parameter). Implementors may also decide to not call +/// the statement's `walk` method to avoid visiting the children of the statement. +/// +/// The visitor offers strongly typed `visit_X` functions for every node type. The function's signature +/// is `fn visit_X(&mut self, stmt: &X, node: &AstNode)`. The `stmt` parameter is the unwrapped, typed +/// node and the `node` parameter is the `AstNode` wrapping the stmt. The `AstNode` node offers access to location +/// information and the AstId. Note that some nodes are not wrapped in an `AstNode` node (e.g. `CompilationUnit`) +/// and therefore only the strongly typed node is passed to the `visit_X` function. /// /// # Example /// ``` @@ -60,191 +91,283 @@ pub trait Walker { /// } /// ``` pub trait AstVisitor: Sized { - /// visits through an `AstNode` and applies the visitor's `walk` method to each node. + /// Visits this `AstNode`. The default implementation calls the `walk` method on the node + /// and will eventually call the strongly typed `visit` method for the node (e.g. visit_assignment + /// if the node is an `AstStatement::Assignment`). + /// # Arguments + /// * `node` - The `AstNode` node to visit. fn visit(&mut self, node: &AstNode) { node.walk(self) } /// Visits a `CompilationUnit` node. /// Make sure to call `walk` on the `CompilationUnit` node to visit its children. + /// # Arguments + /// * `unit` - The unwraped, typed `CompilationUnit` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_compilation_unit(&mut self, unit: &CompilationUnit) { unit.walk(self) } /// Visits an `Implementation` node. /// Make sure to call `walk` on the `Implementation` node to visit its children. + /// # Arguments + /// * `implementation` - The unwraped, typed `Implementation` node to visit. fn visit_implementation(&mut self, implementation: &Implementation) { implementation.walk(self); } /// Visits a `DataTypeDeclaration` node. /// Make sure to call `walk` on the `VariableBlock` node to visit its children. + /// # Arguments + /// * `block` - The unwraped, typed `VariableBlock` node to visit. fn visit_variable_block(&mut self, block: &VariableBlock) { block.walk(self) } /// Visits a `Variable` node. /// Make sure to call `walk` on the `Variable` node to visit its children. + /// # Arguments + /// * `variable` - The unwraped, typed `Variable` node to visit. fn visit_variable(&mut self, variable: &Variable) { variable.walk(self); } /// Visits an enum element `AstNode` node. /// Make sure to call `walk` on the `AstNode` node to visit its children. + /// # Arguments + /// * `element` - The unwraped, typed `AstNode` node to visit. fn visit_enum_element(&mut self, element: &AstNode) { element.walk(self); } /// Visits a `DataTypeDeclaration` node. /// Make sure to call `walk` on the `DataTypeDeclaration` node to visit its children. + /// # Arguments + /// * `data_type_declaration` - The unwraped, typed `DataTypeDeclaration` node to visit. fn visit_data_type_declaration(&mut self, data_type_declaration: &DataTypeDeclaration) { data_type_declaration.walk(self); } /// Visits a `UserTypeDeclaration` node. /// Make sure to call `walk` on the `UserTypeDeclaration` node to visit its children. + /// # Arguments + /// * `user_type` - The unwraped, typed `UserTypeDeclaration` node to visit. fn visit_user_type_declaration(&mut self, user_type: &UserTypeDeclaration) { user_type.walk(self); } /// Visits a `UserTypeDeclaration` node. /// Make sure to call `walk` on the `DataType` node to visit its children. + /// # Arguments + /// * `data_type` - The unwraped, typed `DataType` node to visit. fn visit_data_type(&mut self, data_type: &DataType) { data_type.walk(self); } /// Visits a `Pou` node. /// Make sure to call `walk` on the `Pou` node to visit its children. + /// # Arguments + /// * `pou` - The unwraped, typed `Pou` node to visit. fn visit_pou(&mut self, pou: &Pou) { pou.walk(self); } /// Visits an `EmptyStatement` node. + /// # Arguments + /// * `stmt` - The unwraped, typed `EmptyStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_empty_statement(&mut self, _stmt: &EmptyStatement, _node: &AstNode) {} /// Visits a `DefaultValue` node. + /// # Arguments + /// * `stmt` - The unwraped, typed `DefaultValue` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_default_value(&mut self, _stmt: &DefaultValue, _node: &AstNode) {} /// Visits an `AstLiteral` node. /// Make sure to call `walk` on the `AstLiteral` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `AstLiteral` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_literal(&mut self, stmt: &AstLiteral, _node: &AstNode) { stmt.walk(self) } /// Visits a `MultipliedStatement` node. /// Make sure to call `walk` on the `MultipliedStatement` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `MultipliedStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_multiplied_statement(&mut self, stmt: &MultipliedStatement, _node: &AstNode) { stmt.walk(self) } /// Visits a `ReferenceExpr` node. /// Make sure to call `walk` on the `ReferenceExpr` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `ReferenceExpr` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_reference_expr(&mut self, stmt: &ReferenceExpr, _node: &AstNode) { stmt.walk(self) } /// Visits an `Identifier` node. /// Make sure to call `walk` on the `Identifier` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `Identifier` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_identifier(&mut self, _stmt: &str, _node: &AstNode) {} /// Visits a `DirectAccess` node. /// Make sure to call `walk` on the `DirectAccess` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `DirectAccess` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_direct_access(&mut self, stmt: &DirectAccess, _node: &AstNode) { stmt.walk(self) } /// Visits a `HardwareAccess` node. /// Make sure to call `walk` on the `HardwareAccess` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `HardwareAccess` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_hardware_access(&mut self, stmt: &HardwareAccess, _node: &AstNode) { stmt.walk(self) } /// Visits a `BinaryExpression` node. /// Make sure to call `walk` on the `BinaryExpression` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `BinaryExpression` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_binary_expression(&mut self, stmt: &BinaryExpression, _node: &AstNode) { stmt.walk(self) } /// Visits a `UnaryExpression` node. /// Make sure to call `walk` on the `UnaryExpression` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `UnaryExpression` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_unary_expression(&mut self, stmt: &UnaryExpression, _node: &AstNode) { stmt.walk(self) } /// Visits an `ExpressionList` node. /// Make sure to call `walk` on the `Vec` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `ExpressionList` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_expression_list(&mut self, stmt: &Vec, _node: &AstNode) { visit_all_nodes!(self, stmt); } /// Visits a `ParenExpression` node. /// Make sure to call `walk` on the inner `AstNode` node to visit its children. + /// # Arguments + /// * `inner` - The unwraped, typed inner `AstNode` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_paren_expression(&mut self, inner: &AstNode, _node: &AstNode) { inner.walk(self) } /// Visits a `RangeStatement` node. /// Make sure to call `walk` on the `RangeStatement` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `RangeStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_range_statement(&mut self, stmt: &RangeStatement, _node: &AstNode) { stmt.walk(self) } /// Visits a `VlaRangeStatement` node. + /// # Arguments + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_vla_range_statement(&mut self, _node: &AstNode) {} /// Visits an `Assignment` node. /// Make sure to call `walk` on the `Assignment` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `Assignment` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_assignment(&mut self, stmt: &Assignment, _node: &AstNode) { stmt.walk(self) } /// Visits an `OutputAssignment` node. /// Make sure to call `walk` on the `Assignment` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `Assignment` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_output_assignment(&mut self, stmt: &Assignment, _node: &AstNode) { stmt.walk(self) } /// Visits a `CallStatement` node. /// Make sure to call `walk` on the `CallStatement` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `CallStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_call_statement(&mut self, stmt: &CallStatement, _node: &AstNode) { stmt.walk(self) } /// Visits an `AstControlStatement` node. /// Make sure to call `walk` on the `AstControlStatement` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `AstControlStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_control_statement(&mut self, stmt: &AstControlStatement, _node: &AstNode) { stmt.walk(self) } /// Visits a `CaseCondition` node. /// Make sure to call `walk` on the child-`AstNode` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `CaseCondition` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_case_condition(&mut self, child: &AstNode, _node: &AstNode) { child.walk(self) } /// Visits an `ExitStatement` node. + /// # Arguments + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_exit_statement(&mut self, _node: &AstNode) {} /// Visits a `ContinueStatement` node. + /// # Arguments + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_continue_statement(&mut self, _node: &AstNode) {} /// Visits a `ReturnStatement` node. /// Make sure to call `walk` on the `ReturnStatement` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `ReturnStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_return_statement(&mut self, stmt: &ReturnStatement, _node: &AstNode) { stmt.walk(self) } /// Visits a `JumpStatement` node. /// Make sure to call `walk` on the `JumpStatement` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `JumpStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_jump_statement(&mut self, stmt: &JumpStatement, _node: &AstNode) { stmt.walk(self) } /// Visits a `LabelStatement` node. + /// # Arguments + /// * `stmt` - The unwraped, typed `LabelStatement` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId fn visit_label_statement(&mut self, _stmt: &LabelStatement, _node: &AstNode) {} } -/// Walks through a slice of `ConditionalBlock` and applies the visitor's `walk` method to each node. +/// Helper method that walks through a slice of `ConditionalBlock` and applies the visitor's `walk` method to each node. fn walk_conditional_blocks(visitor: &mut V, blocks: &[ConditionalBlock]) where V: AstVisitor, diff --git a/src/parser/tests/ast_visitor_tests.rs b/src/parser/tests/ast_visitor_tests.rs index 09e649faef..c97533c520 100644 --- a/src/parser/tests/ast_visitor_tests.rs +++ b/src/parser/tests/ast_visitor_tests.rs @@ -7,6 +7,17 @@ use plc_source::source_location::SourceLocationFactory; use crate::{lexer, parser}; +/// This is a simple visitor that collects all identifiers and literals in a given body +/// It is used by unit tests, to easily see if all identifiers (even the deeply nested ones) were visited +/// and therefore that all subtrees of the AST were visited. +/// +/// e.g. if we have a source code like this: +/// ``` +/// PROGRAM prg +/// foo (a := b, c => 4); +/// END_PROGRAM +/// ``` +/// The visitor should collect the following identifiers/literals: "foo", "a", "b", "c" and "4" #[derive(Default)] struct IdentifierCollector { identifiers: Vec, @@ -22,10 +33,13 @@ impl AstVisitor for IdentifierCollector { } } +/// Helper function to create a vector of strings with all characters in the range from start to end fn get_character_range(start: char, end: char) -> Vec { (start as u8..=end as u8).map(|c| c as char).map(|c| c.to_string()).collect() } +/// Helper function to collect all identifiers in a given source code +/// using the IdentifierCollector visitor fn collect_identifiers(src: &str) -> IdentifierCollector { let mut visitor = IdentifierCollector::default(); visit(src, &mut visitor); @@ -33,6 +47,7 @@ fn collect_identifiers(src: &str) -> IdentifierCollector { visitor } +/// Helper function to visit a given source code with a given visitor fn visit(src: &str, visitor: &mut impl AstVisitor) { let id_provider = IdProvider::default(); let (compilation_unit, _) = parser::parse( @@ -46,6 +61,8 @@ fn visit(src: &str, visitor: &mut impl AstVisitor) { #[test] fn test_visit_arithmetic_expressions() { + // GIVEN a source code with arithmetic expressions + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -56,22 +73,28 @@ fn test_visit_arithmetic_expressions() { h := (i / j / k - (l + m)) * n; END_PROGRAM", ); + // THEN we expect to also visit subexpressions in binary and unary expressions assert_eq!(get_character_range('a', 'n'), visitor.identifiers); } #[test] fn test_visit_expression_list() { + // GIVEN a source code with an expression list + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg a,b,c; END_PROGRAM", ); + // THEN we expect to visit all identifiers in the expression list assert_eq!(get_character_range('a', 'c'), visitor.identifiers); } #[test] fn test_if_statement() { + // GIVEN a source code with an if statement + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -84,11 +107,14 @@ fn test_if_statement() { END_IF; END_PROGRAM", ); + // THEN we expect to visit the condition, the body, the elseif condition and body and the else body assert_eq!(get_character_range('a', 'h'), visitor.identifiers); } #[test] fn test_visit_for_loop_statement() { + // GIVEN a source code with a for loop statement + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -98,11 +124,14 @@ fn test_visit_for_loop_statement() { END_FOR; END_PROGRAM", ); + // THEN we expect to visit the loop variable, the start, end and step expressions and the loop body assert_eq!(get_character_range('a', 'f'), visitor.identifiers); } #[test] fn test_visit_while_loop_statement() { + // GIVEN a source code with a while loop statement + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -112,10 +141,13 @@ fn test_visit_while_loop_statement() { END_WHILE; END_PROGRAM", ); + // THEN we expect to visit the condition and the loop body assert_eq!(get_character_range('a', 'd'), visitor.identifiers); } #[test] fn test_visit_repeat_loop_statement() { + // GIVEN a source code with a repeat loop statement + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -125,11 +157,14 @@ fn test_visit_repeat_loop_statement() { UNTIL c > d; END_PROGRAM", ); + // THEN we expect to visit the loop body and the condition assert_eq!(get_character_range('a', 'd'), visitor.identifiers); } #[test] fn test_visit_case_statement() { + // GIVEN a source code with a case statement + let visitor = collect_identifiers( " PROGRAM prg @@ -146,22 +181,28 @@ fn test_visit_case_statement() { END_CASE; END_PROGRAM", ); + // THEN we expect to visit the case expression, the case labels, their bodies and the else body assert_eq!(get_character_range('a', 'j'), visitor.identifiers); } #[test] fn test_visit_multiplied_statement() { + // GIVEN a source code with a multiplied statement + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg 3(a+b); END_PROGRAM", ); + // THEN we expect to visit the multiplied expression and its subexpressions assert_eq!(get_character_range('a', 'b'), visitor.identifiers); } #[test] fn test_visist_array_expressions() { + // GIVEN a source code with array expressions + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -170,22 +211,28 @@ fn test_visist_array_expressions() { g[h+i][j+k]; END_PROGRAM", ); + // THEN we expect to visit the array expressions and the array-accessor expressions assert_eq!(get_character_range('a', 'k'), visitor.identifiers); } #[test] fn test_visit_range_statement() { + // GIVEN a source code with range statements + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg a..b; END_PROGRAM", ); + // THEN we expect to visit the start and end expressions of the range assert_eq!(get_character_range('a', 'b'), visitor.identifiers); } #[test] fn test_visit_assignment_expressions() { + // GIVEN a source code with assignment expressions + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -194,11 +241,14 @@ fn test_visit_assignment_expressions() { e =>; END_PROGRAM", ); + // THEN we expect to visit the left and right side of the assignment expressions assert_eq!(get_character_range('a', 'e'), visitor.identifiers); } #[test] fn test_visit_direct_access_statement_expressions() { + // GIVEN a source code with direct access expressions + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -206,11 +256,14 @@ fn test_visit_direct_access_statement_expressions() { %MD4; END_PROGRAM", ); + // THEN we expect to visit all segments of the direct access assert_eq!(get_character_range('1', '4'), visitor.identifiers); } #[test] fn test_visit_call_statements() { + // GIVEN a source code with call statements + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -219,22 +272,28 @@ fn test_visit_call_statements() { e(f:=(g), h=>i); END_PROGRAM", ); + // THEN we expect to visit the function name and all arguments assert_eq!(get_character_range('a', 'i'), visitor.identifiers); } #[test] fn test_visit_return_statement() { + // GIVEN a source code with a return statement + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " FUNCTION prg : INT RETURN a + b; END_PROGRAM", ); + // THEN we expect to visit the return expression assert_eq!(get_character_range('a', 'b'), visitor.identifiers); } #[test] fn test_visit_into_var_global() { + // GIVEN a source code with a var_global section + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " VAR_GLOBAL @@ -242,11 +301,13 @@ fn test_visit_into_var_global() { c : INT := d; END_VAR", ); + // THEN we expect to visit all initializers (variable names are no AstStatements!) assert_eq!(get_character_range('c', 'd'), visitor.identifiers); } #[test] fn test_visit_data_type_declaration() { + // GIVEN a visitor that collects variables, enum elements and range expressions struct FieldCollector { fields: Vec, } @@ -280,7 +341,7 @@ fn test_visit_data_type_declaration() { } } let mut visitor = FieldCollector { fields: vec![] }; - + // WHEN we visit a source code with a complex datatype visit( " TYPE myStruct: STRUCT @@ -301,6 +362,7 @@ fn test_visit_data_type_declaration() { ); visitor.fields.sort(); + // THEN we expect to visit all fields, enum elements and range expressions assert_eq!( vec![ "a", "b", "c", "e", "end", "enum1", "enum2", "enum3", "max", "min", "myEnum1", "myEnum2", @@ -312,6 +374,7 @@ fn test_visit_data_type_declaration() { #[test] fn test_count_assignments() { + // GIVEN a visitor that counts assignments struct AssignmentCounter { count: usize, } @@ -350,15 +413,18 @@ fn test_count_assignments() { ); let mut visitor = AssignmentCounter { count: 0 }; - + // WHEN we visit a source code with assignments for st in &compilation_unit.implementations[0].statements { visitor.visit(st); } + // THEN we expect to visit all assignments assert_eq!(6, visitor.count); } #[test] fn test_visit_datatype_initializers_statement() { + // GIVEN a source code with datatype initializers + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " TYPE MyStruct: STRUCT @@ -371,8 +437,8 @@ fn test_visit_datatype_initializers_statement() { END_STRUCT END_TYPE", ); - - let mut expected = vec!["1", "3", "4", "7"].iter().map(|c| c.to_string()).collect::>(); + // THEN we expect to visit all initializers and enum elements + let mut expected = ["1", "3", "4", "7"].iter().map(|c| c.to_string()).collect::>(); expected.extend(get_character_range('a', 'm')); assert_eq!(expected, visitor.identifiers); @@ -380,87 +446,20 @@ fn test_visit_datatype_initializers_statement() { #[test] fn test_visit_array_declaration_statement() { + // GIVEN a source code with array declarations + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " TYPE MyArray: ARRAY[(a+b)..(c+d)] OF INT; END_TYPE", ); + // THEN we expect to visit the start and end expressions of the array assert_eq!(get_character_range('a', 'd'), visitor.identifiers); } -#[test] -fn test_visit_if_statement() { - let visitor = collect_identifiers( - " - PROGRAM prg - IF a THEN - b; - ELSIF c THEN - d; - ELSE - e; - END_IF;", - ); - assert_eq!(get_character_range('a', 'e'), visitor.identifiers); -} - -#[test] -fn test_for_loop_visting() { - let visitor = collect_identifiers( - " - PROGRAM prg - FOR a := b TO c BY d DO - e; - END_FOR; - END_PROGRAM", - ); - assert_eq!(get_character_range('a', 'e'), visitor.identifiers); -} - -#[test] -fn test_while_loop_visiting() { - let visitor = collect_identifiers( - " - PROGRAM prg - WHILE a DO - b; - END_WHILE; - END_PROGRAM", - ); - assert_eq!(get_character_range('a', 'b'), visitor.identifiers); - - let visitor = collect_identifiers( - " - PROGRAM prg - REPEAT - a; - UNTIL - b = c - END_REPEAT; - END_PROGRAM", - ); - assert_eq!(get_character_range('a', 'c'), visitor.identifiers); -} - -#[test] -fn test_case_stmt_visiting() { - let visitor = collect_identifiers( - " - PROGRAM prg - CASE a OF - b: - c; - d, e: - f; - ELSE - g; - END_CASE; - END_PROGRAM", - ); - assert_eq!(get_character_range('a', 'g'), visitor.identifiers); -} - #[test] fn test_visit_qualified_expressions() { + // GIVEN a source code with qualified expressions + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -469,11 +468,14 @@ fn test_visit_qualified_expressions() { f.g[h].i; END_PROGRAM", ); + // THEN we expect to visit all segments in the qualified expressions assert_eq!(get_character_range('a', 'i'), visitor.identifiers); } #[test] fn test_visit_variable_block() { + // GIVEN a source code with a variable block + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -488,11 +490,13 @@ fn test_visit_variable_block() { END_VAR END_PROGRAM", ); + // THEN we expect to visit all variables and their initializers assert_eq!(get_character_range('X', 'Y'), visitor.identifiers); } #[test] fn test_visit_continue_exit() { + // THIS test is mainly here to cover the default visit implementation of Continue, Exit and EmptyStatement let visitor = collect_identifiers( " PROGRAM prg @@ -506,6 +510,7 @@ fn test_visit_continue_exit() { #[test] fn test_visit_default_value() { + // GIVEN a Visitor that visits default values struct DefaultValueCollector { visited: bool, } @@ -518,7 +523,7 @@ fn test_visit_default_value() { } let mut visitor = DefaultValueCollector { visited: false }; - + // WHEN we visit a source code with a default value visit( " VAR_GLOBAL CONSTANT @@ -527,11 +532,13 @@ fn test_visit_default_value() { ", &mut visitor, ); + // THEN we expect to visit the default value assert!(visitor.visited); } #[test] fn test_visit_direct_access() { + // GIVEN a Visitor that visits direct accesses struct Visited { visited: bool, } @@ -543,7 +550,7 @@ fn test_visit_direct_access() { } let mut visitor = Visited { visited: false }; - + // WHEN we visit a source code with a direct access visit( " PROGRAM prg @@ -551,6 +558,7 @@ fn test_visit_direct_access() { ", &mut visitor, ); + // THEN we expect to visit the direct access assert!(visitor.visited); let v = collect_identifiers( @@ -564,6 +572,7 @@ fn test_visit_direct_access() { #[test] fn test_invalid_case_condition() { + // this tests ensures that we visit "invalid" statements. (see parser's behavior in parse_statement) struct Visited { visited: bool, } @@ -588,6 +597,8 @@ fn test_invalid_case_condition() { #[test] fn test_visit_string_declaration() { + // GIVEN a source code with a string declaration + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -596,12 +607,14 @@ fn test_visit_string_declaration() { END_VAR ", ); - + // THEN we expect to visit the string length assert_eq!(vec!["X"], visitor.identifiers); } #[test] fn test_visit_pointer_declaration() { + // GIVEN a source code with a pointer declaration + // WHEN we visit all nodes in the AST let visitor = collect_identifiers( " PROGRAM prg @@ -610,6 +623,6 @@ fn test_visit_pointer_declaration() { END_VAR ", ); - + // THEN we expect to visit the pointer type and the initializer assert_eq!(get_character_range('a', 'c'), visitor.identifiers); }