perl_ast/ast.rs
1//! Abstract Syntax Tree definitions for Perl within the parsing and LSP workflow.
2//!
3//! This module defines the comprehensive AST node types that represent parsed Perl code
4//! during the Parse → Index → Navigate → Complete → Analyze stages. The design is optimized
5//! for both direct use in Rust analysis and for generating tree-sitter compatible
6//! S-expressions during large workspace processing operations.
7//!
8//! # LSP Workflow Integration
9//!
10//! The AST structures support Perl tooling workflows by:
11//! - **Parse**: Produced by the parser as the canonical syntax tree
12//! - **Index**: Traversed to build symbol and reference tables
13//! - **Navigate**: Provides locations for definition and reference lookups
14//! - **Complete**: Supplies context for completion, hover, and signature help
15//! - **Analyze**: Feeds semantic analysis, diagnostics, and refactoring
16//!
17//! # Performance Characteristics
18//!
19//! AST structures are optimized for large codebases with:
20//! - Memory-efficient node representation using `Box<Node>` for recursive structures
21//! - Fast pattern matching via enum variants for common Perl constructs
22//! - Location tracking for precise error reporting in large files
23//! - Cheap cloning for parallel analysis tasks
24//!
25//! # Usage Examples
26//!
27//! ## Basic AST Construction
28//!
29//! ```rust
30//! use perl_ast::{Node, NodeKind, SourceLocation};
31//!
32//! // Create a simple variable declaration node
33//! let location = SourceLocation { start: 0, end: 10 };
34//! let node = Node::new(
35//! NodeKind::VariableDeclaration {
36//! declarator: "my".to_string(),
37//! variable: Box::new(Node::new(
38//! NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
39//! location,
40//! )),
41//! attributes: vec![],
42//! initializer: None,
43//! },
44//! location,
45//! );
46//! assert_eq!(node.kind.kind_name(), "VariableDeclaration");
47//! ```
48//!
49//! ## Tree-sitter S-expression Generation
50//!
51//! ```rust
52//! use perl_ast::{Node, NodeKind, SourceLocation};
53//!
54//! let loc = SourceLocation { start: 0, end: 2 };
55//! let num = Node::new(NodeKind::Number { value: "42".to_string() }, loc);
56//! let program = Node::new(NodeKind::Program { statements: vec![num] }, loc);
57//!
58//! let sexp = program.to_sexp();
59//! assert!(sexp.starts_with("(source_file"));
60//! ```
61//!
62//! ## AST Traversal and Analysis
63//!
64//! ```rust
65//! use perl_ast::{Node, NodeKind, SourceLocation};
66//!
67//! fn count_variables(node: &Node) -> usize {
68//! let mut count = 0;
69//! match &node.kind {
70//! NodeKind::Variable { .. } => count += 1,
71//! NodeKind::Program { statements } => {
72//! for stmt in statements {
73//! count += count_variables(stmt);
74//! }
75//! }
76//! _ => {} // Handle other node types as needed
77//! }
78//! count
79//! }
80//!
81//! let loc = SourceLocation { start: 0, end: 5 };
82//! let var = Node::new(
83//! NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
84//! loc,
85//! );
86//! let program = Node::new(NodeKind::Program { statements: vec![var] }, loc);
87//! assert_eq!(count_variables(&program), 1);
88//! ```
89//!
90//! ## Parsing Integration
91//!
92//! In practice the AST is produced by the parser rather than built by hand
93//! (requires `perl-parser-core`):
94//!
95//! ```rust,ignore
96//! use perl_parser_core::Parser;
97//! use perl_ast::NodeKind;
98//!
99//! let mut parser = Parser::new("my $x = 42;");
100//! let ast = parser.parse().expect("should parse");
101//! assert!(matches!(ast.kind, NodeKind::Program { .. }));
102//! ```
103
104// Re-export SourceLocation from perl-position-tracking for unified span handling
105pub use perl_position_tracking::SourceLocation;
106// Re-export Token and TokenKind from perl-token for AST error nodes
107pub use perl_token::{Token, TokenKind};
108use std::fmt;
109use strum::VariantNames as _;
110
111/// Core AST node representing any Perl language construct within parsing workflows.
112///
113/// This is the fundamental building block for representing parsed Perl code. Each node
114/// contains both the semantic information (kind) and positional information (location)
115/// necessary for comprehensive script analysis.
116///
117/// # LSP Workflow Role
118///
119/// Nodes flow through tooling stages:
120/// - **Parse**: Created by the parser as it builds the syntax tree
121/// - **Index**: Visited to build symbol and reference tables
122/// - **Navigate**: Used to resolve definitions, references, and call hierarchy
123/// - **Complete**: Provides contextual information for completion and hover
124/// - **Analyze**: Drives semantic analysis and diagnostics
125///
126/// # Memory Optimization
127///
128/// The structure is designed for efficient memory usage during large-scale parsing:
129/// - `SourceLocation` uses compact position encoding for large files
130/// - `NodeKind` enum variants minimize memory overhead for common constructs
131/// - Clone operations are optimized for shared analysis workflows
132///
133/// # Examples
134///
135/// Construct a variable declaration node manually:
136///
137/// ```
138/// use perl_ast::{Node, NodeKind, SourceLocation};
139///
140/// let loc = SourceLocation { start: 0, end: 11 };
141/// let var = Node::new(
142/// NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
143/// loc,
144/// );
145/// let decl = Node::new(
146/// NodeKind::VariableDeclaration {
147/// declarator: "my".to_string(),
148/// variable: Box::new(var),
149/// attributes: vec![],
150/// initializer: None,
151/// },
152/// loc,
153/// );
154/// assert_eq!(decl.kind.kind_name(), "VariableDeclaration");
155/// ```
156///
157/// Typically you obtain nodes from the parser rather than constructing them by hand:
158///
159/// ```ignore
160/// use perl_parser::Parser;
161///
162/// let mut parser = Parser::new("my $x = 42;");
163/// let ast = parser.parse()?;
164/// println!("AST: {}", ast.to_sexp());
165/// ```
166#[derive(Debug, Clone, PartialEq)]
167pub struct Node {
168 /// The specific type and semantic content of this AST node
169 pub kind: NodeKind,
170 /// Source position information for error reporting and code navigation
171 pub location: SourceLocation,
172}
173
174impl Node {
175 /// Create a new AST node with the given kind and source location.
176 ///
177 /// # Examples
178 ///
179 /// ```
180 /// use perl_ast::{Node, NodeKind, SourceLocation};
181 ///
182 /// let node = Node::new(
183 /// NodeKind::Number { value: "42".to_string() },
184 /// SourceLocation { start: 0, end: 2 },
185 /// );
186 /// assert_eq!(node.kind.kind_name(), "Number");
187 /// assert_eq!(node.location.start, 0);
188 /// ```
189 pub fn new(kind: NodeKind, location: SourceLocation) -> Self {
190 Node { kind, location }
191 }
192
193 /// Convert the AST to a tree-sitter compatible S-expression.
194 ///
195 /// Produces a parenthesized representation compatible with tree-sitter's
196 /// S-expression format, useful for debugging and snapshot testing.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// use perl_ast::{Node, NodeKind, SourceLocation};
202 ///
203 /// let loc = SourceLocation { start: 0, end: 2 };
204 /// let num = Node::new(NodeKind::Number { value: "42".to_string() }, loc);
205 /// let program = Node::new(
206 /// NodeKind::Program { statements: vec![num] },
207 /// loc,
208 /// );
209 /// let sexp = program.to_sexp();
210 /// assert!(sexp.starts_with("(source_file"));
211 /// ```
212 pub fn to_sexp(&self) -> String {
213 match &self.kind {
214 NodeKind::Program { statements } => {
215 let stmts =
216 statements.iter().map(|s| s.to_sexp_inner()).collect::<Vec<_>>().join(" ");
217 format!("(source_file {})", stmts)
218 }
219
220 NodeKind::ExpressionStatement { expression } => {
221 format!("(expression_statement {})", expression.to_sexp())
222 }
223
224 NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
225 let attrs_str = if attributes.is_empty() {
226 String::new()
227 } else {
228 format!(" (attributes {})", attributes.join(" "))
229 };
230 if let Some(init) = initializer {
231 format!(
232 "({}_declaration {}{}{})",
233 declarator,
234 variable.to_sexp(),
235 attrs_str,
236 init.to_sexp()
237 )
238 } else {
239 format!("({}_declaration {}{})", declarator, variable.to_sexp(), attrs_str)
240 }
241 }
242
243 NodeKind::VariableListDeclaration {
244 declarator,
245 variables,
246 attributes,
247 initializer,
248 } => {
249 let vars = variables.iter().map(|v| v.to_sexp()).collect::<Vec<_>>().join(" ");
250 let attrs_str = if attributes.is_empty() {
251 String::new()
252 } else {
253 format!(" (attributes {})", attributes.join(" "))
254 };
255 if let Some(init) = initializer {
256 format!(
257 "({}_declaration ({}){}{})",
258 declarator,
259 vars,
260 attrs_str,
261 init.to_sexp()
262 )
263 } else {
264 format!("({}_declaration ({}){})", declarator, vars, attrs_str)
265 }
266 }
267
268 NodeKind::NestedVariableList { items } => {
269 let item_sexps = items.iter().map(|i| i.to_sexp()).collect::<Vec<_>>().join(" ");
270 format!("(nested_variable_list {})", item_sexps)
271 }
272
273 NodeKind::Variable { sigil, name } => {
274 // Format expected by bless parsing tests: (variable $ name)
275 format!("(variable {} {})", sigil, name)
276 }
277
278 NodeKind::VariableWithAttributes { variable, attributes } => {
279 let attrs = attributes.join(" ");
280 format!("({} (attributes {}))", variable.to_sexp(), attrs)
281 }
282
283 NodeKind::Assignment { lhs, rhs, op } => {
284 format!(
285 "(assignment_{} {} {})",
286 op.replace("=", "assign"),
287 lhs.to_sexp(),
288 rhs.to_sexp()
289 )
290 }
291
292 NodeKind::Binary { op, left, right } => {
293 // Tree-sitter format: (binary_op left right)
294 let op_name = format_binary_operator(op);
295 format!("({} {} {})", op_name, left.to_sexp(), right.to_sexp())
296 }
297
298 NodeKind::Ternary { condition, then_expr, else_expr } => {
299 format!(
300 "(ternary {} {} {})",
301 condition.to_sexp(),
302 then_expr.to_sexp(),
303 else_expr.to_sexp()
304 )
305 }
306
307 NodeKind::Unary { op, operand } => {
308 // Tree-sitter format: (unary_op operand)
309 let op_name = format_unary_operator(op);
310 format!("({} {})", op_name, operand.to_sexp())
311 }
312
313 NodeKind::Diamond => "(diamond)".to_string(),
314
315 NodeKind::Ellipsis => "(ellipsis)".to_string(),
316
317 NodeKind::Undef => "(undef)".to_string(),
318
319 NodeKind::Readline { filehandle } => {
320 if let Some(fh) = filehandle {
321 format!("(readline {})", fh)
322 } else {
323 "(readline)".to_string()
324 }
325 }
326
327 NodeKind::Glob { pattern } => {
328 format!("(glob {})", pattern)
329 }
330 NodeKind::Typeglob { name } => {
331 format!("(typeglob {})", name)
332 }
333
334 NodeKind::Number { value } => {
335 // Format expected by bless parsing tests: (number value)
336 format!("(number {})", value)
337 }
338
339 NodeKind::String { value, interpolated } => {
340 // Escape quotes in string value to prevent S-expression parsing issues
341 let escaped_value = value.replace('\\', "\\\\").replace('"', "\\\"");
342
343 // Format based on interpolation status
344 if *interpolated {
345 format!("(string_interpolated \"{}\")", escaped_value)
346 } else {
347 format!("(string \"{}\")", escaped_value)
348 }
349 }
350
351 NodeKind::Heredoc { delimiter, content, interpolated, indented, command, .. } => {
352 let type_str = if *command {
353 "heredoc_command"
354 } else if *indented {
355 if *interpolated { "heredoc_indented_interpolated" } else { "heredoc_indented" }
356 } else if *interpolated {
357 "heredoc_interpolated"
358 } else {
359 "heredoc"
360 };
361 format!("({} {:?} {:?})", type_str, delimiter, content)
362 }
363
364 NodeKind::ArrayLiteral { elements } => {
365 let elems = elements.iter().map(|e| e.to_sexp()).collect::<Vec<_>>().join(" ");
366 format!("(array {})", elems)
367 }
368
369 NodeKind::HashLiteral { pairs } => {
370 let kvs = pairs
371 .iter()
372 .map(|(k, v)| format!("({} {})", k.to_sexp(), v.to_sexp()))
373 .collect::<Vec<_>>()
374 .join(" ");
375 format!("(hash {})", kvs)
376 }
377
378 NodeKind::Block { statements } => {
379 let stmts = statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ");
380 format!("(block {})", stmts)
381 }
382
383 NodeKind::Eval { block } => {
384 format!("(eval {})", block.to_sexp())
385 }
386
387 NodeKind::Do { block } => {
388 format!("(do {})", block.to_sexp())
389 }
390
391 NodeKind::Defer { block } => {
392 format!("(defer {})", block.to_sexp())
393 }
394
395 NodeKind::Try { body, catch_blocks, finally_block } => {
396 let mut parts = vec![format!("(try {})", body.to_sexp())];
397
398 for (var, block) in catch_blocks {
399 if let Some(v) = var {
400 parts.push(format!("(catch {} {})", v, block.to_sexp()));
401 } else {
402 parts.push(format!("(catch {})", block.to_sexp()));
403 }
404 }
405
406 if let Some(finally) = finally_block {
407 parts.push(format!("(finally {})", finally.to_sexp()));
408 }
409
410 parts.join(" ")
411 }
412
413 NodeKind::If { condition, then_branch, elsif_branches, else_branch, keyword } => {
414 let kw = keyword.as_deref().unwrap_or("if");
415 let mut parts =
416 vec![format!("({} {} {})", kw, condition.to_sexp(), then_branch.to_sexp())];
417
418 for (cond, block) in elsif_branches {
419 parts.push(format!("(elsif {} {})", cond.to_sexp(), block.to_sexp()));
420 }
421
422 if let Some(else_block) = else_branch {
423 parts.push(format!("(else {})", else_block.to_sexp()));
424 }
425
426 parts.join(" ")
427 }
428
429 NodeKind::LabeledStatement { label, statement } => {
430 format!("(labeled_statement {} {})", label, statement.to_sexp())
431 }
432
433 NodeKind::While { condition, body, continue_block, keyword } => {
434 let kw = keyword.as_deref().unwrap_or("while");
435 let mut s = format!("({} {} {})", kw, condition.to_sexp(), body.to_sexp());
436 if let Some(cont) = continue_block {
437 s.push_str(&format!(" (continue {})", cont.to_sexp()));
438 }
439 s
440 }
441 NodeKind::Tie { variable, package, args } => {
442 let mut s = format!("(tie {} {}", variable.to_sexp(), package.to_sexp());
443 for arg in args {
444 s.push_str(&format!(" {}", arg.to_sexp()));
445 }
446 s.push(')');
447 s
448 }
449 NodeKind::Untie { variable } => {
450 format!("(untie {})", variable.to_sexp())
451 }
452 NodeKind::For { init, condition, update, body, continue_block } => {
453 let init_str =
454 init.as_ref().map(|i| i.to_sexp()).unwrap_or_else(|| "()".to_string());
455 let cond_str =
456 condition.as_ref().map(|c| c.to_sexp()).unwrap_or_else(|| "()".to_string());
457 let update_str =
458 update.as_ref().map(|u| u.to_sexp()).unwrap_or_else(|| "()".to_string());
459 let mut result =
460 format!("(for {} {} {} {})", init_str, cond_str, update_str, body.to_sexp());
461 if let Some(cont) = continue_block {
462 result.push_str(&format!(" (continue {})", cont.to_sexp()));
463 }
464 result
465 }
466
467 NodeKind::Foreach { variable, list, body, continue_block } => {
468 let cont = if let Some(cb) = continue_block {
469 format!(" {}", cb.to_sexp())
470 } else {
471 String::new()
472 };
473 format!(
474 "(foreach {} {} {}{})",
475 variable.to_sexp(),
476 list.to_sexp(),
477 body.to_sexp(),
478 cont
479 )
480 }
481
482 NodeKind::Given { expr, body } => {
483 format!("(given {} {})", expr.to_sexp(), body.to_sexp())
484 }
485
486 NodeKind::When { condition, body } => {
487 format!("(when {} {})", condition.to_sexp(), body.to_sexp())
488 }
489
490 NodeKind::Default { body } => {
491 format!("(default {})", body.to_sexp())
492 }
493
494 NodeKind::StatementModifier { statement, modifier, condition } => {
495 format!(
496 "(statement_modifier_{} {} {})",
497 modifier,
498 statement.to_sexp(),
499 condition.to_sexp()
500 )
501 }
502
503 NodeKind::Subroutine {
504 name,
505 prototype,
506 signature,
507 attributes,
508 body,
509 name_span: _,
510 declarator: _,
511 } => {
512 if let Some(sub_name) = name {
513 // Named subroutine - bless test expected format: (sub name () block)
514 let mut parts = vec![sub_name.clone()];
515
516 // Add attributes if present (before prototype/signature)
517 if !attributes.is_empty() {
518 for attr in attributes {
519 parts.push(format!(":{}", attr));
520 }
521 }
522
523 // Add prototype/signature - use () for empty prototype
524 if let Some(proto) = prototype {
525 parts.push(format!("({})", proto.to_sexp()));
526 } else if signature.is_some() {
527 // If there's a signature but no prototype, still show ()
528 parts.push("()".to_string());
529 } else {
530 parts.push("()".to_string());
531 }
532
533 // Add body
534 parts.push(body.to_sexp());
535
536 // Format: (sub name [attrs...] ()(block ...)) - space between name and (), no space between () and block
537 if parts.len() >= 3 && parts[parts.len() - 2] == "()" {
538 let name_and_attrs = parts[0..parts.len() - 2].join(" ");
539 let proto = &parts[parts.len() - 2];
540 let body = &parts[parts.len() - 1];
541 format!("(sub {} {}{})", name_and_attrs, proto, body)
542 } else {
543 format!("(sub {})", parts.join(" "))
544 }
545 } else {
546 // Anonymous subroutine - tree-sitter format
547 let mut parts = Vec::new();
548
549 // Add attributes if present
550 if !attributes.is_empty() {
551 let attrs: Vec<String> = attributes
552 .iter()
553 .map(|_attr| "(attribute (attribute_name))".to_string())
554 .collect();
555 parts.push(format!("(attrlist {})", attrs.join("")));
556 }
557
558 // Add prototype if present
559 if let Some(proto) = prototype {
560 parts.push(proto.to_sexp());
561 }
562
563 // Add signature if present
564 if let Some(sig) = signature {
565 parts.push(sig.to_sexp());
566 }
567
568 // Add body
569 parts.push(body.to_sexp());
570
571 format!("(anonymous_subroutine_expression {})", parts.join(""))
572 }
573 }
574
575 NodeKind::Prototype { content: _ } => "(prototype)".to_string(),
576
577 NodeKind::Signature { parameters } => {
578 let params = parameters.iter().map(|p| p.to_sexp()).collect::<Vec<_>>().join(" ");
579 format!("(signature {})", params)
580 }
581
582 NodeKind::MandatoryParameter { variable } => {
583 format!("(mandatory_parameter {})", variable.to_sexp())
584 }
585
586 NodeKind::OptionalParameter { variable, default_value } => {
587 format!("(optional_parameter {} {})", variable.to_sexp(), default_value.to_sexp())
588 }
589
590 NodeKind::SlurpyParameter { variable } => {
591 format!("(slurpy_parameter {})", variable.to_sexp())
592 }
593
594 NodeKind::NamedParameter { variable } => {
595 format!("(named_parameter {})", variable.to_sexp())
596 }
597
598 NodeKind::Method { name: _, name_span: _, signature, attributes, body } => {
599 let block_contents = match &body.kind {
600 NodeKind::Block { statements } => {
601 statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ")
602 }
603 _ => body.to_sexp(),
604 };
605
606 let mut parts = vec!["(bareword)".to_string()];
607
608 // Add signature if present
609 if let Some(sig) = signature {
610 parts.push(sig.to_sexp());
611 }
612
613 // Add attributes if present
614 if !attributes.is_empty() {
615 let attrs: Vec<String> = attributes
616 .iter()
617 .map(|_attr| "(attribute (attribute_name))".to_string())
618 .collect();
619 parts.push(format!("(attrlist {})", attrs.join("")));
620 }
621
622 parts.push(format!("(block {})", block_contents));
623 format!("(method_declaration_statement {})", parts.join(" "))
624 }
625
626 NodeKind::Return { value } => {
627 if let Some(val) = value {
628 format!("(return {})", val.to_sexp())
629 } else {
630 "(return)".to_string()
631 }
632 }
633
634 NodeKind::LoopControl { op, label } => {
635 if let Some(l) = label {
636 format!("({} {})", op, l)
637 } else {
638 format!("({})", op)
639 }
640 }
641
642 NodeKind::Goto { target } => {
643 format!("(goto {})", target.to_sexp())
644 }
645
646 NodeKind::MethodCall { object, method, args } => {
647 let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
648 format!("(method_call {} {} ({}))", object.to_sexp(), method, args_str)
649 }
650
651 NodeKind::FunctionCall { name, args } => {
652 // Special handling for functions that should use call format in tree-sitter tests
653 if matches!(
654 name.as_str(),
655 "bless"
656 | "shift"
657 | "unshift"
658 | "open"
659 | "die"
660 | "warn"
661 | "print"
662 | "printf"
663 | "say"
664 | "push"
665 | "pop"
666 | "map"
667 | "sort"
668 | "grep"
669 | "keys"
670 | "values"
671 | "each"
672 | "defined"
673 | "scalar"
674 | "ref"
675 ) {
676 let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
677 if args.is_empty() {
678 format!("(call {} ())", name)
679 } else {
680 format!("(call {} ({}))", name, args_str)
681 }
682 } else {
683 // Tree-sitter format varies by context
684 let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
685 if args.is_empty() {
686 "(function_call_expression (function))".to_string()
687 } else {
688 format!("(ambiguous_function_call_expression (function) {})", args_str)
689 }
690 }
691 }
692
693 NodeKind::IndirectCall { method, object, args } => {
694 let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
695 format!("(indirect_call {} {} ({}))", method, object.to_sexp(), args_str)
696 }
697
698 NodeKind::Regex { pattern, replacement, modifiers, has_embedded_code } => {
699 let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
700 format!("(regex {:?} {:?} {:?}{})", pattern, replacement, modifiers, risk_marker)
701 }
702
703 NodeKind::Match { expr, pattern, modifiers, has_embedded_code, negated } => {
704 let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
705 let op = if *negated { "not_match" } else { "match" };
706 format!(
707 "({} {} (regex {:?} {:?}{}))",
708 op,
709 expr.to_sexp(),
710 pattern,
711 modifiers,
712 risk_marker
713 )
714 }
715
716 NodeKind::Substitution {
717 expr,
718 pattern,
719 replacement,
720 modifiers,
721 has_embedded_code,
722 negated,
723 } => {
724 let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
725 let neg_marker = if *negated { " (negated)" } else { "" };
726 format!(
727 "(substitution {} {:?} {:?} {:?}{}{})",
728 expr.to_sexp(),
729 pattern,
730 replacement,
731 modifiers,
732 risk_marker,
733 neg_marker
734 )
735 }
736
737 NodeKind::Transliteration { expr, search, replace, modifiers, negated } => {
738 let neg_marker = if *negated { " (negated)" } else { "" };
739 format!(
740 "(transliteration {} {:?} {:?} {:?}{})",
741 expr.to_sexp(),
742 search,
743 replace,
744 modifiers,
745 neg_marker
746 )
747 }
748
749 NodeKind::Package { name, block, name_span: _ } => {
750 if let Some(blk) = block {
751 format!("(package {} {})", name, blk.to_sexp())
752 } else {
753 format!("(package {})", name)
754 }
755 }
756
757 NodeKind::Use { module, args, has_filter_risk } => {
758 let risk_marker = if *has_filter_risk { " (risk:filter)" } else { "" };
759 if args.is_empty() {
760 format!("(use {}{})", module, risk_marker)
761 } else {
762 let args_str = args.join(" ");
763 format!("(use {} ({}){})", module, args_str, risk_marker)
764 }
765 }
766
767 NodeKind::No { module, args, has_filter_risk } => {
768 let risk_marker = if *has_filter_risk { " (risk:filter)" } else { "" };
769 if args.is_empty() {
770 format!("(no {}{})", module, risk_marker)
771 } else {
772 let args_str = args.join(" ");
773 format!("(no {} ({}){})", module, args_str, risk_marker)
774 }
775 }
776
777 NodeKind::PhaseBlock { phase, phase_span: _, block } => {
778 format!("({} {})", phase, block.to_sexp())
779 }
780
781 NodeKind::DataSection { marker, body } => {
782 if let Some(body_text) = body {
783 format!("(data_section {} \"{}\")", marker, body_text.escape_default())
784 } else {
785 format!("(data_section {})", marker)
786 }
787 }
788
789 NodeKind::Class { name, name_span: _, parents, body } => {
790 if parents.is_empty() {
791 format!("(class {} {})", name, body.to_sexp())
792 } else {
793 format!("(class {} :isa({}) {})", name, parents.join(","), body.to_sexp())
794 }
795 }
796
797 NodeKind::Format { name, name_span: _, body } => {
798 format!("(format {} {:?})", name, body)
799 }
800
801 NodeKind::Identifier { name } => {
802 // Format expected by tests: (identifier name)
803 format!("(identifier {})", name)
804 }
805
806 NodeKind::Error { message, partial, .. } => {
807 if let Some(node) = partial {
808 format!("(ERROR \"{}\" {})", message.escape_default(), node.to_sexp())
809 } else {
810 format!("(ERROR \"{}\")", message.escape_default())
811 }
812 }
813 NodeKind::MissingExpression => "(missing_expression)".to_string(),
814 NodeKind::MissingStatement => "(missing_statement)".to_string(),
815 NodeKind::MissingIdentifier => "(missing_identifier)".to_string(),
816 NodeKind::MissingBlock => "(missing_block)".to_string(),
817 NodeKind::UnknownRest => "(UNKNOWN_REST)".to_string(),
818 }
819 }
820
821 /// Convert the AST to S-expression format that unwraps expression statements in programs
822 pub fn to_sexp_inner(&self) -> String {
823 match &self.kind {
824 NodeKind::ExpressionStatement { expression } => {
825 // Check if this is an anonymous subroutine - if so, keep it wrapped
826 match &expression.kind {
827 NodeKind::Subroutine { name, .. } if name.is_none() => {
828 // Anonymous subroutine should remain wrapped in expression statement
829 self.to_sexp()
830 }
831 _ => {
832 // In the inner format, other expression statements are unwrapped
833 expression.to_sexp()
834 }
835 }
836 }
837 _ => {
838 // For all other node types, use regular to_sexp
839 self.to_sexp()
840 }
841 }
842 }
843
844 /// Call a function on every direct child node of this node.
845 ///
846 /// This enables depth-first traversal for operations like heredoc content attachment.
847 /// The closure receives a mutable reference to each child node.
848 #[inline]
849 pub fn for_each_child_mut<F: FnMut(&mut Node)>(&mut self, mut f: F) {
850 match &mut self.kind {
851 NodeKind::Tie { variable, package, args } => {
852 f(variable);
853 f(package);
854 for arg in args {
855 f(arg);
856 }
857 }
858 NodeKind::Untie { variable } => f(variable),
859
860 // Root program node
861 NodeKind::Program { statements } => {
862 for stmt in statements {
863 f(stmt);
864 }
865 }
866
867 // Statement wrappers
868 NodeKind::ExpressionStatement { expression } => f(expression),
869
870 // Variable declarations
871 NodeKind::VariableDeclaration { variable, initializer, .. } => {
872 f(variable);
873 if let Some(init) = initializer {
874 f(init);
875 }
876 }
877 NodeKind::VariableListDeclaration { variables, initializer, .. } => {
878 for var in variables {
879 f(var);
880 }
881 if let Some(init) = initializer {
882 f(init);
883 }
884 }
885 NodeKind::NestedVariableList { items } => {
886 for item in items {
887 f(item);
888 }
889 }
890 NodeKind::VariableWithAttributes { variable, .. } => f(variable),
891
892 // Binary operations
893 NodeKind::Binary { left, right, .. } => {
894 f(left);
895 f(right);
896 }
897 NodeKind::Ternary { condition, then_expr, else_expr } => {
898 f(condition);
899 f(then_expr);
900 f(else_expr);
901 }
902 NodeKind::Unary { operand, .. } => f(operand),
903 NodeKind::Assignment { lhs, rhs, .. } => {
904 f(lhs);
905 f(rhs);
906 }
907
908 // Control flow
909 NodeKind::Block { statements } => {
910 for stmt in statements {
911 f(stmt);
912 }
913 }
914 NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
915 f(condition);
916 f(then_branch);
917 for (elsif_cond, elsif_body) in elsif_branches {
918 f(elsif_cond);
919 f(elsif_body);
920 }
921 if let Some(else_body) = else_branch {
922 f(else_body);
923 }
924 }
925 NodeKind::While { condition, body, continue_block, .. } => {
926 f(condition);
927 f(body);
928 if let Some(cont) = continue_block {
929 f(cont);
930 }
931 }
932 NodeKind::For { init, condition, update, body, continue_block, .. } => {
933 if let Some(i) = init {
934 f(i);
935 }
936 if let Some(c) = condition {
937 f(c);
938 }
939 if let Some(u) = update {
940 f(u);
941 }
942 f(body);
943 if let Some(cont) = continue_block {
944 f(cont);
945 }
946 }
947 NodeKind::Foreach { variable, list, body, continue_block } => {
948 f(variable);
949 f(list);
950 f(body);
951 if let Some(cb) = continue_block {
952 f(cb);
953 }
954 }
955 NodeKind::Given { expr, body } => {
956 f(expr);
957 f(body);
958 }
959 NodeKind::When { condition, body } => {
960 f(condition);
961 f(body);
962 }
963 NodeKind::Default { body } => f(body),
964 NodeKind::StatementModifier { statement, condition, .. } => {
965 f(statement);
966 f(condition);
967 }
968 NodeKind::LabeledStatement { statement, .. } => f(statement),
969
970 // Eval and Do blocks
971 NodeKind::Eval { block } => f(block),
972 NodeKind::Do { block } => f(block),
973 NodeKind::Defer { block } => f(block),
974 NodeKind::Try { body, catch_blocks, finally_block } => {
975 f(body);
976 for (_, catch_body) in catch_blocks {
977 f(catch_body);
978 }
979 if let Some(finally) = finally_block {
980 f(finally);
981 }
982 }
983
984 // Function calls
985 NodeKind::FunctionCall { args, .. } => {
986 for arg in args {
987 f(arg);
988 }
989 }
990 NodeKind::MethodCall { object, args, .. } => {
991 f(object);
992 for arg in args {
993 f(arg);
994 }
995 }
996 NodeKind::IndirectCall { object, args, .. } => {
997 f(object);
998 for arg in args {
999 f(arg);
1000 }
1001 }
1002
1003 // Functions
1004 NodeKind::Subroutine { prototype, signature, body, .. } => {
1005 if let Some(proto) = prototype {
1006 f(proto);
1007 }
1008 if let Some(sig) = signature {
1009 f(sig);
1010 }
1011 f(body);
1012 }
1013 NodeKind::Method { signature, body, .. } => {
1014 if let Some(sig) = signature {
1015 f(sig);
1016 }
1017 f(body);
1018 }
1019 NodeKind::Return { value } => {
1020 if let Some(v) = value {
1021 f(v);
1022 }
1023 }
1024 NodeKind::Goto { target } => f(target),
1025 NodeKind::Signature { parameters } => {
1026 for param in parameters {
1027 f(param);
1028 }
1029 }
1030 NodeKind::MandatoryParameter { variable } => f(variable),
1031 NodeKind::OptionalParameter { variable, default_value } => {
1032 f(variable);
1033 f(default_value);
1034 }
1035 NodeKind::SlurpyParameter { variable } => f(variable),
1036 NodeKind::NamedParameter { variable } => f(variable),
1037
1038 // Pattern matching
1039 NodeKind::Match { expr, .. } => f(expr),
1040 NodeKind::Substitution { expr, .. } => f(expr),
1041 NodeKind::Transliteration { expr, .. } => f(expr),
1042
1043 // Containers
1044 NodeKind::ArrayLiteral { elements } => {
1045 for elem in elements {
1046 f(elem);
1047 }
1048 }
1049 NodeKind::HashLiteral { pairs } => {
1050 for (key, value) in pairs {
1051 f(key);
1052 f(value);
1053 }
1054 }
1055
1056 // Package system
1057 NodeKind::Package { block, .. } => {
1058 if let Some(b) = block {
1059 f(b);
1060 }
1061 }
1062 NodeKind::PhaseBlock { block, .. } => f(block),
1063 NodeKind::Class { body, .. } => f(body),
1064
1065 // Error node might have a partial valid tree
1066 NodeKind::Error { partial, .. } => {
1067 if let Some(node) = partial {
1068 f(node);
1069 }
1070 }
1071
1072 // Leaf nodes (no children to traverse)
1073 NodeKind::Variable { .. }
1074 | NodeKind::Identifier { .. }
1075 | NodeKind::Number { .. }
1076 | NodeKind::String { .. }
1077 | NodeKind::Heredoc { .. }
1078 | NodeKind::Regex { .. }
1079 | NodeKind::Readline { .. }
1080 | NodeKind::Glob { .. }
1081 | NodeKind::Typeglob { .. }
1082 | NodeKind::Diamond
1083 | NodeKind::Ellipsis
1084 | NodeKind::Undef
1085 | NodeKind::Use { .. }
1086 | NodeKind::No { .. }
1087 | NodeKind::Prototype { .. }
1088 | NodeKind::DataSection { .. }
1089 | NodeKind::Format { .. }
1090 | NodeKind::LoopControl { .. }
1091 | NodeKind::MissingExpression
1092 | NodeKind::MissingStatement
1093 | NodeKind::MissingIdentifier
1094 | NodeKind::MissingBlock
1095 | NodeKind::UnknownRest => {}
1096 }
1097 }
1098
1099 /// Call a function on every direct child node of this node (immutable version).
1100 ///
1101 /// This enables depth-first traversal for read-only operations like AST analysis.
1102 /// The closure receives an immutable reference to each child node.
1103 #[inline]
1104 pub fn for_each_child<'a, F: FnMut(&'a Node)>(&'a self, mut f: F) {
1105 match &self.kind {
1106 NodeKind::Tie { variable, package, args } => {
1107 f(variable);
1108 f(package);
1109 for arg in args {
1110 f(arg);
1111 }
1112 }
1113 NodeKind::Untie { variable } => f(variable),
1114
1115 // Root program node
1116 NodeKind::Program { statements } => {
1117 for stmt in statements {
1118 f(stmt);
1119 }
1120 }
1121
1122 // Statement wrappers
1123 NodeKind::ExpressionStatement { expression } => f(expression),
1124
1125 // Variable declarations
1126 NodeKind::VariableDeclaration { variable, initializer, .. } => {
1127 f(variable);
1128 if let Some(init) = initializer {
1129 f(init);
1130 }
1131 }
1132 NodeKind::VariableListDeclaration { variables, initializer, .. } => {
1133 for var in variables {
1134 f(var);
1135 }
1136 if let Some(init) = initializer {
1137 f(init);
1138 }
1139 }
1140 NodeKind::NestedVariableList { items } => {
1141 for item in items {
1142 f(item);
1143 }
1144 }
1145 NodeKind::VariableWithAttributes { variable, .. } => f(variable),
1146
1147 // Binary operations
1148 NodeKind::Binary { left, right, .. } => {
1149 f(left);
1150 f(right);
1151 }
1152 NodeKind::Ternary { condition, then_expr, else_expr } => {
1153 f(condition);
1154 f(then_expr);
1155 f(else_expr);
1156 }
1157 NodeKind::Unary { operand, .. } => f(operand),
1158 NodeKind::Assignment { lhs, rhs, .. } => {
1159 f(lhs);
1160 f(rhs);
1161 }
1162
1163 // Control flow
1164 NodeKind::Block { statements } => {
1165 for stmt in statements {
1166 f(stmt);
1167 }
1168 }
1169 NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
1170 f(condition);
1171 f(then_branch);
1172 for (elsif_cond, elsif_body) in elsif_branches {
1173 f(elsif_cond);
1174 f(elsif_body);
1175 }
1176 if let Some(else_body) = else_branch {
1177 f(else_body);
1178 }
1179 }
1180 NodeKind::While { condition, body, continue_block, .. } => {
1181 f(condition);
1182 f(body);
1183 if let Some(cont) = continue_block {
1184 f(cont);
1185 }
1186 }
1187 NodeKind::For { init, condition, update, body, continue_block, .. } => {
1188 if let Some(i) = init {
1189 f(i);
1190 }
1191 if let Some(c) = condition {
1192 f(c);
1193 }
1194 if let Some(u) = update {
1195 f(u);
1196 }
1197 f(body);
1198 if let Some(cont) = continue_block {
1199 f(cont);
1200 }
1201 }
1202 NodeKind::Foreach { variable, list, body, continue_block } => {
1203 f(variable);
1204 f(list);
1205 f(body);
1206 if let Some(cb) = continue_block {
1207 f(cb);
1208 }
1209 }
1210 NodeKind::Given { expr, body } => {
1211 f(expr);
1212 f(body);
1213 }
1214 NodeKind::When { condition, body } => {
1215 f(condition);
1216 f(body);
1217 }
1218 NodeKind::Default { body } => f(body),
1219 NodeKind::StatementModifier { statement, condition, .. } => {
1220 f(statement);
1221 f(condition);
1222 }
1223 NodeKind::LabeledStatement { statement, .. } => f(statement),
1224
1225 // Eval and Do blocks
1226 NodeKind::Eval { block } => f(block),
1227 NodeKind::Do { block } => f(block),
1228 NodeKind::Defer { block } => f(block),
1229 NodeKind::Try { body, catch_blocks, finally_block } => {
1230 f(body);
1231 for (_, catch_body) in catch_blocks {
1232 f(catch_body);
1233 }
1234 if let Some(finally) = finally_block {
1235 f(finally);
1236 }
1237 }
1238
1239 // Function calls
1240 NodeKind::FunctionCall { args, .. } => {
1241 for arg in args {
1242 f(arg);
1243 }
1244 }
1245 NodeKind::MethodCall { object, args, .. } => {
1246 f(object);
1247 for arg in args {
1248 f(arg);
1249 }
1250 }
1251 NodeKind::IndirectCall { object, args, .. } => {
1252 f(object);
1253 for arg in args {
1254 f(arg);
1255 }
1256 }
1257
1258 // Functions
1259 NodeKind::Subroutine { prototype, signature, body, .. } => {
1260 if let Some(proto) = prototype {
1261 f(proto);
1262 }
1263 if let Some(sig) = signature {
1264 f(sig);
1265 }
1266 f(body);
1267 }
1268 NodeKind::Method { signature, body, .. } => {
1269 if let Some(sig) = signature {
1270 f(sig);
1271 }
1272 f(body);
1273 }
1274 NodeKind::Return { value } => {
1275 if let Some(v) = value {
1276 f(v);
1277 }
1278 }
1279 NodeKind::Goto { target } => f(target),
1280 NodeKind::Signature { parameters } => {
1281 for param in parameters {
1282 f(param);
1283 }
1284 }
1285 NodeKind::MandatoryParameter { variable } => f(variable),
1286 NodeKind::OptionalParameter { variable, default_value } => {
1287 f(variable);
1288 f(default_value);
1289 }
1290 NodeKind::SlurpyParameter { variable } => f(variable),
1291 NodeKind::NamedParameter { variable } => f(variable),
1292
1293 // Pattern matching
1294 NodeKind::Match { expr, .. } => f(expr),
1295 NodeKind::Substitution { expr, .. } => f(expr),
1296 NodeKind::Transliteration { expr, .. } => f(expr),
1297
1298 // Containers
1299 NodeKind::ArrayLiteral { elements } => {
1300 for elem in elements {
1301 f(elem);
1302 }
1303 }
1304 NodeKind::HashLiteral { pairs } => {
1305 for (key, value) in pairs {
1306 f(key);
1307 f(value);
1308 }
1309 }
1310
1311 // Package system
1312 NodeKind::Package { block, .. } => {
1313 if let Some(b) = block {
1314 f(b);
1315 }
1316 }
1317 NodeKind::PhaseBlock { block, .. } => f(block),
1318 NodeKind::Class { body, .. } => f(body),
1319
1320 // Error node might have a partial valid tree
1321 NodeKind::Error { partial, .. } => {
1322 if let Some(node) = partial {
1323 f(node);
1324 }
1325 }
1326
1327 // Leaf nodes (no children to traverse)
1328 NodeKind::Variable { .. }
1329 | NodeKind::Identifier { .. }
1330 | NodeKind::Number { .. }
1331 | NodeKind::String { .. }
1332 | NodeKind::Heredoc { .. }
1333 | NodeKind::Regex { .. }
1334 | NodeKind::Readline { .. }
1335 | NodeKind::Glob { .. }
1336 | NodeKind::Typeglob { .. }
1337 | NodeKind::Diamond
1338 | NodeKind::Ellipsis
1339 | NodeKind::Undef
1340 | NodeKind::Use { .. }
1341 | NodeKind::No { .. }
1342 | NodeKind::Prototype { .. }
1343 | NodeKind::DataSection { .. }
1344 | NodeKind::Format { .. }
1345 | NodeKind::LoopControl { .. }
1346 | NodeKind::MissingExpression
1347 | NodeKind::MissingStatement
1348 | NodeKind::MissingIdentifier
1349 | NodeKind::MissingBlock
1350 | NodeKind::UnknownRest => {}
1351 }
1352 }
1353
1354 /// Count the total number of nodes in this subtree (inclusive).
1355 ///
1356 /// # Examples
1357 ///
1358 /// ```
1359 /// use perl_ast::{Node, NodeKind, SourceLocation};
1360 ///
1361 /// let loc = SourceLocation { start: 0, end: 1 };
1362 /// let leaf = Node::new(NodeKind::Number { value: "1".to_string() }, loc);
1363 /// assert_eq!(leaf.count_nodes(), 1);
1364 ///
1365 /// let program = Node::new(
1366 /// NodeKind::Program { statements: vec![leaf] },
1367 /// loc,
1368 /// );
1369 /// assert_eq!(program.count_nodes(), 2);
1370 /// ```
1371 pub fn count_nodes(&self) -> usize {
1372 let mut count = 1;
1373 self.for_each_child(|child| {
1374 count += child.count_nodes();
1375 });
1376 count
1377 }
1378
1379 /// Collect direct child nodes into a vector for convenience APIs.
1380 ///
1381 /// # Examples
1382 ///
1383 /// ```
1384 /// use perl_ast::{Node, NodeKind, SourceLocation};
1385 ///
1386 /// let loc = SourceLocation { start: 0, end: 1 };
1387 /// let stmt = Node::new(NodeKind::Number { value: "1".to_string() }, loc);
1388 /// let program = Node::new(
1389 /// NodeKind::Program { statements: vec![stmt] },
1390 /// loc,
1391 /// );
1392 /// assert_eq!(program.children().len(), 1);
1393 /// ```
1394 #[inline]
1395 pub fn children(&self) -> Vec<&Node> {
1396 let mut children = Vec::new();
1397 self.for_each_child(|child| children.push(child));
1398 children
1399 }
1400
1401 /// Count direct child nodes without allocating an intermediate vector.
1402 ///
1403 /// This is more efficient than `children().len()` when callers only need
1404 /// cardinality.
1405 #[inline]
1406 pub fn child_count(&self) -> usize {
1407 let mut count = 0;
1408 self.for_each_child(|_| count += 1);
1409 count
1410 }
1411
1412 /// Get the first direct child node, if any.
1413 ///
1414 /// Optimized to avoid allocating the children vector.
1415 #[inline]
1416 pub fn first_child(&self) -> Option<&Node> {
1417 let mut result = None;
1418 self.for_each_child(|child| {
1419 if result.is_none() {
1420 result = Some(child);
1421 }
1422 });
1423 result
1424 }
1425
1426 /// Returns `true` when this node's source span contains `offset`.
1427 ///
1428 /// The start position is inclusive and the end position is exclusive.
1429 #[inline]
1430 pub fn contains_offset(&self, offset: usize) -> bool {
1431 self.location.start <= offset && offset < self.location.end
1432 }
1433
1434 /// Find the most specific node whose source span contains `offset`.
1435 ///
1436 /// Returns `None` when `offset` is outside this node. Otherwise, returns this
1437 /// node or the deepest descendant whose span contains the offset. This is useful
1438 /// for LSP features that need to map a cursor byte offset to the smallest AST
1439 /// construct at that position.
1440 ///
1441 /// The same half-open span semantics as [`Node::contains_offset`] apply: start
1442 /// positions are inclusive and end positions are exclusive.
1443 ///
1444 /// # Examples
1445 ///
1446 /// ```
1447 /// use perl_ast::{Node, NodeKind, SourceLocation};
1448 ///
1449 /// let left = Node::new(
1450 /// NodeKind::Identifier { name: "left".to_string() },
1451 /// SourceLocation { start: 0, end: 4 },
1452 /// );
1453 /// let right = Node::new(
1454 /// NodeKind::Number { value: "1".to_string() },
1455 /// SourceLocation { start: 7, end: 8 },
1456 /// );
1457 /// let expr = Node::new(
1458 /// NodeKind::Binary {
1459 /// op: "+".to_string(),
1460 /// left: Box::new(left),
1461 /// right: Box::new(right),
1462 /// },
1463 /// SourceLocation { start: 0, end: 8 },
1464 /// );
1465 ///
1466 /// assert_eq!(
1467 /// expr.find_deepest_containing_offset(7).map(|node| node.kind.kind_name()),
1468 /// Some("Number"),
1469 /// );
1470 /// assert_eq!(expr.find_deepest_containing_offset(8), None);
1471 /// ```
1472 #[inline]
1473 pub fn find_deepest_containing_offset(&self, offset: usize) -> Option<&Node> {
1474 if !self.contains_offset(offset) {
1475 return None;
1476 }
1477
1478 let mut result = self;
1479 self.for_each_child(|child| {
1480 if let Some(descendant) = child.find_deepest_containing_offset(offset) {
1481 result = descendant;
1482 }
1483 });
1484 Some(result)
1485 }
1486
1487 /// Returns the byte length of this node's source span.
1488 ///
1489 /// Uses saturating subtraction so malformed spans never underflow.
1490 #[inline]
1491 pub fn span_len(&self) -> usize {
1492 self.location.end.saturating_sub(self.location.start)
1493 }
1494
1495 /// Get the last direct child node, if any.
1496 ///
1497 /// Optimized to avoid allocating the children vector.
1498 ///
1499 /// # Examples
1500 ///
1501 /// ```
1502 /// use perl_ast::{Node, NodeKind, SourceLocation};
1503 ///
1504 /// let loc = SourceLocation { start: 0, end: 1 };
1505 /// let first = Node::new(NodeKind::Number { value: "1".to_string() }, loc);
1506 /// let second = Node::new(NodeKind::Number { value: "2".to_string() }, loc);
1507 /// let program = Node::new(
1508 /// NodeKind::Program { statements: vec![first, second] },
1509 /// loc,
1510 /// );
1511 ///
1512 /// assert_eq!(program.last_child().map(|n| n.kind.kind_name()), Some("Number"));
1513 /// assert_eq!(Node::new(NodeKind::Block { statements: vec![] }, loc).last_child(), None);
1514 /// ```
1515 #[inline]
1516 pub fn last_child(&self) -> Option<&Node> {
1517 let mut result = None;
1518 self.for_each_child(|child| {
1519 result = Some(child);
1520 });
1521 result
1522 }
1523}
1524
1525/// Comprehensive enumeration of all Perl language constructs supported by the parser.
1526///
1527/// This enum represents every possible AST node type that can be parsed from Perl code
1528/// during the Parse → Index → Navigate → Complete → Analyze workflow. Each variant captures
1529/// the semantic meaning and structural relationships needed for complete script analysis
1530/// and transformation.
1531///
1532/// # LSP Workflow Integration
1533///
1534/// Node kinds are processed differently across workflow stages:
1535/// - **Parse**: All variants are produced by the parser
1536/// - **Index**: Symbol-bearing variants feed workspace indexing
1537/// - **Navigate**: Call and reference variants support navigation features
1538/// - **Complete**: Expression variants provide completion context
1539/// - **Analyze**: Semantic variants drive diagnostics and refactoring
1540///
1541/// # Examples
1542///
1543/// Pattern-match on node kinds to extract semantic information:
1544///
1545/// ```
1546/// use perl_ast::{Node, NodeKind, SourceLocation};
1547///
1548/// let loc = SourceLocation { start: 0, end: 5 };
1549/// let node = Node::new(
1550/// NodeKind::Variable { sigil: "$".to_string(), name: "foo".to_string() },
1551/// loc,
1552/// );
1553///
1554/// assert!(matches!(
1555/// &node.kind,
1556/// NodeKind::Variable { sigil, name } if sigil == "$" && name == "foo"
1557/// ));
1558/// ```
1559///
1560/// Use [`kind_name()`](NodeKind::kind_name) for debugging and diagnostics:
1561///
1562/// ```
1563/// use perl_ast::NodeKind;
1564///
1565/// let kind = NodeKind::Number { value: "99".to_string() };
1566/// assert_eq!(kind.kind_name(), "Number");
1567///
1568/// let kind = NodeKind::Variable { sigil: "@".to_string(), name: "list".to_string() };
1569/// assert_eq!(kind.kind_name(), "Variable");
1570/// ```
1571///
1572/// # Performance Considerations
1573///
1574/// The enum design optimizes for large codebases:
1575/// - Box pointers minimize stack usage for recursive structures
1576/// - Vector storage enables efficient bulk operations on child nodes
1577/// - Clone operations optimized for concurrent analysis workflows
1578/// - Pattern matching performance tuned for common Perl constructs
1579#[derive(Debug, Clone, PartialEq, strum::VariantNames)]
1580pub enum NodeKind {
1581 /// Top-level program containing all statements in an Perl script
1582 ///
1583 /// This is the root node for any parsed Perl script content, containing all
1584 /// top-level statements found during the Parse stage of LSP workflow.
1585 Program {
1586 /// All top-level statements in the Perl script
1587 statements: Vec<Node>,
1588 },
1589
1590 /// Statement wrapper for expressions that appear at statement level
1591 ///
1592 /// Used during Analyze stage to distinguish between expressions used as
1593 /// statements versus expressions within other contexts during Perl parsing.
1594 ExpressionStatement {
1595 /// The expression being used as a statement
1596 expression: Box<Node>,
1597 },
1598
1599 /// Variable declaration with scope declarator in Perl script processing
1600 ///
1601 /// Represents declarations like `my $var`, `our $global`, `local $dynamic`, etc.
1602 /// Critical for Analyze stage symbol table construction during Perl parsing.
1603 VariableDeclaration {
1604 /// Scope declarator: "my", "our", "local", "state"
1605 declarator: String,
1606 /// The variable being declared
1607 variable: Box<Node>,
1608 /// Variable attributes (e.g., ":shared", ":locked")
1609 attributes: Vec<String>,
1610 /// Optional initializer expression
1611 initializer: Option<Box<Node>>,
1612 },
1613
1614 /// Multiple variable declaration in a single statement
1615 ///
1616 /// Handles constructs like `my ($x, $y) = @values` common in Perl script processing.
1617 /// Supports efficient bulk variable analysis during Navigate stage operations.
1618 VariableListDeclaration {
1619 /// Scope declarator for all variables in the list
1620 declarator: String,
1621 /// All variables being declared in the list
1622 variables: Vec<Node>,
1623 /// Attributes applied to the variable list
1624 attributes: Vec<String>,
1625 /// Optional initializer for the entire variable list
1626 initializer: Option<Box<Node>>,
1627 },
1628
1629 /// Nested variable list within a lexical list declaration.
1630 ///
1631 /// Represents a parenthesised group of variables inside a `my`/`our`/`state`
1632 /// list declaration, such as the `($b, $c)` in `my ($a, ($b, $c)) = ...`.
1633 /// A nested group with exactly one item is returned unwrapped (as the item
1634 /// itself), so this variant only appears for two-or-more-item groups.
1635 NestedVariableList {
1636 /// The variables or nested lists inside the inner parentheses.
1637 items: Vec<Node>,
1638 },
1639
1640 /// Perl variable reference (scalar, array, hash, etc.) in Perl parsing workflow
1641 Variable {
1642 /// Variable sigil indicating type: $, @, %, &, *
1643 sigil: String, // $, @, %, &, *
1644 /// Variable name without sigil
1645 name: String,
1646 },
1647
1648 /// Variable with additional attributes for enhanced LSP workflow
1649 VariableWithAttributes {
1650 /// The base variable node
1651 variable: Box<Node>,
1652 /// List of attribute names applied to the variable
1653 attributes: Vec<String>,
1654 },
1655
1656 /// Assignment operation for LSP data processing workflows
1657 Assignment {
1658 /// Left-hand side of assignment
1659 lhs: Box<Node>,
1660 /// Right-hand side of assignment
1661 rhs: Box<Node>,
1662 /// Assignment operator: =, +=, -=, etc.
1663 op: String, // =, +=, -=, etc.
1664 },
1665
1666 // Expressions
1667 /// Binary operation for Perl parsing workflow calculations
1668 Binary {
1669 /// Binary operator
1670 op: String,
1671 /// Left operand
1672 left: Box<Node>,
1673 /// Right operand
1674 right: Box<Node>,
1675 },
1676
1677 /// Ternary conditional expression for Perl parsing workflow logic
1678 Ternary {
1679 /// Condition to evaluate
1680 condition: Box<Node>,
1681 /// Expression when condition is true
1682 then_expr: Box<Node>,
1683 /// Expression when condition is false
1684 else_expr: Box<Node>,
1685 },
1686
1687 /// Unary operation for Perl parsing workflow
1688 Unary {
1689 /// Unary operator
1690 op: String,
1691 /// Operand to apply operator to
1692 operand: Box<Node>,
1693 },
1694
1695 // I/O operations
1696 /// Diamond operator for file input in Perl parsing workflow
1697 Diamond, // <>
1698
1699 /// Ellipsis operator for Perl parsing workflow
1700 Ellipsis, // ...
1701
1702 /// Undef value for Perl parsing workflow
1703 Undef, // undef
1704
1705 /// Readline operation for LSP file processing
1706 Readline {
1707 /// Optional filehandle: `<STDIN>`, `<$fh>`, etc.
1708 filehandle: Option<String>, // <STDIN>, <$fh>, etc.
1709 },
1710
1711 /// Glob pattern for LSP workspace file matching
1712 Glob {
1713 /// Pattern string for file matching
1714 pattern: String, // <*.txt>
1715 },
1716
1717 /// Typeglob expression: `*foo` or `*main::bar`
1718 ///
1719 /// Provides access to all symbol table entries for a given name.
1720 Typeglob {
1721 /// Name of the symbol (including package qualification)
1722 name: String,
1723 },
1724
1725 /// Numeric literal in Perl code (integer, float, hex, octal, binary)
1726 ///
1727 /// Represents all numeric literal forms: `42`, `3.14`, `0x1A`, `0o755`, `0b1010`.
1728 Number {
1729 /// String representation preserving original format
1730 value: String,
1731 },
1732
1733 /// String literal with optional interpolation
1734 ///
1735 /// Handles both single-quoted (`'literal'`) and double-quoted (`"$interpolated"`) strings.
1736 String {
1737 /// String content (after quote processing)
1738 value: String,
1739 /// Whether the string supports variable interpolation
1740 interpolated: bool,
1741 },
1742
1743 /// Heredoc string literal for multi-line content
1744 ///
1745 /// Supports all heredoc forms: `<<EOF`, `<<'EOF'`, `<<"EOF"`, `<<~EOF` (indented).
1746 Heredoc {
1747 /// Delimiter marking heredoc boundaries
1748 delimiter: String,
1749 /// Content between delimiters
1750 content: String,
1751 /// Whether content supports variable interpolation
1752 interpolated: bool,
1753 /// Whether leading whitespace is stripped (<<~ form)
1754 indented: bool,
1755 /// Whether this is a command execution heredoc (<<`EOF`)
1756 command: bool,
1757 /// Body span for breakpoint detection (populated by drain_pending_heredocs)
1758 body_span: Option<SourceLocation>,
1759 },
1760
1761 /// Array literal expression: `(1, 2, 3)` or `[1, 2, 3]`
1762 ArrayLiteral {
1763 /// Elements in the array
1764 elements: Vec<Node>,
1765 },
1766
1767 /// Hash literal expression: `(key => 'value')` or `{key => 'value'}`
1768 HashLiteral {
1769 /// Key-value pairs in the hash
1770 pairs: Vec<(Node, Node)>,
1771 },
1772
1773 /// Block of statements: `{ ... }`
1774 ///
1775 /// Used for control structures, subroutine bodies, and bare blocks.
1776 Block {
1777 /// Statements within the block
1778 statements: Vec<Node>,
1779 },
1780
1781 /// Eval block for exception handling: `eval { ... }`
1782 Eval {
1783 /// Block to evaluate with exception trapping
1784 block: Box<Node>,
1785 },
1786
1787 /// Do block for file inclusion or expression evaluation: `do { ... }` or `do "file"`
1788 Do {
1789 /// Block to execute or file expression
1790 block: Box<Node>,
1791 },
1792
1793 /// Defer block for deferred cleanup on scope exit (Perl 5.36+ experimental, stable in 5.40)
1794 Defer {
1795 /// Block to execute on scope exit
1796 block: Box<Node>,
1797 },
1798
1799 /// Try-catch-finally for modern exception handling (Syntax::Keyword::Try style)
1800 Try {
1801 /// Try block body
1802 body: Box<Node>,
1803 /// Catch blocks: (optional exception variable, handler block)
1804 catch_blocks: Vec<(Option<String>, Box<Node>)>,
1805 /// Optional finally block
1806 finally_block: Option<Box<Node>>,
1807 },
1808
1809 /// If-elsif-else conditional statement
1810 If {
1811 /// Condition expression
1812 condition: Box<Node>,
1813 /// Then branch block
1814 then_branch: Box<Node>,
1815 /// Elsif branches: (condition, block) pairs
1816 elsif_branches: Vec<(Box<Node>, Box<Node>)>,
1817 /// Optional else branch
1818 else_branch: Option<Box<Node>>,
1819 /// Original keyword: None for 'if', Some("unless") for 'unless' block form.
1820 keyword: Option<String>,
1821 },
1822
1823 /// Statement with a label for loop control: `LABEL: while (...)`
1824 LabeledStatement {
1825 /// Label name (e.g., "OUTER", "LINE")
1826 label: String,
1827 /// Labeled statement (typically a loop)
1828 statement: Box<Node>,
1829 },
1830
1831 /// While loop: `while (condition) { ... }`
1832 While {
1833 /// Loop condition
1834 condition: Box<Node>,
1835 /// Loop body
1836 body: Box<Node>,
1837 /// Optional continue block
1838 continue_block: Option<Box<Node>>,
1839 /// Original keyword: None for 'while', Some("until") for 'until' block form.
1840 keyword: Option<String>,
1841 },
1842
1843 /// Tie operation for binding variables to objects: `tie %hash, 'Package', @args`
1844 Tie {
1845 /// Variable being tied
1846 variable: Box<Node>,
1847 /// Class/package name to tie to
1848 package: Box<Node>,
1849 /// Arguments passed to TIE* method
1850 args: Vec<Node>,
1851 },
1852
1853 /// Untie operation for unbinding variables: `untie %hash`
1854 Untie {
1855 /// Variable being untied
1856 variable: Box<Node>,
1857 },
1858
1859 /// C-style for loop: `for (init; cond; update) { ... }`
1860 For {
1861 /// Initialization expression
1862 init: Option<Box<Node>>,
1863 /// Loop condition
1864 condition: Option<Box<Node>>,
1865 /// Update expression
1866 update: Option<Box<Node>>,
1867 /// Loop body
1868 body: Box<Node>,
1869 /// Optional continue block
1870 continue_block: Option<Box<Node>>,
1871 },
1872
1873 /// Foreach loop: `foreach my $item (@list) { ... }`
1874 Foreach {
1875 /// Iterator variable
1876 variable: Box<Node>,
1877 /// List to iterate
1878 list: Box<Node>,
1879 /// Loop body
1880 body: Box<Node>,
1881 /// Optional continue block
1882 continue_block: Option<Box<Node>>,
1883 },
1884
1885 /// Given statement for switch-like matching (Perl 5.10+)
1886 Given {
1887 /// Expression to match against
1888 expr: Box<Node>,
1889 /// Body containing when/default blocks
1890 body: Box<Node>,
1891 },
1892
1893 /// When clause in given/switch: `when ($pattern) { ... }`
1894 When {
1895 /// Pattern to match
1896 condition: Box<Node>,
1897 /// Handler block
1898 body: Box<Node>,
1899 },
1900
1901 /// Default clause in given/switch: `default { ... }`
1902 Default {
1903 /// Handler block for unmatched cases
1904 body: Box<Node>,
1905 },
1906
1907 /// Statement modifier syntax: `print "ok" if $condition`
1908 StatementModifier {
1909 /// Statement to conditionally execute
1910 statement: Box<Node>,
1911 /// Modifier keyword: if, unless, while, until, for, foreach
1912 modifier: String,
1913 /// Modifier condition
1914 condition: Box<Node>,
1915 },
1916
1917 // Functions
1918 /// Subroutine declaration (function) including name, prototype, signature and body.
1919 Subroutine {
1920 /// Name of the subroutine
1921 ///
1922 /// # Precise Navigation Support
1923 /// - Added name_span for exact LSP navigation
1924 /// - Enables precise go-to-definition and hover behavior
1925 /// - O(1) span lookup in workspace symbols
1926 ///
1927 /// ## Integration Points
1928 /// - Semantic token providers
1929 /// - Cross-reference generation
1930 /// - Symbol renaming
1931 name: Option<String>,
1932
1933 /// Source location span of the subroutine name
1934 ///
1935 /// ## Usage Notes
1936 /// - Always corresponds to the name field
1937 /// - Provides constant-time position information
1938 /// - Essential for precise editor interactions
1939 name_span: Option<SourceLocation>,
1940
1941 /// Optional scope declarator: "my", "our", or "state" for lexical/package-scoped subs
1942 ///
1943 /// # Lexical Subroutines
1944 /// - Perl 5.18+ feature: `my sub helper { ... }`, `our sub global { ... }`, `state sub memo { ... }`
1945 /// - Distinguishes lexical scope binding from package-scoped subroutines
1946 /// - Essential for scope tracking, renaming, and dead code detection
1947 ///
1948 /// # Values
1949 /// - `None` — package-scoped subroutine (no declarator)
1950 /// - `Some("my")` — lexical subroutine with lexical binding
1951 /// - `Some("our")` — package-scoped subroutine with explicit package declaration
1952 /// - `Some("state")` — persistent lexical subroutine (persistent across invocations)
1953 declarator: Option<String>,
1954
1955 /// Optional prototype node (e.g. `($;@)`).
1956 prototype: Option<Box<Node>>,
1957 /// Optional signature node (Perl 5.20+ feature).
1958 signature: Option<Box<Node>>,
1959 /// Attributes attached to the subroutine (`:lvalue`, etc.).
1960 attributes: Vec<String>,
1961 /// The body block of the subroutine.
1962 body: Box<Node>,
1963 },
1964
1965 /// Subroutine prototype specification: `sub foo ($;@) { ... }`
1966 Prototype {
1967 /// Prototype string defining argument behavior
1968 content: String,
1969 },
1970
1971 /// Subroutine signature (Perl 5.20+): `sub foo ($x, $y = 0) { ... }`
1972 Signature {
1973 /// List of signature parameters
1974 parameters: Vec<Node>,
1975 },
1976
1977 /// Mandatory signature parameter: `$x` in `sub foo ($x) { }`
1978 MandatoryParameter {
1979 /// Variable being bound
1980 variable: Box<Node>,
1981 },
1982
1983 /// Optional signature parameter with default: `$y = 0` in `sub foo ($y = 0) { }`
1984 OptionalParameter {
1985 /// Variable being bound
1986 variable: Box<Node>,
1987 /// Default value expression
1988 default_value: Box<Node>,
1989 },
1990
1991 /// Slurpy parameter collecting remaining args: `@rest` or `%opts` in signature
1992 SlurpyParameter {
1993 /// Array or hash variable to receive remaining arguments
1994 variable: Box<Node>,
1995 },
1996
1997 /// Named parameter placeholder in signature (future Perl feature)
1998 NamedParameter {
1999 /// Variable for named parameter binding
2000 variable: Box<Node>,
2001 },
2002
2003 /// Method declaration (Perl 5.38+ with `use feature 'class'`)
2004 Method {
2005 /// Method name
2006 name: String,
2007 /// Source location span of the method name
2008 name_span: Option<SourceLocation>,
2009 /// Optional signature
2010 signature: Option<Box<Node>>,
2011 /// Method attributes (e.g., `:lvalue`)
2012 attributes: Vec<String>,
2013 /// Method body
2014 body: Box<Node>,
2015 },
2016
2017 /// Return statement: `return;` or `return $value;`
2018 Return {
2019 /// Optional return value
2020 value: Option<Box<Node>>,
2021 },
2022
2023 /// Loop control statement: `next`, `last`, or `redo`
2024 LoopControl {
2025 /// Control keyword: "next", "last", or "redo"
2026 op: String,
2027 /// Optional label: `next LABEL`
2028 label: Option<String>,
2029 },
2030
2031 /// Goto statement: `goto LABEL`, `goto &sub`, or `goto $expr`
2032 Goto {
2033 /// The target of the goto (label identifier, sub reference, or expression)
2034 target: Box<Node>,
2035 },
2036
2037 /// Method call: `$obj->method(@args)` or `$obj->method`
2038 MethodCall {
2039 /// Object or class expression
2040 object: Box<Node>,
2041 /// Method name being called
2042 method: String,
2043 /// Method arguments
2044 args: Vec<Node>,
2045 },
2046
2047 /// Function call: `foo(@args)` or `foo()`
2048 FunctionCall {
2049 /// Function name (may be qualified: `Package::func`)
2050 name: String,
2051 /// Function arguments
2052 args: Vec<Node>,
2053 },
2054
2055 /// Indirect object call (legacy syntax): `new Class @args`
2056 IndirectCall {
2057 /// Method name
2058 method: String,
2059 /// Object or class
2060 object: Box<Node>,
2061 /// Arguments
2062 args: Vec<Node>,
2063 },
2064
2065 /// Regex literal: `/pattern/modifiers` or `qr/pattern/modifiers`
2066 Regex {
2067 /// Regular expression pattern
2068 pattern: String,
2069 /// Replacement string (for s/// when parsed as regex)
2070 replacement: Option<String>,
2071 /// Regex modifiers (i, m, s, x, g, etc.)
2072 modifiers: String,
2073 /// Whether the regex contains embedded code `(?{...})`
2074 has_embedded_code: bool,
2075 },
2076
2077 /// Match operation: `$str =~ /pattern/modifiers` or `$str !~ /pattern/modifiers`
2078 Match {
2079 /// Expression to match against
2080 expr: Box<Node>,
2081 /// Pattern to match
2082 pattern: String,
2083 /// Match modifiers
2084 modifiers: String,
2085 /// Whether the regex contains embedded code `(?{...})`
2086 has_embedded_code: bool,
2087 /// Whether the binding operator was `!~` (negated match)
2088 negated: bool,
2089 },
2090
2091 /// Substitution operation: `$str =~ s/pattern/replacement/modifiers`
2092 Substitution {
2093 /// Expression to substitute in
2094 expr: Box<Node>,
2095 /// Pattern to find
2096 pattern: String,
2097 /// Replacement string
2098 replacement: String,
2099 /// Substitution modifiers (g, e, r, etc.)
2100 modifiers: String,
2101 /// Whether the substitution contains embedded code — either a `(?{...})` inline
2102 /// code block in the pattern, or the `e`/`ee` modifier which evaluates the
2103 /// replacement string as Perl code (equivalent to `eval`).
2104 has_embedded_code: bool,
2105 /// Whether the binding operator was `!~` (negated match)
2106 negated: bool,
2107 },
2108
2109 /// Transliteration operation: `$str =~ tr/search/replace/` or `y///`
2110 Transliteration {
2111 /// Expression to transliterate
2112 expr: Box<Node>,
2113 /// Characters to search for
2114 search: String,
2115 /// Replacement characters
2116 replace: String,
2117 /// Transliteration modifiers (c, d, s, r)
2118 modifiers: String,
2119 /// Whether the binding operator was `!~` (negated match)
2120 negated: bool,
2121 },
2122
2123 // Package system
2124 /// Package declaration (e.g. `package Foo;`) and optional inline block form.
2125 Package {
2126 /// Name of the package
2127 ///
2128 /// # Precise Navigation Support
2129 /// - Added name_span for exact LSP navigation
2130 /// - Enables precise go-to-definition and hover behavior
2131 /// - O(1) span lookup in workspace symbols
2132 ///
2133 /// ## Integration Points
2134 /// - Workspace indexing
2135 /// - Cross-module symbol resolution
2136 /// - Code action providers
2137 name: String,
2138
2139 /// Source location span of the package name
2140 ///
2141 /// ## Usage Notes
2142 /// - Always corresponds to the name field
2143 /// - Provides constant-time position information
2144 /// - Essential for precise editor interactions
2145 name_span: SourceLocation,
2146
2147 /// Optional inline block for `package Foo { ... }` declarations.
2148 block: Option<Box<Node>>,
2149 },
2150
2151 /// Use statement for module loading: `use Module qw(imports);`
2152 Use {
2153 /// Module name to load
2154 module: String,
2155 /// Import arguments (symbols to import)
2156 args: Vec<String>,
2157 /// Whether this module is a known source filter (security risk)
2158 has_filter_risk: bool,
2159 },
2160
2161 /// No statement for disabling features: `no strict;`
2162 No {
2163 /// Module/pragma name to disable
2164 module: String,
2165 /// Arguments for the no statement
2166 args: Vec<String>,
2167 /// Whether this module is a known source filter (security risk)
2168 has_filter_risk: bool,
2169 },
2170
2171 /// Phase block for compile/runtime hooks: `BEGIN`, `END`, `CHECK`, `INIT`, `UNITCHECK`
2172 PhaseBlock {
2173 /// Phase name: BEGIN, END, CHECK, INIT, UNITCHECK
2174 phase: String,
2175 /// Source location span of the phase block name for precise navigation
2176 phase_span: Option<SourceLocation>,
2177 /// Block to execute during the specified phase
2178 block: Box<Node>,
2179 },
2180
2181 /// Data section marker: `__DATA__` or `__END__`
2182 DataSection {
2183 /// Section marker (__DATA__ or __END__)
2184 marker: String,
2185 /// Content following the marker (if any)
2186 body: Option<String>,
2187 },
2188
2189 /// Class declaration (Perl 5.38+ with `use feature 'class'`)
2190 Class {
2191 /// Class name
2192 name: String,
2193 /// Source location span of the class name
2194 name_span: Option<SourceLocation>,
2195 /// Parent class names from `:isa(Parent)` attributes
2196 parents: Vec<String>,
2197 /// Class body containing methods and attributes
2198 body: Box<Node>,
2199 },
2200
2201 /// Format declaration for legacy report generation
2202 Format {
2203 /// Format name (defaults to filehandle name)
2204 name: String,
2205 /// Source location span of the format name
2206 name_span: Option<SourceLocation>,
2207 /// Format specification body
2208 body: String,
2209 },
2210
2211 /// Bare identifier (bareword or package-qualified name)
2212 Identifier {
2213 /// Identifier string
2214 name: String,
2215 },
2216
2217 /// Parse error placeholder with error message and recovery context
2218 Error {
2219 /// Error description
2220 message: String,
2221 /// Expected token types (if any)
2222 expected: Vec<TokenKind>,
2223 /// The token actually found (if any)
2224 found: Option<Token>,
2225 /// Partial AST node parsed before error (if any)
2226 partial: Option<Box<Node>>,
2227 },
2228
2229 /// Missing expression where one was expected.
2230 ///
2231 /// Emitted by `recover_missing_infix_rhs` when a binary operator has no
2232 /// right-hand-side (e.g. `1 +` at end of input). This is the **only**
2233 /// `Missing*` variant currently emitted by the production parser.
2234 MissingExpression,
2235
2236 /// RESERVED — not currently emitted by the parser.
2237 ///
2238 /// Retained for API symmetry and future error-recovery work. If recovery
2239 /// starts emitting this variant, add real parser fixture tests before
2240 /// shipping. Do not pattern-match on this variant expecting it to appear
2241 /// in normal parse output.
2242 MissingStatement,
2243
2244 /// RESERVED — not currently emitted by the parser.
2245 ///
2246 /// Retained for API symmetry and future error-recovery work. If recovery
2247 /// starts emitting this variant, add real parser fixture tests before
2248 /// shipping. Do not pattern-match on this variant expecting it to appear
2249 /// in normal parse output.
2250 MissingIdentifier,
2251
2252 /// RESERVED — not currently emitted by the parser.
2253 ///
2254 /// Retained for API symmetry and future error-recovery work. If recovery
2255 /// starts emitting this variant, add real parser fixture tests before
2256 /// shipping. Do not pattern-match on this variant expecting it to appear
2257 /// in normal parse output.
2258 MissingBlock,
2259
2260 /// Lexer budget exceeded marker preserving partial parse results
2261 ///
2262 /// Used when recursion or token limits are hit to preserve already-parsed content.
2263 UnknownRest,
2264}
2265
2266impl NodeKind {
2267 /// Get the name of this `NodeKind` as a static string.
2268 ///
2269 /// Useful for diagnostics, logging, and human-readable AST dumps.
2270 ///
2271 /// # Examples
2272 ///
2273 /// ```
2274 /// use perl_ast::NodeKind;
2275 ///
2276 /// let kind = NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() };
2277 /// assert_eq!(kind.kind_name(), "Variable");
2278 ///
2279 /// let kind = NodeKind::Program { statements: vec![] };
2280 /// assert_eq!(kind.kind_name(), "Program");
2281 /// ```
2282 pub fn kind_name(&self) -> &'static str {
2283 match self {
2284 NodeKind::Program { .. } => "Program",
2285 NodeKind::ExpressionStatement { .. } => "ExpressionStatement",
2286 NodeKind::VariableDeclaration { .. } => "VariableDeclaration",
2287 NodeKind::VariableListDeclaration { .. } => "VariableListDeclaration",
2288 NodeKind::NestedVariableList { .. } => "NestedVariableList",
2289 NodeKind::Variable { .. } => "Variable",
2290 NodeKind::VariableWithAttributes { .. } => "VariableWithAttributes",
2291 NodeKind::Assignment { .. } => "Assignment",
2292 NodeKind::Binary { .. } => "Binary",
2293 NodeKind::Ternary { .. } => "Ternary",
2294 NodeKind::Unary { .. } => "Unary",
2295 NodeKind::Diamond => "Diamond",
2296 NodeKind::Ellipsis => "Ellipsis",
2297 NodeKind::Undef => "Undef",
2298 NodeKind::Readline { .. } => "Readline",
2299 NodeKind::Glob { .. } => "Glob",
2300 NodeKind::Typeglob { .. } => "Typeglob",
2301 NodeKind::Number { .. } => "Number",
2302 NodeKind::String { .. } => "String",
2303 NodeKind::Heredoc { .. } => "Heredoc",
2304 NodeKind::ArrayLiteral { .. } => "ArrayLiteral",
2305 NodeKind::HashLiteral { .. } => "HashLiteral",
2306 NodeKind::Block { .. } => "Block",
2307 NodeKind::Eval { .. } => "Eval",
2308 NodeKind::Do { .. } => "Do",
2309 NodeKind::Defer { .. } => "Defer",
2310 NodeKind::Try { .. } => "Try",
2311 NodeKind::If { .. } => "If",
2312 NodeKind::LabeledStatement { .. } => "LabeledStatement",
2313 NodeKind::While { .. } => "While",
2314 NodeKind::Tie { .. } => "Tie",
2315 NodeKind::Untie { .. } => "Untie",
2316 NodeKind::For { .. } => "For",
2317 NodeKind::Foreach { .. } => "Foreach",
2318 NodeKind::Given { .. } => "Given",
2319 NodeKind::When { .. } => "When",
2320 NodeKind::Default { .. } => "Default",
2321 NodeKind::StatementModifier { .. } => "StatementModifier",
2322 NodeKind::Subroutine { .. } => "Subroutine",
2323 NodeKind::Prototype { .. } => "Prototype",
2324 NodeKind::Signature { .. } => "Signature",
2325 NodeKind::MandatoryParameter { .. } => "MandatoryParameter",
2326 NodeKind::OptionalParameter { .. } => "OptionalParameter",
2327 NodeKind::SlurpyParameter { .. } => "SlurpyParameter",
2328 NodeKind::NamedParameter { .. } => "NamedParameter",
2329 NodeKind::Method { .. } => "Method",
2330 NodeKind::Return { .. } => "Return",
2331 NodeKind::LoopControl { .. } => "LoopControl",
2332 NodeKind::Goto { .. } => "Goto",
2333 NodeKind::MethodCall { .. } => "MethodCall",
2334 NodeKind::FunctionCall { .. } => "FunctionCall",
2335 NodeKind::IndirectCall { .. } => "IndirectCall",
2336 NodeKind::Regex { .. } => "Regex",
2337 NodeKind::Match { .. } => "Match",
2338 NodeKind::Substitution { .. } => "Substitution",
2339 NodeKind::Transliteration { .. } => "Transliteration",
2340 NodeKind::Package { .. } => "Package",
2341 NodeKind::Use { .. } => "Use",
2342 NodeKind::No { .. } => "No",
2343 NodeKind::PhaseBlock { .. } => "PhaseBlock",
2344 NodeKind::DataSection { .. } => "DataSection",
2345 NodeKind::Class { .. } => "Class",
2346 NodeKind::Format { .. } => "Format",
2347 NodeKind::Identifier { .. } => "Identifier",
2348 NodeKind::Error { .. } => "Error",
2349 NodeKind::MissingExpression => "MissingExpression",
2350 NodeKind::MissingStatement => "MissingStatement",
2351 NodeKind::MissingIdentifier => "MissingIdentifier",
2352 NodeKind::MissingBlock => "MissingBlock",
2353 NodeKind::UnknownRest => "UnknownRest",
2354 }
2355 }
2356
2357 /// Canonical list of **all** `kind_name()` strings, in declaration order.
2358 ///
2359 /// Auto-derived from the `NodeKind` enum via `strum::VariantNames` — adding a new
2360 /// variant automatically updates this list. No manual maintenance required.
2361 ///
2362 /// Every consumer that needs the full set of NodeKind names should reference
2363 /// this constant instead of maintaining a hand-written copy.
2364 pub const ALL_KIND_NAMES: &[&'static str] = NodeKind::VARIANTS;
2365
2366 /// Subset of `ALL_KIND_NAMES` that represent synthetic/recovery nodes.
2367 ///
2368 /// These kinds are only produced by `parse_with_recovery()` on malformed
2369 /// input and should not be expected in clean parses.
2370 pub const RECOVERY_KIND_NAMES: &[&'static str] = &[
2371 "Error",
2372 "MissingBlock",
2373 "MissingExpression",
2374 "MissingIdentifier",
2375 "MissingStatement",
2376 "UnknownRest",
2377 ];
2378}
2379
2380impl fmt::Display for NodeKind {
2381 /// Formats as the canonical `kind_name()` string.
2382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2383 f.write_str(self.kind_name())
2384 }
2385}
2386
2387impl fmt::Display for Node {
2388 /// Formats as the tree-sitter compatible S-expression.
2389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2390 f.write_str(&self.to_sexp())
2391 }
2392}
2393
2394/// Format unary operator for S-expression output
2395fn format_unary_operator(op: &str) -> String {
2396 match op {
2397 // Arithmetic unary operators
2398 "+" => "unary_+".to_string(),
2399 "-" => "unary_-".to_string(),
2400
2401 // Logical unary operators
2402 "!" => "unary_not".to_string(),
2403 "not" => "unary_not".to_string(),
2404
2405 // Bitwise complement
2406 "~" => "unary_complement".to_string(),
2407
2408 // Reference operator
2409 "\\" => "unary_ref".to_string(),
2410
2411 // Postfix operators
2412 "++" => "unary_++".to_string(),
2413 "--" => "unary_--".to_string(),
2414
2415 // File test operators
2416 "-f" => "unary_-f".to_string(),
2417 "-d" => "unary_-d".to_string(),
2418 "-e" => "unary_-e".to_string(),
2419 "-r" => "unary_-r".to_string(),
2420 "-w" => "unary_-w".to_string(),
2421 "-x" => "unary_-x".to_string(),
2422 "-o" => "unary_-o".to_string(),
2423 "-R" => "unary_-R".to_string(),
2424 "-W" => "unary_-W".to_string(),
2425 "-X" => "unary_-X".to_string(),
2426 "-O" => "unary_-O".to_string(),
2427 "-s" => "unary_-s".to_string(),
2428 "-p" => "unary_-p".to_string(),
2429 "-S" => "unary_-S".to_string(),
2430 "-b" => "unary_-b".to_string(),
2431 "-c" => "unary_-c".to_string(),
2432 "-t" => "unary_-t".to_string(),
2433 "-u" => "unary_-u".to_string(),
2434 "-g" => "unary_-g".to_string(),
2435 "-k" => "unary_-k".to_string(),
2436 "-T" => "unary_-T".to_string(),
2437 "-B" => "unary_-B".to_string(),
2438 "-M" => "unary_-M".to_string(),
2439 "-A" => "unary_-A".to_string(),
2440 "-C" => "unary_-C".to_string(),
2441 "-l" => "unary_-l".to_string(),
2442 "-z" => "unary_-z".to_string(),
2443
2444 // Postfix dereferencing
2445 "->@*" => "unary_->@*".to_string(),
2446 "->%*" => "unary_->%*".to_string(),
2447 "->$*" => "unary_->$*".to_string(),
2448 "->&*" => "unary_->&*".to_string(),
2449 "->**" => "unary_->**".to_string(),
2450
2451 // Defined operator
2452 "defined" => "unary_defined".to_string(),
2453
2454 // Default case for unknown operators
2455 _ => format!("unary_{}", op.replace(' ', "_")),
2456 }
2457}
2458
2459/// Format binary operator for S-expression output
2460fn format_binary_operator(op: &str) -> String {
2461 match op {
2462 // Arithmetic operators
2463 "+" => "binary_+".to_string(),
2464 "-" => "binary_-".to_string(),
2465 "*" => "binary_*".to_string(),
2466 "/" => "binary_/".to_string(),
2467 "%" => "binary_%".to_string(),
2468 "**" => "binary_**".to_string(),
2469
2470 // Comparison operators
2471 "==" => "binary_==".to_string(),
2472 "!=" => "binary_!=".to_string(),
2473 "<" => "binary_<".to_string(),
2474 ">" => "binary_>".to_string(),
2475 "<=" => "binary_<=".to_string(),
2476 ">=" => "binary_>=".to_string(),
2477 "<=>" => "binary_<=>".to_string(),
2478
2479 // String comparison
2480 "eq" => "binary_eq".to_string(),
2481 "ne" => "binary_ne".to_string(),
2482 "lt" => "binary_lt".to_string(),
2483 "le" => "binary_le".to_string(),
2484 "gt" => "binary_gt".to_string(),
2485 "ge" => "binary_ge".to_string(),
2486 "cmp" => "binary_cmp".to_string(),
2487
2488 // Logical operators
2489 "&&" => "binary_&&".to_string(),
2490 "||" => "binary_||".to_string(),
2491 "and" => "binary_and".to_string(),
2492 "or" => "binary_or".to_string(),
2493 "xor" => "binary_xor".to_string(),
2494
2495 // Bitwise operators
2496 "&" => "binary_&".to_string(),
2497 "|" => "binary_|".to_string(),
2498 "^" => "binary_^".to_string(),
2499 "<<" => "binary_<<".to_string(),
2500 ">>" => "binary_>>".to_string(),
2501
2502 // Pattern matching
2503 "=~" => "binary_=~".to_string(),
2504 "!~" => "binary_!~".to_string(),
2505
2506 // Smart match
2507 "~~" => "binary_~~".to_string(),
2508
2509 // String repetition
2510 "x" => "binary_x".to_string(),
2511
2512 // Concatenation
2513 "." => "binary_.".to_string(),
2514
2515 // Range operators
2516 ".." => "binary_..".to_string(),
2517 "..." => "binary_...".to_string(),
2518
2519 // Type checking
2520 "isa" => "binary_isa".to_string(),
2521
2522 // Assignment operators
2523 "=" => "binary_=".to_string(),
2524 "+=" => "binary_+=".to_string(),
2525 "-=" => "binary_-=".to_string(),
2526 "*=" => "binary_*=".to_string(),
2527 "/=" => "binary_/=".to_string(),
2528 "%=" => "binary_%=".to_string(),
2529 "**=" => "binary_**=".to_string(),
2530 ".=" => "binary_.=".to_string(),
2531 "&=" => "binary_&=".to_string(),
2532 "|=" => "binary_|=".to_string(),
2533 "^=" => "binary_^=".to_string(),
2534 "<<=" => "binary_<<=".to_string(),
2535 ">>=" => "binary_>>=".to_string(),
2536 "&&=" => "binary_&&=".to_string(),
2537 "||=" => "binary_||=".to_string(),
2538 "//=" => "binary_//=".to_string(),
2539
2540 // Defined-or operator
2541 "//" => "binary_//".to_string(),
2542
2543 // Method calls and dereferencing
2544 "->" => "binary_->".to_string(),
2545
2546 // Hash/array access
2547 "{}" => "binary_{}".to_string(),
2548 "[]" => "binary_[]".to_string(),
2549
2550 // Arrow hash/array dereference
2551 "->{}" => "arrow_hash_deref".to_string(),
2552 "->[]" => "arrow_array_deref".to_string(),
2553
2554 // Default case for unknown operators
2555 _ => format!("binary_{}", op.replace(' ', "_")),
2556 }
2557}
2558
2559// SourceLocation is now provided by perl-position-tracking crate
2560// See the re-export at the top of this file
2561
2562#[cfg(test)]
2563mod tests {
2564 use super::*;
2565 use std::collections::BTreeSet;
2566
2567 /// Build a dummy instance for every `NodeKind` variant and return its
2568 /// `kind_name()`. This ensures the compiler forces us to update here
2569 /// whenever a variant is added/removed.
2570 fn all_kind_names_from_variants() -> BTreeSet<&'static str> {
2571 let loc = SourceLocation { start: 0, end: 0 };
2572 let dummy_node = || Node::new(NodeKind::Undef, loc);
2573
2574 let variants: Vec<NodeKind> = vec![
2575 NodeKind::Program { statements: vec![] },
2576 NodeKind::ExpressionStatement { expression: Box::new(dummy_node()) },
2577 NodeKind::VariableDeclaration {
2578 declarator: String::new(),
2579 variable: Box::new(dummy_node()),
2580 attributes: vec![],
2581 initializer: None,
2582 },
2583 NodeKind::VariableListDeclaration {
2584 declarator: String::new(),
2585 variables: vec![],
2586 attributes: vec![],
2587 initializer: None,
2588 },
2589 NodeKind::NestedVariableList { items: vec![] },
2590 NodeKind::Variable { sigil: String::new(), name: String::new() },
2591 NodeKind::VariableWithAttributes {
2592 variable: Box::new(dummy_node()),
2593 attributes: vec![],
2594 },
2595 NodeKind::Assignment {
2596 lhs: Box::new(dummy_node()),
2597 rhs: Box::new(dummy_node()),
2598 op: String::new(),
2599 },
2600 NodeKind::Binary {
2601 op: String::new(),
2602 left: Box::new(dummy_node()),
2603 right: Box::new(dummy_node()),
2604 },
2605 NodeKind::Ternary {
2606 condition: Box::new(dummy_node()),
2607 then_expr: Box::new(dummy_node()),
2608 else_expr: Box::new(dummy_node()),
2609 },
2610 NodeKind::Unary { op: String::new(), operand: Box::new(dummy_node()) },
2611 NodeKind::Diamond,
2612 NodeKind::Ellipsis,
2613 NodeKind::Undef,
2614 NodeKind::Readline { filehandle: None },
2615 NodeKind::Glob { pattern: String::new() },
2616 NodeKind::Typeglob { name: String::new() },
2617 NodeKind::Number { value: String::new() },
2618 NodeKind::String { value: String::new(), interpolated: false },
2619 NodeKind::Heredoc {
2620 delimiter: String::new(),
2621 content: String::new(),
2622 interpolated: false,
2623 indented: false,
2624 command: false,
2625 body_span: None,
2626 },
2627 NodeKind::ArrayLiteral { elements: vec![] },
2628 NodeKind::HashLiteral { pairs: vec![] },
2629 NodeKind::Block { statements: vec![] },
2630 NodeKind::Eval { block: Box::new(dummy_node()) },
2631 NodeKind::Do { block: Box::new(dummy_node()) },
2632 NodeKind::Defer { block: Box::new(dummy_node()) },
2633 NodeKind::Try {
2634 body: Box::new(dummy_node()),
2635 catch_blocks: vec![],
2636 finally_block: None,
2637 },
2638 NodeKind::If {
2639 condition: Box::new(dummy_node()),
2640 then_branch: Box::new(dummy_node()),
2641 elsif_branches: vec![],
2642 else_branch: None,
2643 keyword: None,
2644 },
2645 NodeKind::LabeledStatement { label: String::new(), statement: Box::new(dummy_node()) },
2646 NodeKind::While {
2647 condition: Box::new(dummy_node()),
2648 body: Box::new(dummy_node()),
2649 continue_block: None,
2650 keyword: None,
2651 },
2652 NodeKind::Tie {
2653 variable: Box::new(dummy_node()),
2654 package: Box::new(dummy_node()),
2655 args: vec![],
2656 },
2657 NodeKind::Untie { variable: Box::new(dummy_node()) },
2658 NodeKind::For {
2659 init: None,
2660 condition: None,
2661 update: None,
2662 body: Box::new(dummy_node()),
2663 continue_block: None,
2664 },
2665 NodeKind::Foreach {
2666 variable: Box::new(dummy_node()),
2667 list: Box::new(dummy_node()),
2668 body: Box::new(dummy_node()),
2669 continue_block: None,
2670 },
2671 NodeKind::Given { expr: Box::new(dummy_node()), body: Box::new(dummy_node()) },
2672 NodeKind::When { condition: Box::new(dummy_node()), body: Box::new(dummy_node()) },
2673 NodeKind::Default { body: Box::new(dummy_node()) },
2674 NodeKind::StatementModifier {
2675 statement: Box::new(dummy_node()),
2676 modifier: String::new(),
2677 condition: Box::new(dummy_node()),
2678 },
2679 NodeKind::Subroutine {
2680 name: None,
2681 name_span: None,
2682 declarator: None,
2683 prototype: None,
2684 signature: None,
2685 attributes: vec![],
2686 body: Box::new(dummy_node()),
2687 },
2688 NodeKind::Prototype { content: String::new() },
2689 NodeKind::Signature { parameters: vec![] },
2690 NodeKind::MandatoryParameter { variable: Box::new(dummy_node()) },
2691 NodeKind::OptionalParameter {
2692 variable: Box::new(dummy_node()),
2693 default_value: Box::new(dummy_node()),
2694 },
2695 NodeKind::SlurpyParameter { variable: Box::new(dummy_node()) },
2696 NodeKind::NamedParameter { variable: Box::new(dummy_node()) },
2697 NodeKind::Method {
2698 name: String::new(),
2699 name_span: None,
2700 signature: None,
2701 attributes: vec![],
2702 body: Box::new(dummy_node()),
2703 },
2704 NodeKind::Return { value: None },
2705 NodeKind::LoopControl { op: String::new(), label: None },
2706 NodeKind::Goto { target: Box::new(dummy_node()) },
2707 NodeKind::MethodCall {
2708 object: Box::new(dummy_node()),
2709 method: String::new(),
2710 args: vec![],
2711 },
2712 NodeKind::FunctionCall { name: String::new(), args: vec![] },
2713 NodeKind::IndirectCall {
2714 method: String::new(),
2715 object: Box::new(dummy_node()),
2716 args: vec![],
2717 },
2718 NodeKind::Regex {
2719 pattern: String::new(),
2720 replacement: None,
2721 modifiers: String::new(),
2722 has_embedded_code: false,
2723 },
2724 NodeKind::Match {
2725 expr: Box::new(dummy_node()),
2726 pattern: String::new(),
2727 modifiers: String::new(),
2728 has_embedded_code: false,
2729 negated: false,
2730 },
2731 NodeKind::Substitution {
2732 expr: Box::new(dummy_node()),
2733 pattern: String::new(),
2734 replacement: String::new(),
2735 modifiers: String::new(),
2736 has_embedded_code: false,
2737 negated: false,
2738 },
2739 NodeKind::Transliteration {
2740 expr: Box::new(dummy_node()),
2741 search: String::new(),
2742 replace: String::new(),
2743 modifiers: String::new(),
2744 negated: false,
2745 },
2746 NodeKind::Package { name: String::new(), name_span: loc, block: None },
2747 NodeKind::Use { module: String::new(), args: vec![], has_filter_risk: false },
2748 NodeKind::No { module: String::new(), args: vec![], has_filter_risk: false },
2749 NodeKind::PhaseBlock {
2750 phase: String::new(),
2751 phase_span: None,
2752 block: Box::new(dummy_node()),
2753 },
2754 NodeKind::DataSection { marker: String::new(), body: None },
2755 NodeKind::Class {
2756 name: String::new(),
2757 name_span: None,
2758 parents: vec![],
2759 body: Box::new(dummy_node()),
2760 },
2761 NodeKind::Format { name: String::new(), name_span: None, body: String::new() },
2762 NodeKind::Identifier { name: String::new() },
2763 NodeKind::Error {
2764 message: String::new(),
2765 expected: vec![],
2766 found: None,
2767 partial: None,
2768 },
2769 NodeKind::MissingExpression,
2770 NodeKind::MissingStatement,
2771 NodeKind::MissingIdentifier,
2772 NodeKind::MissingBlock,
2773 NodeKind::UnknownRest,
2774 ];
2775
2776 variants.iter().map(|v| v.kind_name()).collect()
2777 }
2778
2779 #[test]
2780 fn for_each_child_mut_nested_variable_list() {
2781 // Covers lines 876-879: NestedVariableList arm in for_each_child_mut.
2782 let loc = SourceLocation { start: 0, end: 10 };
2783 let item_a =
2784 Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "a".to_string() }, loc);
2785 let item_b =
2786 Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "b".to_string() }, loc);
2787 let mut node = Node::new(NodeKind::NestedVariableList { items: vec![item_a, item_b] }, loc);
2788 let mut count = 0;
2789 node.for_each_child_mut(|_child| count += 1);
2790 assert_eq!(count, 2, "for_each_child_mut should visit both items in NestedVariableList");
2791 }
2792
2793 #[test]
2794 fn for_each_child_nested_variable_list() {
2795 // Covers lines 1131-1134: NestedVariableList arm in for_each_child.
2796 let loc = SourceLocation { start: 0, end: 10 };
2797 let item_a =
2798 Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() }, loc);
2799 let item_b =
2800 Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "y".to_string() }, loc);
2801 let node = Node::new(NodeKind::NestedVariableList { items: vec![item_a, item_b] }, loc);
2802 let mut names = Vec::new();
2803 node.for_each_child(|child| {
2804 if let NodeKind::Variable { name, .. } = &child.kind {
2805 names.push(name.clone());
2806 }
2807 });
2808 assert_eq!(
2809 names,
2810 vec!["x", "y"],
2811 "for_each_child should visit all items in NestedVariableList"
2812 );
2813 }
2814
2815 #[test]
2816 fn all_kind_names_is_consistent_with_kind_name() {
2817 let from_enum = all_kind_names_from_variants();
2818 let from_const: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
2819
2820 // Check for duplicates in the const array
2821 assert_eq!(
2822 NodeKind::ALL_KIND_NAMES.len(),
2823 from_const.len(),
2824 "ALL_KIND_NAMES contains duplicates"
2825 );
2826
2827 let only_in_enum: Vec<_> = from_enum.difference(&from_const).collect();
2828 let only_in_const: Vec<_> = from_const.difference(&from_enum).collect();
2829
2830 assert!(
2831 only_in_enum.is_empty() && only_in_const.is_empty(),
2832 "ALL_KIND_NAMES is out of sync with NodeKind variants:\n \
2833 in enum but not in ALL_KIND_NAMES: {only_in_enum:?}\n \
2834 in ALL_KIND_NAMES but not in enum: {only_in_const:?}"
2835 );
2836 }
2837
2838 /// Construct recovery variants and return their `kind_name()` strings.
2839 ///
2840 /// Adding a recovery variant to `NodeKind` without updating `RECOVERY_KIND_NAMES`
2841 /// will cause `recovery_kind_names_is_consistent_with_kind_name` to fail.
2842 fn recovery_kind_names_from_variants() -> BTreeSet<&'static str> {
2843 vec![
2844 NodeKind::Error {
2845 message: String::new(),
2846 expected: vec![],
2847 found: None,
2848 partial: None,
2849 },
2850 NodeKind::MissingExpression,
2851 NodeKind::MissingStatement,
2852 NodeKind::MissingIdentifier,
2853 NodeKind::MissingBlock,
2854 NodeKind::UnknownRest,
2855 ]
2856 .iter()
2857 .map(|v| v.kind_name())
2858 .collect()
2859 }
2860
2861 #[test]
2862 fn recovery_kind_names_is_consistent_with_kind_name() {
2863 let from_enum = recovery_kind_names_from_variants();
2864 let from_const: BTreeSet<&str> = NodeKind::RECOVERY_KIND_NAMES.iter().copied().collect();
2865 let only_in_enum: Vec<_> = from_enum.difference(&from_const).collect();
2866 let only_in_const: Vec<_> = from_const.difference(&from_enum).collect();
2867 assert!(
2868 only_in_enum.is_empty() && only_in_const.is_empty(),
2869 "RECOVERY_KIND_NAMES is out of sync with recovery variants:\n \
2870 in enum but not in RECOVERY_KIND_NAMES: {only_in_enum:?}\n \
2871 in RECOVERY_KIND_NAMES but not in enum: {only_in_const:?}"
2872 );
2873 }
2874
2875 #[test]
2876 fn recovery_kind_names_is_subset_of_all() {
2877 let all: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
2878 let recovery: BTreeSet<&str> = NodeKind::RECOVERY_KIND_NAMES.iter().copied().collect();
2879
2880 // No duplicates
2881 assert_eq!(
2882 NodeKind::RECOVERY_KIND_NAMES.len(),
2883 recovery.len(),
2884 "RECOVERY_KIND_NAMES contains duplicates"
2885 );
2886
2887 let not_in_all: Vec<_> = recovery.difference(&all).collect();
2888 assert!(
2889 not_in_all.is_empty(),
2890 "RECOVERY_KIND_NAMES contains entries not in ALL_KIND_NAMES: {not_in_all:?}"
2891 );
2892 }
2893
2894 #[test]
2895 fn all_kind_names_not_empty() {
2896 // Regression guard: ALL_KIND_NAMES should always be populated
2897 assert!(
2898 !NodeKind::ALL_KIND_NAMES.is_empty(),
2899 "ALL_KIND_NAMES should not be empty; strum derivation failed"
2900 );
2901 }
2902
2903 #[test]
2904 fn all_kind_names_no_empty_strings() {
2905 // Boundary condition: no entry should be an empty string
2906 for (i, name) in NodeKind::ALL_KIND_NAMES.iter().enumerate() {
2907 assert!(!name.is_empty(), "ALL_KIND_NAMES[{}] is empty string", i);
2908 }
2909 }
2910
2911 #[test]
2912 fn all_kind_names_starts_with_program() {
2913 // Regression guard: first variant is Program (declaration order invariant)
2914 assert_eq!(
2915 NodeKind::ALL_KIND_NAMES.first(),
2916 Some(&"Program"),
2917 "First variant in ALL_KIND_NAMES should be 'Program' (declaration order)"
2918 );
2919 }
2920
2921 #[test]
2922 fn all_kind_names_ends_with_unknown_rest() {
2923 // Regression guard: last variant is UnknownRest (declaration order invariant)
2924 assert_eq!(
2925 NodeKind::ALL_KIND_NAMES.last(),
2926 Some(&"UnknownRest"),
2927 "Last variant in ALL_KIND_NAMES should be 'UnknownRest' (declaration order)"
2928 );
2929 }
2930
2931 #[test]
2932 fn all_kind_names_valid_kind_names() {
2933 // Regression guard: every string in ALL_KIND_NAMES is a valid kind_name() output
2934 for (i, name) in NodeKind::ALL_KIND_NAMES.iter().enumerate() {
2935 let found = all_kind_names_from_variants().contains(name);
2936 assert!(
2937 found,
2938 "ALL_KIND_NAMES[{}] = '{}' is not a valid kind_name() return value",
2939 i, name
2940 );
2941 }
2942 }
2943
2944 #[test]
2945 fn all_kind_names_exact_match_with_variants_set() {
2946 // Regression guard: ALL_KIND_NAMES contains exactly the same names as all variants
2947 let from_enum = all_kind_names_from_variants();
2948 let from_const: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
2949
2950 assert_eq!(from_enum, from_const, "ALL_KIND_NAMES set does not match variant kind_names");
2951 }
2952
2953 #[test]
2954 fn all_kind_names_no_whitespace_padding() {
2955 // Boundary condition: no leading/trailing whitespace in variant names
2956 for (i, name) in NodeKind::ALL_KIND_NAMES.iter().enumerate() {
2957 assert_eq!(
2958 *name,
2959 name.trim(),
2960 "ALL_KIND_NAMES[{}] = '{}' has leading/trailing whitespace",
2961 i,
2962 name
2963 );
2964 }
2965 }
2966
2967 #[test]
2968 fn all_kind_names_count_regression_guard() {
2969 // Regression guard: ALL_KIND_NAMES must have at least 70 entries.
2970 // The previous hand-maintained list had 70 variants (including NestedVariableList
2971 // added in #1457). Failing below that count means a variant was deleted or the
2972 // strum derivation silently stopped working.
2973 //
2974 // Note: the previous test `all_kind_names_strum_derived_stability` asserted
2975 // `NodeKind::VARIANTS == NodeKind::ALL_KIND_NAMES`, which is trivially true by
2976 // definition (ALL_KIND_NAMES = NodeKind::VARIANTS). That assertion was vacuous
2977 // and has been replaced with this count guard.
2978 assert!(
2979 NodeKind::ALL_KIND_NAMES.len() >= 70,
2980 "ALL_KIND_NAMES has only {} entries; expected >= 70. \
2981 A variant may have been accidentally removed, or strum::VariantNames \
2982 is not being applied correctly.",
2983 NodeKind::ALL_KIND_NAMES.len()
2984 );
2985 }
2986}