use crate::ast::NodeKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NodeKindCategory {
Program,
Statement,
Expression,
Declaration,
Scope,
Literal,
Operator,
CommentDoc,
Recovery,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeKindFlags {
pub executable: bool,
pub introduces_scope: bool,
pub declares_symbol: bool,
pub references_symbol: bool,
pub contains_children: bool,
pub recovery_artifact: bool,
pub safe_for_breakpoint: bool,
}
impl NodeKindFlags {
pub fn validate(&self) -> Result<(), String> {
if self.recovery_artifact && self.safe_for_breakpoint {
Err("recovery_artifact and safe_for_breakpoint must not both be true".into())
} else {
Ok(())
}
}
}
macro_rules! flags {
(
exec=$exec:expr,
scope=$scope:expr,
decl=$decl:expr,
refs=$refs:expr,
children=$children:expr,
recovery=$recovery:expr,
bp=$bp:expr
) => {
NodeKindFlags {
executable: $exec,
introduces_scope: $scope,
declares_symbol: $decl,
references_symbol: $refs,
contains_children: $children,
recovery_artifact: $recovery,
safe_for_breakpoint: $bp,
}
};
}
impl NodeKind {
pub fn category(&self) -> NodeKindCategory {
match self {
NodeKind::Program { .. } => NodeKindCategory::Program,
NodeKind::ExpressionStatement { .. }
| NodeKind::Defer { .. }
| NodeKind::Try { .. }
| NodeKind::If { .. }
| NodeKind::LabeledStatement { .. }
| NodeKind::While { .. }
| NodeKind::For { .. }
| NodeKind::Foreach { .. }
| NodeKind::Given { .. }
| NodeKind::When { .. }
| NodeKind::Default { .. }
| NodeKind::StatementModifier { .. }
| NodeKind::Return { .. }
| NodeKind::LoopControl { .. }
| NodeKind::Goto { .. } => NodeKindCategory::Statement,
NodeKind::Variable { .. }
| NodeKind::VariableWithAttributes { .. }
| NodeKind::Assignment { .. }
| NodeKind::Binary { .. }
| NodeKind::Ternary { .. }
| NodeKind::Unary { .. }
| NodeKind::Diamond
| NodeKind::Readline { .. }
| NodeKind::Glob { .. }
| NodeKind::Typeglob { .. }
| NodeKind::ArrayLiteral { .. }
| NodeKind::HashLiteral { .. }
| NodeKind::Eval { .. }
| NodeKind::Do { .. }
| NodeKind::Tie { .. }
| NodeKind::Untie { .. }
| NodeKind::MethodCall { .. }
| NodeKind::FunctionCall { .. }
| NodeKind::IndirectCall { .. }
| NodeKind::Match { .. }
| NodeKind::Substitution { .. }
| NodeKind::Transliteration { .. }
| NodeKind::Identifier { .. } => NodeKindCategory::Expression,
NodeKind::VariableDeclaration { .. }
| NodeKind::VariableListDeclaration { .. }
| NodeKind::NestedVariableList { .. }
| NodeKind::Subroutine { .. }
| NodeKind::Prototype { .. }
| NodeKind::Signature { .. }
| NodeKind::MandatoryParameter { .. }
| NodeKind::OptionalParameter { .. }
| NodeKind::SlurpyParameter { .. }
| NodeKind::NamedParameter { .. }
| NodeKind::Method { .. }
| NodeKind::Package { .. }
| NodeKind::Use { .. }
| NodeKind::No { .. }
| NodeKind::PhaseBlock { .. }
| NodeKind::DataSection { .. }
| NodeKind::Class { .. }
| NodeKind::Format { .. } => NodeKindCategory::Declaration,
NodeKind::Block { .. } => NodeKindCategory::Scope,
NodeKind::Number { .. }
| NodeKind::String { .. }
| NodeKind::Heredoc { .. }
| NodeKind::Regex { .. }
| NodeKind::Undef => NodeKindCategory::Literal,
NodeKind::Ellipsis => NodeKindCategory::Operator,
NodeKind::Error { .. }
| NodeKind::MissingExpression
| NodeKind::MissingStatement
| NodeKind::MissingIdentifier
| NodeKind::MissingBlock
| NodeKind::UnknownRest => NodeKindCategory::Recovery,
}
}
pub fn flags(&self) -> NodeKindFlags {
match self {
NodeKind::Program { .. } => flags!(
exec = false,
scope = true,
decl = false,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::ExpressionStatement { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::VariableDeclaration { .. } => flags!(
exec = true,
scope = true,
decl = true,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::VariableListDeclaration { .. } => flags!(
exec = true,
scope = true,
decl = true,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::NestedVariableList { .. } => flags!(
exec = false,
scope = false,
decl = true,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::Variable { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = true,
children = false,
recovery = false,
bp = false
),
NodeKind::VariableWithAttributes { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = false
),
NodeKind::Assignment { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Binary { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Ternary { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Unary { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Diamond => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = true
),
NodeKind::Ellipsis => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Undef => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Readline { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = false,
recovery = false,
bp = true
),
NodeKind::Glob { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = true
),
NodeKind::Typeglob { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = true,
children = false,
recovery = false,
bp = false
),
NodeKind::Number { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::String { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Heredoc { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = true
),
NodeKind::ArrayLiteral { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::HashLiteral { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::Block { .. } => flags!(
exec = true,
scope = true,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Eval { .. } => flags!(
exec = true,
scope = true,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Do { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Defer { .. } => flags!(
exec = true,
scope = true,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Try { .. } => flags!(
exec = true,
scope = true,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::If { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::LabeledStatement { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::While { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Tie { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Untie { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::For { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Foreach { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Given { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::When { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Default { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::StatementModifier { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Subroutine { .. } => flags!(
exec = false,
scope = true,
decl = true,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Prototype { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Signature { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::MandatoryParameter { .. } => flags!(
exec = false,
scope = false,
decl = true,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::OptionalParameter { .. } => flags!(
exec = false,
scope = false,
decl = true,
refs = true,
children = true,
recovery = false,
bp = false
),
NodeKind::SlurpyParameter { .. } => flags!(
exec = false,
scope = false,
decl = true,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::NamedParameter { .. } => flags!(
exec = false,
scope = false,
decl = true,
refs = false,
children = true,
recovery = false,
bp = false
),
NodeKind::Method { .. } => flags!(
exec = false,
scope = true,
decl = true,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Return { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::LoopControl { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = true
),
NodeKind::Goto { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::MethodCall { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::FunctionCall { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::IndirectCall { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Regex { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Match { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Substitution { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Transliteration { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = true,
children = true,
recovery = false,
bp = true
),
NodeKind::Package { .. } => flags!(
exec = true,
scope = true,
decl = true,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Use { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::No { .. } => flags!(
exec = true,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::PhaseBlock { .. } => flags!(
exec = true,
scope = true,
decl = false,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::DataSection { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Class { .. } => flags!(
exec = false,
scope = true,
decl = true,
refs = false,
children = true,
recovery = false,
bp = true
),
NodeKind::Format { .. } => flags!(
exec = false,
scope = false,
decl = true,
refs = false,
children = false,
recovery = false,
bp = false
),
NodeKind::Identifier { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = true,
children = false,
recovery = false,
bp = false
),
NodeKind::Error { .. } => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = true,
recovery = true,
bp = false
),
NodeKind::MissingExpression => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = true,
bp = false
),
NodeKind::MissingStatement => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = true,
bp = false
),
NodeKind::MissingIdentifier => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = true,
bp = false
),
NodeKind::MissingBlock => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = true,
bp = false
),
NodeKind::UnknownRest => flags!(
exec = false,
scope = false,
decl = false,
refs = false,
children = false,
recovery = true,
bp = false
),
}
}
#[inline]
pub fn is_executable(&self) -> bool {
self.flags().executable
}
#[inline]
pub fn introduces_scope(&self) -> bool {
self.flags().introduces_scope
}
#[inline]
pub fn declares_symbol(&self) -> bool {
self.flags().declares_symbol
}
#[inline]
pub fn references_symbol(&self) -> bool {
self.flags().references_symbol
}
#[inline]
pub fn safe_for_breakpoint(&self) -> bool {
self.flags().safe_for_breakpoint
}
#[inline]
pub fn is_recovery(&self) -> bool {
self.flags().recovery_artifact
}
#[inline]
pub fn contains_children(&self) -> bool {
self.flags().contains_children
}
}
#[cfg(test)]
mod tests {
use super::NodeKindCategory;
use super::NodeKindFlags;
use crate::ast::{Node, NodeKind};
use perl_position_tracking::SourceLocation;
fn loc() -> SourceLocation {
SourceLocation::new(0, 1)
}
fn leaf() -> Node {
Node::new(NodeKind::Identifier { name: "x".to_string() }, loc())
}
fn block_node() -> Node {
Node::new(NodeKind::Block { statements: vec![] }, loc())
}
fn all_variants() -> Vec<NodeKind> {
vec![
NodeKind::Program { statements: vec![] },
NodeKind::ExpressionStatement { expression: Box::new(leaf()) },
NodeKind::VariableDeclaration {
declarator: "my".to_string(),
variable: Box::new(leaf()),
attributes: vec![],
initializer: None,
},
NodeKind::VariableListDeclaration {
declarator: "my".to_string(),
variables: vec![],
attributes: vec![],
initializer: None,
},
NodeKind::NestedVariableList { items: vec![] },
NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
NodeKind::VariableWithAttributes { variable: Box::new(leaf()), attributes: vec![] },
NodeKind::Assignment {
lhs: Box::new(leaf()),
rhs: Box::new(leaf()),
op: "=".to_string(),
},
NodeKind::Binary {
op: "+".to_string(),
left: Box::new(leaf()),
right: Box::new(leaf()),
},
NodeKind::Ternary {
condition: Box::new(leaf()),
then_expr: Box::new(leaf()),
else_expr: Box::new(leaf()),
},
NodeKind::Unary { op: "-".to_string(), operand: Box::new(leaf()) },
NodeKind::Diamond,
NodeKind::Ellipsis,
NodeKind::Undef,
NodeKind::Readline { filehandle: None },
NodeKind::Glob { pattern: "*.pl".to_string() },
NodeKind::Typeglob { name: "foo".to_string() },
NodeKind::Number { value: "42".to_string() },
NodeKind::String { value: "hello".to_string(), interpolated: false },
NodeKind::Heredoc {
delimiter: "EOF".to_string(),
content: "body".to_string(),
interpolated: false,
indented: false,
command: false,
body_span: None,
},
NodeKind::ArrayLiteral { elements: vec![] },
NodeKind::HashLiteral { pairs: vec![] },
NodeKind::Block { statements: vec![] },
NodeKind::Eval { block: Box::new(block_node()) },
NodeKind::Do { block: Box::new(block_node()) },
NodeKind::Defer { block: Box::new(block_node()) },
NodeKind::Try {
body: Box::new(block_node()),
catch_blocks: vec![],
finally_block: None,
},
NodeKind::If {
condition: Box::new(leaf()),
then_branch: Box::new(block_node()),
elsif_branches: vec![],
else_branch: None,
keyword: None,
},
NodeKind::LabeledStatement {
label: "OUTER".to_string(),
statement: Box::new(Node::new(
NodeKind::LoopControl { op: "next".to_string(), label: None },
loc(),
)),
},
NodeKind::While {
condition: Box::new(leaf()),
body: Box::new(block_node()),
continue_block: None,
keyword: None,
},
NodeKind::Tie { variable: Box::new(leaf()), package: Box::new(leaf()), args: vec![] },
NodeKind::Untie { variable: Box::new(leaf()) },
NodeKind::For {
init: None,
condition: None,
update: None,
body: Box::new(block_node()),
continue_block: None,
},
NodeKind::Foreach {
variable: Box::new(leaf()),
list: Box::new(leaf()),
body: Box::new(block_node()),
continue_block: None,
},
NodeKind::Given { expr: Box::new(leaf()), body: Box::new(block_node()) },
NodeKind::When { condition: Box::new(leaf()), body: Box::new(block_node()) },
NodeKind::Default { body: Box::new(block_node()) },
NodeKind::StatementModifier {
statement: Box::new(leaf()),
modifier: "if".to_string(),
condition: Box::new(leaf()),
},
NodeKind::Subroutine {
name: Some("foo".to_string()),
name_span: None,
declarator: None,
prototype: None,
signature: None,
attributes: vec![],
body: Box::new(block_node()),
},
NodeKind::Prototype { content: "$@".to_string() },
NodeKind::Signature { parameters: vec![] },
NodeKind::MandatoryParameter { variable: Box::new(leaf()) },
NodeKind::OptionalParameter {
variable: Box::new(leaf()),
default_value: Box::new(leaf()),
},
NodeKind::SlurpyParameter { variable: Box::new(leaf()) },
NodeKind::NamedParameter { variable: Box::new(leaf()) },
NodeKind::Method {
name: "bar".to_string(),
name_span: None,
signature: None,
attributes: vec![],
body: Box::new(block_node()),
},
NodeKind::Return { value: None },
NodeKind::LoopControl { op: "next".to_string(), label: None },
NodeKind::Goto { target: Box::new(leaf()) },
NodeKind::MethodCall {
object: Box::new(leaf()),
method: "foo".to_string(),
args: vec![],
},
NodeKind::FunctionCall { name: "print".to_string(), args: vec![] },
NodeKind::IndirectCall {
method: "new".to_string(),
object: Box::new(leaf()),
args: vec![],
},
NodeKind::Regex {
pattern: "foo".to_string(),
replacement: None,
modifiers: "".to_string(),
has_embedded_code: false,
},
NodeKind::Match {
expr: Box::new(leaf()),
pattern: "foo".to_string(),
modifiers: "".to_string(),
has_embedded_code: false,
negated: false,
},
NodeKind::Substitution {
expr: Box::new(leaf()),
pattern: "foo".to_string(),
replacement: "bar".to_string(),
modifiers: "".to_string(),
has_embedded_code: false,
negated: false,
},
NodeKind::Transliteration {
expr: Box::new(leaf()),
search: "a".to_string(),
replace: "b".to_string(),
modifiers: "".to_string(),
negated: false,
},
NodeKind::Package { name: "Foo".to_string(), name_span: loc(), block: None },
NodeKind::Use { module: "strict".to_string(), args: vec![], has_filter_risk: false },
NodeKind::No { module: "strict".to_string(), args: vec![], has_filter_risk: false },
NodeKind::PhaseBlock {
phase: "BEGIN".to_string(),
phase_span: None,
block: Box::new(block_node()),
},
NodeKind::DataSection { marker: "__DATA__".to_string(), body: None },
NodeKind::Class {
name: "Foo".to_string(),
name_span: None,
parents: vec![],
body: Box::new(block_node()),
},
NodeKind::Format { name: "STDOUT".to_string(), name_span: None, body: "".to_string() },
NodeKind::Identifier { name: "foo".to_string() },
NodeKind::Error {
message: "oops".to_string(),
expected: vec![],
found: None,
partial: None,
},
NodeKind::MissingExpression,
NodeKind::MissingStatement,
NodeKind::MissingIdentifier,
NodeKind::MissingBlock,
NodeKind::UnknownRest,
]
}
#[test]
fn all_variants_return_category_and_valid_flags() {
for kind in all_variants() {
let _cat = kind.category();
let flags = kind.flags();
assert!(
flags.validate().is_ok(),
"variant {} failed validate(): {:?}",
kind.kind_name(),
flags.validate()
);
}
}
#[test]
fn category_spot_checks() {
assert_eq!(NodeKind::Program { statements: vec![] }.category(), NodeKindCategory::Program);
assert_eq!(
NodeKind::If {
condition: Box::new(leaf()),
then_branch: Box::new(block_node()),
elsif_branches: vec![],
else_branch: None,
keyword: None,
}
.category(),
NodeKindCategory::Statement
);
assert_eq!(
NodeKind::FunctionCall { name: "print".to_string(), args: vec![] }.category(),
NodeKindCategory::Expression
);
assert_eq!(
NodeKind::Subroutine {
name: Some("foo".to_string()),
name_span: None,
declarator: None,
prototype: None,
signature: None,
attributes: vec![],
body: Box::new(block_node()),
}
.category(),
NodeKindCategory::Declaration
);
assert_eq!(NodeKind::Block { statements: vec![] }.category(), NodeKindCategory::Scope);
assert_eq!(
NodeKind::Number { value: "1".to_string() }.category(),
NodeKindCategory::Literal
);
assert_eq!(NodeKind::Ellipsis.category(), NodeKindCategory::Operator);
assert_eq!(NodeKind::MissingExpression.category(), NodeKindCategory::Recovery);
assert_eq!(NodeKind::UnknownRest.category(), NodeKindCategory::Recovery);
}
#[test]
fn recovery_artifact_implies_not_safe_for_breakpoint() {
for kind in all_variants() {
let flags = kind.flags();
if flags.recovery_artifact {
assert!(
!flags.safe_for_breakpoint,
"variant {} has recovery_artifact=true but safe_for_breakpoint=true",
kind.kind_name()
);
}
}
}
#[test]
fn convenience_accessors_match_flags() {
for kind in all_variants() {
let flags = kind.flags();
assert_eq!(
kind.is_executable(),
flags.executable,
"{}: is_executable() != flags.executable",
kind.kind_name()
);
assert_eq!(
kind.introduces_scope(),
flags.introduces_scope,
"{}: introduces_scope() != flags.introduces_scope",
kind.kind_name()
);
assert_eq!(
kind.declares_symbol(),
flags.declares_symbol,
"{}: declares_symbol() != flags.declares_symbol",
kind.kind_name()
);
assert_eq!(
kind.references_symbol(),
flags.references_symbol,
"{}: references_symbol() != flags.references_symbol",
kind.kind_name()
);
assert_eq!(
kind.contains_children(),
flags.contains_children,
"{}: contains_children() != flags.contains_children",
kind.kind_name()
);
assert_eq!(
kind.safe_for_breakpoint(),
flags.safe_for_breakpoint,
"{}: safe_for_breakpoint() != flags.safe_for_breakpoint",
kind.kind_name()
);
assert_eq!(
kind.is_recovery(),
flags.recovery_artifact,
"{}: is_recovery() != flags.recovery_artifact",
kind.kind_name()
);
}
}
#[test]
fn flag_values_for_representative_variants() {
let fc = NodeKind::FunctionCall { name: "say".to_string(), args: vec![] };
assert!(fc.is_executable());
assert!(fc.references_symbol());
assert!(fc.safe_for_breakpoint());
assert!(!fc.introduces_scope());
assert!(!fc.declares_symbol());
assert!(!fc.is_recovery());
let vd = NodeKind::VariableDeclaration {
declarator: "my".to_string(),
variable: Box::new(leaf()),
attributes: vec![],
initializer: None,
};
assert!(vd.is_executable());
assert!(vd.introduces_scope());
assert!(vd.declares_symbol());
assert!(vd.safe_for_breakpoint());
let num = NodeKind::Number { value: "0".to_string() };
assert!(!num.is_executable());
assert!(!num.introduces_scope());
assert!(!num.declares_symbol());
assert!(!num.references_symbol());
assert!(!num.safe_for_breakpoint());
assert!(!num.is_recovery());
let err = NodeKind::Error {
message: "bad".to_string(),
expected: vec![],
found: None,
partial: None,
};
assert!(err.is_recovery());
assert!(!err.safe_for_breakpoint());
for recovery_kind in [
NodeKind::MissingExpression,
NodeKind::MissingStatement,
NodeKind::MissingIdentifier,
NodeKind::MissingBlock,
NodeKind::UnknownRest,
] {
assert!(
recovery_kind.is_recovery(),
"{} should be recovery",
recovery_kind.kind_name()
);
assert!(
!recovery_kind.safe_for_breakpoint(),
"{} should not be safe_for_breakpoint",
recovery_kind.kind_name()
);
}
let blk = NodeKind::Block { statements: vec![] };
assert!(blk.introduces_scope());
assert!(blk.is_executable());
assert!(blk.safe_for_breakpoint());
}
#[test]
fn flags_validate_rejects_recovery_with_breakpoint() {
let good = NodeKindFlags {
executable: false,
introduces_scope: false,
declares_symbol: false,
references_symbol: false,
contains_children: false,
recovery_artifact: true,
safe_for_breakpoint: false,
};
assert!(good.validate().is_ok());
let bad = NodeKindFlags {
executable: false,
introduces_scope: false,
declares_symbol: false,
references_symbol: false,
contains_children: false,
recovery_artifact: true,
safe_for_breakpoint: true, };
assert!(bad.validate().is_err());
}
#[test]
fn contains_children_accessor_leaf_variants() {
assert!(
!NodeKind::Number { value: "42".to_string() }.contains_children(),
"Number should not contain children"
);
assert!(
!NodeKind::String { value: "hello".to_string(), interpolated: false }
.contains_children(),
"String should not contain children"
);
assert!(
!NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() }
.contains_children(),
"Variable should not contain children"
);
assert!(!NodeKind::Diamond.contains_children(), "Diamond should not contain children");
assert!(!NodeKind::Undef.contains_children(), "Undef should not contain children");
}
#[test]
fn contains_children_accessor_parent_variants() {
assert!(
NodeKind::Block { statements: vec![leaf()] }.contains_children(),
"Block with statements should contain children"
);
assert!(
NodeKind::Program { statements: vec![leaf()] }.contains_children(),
"Program should contain children"
);
assert!(
NodeKind::VariableDeclaration {
declarator: "my".to_string(),
variable: Box::new(leaf()),
attributes: vec![],
initializer: None,
}
.contains_children(),
"VariableDeclaration with variable should contain children"
);
assert!(
NodeKind::Binary {
op: "+".to_string(),
left: Box::new(leaf()),
right: Box::new(leaf()),
}
.contains_children(),
"Binary should contain children"
);
assert!(
NodeKind::FunctionCall { name: "print".to_string(), args: vec![leaf()] }
.contains_children(),
"FunctionCall with args should contain children"
);
}
fn all_variants_maximal() -> Vec<Node> {
let n = |kind| Node::new(kind, loc());
vec![
n(NodeKind::Program { statements: vec![leaf()] }),
n(NodeKind::ExpressionStatement { expression: Box::new(leaf()) }),
n(NodeKind::VariableDeclaration {
declarator: "my".to_string(),
variable: Box::new(leaf()),
attributes: vec![],
initializer: Some(Box::new(leaf())),
}),
n(NodeKind::VariableListDeclaration {
declarator: "my".to_string(),
variables: vec![leaf()],
attributes: vec![],
initializer: Some(Box::new(leaf())),
}),
n(NodeKind::NestedVariableList { items: vec![leaf()] }),
n(NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() }),
n(NodeKind::VariableWithAttributes { variable: Box::new(leaf()), attributes: vec![] }),
n(NodeKind::Assignment {
lhs: Box::new(leaf()),
rhs: Box::new(leaf()),
op: "=".to_string(),
}),
n(NodeKind::Binary {
op: "+".to_string(),
left: Box::new(leaf()),
right: Box::new(leaf()),
}),
n(NodeKind::Ternary {
condition: Box::new(leaf()),
then_expr: Box::new(leaf()),
else_expr: Box::new(leaf()),
}),
n(NodeKind::Unary { op: "-".to_string(), operand: Box::new(leaf()) }),
n(NodeKind::Diamond),
n(NodeKind::Ellipsis),
n(NodeKind::Undef),
n(NodeKind::Readline { filehandle: Some("STDIN".to_string()) }),
n(NodeKind::Glob { pattern: "*.pl".to_string() }),
n(NodeKind::Typeglob { name: "foo".to_string() }),
n(NodeKind::Number { value: "42".to_string() }),
n(NodeKind::String { value: "hello".to_string(), interpolated: false }),
n(NodeKind::Heredoc {
delimiter: "EOF".to_string(),
content: "body".to_string(),
interpolated: false,
indented: false,
command: false,
body_span: None,
}),
n(NodeKind::ArrayLiteral { elements: vec![leaf()] }),
n(NodeKind::HashLiteral { pairs: vec![(leaf(), leaf())] }),
n(NodeKind::Block { statements: vec![leaf()] }),
n(NodeKind::Eval { block: Box::new(block_node()) }),
n(NodeKind::Do { block: Box::new(block_node()) }),
n(NodeKind::Defer { block: Box::new(block_node()) }),
n(NodeKind::Try {
body: Box::new(block_node()),
catch_blocks: vec![(None, Box::new(block_node()))],
finally_block: Some(Box::new(block_node())),
}),
n(NodeKind::If {
condition: Box::new(leaf()),
then_branch: Box::new(block_node()),
elsif_branches: vec![(Box::new(leaf()), Box::new(block_node()))],
else_branch: Some(Box::new(block_node())),
keyword: None,
}),
n(NodeKind::LabeledStatement {
label: "OUTER".to_string(),
statement: Box::new(leaf()),
}),
n(NodeKind::While {
condition: Box::new(leaf()),
body: Box::new(block_node()),
continue_block: Some(Box::new(block_node())),
keyword: None,
}),
n(NodeKind::Tie {
variable: Box::new(leaf()),
package: Box::new(leaf()),
args: vec![leaf()],
}),
n(NodeKind::Untie { variable: Box::new(leaf()) }),
n(NodeKind::For {
init: Some(Box::new(leaf())),
condition: Some(Box::new(leaf())),
update: Some(Box::new(leaf())),
body: Box::new(block_node()),
continue_block: Some(Box::new(block_node())),
}),
n(NodeKind::Foreach {
variable: Box::new(leaf()),
list: Box::new(leaf()),
body: Box::new(block_node()),
continue_block: Some(Box::new(block_node())),
}),
n(NodeKind::Given { expr: Box::new(leaf()), body: Box::new(block_node()) }),
n(NodeKind::When { condition: Box::new(leaf()), body: Box::new(block_node()) }),
n(NodeKind::Default { body: Box::new(block_node()) }),
n(NodeKind::StatementModifier {
statement: Box::new(leaf()),
modifier: "if".to_string(),
condition: Box::new(leaf()),
}),
n(NodeKind::Subroutine {
name: Some("foo".to_string()),
name_span: None,
declarator: None,
prototype: Some(Box::new(Node::new(
NodeKind::Prototype { content: "$@".to_string() },
loc(),
))),
signature: Some(Box::new(Node::new(
NodeKind::Signature { parameters: vec![] },
loc(),
))),
attributes: vec![],
body: Box::new(block_node()),
}),
n(NodeKind::Prototype { content: "$@".to_string() }),
n(NodeKind::Signature { parameters: vec![leaf()] }),
n(NodeKind::MandatoryParameter { variable: Box::new(leaf()) }),
n(NodeKind::OptionalParameter {
variable: Box::new(leaf()),
default_value: Box::new(leaf()),
}),
n(NodeKind::SlurpyParameter { variable: Box::new(leaf()) }),
n(NodeKind::NamedParameter { variable: Box::new(leaf()) }),
n(NodeKind::Method {
name: "bar".to_string(),
name_span: None,
signature: Some(Box::new(Node::new(
NodeKind::Signature { parameters: vec![] },
loc(),
))),
attributes: vec![],
body: Box::new(block_node()),
}),
n(NodeKind::Return { value: Some(Box::new(leaf())) }),
n(NodeKind::LoopControl { op: "next".to_string(), label: None }),
n(NodeKind::Goto { target: Box::new(leaf()) }),
n(NodeKind::MethodCall {
object: Box::new(leaf()),
method: "foo".to_string(),
args: vec![leaf()],
}),
n(NodeKind::FunctionCall { name: "print".to_string(), args: vec![leaf()] }),
n(NodeKind::IndirectCall {
method: "new".to_string(),
object: Box::new(leaf()),
args: vec![leaf()],
}),
n(NodeKind::Regex {
pattern: "foo".to_string(),
replacement: None,
modifiers: "".to_string(),
has_embedded_code: false,
}),
n(NodeKind::Match {
expr: Box::new(leaf()),
pattern: "foo".to_string(),
modifiers: "".to_string(),
has_embedded_code: false,
negated: false,
}),
n(NodeKind::Substitution {
expr: Box::new(leaf()),
pattern: "foo".to_string(),
replacement: "bar".to_string(),
modifiers: "".to_string(),
has_embedded_code: false,
negated: false,
}),
n(NodeKind::Transliteration {
expr: Box::new(leaf()),
search: "a".to_string(),
replace: "b".to_string(),
modifiers: "".to_string(),
negated: false,
}),
n(NodeKind::Package {
name: "Foo".to_string(),
name_span: loc(),
block: Some(Box::new(block_node())),
}),
n(NodeKind::Use {
module: "strict".to_string(),
args: vec!["foo".to_string()],
has_filter_risk: false,
}),
n(NodeKind::No {
module: "strict".to_string(),
args: vec!["foo".to_string()],
has_filter_risk: false,
}),
n(NodeKind::PhaseBlock {
phase: "BEGIN".to_string(),
phase_span: None,
block: Box::new(block_node()),
}),
n(NodeKind::DataSection { marker: "__DATA__".to_string(), body: None }),
n(NodeKind::Class {
name: "Foo".to_string(),
name_span: None,
parents: vec![],
body: Box::new(block_node()),
}),
n(NodeKind::Format {
name: "STDOUT".to_string(),
name_span: None,
body: "".to_string(),
}),
n(NodeKind::Identifier { name: "foo".to_string() }),
n(NodeKind::Error {
message: "oops".to_string(),
expected: vec![],
found: None,
partial: Some(Box::new(leaf())),
}),
n(NodeKind::MissingExpression),
n(NodeKind::MissingStatement),
n(NodeKind::MissingIdentifier),
n(NodeKind::MissingBlock),
n(NodeKind::UnknownRest),
]
}
#[test]
fn contains_children_matches_for_each_child() {
for node in all_variants_maximal() {
let has_children = node.child_count() > 0;
assert_eq!(
node.kind.contains_children(),
has_children,
"{}: contains_children() = {} but for_each_child yields {} children",
node.kind.kind_name(),
node.kind.contains_children(),
node.child_count(),
);
}
}
}