Skip to main content

plsql_parser/
visit.rs

1//! AST visitor and walker traits.
2//!
3//! This module provides the [`Visitor`] trait for traversing AST nodes, and
4//! the [`walk`] module with default traversal implementations.
5//!
6//! # Design
7//!
8//! - The [`Visitor`] trait has a `visit_*` method for each AST node type.
9//! - Each `visit_*` method has a default implementation that calls the
10//!   corresponding `walk_*` function, which recurses into children.
11//! - Override individual `visit_*` methods to intercept specific node types
12//!   without reimplementing the full traversal.
13//!
14//! # Extension
15//!
16//! As the AST grows (PARSE-004 through PARSE-011), new `visit_*` / `walk_*`
17//! pairs will be added.  The existing `visit_source_file` entry point
18//! dispatches to all child nodes.
19
20use plsql_core::Span;
21
22use crate::ast::{AstDecl, SourceFile};
23
24// ---------------------------------------------------------------------------
25// Visitor trait
26// ---------------------------------------------------------------------------
27
28/// Trait for visiting AST nodes.
29///
30/// Every method has a default implementation that recurses into children via
31/// the corresponding `walk_*` function.  Override only the methods you care
32/// about.
33///
34/// # Example
35///
36/// ```ignore
37/// use plsql_parser::visit::{Visitor, walk};
38/// use plsql_parser::ast::{SourceFile, AstDecl};
39///
40/// struct DeclCounter {
41///     count: usize,
42/// }
43///
44/// impl Visitor for DeclCounter {
45///     fn visit_decl(&mut self, decl: &AstDecl) {
46///         self.count += 1;
47///         walk::walk_decl(self, decl);
48///     }
49/// }
50/// ```
51pub trait Visitor: Sized {
52    /// Visit a source file (the root of the AST).
53    ///
54    /// Default: walk all declarations.
55    fn visit_source_file(&mut self, source_file: &SourceFile) {
56        walk::walk_source_file(self, source_file);
57    }
58
59    /// Visit a top-level declaration.
60    ///
61    /// Default: walk the specific declaration variant.
62    fn visit_decl(&mut self, decl: &AstDecl) {
63        walk::walk_decl(self, decl);
64    }
65
66    /// Visit a package specification.
67    fn visit_package_spec(&mut self, _name: &str, _span: &Span) {}
68
69    /// Visit a package body.
70    fn visit_package_body(&mut self, _name: &str, _span: &Span) {}
71
72    /// Visit a standalone procedure.
73    fn visit_procedure(&mut self, _name: &str, _span: &Span) {}
74
75    /// Visit a standalone function.
76    fn visit_function(&mut self, _name: &str, _span: &Span) {}
77
78    /// Visit a trigger.
79    fn visit_trigger(&mut self, _name: &str, _span: &Span) {}
80
81    /// Visit a view.
82    fn visit_view(&mut self, _name: &str, _span: &Span) {}
83
84    /// Visit a type specification.
85    fn visit_type_spec(&mut self, _name: &str, _span: &Span) {}
86
87    /// Visit a type body.
88    fn visit_type_body(&mut self, _name: &str, _span: &Span) {}
89
90    /// Visit a DDL statement.
91    fn visit_ddl(&mut self, _kind: &str, _span: &Span) {}
92
93    /// Visit an unknown/unclassified declaration (R13).
94    fn visit_unknown(&mut self, _span: &Span) {}
95}
96
97// ---------------------------------------------------------------------------
98// Walk functions (default traversal)
99// ---------------------------------------------------------------------------
100
101/// Default traversal implementations.
102///
103/// Each `walk_*` function calls the corresponding `visit_*` methods on
104/// child nodes.  Override `visit_*` to intercept; call `walk_*` to
105/// continue traversal.
106pub mod walk {
107    use super::*;
108
109    /// Walk all declarations in a source file.
110    pub fn walk_source_file<V: Visitor>(visitor: &mut V, source_file: &SourceFile) {
111        for decl in &source_file.declarations {
112            visitor.visit_decl(decl);
113        }
114    }
115
116    /// Walk a declaration by dispatching to the variant-specific visitor.
117    pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: &AstDecl) {
118        match decl {
119            AstDecl::PackageSpec { name, span } => visitor.visit_package_spec(name, span),
120            AstDecl::PackageBody { name, span } => visitor.visit_package_body(name, span),
121            AstDecl::Procedure { name, span } => visitor.visit_procedure(name, span),
122            AstDecl::Function { name, span } => visitor.visit_function(name, span),
123            AstDecl::Trigger { name, span } => visitor.visit_trigger(name, span),
124            AstDecl::View { name, span } => visitor.visit_view(name, span),
125            AstDecl::TypeSpec { name, span } => visitor.visit_type_spec(name, span),
126            AstDecl::TypeBody { name, span } => visitor.visit_type_body(name, span),
127            AstDecl::Ddl { kind, span, .. } => visitor.visit_ddl(kind, span),
128            AstDecl::Unknown { span, .. } => visitor.visit_unknown(span),
129        }
130    }
131}
132
133// ---------------------------------------------------------------------------
134// Convenience functions
135// ---------------------------------------------------------------------------
136
137/// Walk the entire AST with the given visitor.
138pub fn visit_source_file<V: Visitor>(visitor: &mut V, source_file: &SourceFile) {
139    visitor.visit_source_file(source_file);
140}
141
142// ---------------------------------------------------------------------------
143// Tests
144// ---------------------------------------------------------------------------
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::ast::AstDecl;
150    use plsql_core::{FileId, Position};
151
152    fn span(offset: u32, len: u32) -> Span {
153        Span::new(
154            FileId::new(0),
155            Position::new(1, 1, offset),
156            Position::new(1, 1, offset + len),
157        )
158    }
159
160    /// A visitor that counts declarations.
161    struct DeclCounter {
162        count: usize,
163        package_count: usize,
164        unknown_count: usize,
165    }
166
167    impl DeclCounter {
168        fn new() -> Self {
169            Self {
170                count: 0,
171                package_count: 0,
172                unknown_count: 0,
173            }
174        }
175    }
176
177    impl Visitor for DeclCounter {
178        fn visit_decl(&mut self, decl: &AstDecl) {
179            self.count += 1;
180            walk::walk_decl(self, decl);
181        }
182
183        fn visit_package_spec(&mut self, _name: &str, _span: &Span) {
184            self.package_count += 1;
185        }
186
187        fn visit_package_body(&mut self, _name: &str, _span: &Span) {
188            self.package_count += 1;
189        }
190
191        fn visit_unknown(&mut self, _span: &Span) {
192            self.unknown_count += 1;
193        }
194    }
195
196    #[test]
197    fn visitor_counts_declarations() {
198        let sf = SourceFile {
199            span: span(0, 200),
200            declarations: vec![
201                AstDecl::PackageSpec {
202                    name: "pkg_a".into(),
203                    span: span(0, 50),
204                },
205                AstDecl::PackageBody {
206                    name: "pkg_a".into(),
207                    span: span(50, 50),
208                },
209                AstDecl::Procedure {
210                    name: "standalone_p".into(),
211                    span: span(100, 50),
212                },
213                AstDecl::Unknown {
214                    span: span(150, 50),
215                    antlr_rule_path: None,
216                },
217            ],
218        };
219
220        let mut counter = DeclCounter::new();
221        visit_source_file(&mut counter, &sf);
222
223        assert_eq!(counter.count, 4);
224        assert_eq!(counter.package_count, 2);
225        assert_eq!(counter.unknown_count, 1);
226    }
227
228    /// A visitor that collects declaration names.
229    struct NameCollector {
230        names: Vec<String>,
231    }
232
233    impl NameCollector {
234        fn new() -> Self {
235            Self { names: Vec::new() }
236        }
237    }
238
239    impl Visitor for NameCollector {
240        fn visit_package_spec(&mut self, name: &str, _span: &Span) {
241            self.names.push(format!("package_spec:{name}"));
242        }
243
244        fn visit_package_body(&mut self, name: &str, _span: &Span) {
245            self.names.push(format!("package_body:{name}"));
246        }
247
248        fn visit_procedure(&mut self, name: &str, _span: &Span) {
249            self.names.push(format!("procedure:{name}"));
250        }
251
252        fn visit_function(&mut self, name: &str, _span: &Span) {
253            self.names.push(format!("function:{name}"));
254        }
255
256        fn visit_trigger(&mut self, name: &str, _span: &Span) {
257            self.names.push(format!("trigger:{name}"));
258        }
259
260        fn visit_view(&mut self, name: &str, _span: &Span) {
261            self.names.push(format!("view:{name}"));
262        }
263    }
264
265    #[test]
266    fn visitor_collects_names() {
267        let sf = SourceFile {
268            span: span(0, 100),
269            declarations: vec![
270                AstDecl::PackageSpec {
271                    name: "emp_pkg".into(),
272                    span: span(0, 20),
273                },
274                AstDecl::Function {
275                    name: "get_name".into(),
276                    span: span(20, 20),
277                },
278                AstDecl::View {
279                    name: "v_emp".into(),
280                    span: span(40, 20),
281                },
282                AstDecl::Trigger {
283                    name: "trg_audit".into(),
284                    span: span(60, 20),
285                },
286            ],
287        };
288
289        let mut collector = NameCollector::new();
290        visit_source_file(&mut collector, &sf);
291
292        assert_eq!(
293            collector.names,
294            vec![
295                "package_spec:emp_pkg",
296                "function:get_name",
297                "view:v_emp",
298                "trigger:trg_audit",
299            ]
300        );
301    }
302
303    /// A visitor that counts DDL statements by kind.
304    struct DdlCounter {
305        creates: usize,
306        alters: usize,
307        drops: usize,
308    }
309
310    impl DdlCounter {
311        fn new() -> Self {
312            Self {
313                creates: 0,
314                alters: 0,
315                drops: 0,
316            }
317        }
318    }
319
320    impl Visitor for DdlCounter {
321        fn visit_ddl(&mut self, kind: &str, _span: &Span) {
322            match kind {
323                "CREATE" => self.creates += 1,
324                "ALTER" => self.alters += 1,
325                "DROP" => self.drops += 1,
326                _ => {}
327            }
328        }
329    }
330
331    #[test]
332    fn visitor_counts_ddl_by_kind() {
333        let sf = SourceFile {
334            span: span(0, 100),
335            declarations: vec![
336                AstDecl::Ddl {
337                    kind: "CREATE".into(),
338                    span: span(0, 20),
339                    antlr_rule_path: None,
340                },
341                AstDecl::Ddl {
342                    kind: "ALTER".into(),
343                    span: span(20, 20),
344                    antlr_rule_path: None,
345                },
346                AstDecl::Ddl {
347                    kind: "DROP".into(),
348                    span: span(40, 20),
349                    antlr_rule_path: None,
350                },
351                AstDecl::Ddl {
352                    kind: "CREATE".into(),
353                    span: span(60, 20),
354                    antlr_rule_path: None,
355                },
356            ],
357        };
358
359        let mut counter = DdlCounter::new();
360        visit_source_file(&mut counter, &sf);
361
362        assert_eq!(counter.creates, 2);
363        assert_eq!(counter.alters, 1);
364        assert_eq!(counter.drops, 1);
365    }
366
367    #[test]
368    fn default_visitor_recurse_all_variants() {
369        let sf = SourceFile {
370            span: span(0, 200),
371            declarations: vec![
372                AstDecl::PackageSpec {
373                    name: "a".into(),
374                    span: span(0, 10),
375                },
376                AstDecl::PackageBody {
377                    name: "a".into(),
378                    span: span(10, 10),
379                },
380                AstDecl::Procedure {
381                    name: "p".into(),
382                    span: span(20, 10),
383                },
384                AstDecl::Function {
385                    name: "f".into(),
386                    span: span(30, 10),
387                },
388                AstDecl::Trigger {
389                    name: "t".into(),
390                    span: span(40, 10),
391                },
392                AstDecl::View {
393                    name: "v".into(),
394                    span: span(50, 10),
395                },
396                AstDecl::TypeSpec {
397                    name: "ty".into(),
398                    span: span(60, 10),
399                },
400                AstDecl::TypeBody {
401                    name: "ty".into(),
402                    span: span(70, 10),
403                },
404                AstDecl::Ddl {
405                    kind: "CREATE".into(),
406                    span: span(80, 10),
407                    antlr_rule_path: None,
408                },
409                AstDecl::Unknown {
410                    span: span(90, 10),
411                    antlr_rule_path: None,
412                },
413            ],
414        };
415
416        // Use a minimal visitor that does nothing — just verify no panic.
417        struct NoOpVisitor;
418        impl Visitor for NoOpVisitor {}
419
420        let mut visitor = NoOpVisitor;
421        visit_source_file(&mut visitor, &sf);
422    }
423}