use super::CstNode;
use crate::parsers::SyntaxKind;
pub trait Visitor: Sized {
fn visit_node(&mut self, node: &CstNode) {
walk_node(self, node);
}
fn visit_dim_statement(&mut self, _node: &CstNode) {}
fn visit_const_statement(&mut self, _node: &CstNode) {}
fn visit_sub_statement(&mut self, _node: &CstNode) {}
fn visit_function_statement(&mut self, _node: &CstNode) {}
fn visit_property_statement(&mut self, _node: &CstNode) {}
fn visit_if_statement(&mut self, _node: &CstNode) {}
fn visit_select_case_statement(&mut self, _node: &CstNode) {}
fn visit_for_statement(&mut self, _node: &CstNode) {}
fn visit_for_each_statement(&mut self, _node: &CstNode) {}
fn visit_while_statement(&mut self, _node: &CstNode) {}
fn visit_do_statement(&mut self, _node: &CstNode) {}
fn visit_with_statement(&mut self, _node: &CstNode) {}
fn visit_exit_statement(&mut self, _node: &CstNode) {}
fn visit_goto_statement(&mut self, _node: &CstNode) {}
fn visit_on_error_statement(&mut self, _node: &CstNode) {}
fn visit_resume_statement(&mut self, _node: &CstNode) {}
fn visit_call_statement(&mut self, _node: &CstNode) {}
fn visit_assignment_statement(&mut self, _node: &CstNode) {}
fn visit_redim_statement(&mut self, _node: &CstNode) {}
fn visit_erase_statement(&mut self, _node: &CstNode) {}
fn visit_type_statement(&mut self, _node: &CstNode) {}
fn visit_enum_statement(&mut self, _node: &CstNode) {}
fn visit_identifier(&mut self, _node: &CstNode) {}
fn visit_literal_expression(&mut self, _node: &CstNode) {}
fn visit_option_statement(&mut self, _node: &CstNode) {}
fn visit_attribute_statement(&mut self, _node: &CstNode) {}
fn visit_implements_statement(&mut self, _node: &CstNode) {}
fn visit_event_statement(&mut self, _node: &CstNode) {}
fn visit_declare_statement(&mut self, _node: &CstNode) {}
fn visit_keyword(&mut self, _node: &CstNode) {}
fn visit_operator(&mut self, _node: &CstNode) {}
fn visit_punctuation(&mut self, _node: &CstNode) {}
fn visit_end_of_line_comment(&mut self, _node: &CstNode) {}
fn visit_rem_comment(&mut self, _node: &CstNode) {}
fn visit_whitespace(&mut self, _node: &CstNode) {}
}
pub trait VisitorMut: Sized {
fn visit_node_mut(&mut self, node: &mut CstNode) {
walk_node_mut(self, node);
}
fn visit_dim_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_sub_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_function_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_if_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_for_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_while_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_assignment_statement_mut(&mut self, _node: &mut CstNode) {}
fn visit_identifier_mut(&mut self, _node: &mut CstNode) {}
fn visit_literal_expression_mut(&mut self, _node: &mut CstNode) {}
fn visit_binary_expression_mut(&mut self, _node: &mut CstNode) {}
}
pub fn walk_node<V: Visitor>(visitor: &mut V, node: &CstNode) {
match node.kind() {
SyntaxKind::DimStatement => visitor.visit_dim_statement(node),
SyntaxKind::ConstStatement => visitor.visit_const_statement(node),
SyntaxKind::SubStatement => visitor.visit_sub_statement(node),
SyntaxKind::FunctionStatement => visitor.visit_function_statement(node),
SyntaxKind::PropertyStatement => visitor.visit_property_statement(node),
SyntaxKind::IfStatement => visitor.visit_if_statement(node),
SyntaxKind::SelectCaseStatement => visitor.visit_select_case_statement(node),
SyntaxKind::ForStatement => visitor.visit_for_statement(node),
SyntaxKind::ForEachStatement => visitor.visit_for_each_statement(node),
SyntaxKind::WhileStatement => visitor.visit_while_statement(node),
SyntaxKind::DoStatement => visitor.visit_do_statement(node),
SyntaxKind::WithStatement => visitor.visit_with_statement(node),
SyntaxKind::ExitStatement => visitor.visit_exit_statement(node),
SyntaxKind::GotoStatement => visitor.visit_goto_statement(node),
SyntaxKind::OnErrorStatement => visitor.visit_on_error_statement(node),
SyntaxKind::ResumeStatement => visitor.visit_resume_statement(node),
SyntaxKind::CallStatement => visitor.visit_call_statement(node),
SyntaxKind::AssignmentStatement => visitor.visit_assignment_statement(node),
SyntaxKind::ReDimStatement => visitor.visit_redim_statement(node),
SyntaxKind::EraseStatement => visitor.visit_erase_statement(node),
SyntaxKind::TypeStatement => visitor.visit_type_statement(node),
SyntaxKind::EnumStatement => visitor.visit_enum_statement(node),
SyntaxKind::Identifier => visitor.visit_identifier(node),
SyntaxKind::LiteralExpression => visitor.visit_literal_expression(node),
SyntaxKind::OptionStatement => visitor.visit_option_statement(node),
SyntaxKind::AttributeStatement => visitor.visit_attribute_statement(node),
SyntaxKind::ImplementsStatement => visitor.visit_implements_statement(node),
SyntaxKind::EventStatement => visitor.visit_event_statement(node),
SyntaxKind::DeclareStatement => visitor.visit_declare_statement(node),
kind if is_keyword(kind) => visitor.visit_keyword(node),
kind if is_operator(kind) => visitor.visit_operator(node),
kind if is_punctuation(kind) => visitor.visit_punctuation(node),
SyntaxKind::EndOfLineComment => visitor.visit_end_of_line_comment(node),
SyntaxKind::RemComment => visitor.visit_rem_comment(node),
SyntaxKind::Whitespace | SyntaxKind::Newline => visitor.visit_whitespace(node),
_ => {}
}
for child in node.children() {
visitor.visit_node(child);
}
}
pub fn walk_node_mut<V: VisitorMut>(visitor: &mut V, node: &mut CstNode) {
match node.kind() {
SyntaxKind::DimStatement => visitor.visit_dim_statement_mut(node),
SyntaxKind::SubStatement => visitor.visit_sub_statement_mut(node),
SyntaxKind::FunctionStatement => visitor.visit_function_statement_mut(node),
SyntaxKind::IfStatement => visitor.visit_if_statement_mut(node),
SyntaxKind::ForStatement => visitor.visit_for_statement_mut(node),
SyntaxKind::WhileStatement => visitor.visit_while_statement_mut(node),
SyntaxKind::AssignmentStatement => visitor.visit_assignment_statement_mut(node),
SyntaxKind::Identifier => visitor.visit_identifier_mut(node),
SyntaxKind::LiteralExpression => visitor.visit_literal_expression_mut(node),
SyntaxKind::BinaryExpression => visitor.visit_binary_expression_mut(node),
_ => {}
}
let children: Vec<_> = node.children().to_vec();
for mut child in children {
visitor.visit_node_mut(&mut child);
}
}
fn is_keyword(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::SubKeyword
| SyntaxKind::FunctionKeyword
| SyntaxKind::EndKeyword
| SyntaxKind::IfKeyword
| SyntaxKind::ThenKeyword
| SyntaxKind::ElseKeyword
| SyntaxKind::SelectKeyword
| SyntaxKind::CaseKeyword
| SyntaxKind::ForKeyword
| SyntaxKind::ToKeyword
| SyntaxKind::NextKeyword
| SyntaxKind::WhileKeyword
| SyntaxKind::WendKeyword
| SyntaxKind::DoKeyword
| SyntaxKind::LoopKeyword
| SyntaxKind::DimKeyword
| SyntaxKind::AsKeyword
| SyntaxKind::ConstKeyword
| SyntaxKind::PrivateKeyword
| SyntaxKind::PublicKeyword
| SyntaxKind::StaticKeyword
| SyntaxKind::TypeKeyword
| SyntaxKind::EnumKeyword
| SyntaxKind::WithKeyword
| SyntaxKind::NewKeyword
| SyntaxKind::SetKeyword
| SyntaxKind::GetKeyword
| SyntaxKind::LetKeyword
| SyntaxKind::PropertyKeyword
| SyntaxKind::ByValKeyword
| SyntaxKind::ByRefKeyword
| SyntaxKind::OptionalKeyword
| SyntaxKind::ParamArrayKeyword
| SyntaxKind::CallKeyword
| SyntaxKind::GotoKeyword
| SyntaxKind::OnKeyword
| SyntaxKind::ErrorKeyword
| SyntaxKind::ResumeKeyword
| SyntaxKind::ExitKeyword
| SyntaxKind::ReDimKeyword
| SyntaxKind::PreserveKeyword
| SyntaxKind::EraseKeyword
| SyntaxKind::OptionKeyword
| SyntaxKind::ExplicitKeyword
| SyntaxKind::CompareKeyword
| SyntaxKind::AttributeKeyword
| SyntaxKind::ImplementsKeyword
| SyntaxKind::EventKeyword
| SyntaxKind::RaiseEventKeyword
| SyntaxKind::DeclareKeyword
| SyntaxKind::LibKeyword
| SyntaxKind::AliasKeyword
)
}
fn is_operator(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::AdditionOperator
| SyntaxKind::SubtractionOperator
| SyntaxKind::MultiplicationOperator
| SyntaxKind::DivisionOperator
| SyntaxKind::BackwardSlashOperator
| SyntaxKind::ExponentiationOperator
| SyntaxKind::Ampersand
| SyntaxKind::EqualityOperator
| SyntaxKind::LessThanOperator
| SyntaxKind::GreaterThanOperator
| SyntaxKind::LessThanOrEqualOperator
| SyntaxKind::GreaterThanOrEqualOperator
| SyntaxKind::InequalityOperator
| SyntaxKind::AndKeyword
| SyntaxKind::OrKeyword
| SyntaxKind::NotKeyword
| SyntaxKind::XorKeyword
| SyntaxKind::EqvKeyword
| SyntaxKind::ImpKeyword
| SyntaxKind::ModKeyword
| SyntaxKind::IsKeyword
| SyntaxKind::LikeKeyword
)
}
fn is_punctuation(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::LeftParenthesis
| SyntaxKind::RightParenthesis
| SyntaxKind::Comma
| SyntaxKind::PeriodOperator
| SyntaxKind::ColonOperator
| SyntaxKind::Semicolon
| SyntaxKind::Octothorpe
| SyntaxKind::ExclamationMark
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ConcreteSyntaxTree;
#[test]
fn visitor_counts_identifiers() {
struct IdentifierCounter {
count: usize,
}
impl Visitor for IdentifierCounter {
fn visit_identifier(&mut self, _node: &CstNode) {
self.count += 1;
}
}
let source = "Sub Test()\nDim x As Integer\nDim y As String\nx = y\nEnd Sub";
let (cst_opt, _) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
let cst = cst_opt.expect("Failed to parse");
let root = cst.to_root_node();
let mut counter = IdentifierCounter { count: 0 };
counter.visit_node(&root);
assert!(counter.count >= 5);
}
#[test]
fn visitor_collects_sub_names() {
struct SubCollector {
names: Vec<String>,
}
impl Visitor for SubCollector {
fn visit_sub_statement(&mut self, node: &CstNode) {
if let Some(id) = node.find(SyntaxKind::Identifier) {
self.names.push(id.text().to_string());
}
}
}
let source = "Sub Alpha()\nEnd Sub\nSub Beta()\nEnd Sub\nSub Gamma()\nEnd Sub";
let (cst_opt, _) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
let cst = cst_opt.expect("Failed to parse");
let root = cst.to_root_node();
let mut collector = SubCollector { names: Vec::new() };
collector.visit_node(&root);
assert_eq!(collector.names.len(), 3);
assert!(collector.names.contains(&"Alpha".to_string()));
assert!(collector.names.contains(&"Beta".to_string()));
assert!(collector.names.contains(&"Gamma".to_string()));
}
#[test]
fn visitor_finds_dim_statements() {
struct DimCounter {
count: usize,
}
impl Visitor for DimCounter {
fn visit_dim_statement(&mut self, _node: &CstNode) {
self.count += 1;
}
}
let source = "Sub Test()\nDim x As Integer\nDim y, z As String\nEnd Sub";
let (cst_opt, _) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
let cst = cst_opt.expect("Failed to parse");
let root = cst.to_root_node();
let mut counter = DimCounter { count: 0 };
counter.visit_node(&root);
assert_eq!(counter.count, 2);
}
#[test]
fn visitor_multi_pass_analysis() {
struct DeclarationFinder {
declarations: Vec<String>,
}
impl Visitor for DeclarationFinder {
fn visit_dim_statement(&mut self, node: &CstNode) {
self.declarations.push(node.text().to_string());
}
}
struct StatementCounter {
subs: usize,
dims: usize,
}
impl Visitor for StatementCounter {
fn visit_sub_statement(&mut self, _node: &CstNode) {
self.subs += 1;
}
fn visit_dim_statement(&mut self, _node: &CstNode) {
self.dims += 1;
}
}
let source =
"Sub Test()\nDim x As Integer\nEnd Sub\nSub Another()\nDim y As String\nEnd Sub";
let (cst_opt, _) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
let cst = cst_opt.expect("Failed to parse");
let root = cst.to_root_node();
let mut decl_finder = DeclarationFinder {
declarations: Vec::new(),
};
decl_finder.visit_node(&root);
assert_eq!(decl_finder.declarations.len(), 2);
let mut counter = StatementCounter { subs: 0, dims: 0 };
counter.visit_node(&root);
assert_eq!(counter.subs, 2);
assert_eq!(counter.dims, 2);
}
#[test]
fn visitor_handles_nested_structures() {
struct NestedCounter {
if_count: usize,
for_count: usize,
}
impl Visitor for NestedCounter {
fn visit_if_statement(&mut self, _node: &CstNode) {
self.if_count += 1;
}
fn visit_for_statement(&mut self, _node: &CstNode) {
self.for_count += 1;
}
}
let source = r"Sub Test()
If x > 0 Then
For i = 1 To 10
If i < 5 Then
End If
Next i
End If
End Sub";
let (cst_opt, _) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
let cst = cst_opt.expect("Failed to parse");
let root = cst.to_root_node();
let mut counter = NestedCounter {
if_count: 0,
for_count: 0,
};
counter.visit_node(&root);
assert_eq!(counter.if_count, 2);
assert_eq!(counter.for_count, 1);
}
}