pub use perl_position_tracking::SourceLocation;
pub use perl_token::{Token, TokenKind};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub kind: NodeKind,
pub location: SourceLocation,
}
impl Node {
pub fn new(kind: NodeKind, location: SourceLocation) -> Self {
Node { kind, location }
}
pub fn to_sexp(&self) -> String {
match &self.kind {
NodeKind::Program { statements } => {
let stmts =
statements.iter().map(|s| s.to_sexp_inner()).collect::<Vec<_>>().join(" ");
format!("(source_file {})", stmts)
}
NodeKind::ExpressionStatement { expression } => {
format!("(expression_statement {})", expression.to_sexp())
}
NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
let attrs_str = if attributes.is_empty() {
String::new()
} else {
format!(" (attributes {})", attributes.join(" "))
};
if let Some(init) = initializer {
format!(
"({}_declaration {}{}{})",
declarator,
variable.to_sexp(),
attrs_str,
init.to_sexp()
)
} else {
format!("({}_declaration {}{})", declarator, variable.to_sexp(), attrs_str)
}
}
NodeKind::VariableListDeclaration {
declarator,
variables,
attributes,
initializer,
} => {
let vars = variables.iter().map(|v| v.to_sexp()).collect::<Vec<_>>().join(" ");
let attrs_str = if attributes.is_empty() {
String::new()
} else {
format!(" (attributes {})", attributes.join(" "))
};
if let Some(init) = initializer {
format!(
"({}_declaration ({}){}{})",
declarator,
vars,
attrs_str,
init.to_sexp()
)
} else {
format!("({}_declaration ({}){})", declarator, vars, attrs_str)
}
}
NodeKind::Variable { sigil, name } => {
format!("(variable {} {})", sigil, name)
}
NodeKind::VariableWithAttributes { variable, attributes } => {
let attrs = attributes.join(" ");
format!("({} (attributes {}))", variable.to_sexp(), attrs)
}
NodeKind::Assignment { lhs, rhs, op } => {
format!(
"(assignment_{} {} {})",
op.replace("=", "assign"),
lhs.to_sexp(),
rhs.to_sexp()
)
}
NodeKind::Binary { op, left, right } => {
let op_name = format_binary_operator(op);
format!("({} {} {})", op_name, left.to_sexp(), right.to_sexp())
}
NodeKind::Ternary { condition, then_expr, else_expr } => {
format!(
"(ternary {} {} {})",
condition.to_sexp(),
then_expr.to_sexp(),
else_expr.to_sexp()
)
}
NodeKind::Unary { op, operand } => {
let op_name = format_unary_operator(op);
format!("({} {})", op_name, operand.to_sexp())
}
NodeKind::Diamond => "(diamond)".to_string(),
NodeKind::Ellipsis => "(ellipsis)".to_string(),
NodeKind::Undef => "(undef)".to_string(),
NodeKind::Readline { filehandle } => {
if let Some(fh) = filehandle {
format!("(readline {})", fh)
} else {
"(readline)".to_string()
}
}
NodeKind::Glob { pattern } => {
format!("(glob {})", pattern)
}
NodeKind::Typeglob { name } => {
format!("(typeglob {})", name)
}
NodeKind::Number { value } => {
format!("(number {})", value)
}
NodeKind::String { value, interpolated } => {
let escaped_value = value.replace('\\', "\\\\").replace('"', "\\\"");
if *interpolated {
format!("(string_interpolated \"{}\")", escaped_value)
} else {
format!("(string \"{}\")", escaped_value)
}
}
NodeKind::Heredoc { delimiter, content, interpolated, indented, command, .. } => {
let type_str = if *command {
"heredoc_command"
} else if *indented {
if *interpolated { "heredoc_indented_interpolated" } else { "heredoc_indented" }
} else if *interpolated {
"heredoc_interpolated"
} else {
"heredoc"
};
format!("({} {:?} {:?})", type_str, delimiter, content)
}
NodeKind::ArrayLiteral { elements } => {
let elems = elements.iter().map(|e| e.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(array {})", elems)
}
NodeKind::HashLiteral { pairs } => {
let kvs = pairs
.iter()
.map(|(k, v)| format!("({} {})", k.to_sexp(), v.to_sexp()))
.collect::<Vec<_>>()
.join(" ");
format!("(hash {})", kvs)
}
NodeKind::Block { statements } => {
let stmts = statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(block {})", stmts)
}
NodeKind::Eval { block } => {
format!("(eval {})", block.to_sexp())
}
NodeKind::Do { block } => {
format!("(do {})", block.to_sexp())
}
NodeKind::Defer { block } => {
format!("(defer {})", block.to_sexp())
}
NodeKind::Try { body, catch_blocks, finally_block } => {
let mut parts = vec![format!("(try {})", body.to_sexp())];
for (var, block) in catch_blocks {
if let Some(v) = var {
parts.push(format!("(catch {} {})", v, block.to_sexp()));
} else {
parts.push(format!("(catch {})", block.to_sexp()));
}
}
if let Some(finally) = finally_block {
parts.push(format!("(finally {})", finally.to_sexp()));
}
parts.join(" ")
}
NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
let mut parts =
vec![format!("(if {} {})", condition.to_sexp(), then_branch.to_sexp())];
for (cond, block) in elsif_branches {
parts.push(format!("(elsif {} {})", cond.to_sexp(), block.to_sexp()));
}
if let Some(else_block) = else_branch {
parts.push(format!("(else {})", else_block.to_sexp()));
}
parts.join(" ")
}
NodeKind::LabeledStatement { label, statement } => {
format!("(labeled_statement {} {})", label, statement.to_sexp())
}
NodeKind::While { condition, body, continue_block } => {
let mut s = format!("(while {} {})", condition.to_sexp(), body.to_sexp());
if let Some(cont) = continue_block {
s.push_str(&format!(" (continue {})", cont.to_sexp()));
}
s
}
NodeKind::Tie { variable, package, args } => {
let mut s = format!("(tie {} {}", variable.to_sexp(), package.to_sexp());
for arg in args {
s.push_str(&format!(" {}", arg.to_sexp()));
}
s.push(')');
s
}
NodeKind::Untie { variable } => {
format!("(untie {})", variable.to_sexp())
}
NodeKind::For { init, condition, update, body, continue_block } => {
let init_str =
init.as_ref().map(|i| i.to_sexp()).unwrap_or_else(|| "()".to_string());
let cond_str =
condition.as_ref().map(|c| c.to_sexp()).unwrap_or_else(|| "()".to_string());
let update_str =
update.as_ref().map(|u| u.to_sexp()).unwrap_or_else(|| "()".to_string());
let mut result =
format!("(for {} {} {} {})", init_str, cond_str, update_str, body.to_sexp());
if let Some(cont) = continue_block {
result.push_str(&format!(" (continue {})", cont.to_sexp()));
}
result
}
NodeKind::Foreach { variable, list, body, continue_block } => {
let cont = if let Some(cb) = continue_block {
format!(" {}", cb.to_sexp())
} else {
String::new()
};
format!(
"(foreach {} {} {}{})",
variable.to_sexp(),
list.to_sexp(),
body.to_sexp(),
cont
)
}
NodeKind::Given { expr, body } => {
format!("(given {} {})", expr.to_sexp(), body.to_sexp())
}
NodeKind::When { condition, body } => {
format!("(when {} {})", condition.to_sexp(), body.to_sexp())
}
NodeKind::Default { body } => {
format!("(default {})", body.to_sexp())
}
NodeKind::StatementModifier { statement, modifier, condition } => {
format!(
"(statement_modifier_{} {} {})",
modifier,
statement.to_sexp(),
condition.to_sexp()
)
}
NodeKind::Subroutine { name, prototype, signature, attributes, body, name_span: _ } => {
if let Some(sub_name) = name {
let mut parts = vec![sub_name.clone()];
if !attributes.is_empty() {
for attr in attributes {
parts.push(format!(":{}", attr));
}
}
if let Some(proto) = prototype {
parts.push(format!("({})", proto.to_sexp()));
} else if signature.is_some() {
parts.push("()".to_string());
} else {
parts.push("()".to_string());
}
parts.push(body.to_sexp());
if parts.len() >= 3 && parts[parts.len() - 2] == "()" {
let name_and_attrs = parts[0..parts.len() - 2].join(" ");
let proto = &parts[parts.len() - 2];
let body = &parts[parts.len() - 1];
format!("(sub {} {}{})", name_and_attrs, proto, body)
} else {
format!("(sub {})", parts.join(" "))
}
} else {
let mut parts = Vec::new();
if !attributes.is_empty() {
let attrs: Vec<String> = attributes
.iter()
.map(|_attr| "(attribute (attribute_name))".to_string())
.collect();
parts.push(format!("(attrlist {})", attrs.join("")));
}
if let Some(proto) = prototype {
parts.push(proto.to_sexp());
}
if let Some(sig) = signature {
parts.push(sig.to_sexp());
}
parts.push(body.to_sexp());
format!("(anonymous_subroutine_expression {})", parts.join(""))
}
}
NodeKind::Prototype { content: _ } => "(prototype)".to_string(),
NodeKind::Signature { parameters } => {
let params = parameters.iter().map(|p| p.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(signature {})", params)
}
NodeKind::MandatoryParameter { variable } => {
format!("(mandatory_parameter {})", variable.to_sexp())
}
NodeKind::OptionalParameter { variable, default_value } => {
format!("(optional_parameter {} {})", variable.to_sexp(), default_value.to_sexp())
}
NodeKind::SlurpyParameter { variable } => {
format!("(slurpy_parameter {})", variable.to_sexp())
}
NodeKind::NamedParameter { variable } => {
format!("(named_parameter {})", variable.to_sexp())
}
NodeKind::Method { name: _, signature, attributes, body } => {
let block_contents = match &body.kind {
NodeKind::Block { statements } => {
statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ")
}
_ => body.to_sexp(),
};
let mut parts = vec!["(bareword)".to_string()];
if let Some(sig) = signature {
parts.push(sig.to_sexp());
}
if !attributes.is_empty() {
let attrs: Vec<String> = attributes
.iter()
.map(|_attr| "(attribute (attribute_name))".to_string())
.collect();
parts.push(format!("(attrlist {})", attrs.join("")));
}
parts.push(format!("(block {})", block_contents));
format!("(method_declaration_statement {})", parts.join(" "))
}
NodeKind::Return { value } => {
if let Some(val) = value {
format!("(return {})", val.to_sexp())
} else {
"(return)".to_string()
}
}
NodeKind::LoopControl { op, label } => {
if let Some(l) = label {
format!("({} {})", op, l)
} else {
format!("({})", op)
}
}
NodeKind::Goto { target } => {
format!("(goto {})", target.to_sexp())
}
NodeKind::MethodCall { object, method, args } => {
let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(method_call {} {} ({}))", object.to_sexp(), method, args_str)
}
NodeKind::FunctionCall { name, args } => {
if matches!(
name.as_str(),
"bless"
| "shift"
| "unshift"
| "open"
| "die"
| "warn"
| "print"
| "printf"
| "say"
| "push"
| "pop"
| "map"
| "sort"
| "grep"
| "keys"
| "values"
| "each"
| "defined"
| "scalar"
| "ref"
) {
let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
if args.is_empty() {
format!("(call {} ())", name)
} else {
format!("(call {} ({}))", name, args_str)
}
} else {
let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
if args.is_empty() {
"(function_call_expression (function))".to_string()
} else {
format!("(ambiguous_function_call_expression (function) {})", args_str)
}
}
}
NodeKind::IndirectCall { method, object, args } => {
let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
format!("(indirect_call {} {} ({}))", method, object.to_sexp(), args_str)
}
NodeKind::Regex { pattern, replacement, modifiers, has_embedded_code } => {
let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
format!("(regex {:?} {:?} {:?}{})", pattern, replacement, modifiers, risk_marker)
}
NodeKind::Match { expr, pattern, modifiers, has_embedded_code, negated } => {
let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
let op = if *negated { "not_match" } else { "match" };
format!(
"({} {} (regex {:?} {:?}{}))",
op,
expr.to_sexp(),
pattern,
modifiers,
risk_marker
)
}
NodeKind::Substitution {
expr,
pattern,
replacement,
modifiers,
has_embedded_code,
negated,
} => {
let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
let neg_marker = if *negated { " (negated)" } else { "" };
format!(
"(substitution {} {:?} {:?} {:?}{}{})",
expr.to_sexp(),
pattern,
replacement,
modifiers,
risk_marker,
neg_marker
)
}
NodeKind::Transliteration { expr, search, replace, modifiers, negated } => {
let neg_marker = if *negated { " (negated)" } else { "" };
format!(
"(transliteration {} {:?} {:?} {:?}{})",
expr.to_sexp(),
search,
replace,
modifiers,
neg_marker
)
}
NodeKind::Package { name, block, name_span: _ } => {
if let Some(blk) = block {
format!("(package {} {})", name, blk.to_sexp())
} else {
format!("(package {})", name)
}
}
NodeKind::Use { module, args, has_filter_risk } => {
let risk_marker = if *has_filter_risk { " (risk:filter)" } else { "" };
if args.is_empty() {
format!("(use {}{})", module, risk_marker)
} else {
let args_str = args.join(" ");
format!("(use {} ({}){})", module, args_str, risk_marker)
}
}
NodeKind::No { module, args, has_filter_risk } => {
let risk_marker = if *has_filter_risk { " (risk:filter)" } else { "" };
if args.is_empty() {
format!("(no {}{})", module, risk_marker)
} else {
let args_str = args.join(" ");
format!("(no {} ({}){})", module, args_str, risk_marker)
}
}
NodeKind::PhaseBlock { phase, phase_span: _, block } => {
format!("({} {})", phase, block.to_sexp())
}
NodeKind::DataSection { marker, body } => {
if let Some(body_text) = body {
format!("(data_section {} \"{}\")", marker, body_text.escape_default())
} else {
format!("(data_section {})", marker)
}
}
NodeKind::Class { name, parents, body } => {
if parents.is_empty() {
format!("(class {} {})", name, body.to_sexp())
} else {
format!("(class {} :isa({}) {})", name, parents.join(","), body.to_sexp())
}
}
NodeKind::Format { name, body } => {
format!("(format {} {:?})", name, body)
}
NodeKind::Identifier { name } => {
format!("(identifier {})", name)
}
NodeKind::Error { message, partial, .. } => {
if let Some(node) = partial {
format!("(ERROR \"{}\" {})", message.escape_default(), node.to_sexp())
} else {
format!("(ERROR \"{}\")", message.escape_default())
}
}
NodeKind::MissingExpression => "(missing_expression)".to_string(),
NodeKind::MissingStatement => "(missing_statement)".to_string(),
NodeKind::MissingIdentifier => "(missing_identifier)".to_string(),
NodeKind::MissingBlock => "(missing_block)".to_string(),
NodeKind::UnknownRest => "(UNKNOWN_REST)".to_string(),
}
}
pub fn to_sexp_inner(&self) -> String {
match &self.kind {
NodeKind::ExpressionStatement { expression } => {
match &expression.kind {
NodeKind::Subroutine { name, .. } if name.is_none() => {
self.to_sexp()
}
_ => {
expression.to_sexp()
}
}
}
_ => {
self.to_sexp()
}
}
}
#[inline]
pub fn for_each_child_mut<F: FnMut(&mut Node)>(&mut self, mut f: F) {
match &mut self.kind {
NodeKind::Tie { variable, package, args } => {
f(variable);
f(package);
for arg in args {
f(arg);
}
}
NodeKind::Untie { variable } => f(variable),
NodeKind::Program { statements } => {
for stmt in statements {
f(stmt);
}
}
NodeKind::ExpressionStatement { expression } => f(expression),
NodeKind::VariableDeclaration { variable, initializer, .. } => {
f(variable);
if let Some(init) = initializer {
f(init);
}
}
NodeKind::VariableListDeclaration { variables, initializer, .. } => {
for var in variables {
f(var);
}
if let Some(init) = initializer {
f(init);
}
}
NodeKind::VariableWithAttributes { variable, .. } => f(variable),
NodeKind::Binary { left, right, .. } => {
f(left);
f(right);
}
NodeKind::Ternary { condition, then_expr, else_expr } => {
f(condition);
f(then_expr);
f(else_expr);
}
NodeKind::Unary { operand, .. } => f(operand),
NodeKind::Assignment { lhs, rhs, .. } => {
f(lhs);
f(rhs);
}
NodeKind::Block { statements } => {
for stmt in statements {
f(stmt);
}
}
NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
f(condition);
f(then_branch);
for (elsif_cond, elsif_body) in elsif_branches {
f(elsif_cond);
f(elsif_body);
}
if let Some(else_body) = else_branch {
f(else_body);
}
}
NodeKind::While { condition, body, continue_block, .. } => {
f(condition);
f(body);
if let Some(cont) = continue_block {
f(cont);
}
}
NodeKind::For { init, condition, update, body, continue_block, .. } => {
if let Some(i) = init {
f(i);
}
if let Some(c) = condition {
f(c);
}
if let Some(u) = update {
f(u);
}
f(body);
if let Some(cont) = continue_block {
f(cont);
}
}
NodeKind::Foreach { variable, list, body, continue_block } => {
f(variable);
f(list);
f(body);
if let Some(cb) = continue_block {
f(cb);
}
}
NodeKind::Given { expr, body } => {
f(expr);
f(body);
}
NodeKind::When { condition, body } => {
f(condition);
f(body);
}
NodeKind::Default { body } => f(body),
NodeKind::StatementModifier { statement, condition, .. } => {
f(statement);
f(condition);
}
NodeKind::LabeledStatement { statement, .. } => f(statement),
NodeKind::Eval { block } => f(block),
NodeKind::Do { block } => f(block),
NodeKind::Defer { block } => f(block),
NodeKind::Try { body, catch_blocks, finally_block } => {
f(body);
for (_, catch_body) in catch_blocks {
f(catch_body);
}
if let Some(finally) = finally_block {
f(finally);
}
}
NodeKind::FunctionCall { args, .. } => {
for arg in args {
f(arg);
}
}
NodeKind::MethodCall { object, args, .. } => {
f(object);
for arg in args {
f(arg);
}
}
NodeKind::IndirectCall { object, args, .. } => {
f(object);
for arg in args {
f(arg);
}
}
NodeKind::Subroutine { prototype, signature, body, .. } => {
if let Some(proto) = prototype {
f(proto);
}
if let Some(sig) = signature {
f(sig);
}
f(body);
}
NodeKind::Method { signature, body, .. } => {
if let Some(sig) = signature {
f(sig);
}
f(body);
}
NodeKind::Return { value } => {
if let Some(v) = value {
f(v);
}
}
NodeKind::Goto { target } => f(target),
NodeKind::Signature { parameters } => {
for param in parameters {
f(param);
}
}
NodeKind::MandatoryParameter { variable } => f(variable),
NodeKind::OptionalParameter { variable, default_value } => {
f(variable);
f(default_value);
}
NodeKind::SlurpyParameter { variable } => f(variable),
NodeKind::NamedParameter { variable } => f(variable),
NodeKind::Match { expr, .. } => f(expr),
NodeKind::Substitution { expr, .. } => f(expr),
NodeKind::Transliteration { expr, .. } => f(expr),
NodeKind::ArrayLiteral { elements } => {
for elem in elements {
f(elem);
}
}
NodeKind::HashLiteral { pairs } => {
for (key, value) in pairs {
f(key);
f(value);
}
}
NodeKind::Package { block, .. } => {
if let Some(b) = block {
f(b);
}
}
NodeKind::PhaseBlock { block, .. } => f(block),
NodeKind::Class { body, .. } => f(body),
NodeKind::Error { partial, .. } => {
if let Some(node) = partial {
f(node);
}
}
NodeKind::Variable { .. }
| NodeKind::Identifier { .. }
| NodeKind::Number { .. }
| NodeKind::String { .. }
| NodeKind::Heredoc { .. }
| NodeKind::Regex { .. }
| NodeKind::Readline { .. }
| NodeKind::Glob { .. }
| NodeKind::Typeglob { .. }
| NodeKind::Diamond
| NodeKind::Ellipsis
| NodeKind::Undef
| NodeKind::Use { .. }
| NodeKind::No { .. }
| NodeKind::Prototype { .. }
| NodeKind::DataSection { .. }
| NodeKind::Format { .. }
| NodeKind::LoopControl { .. }
| NodeKind::MissingExpression
| NodeKind::MissingStatement
| NodeKind::MissingIdentifier
| NodeKind::MissingBlock
| NodeKind::UnknownRest => {}
}
}
#[inline]
pub fn for_each_child<'a, F: FnMut(&'a Node)>(&'a self, mut f: F) {
match &self.kind {
NodeKind::Tie { variable, package, args } => {
f(variable);
f(package);
for arg in args {
f(arg);
}
}
NodeKind::Untie { variable } => f(variable),
NodeKind::Program { statements } => {
for stmt in statements {
f(stmt);
}
}
NodeKind::ExpressionStatement { expression } => f(expression),
NodeKind::VariableDeclaration { variable, initializer, .. } => {
f(variable);
if let Some(init) = initializer {
f(init);
}
}
NodeKind::VariableListDeclaration { variables, initializer, .. } => {
for var in variables {
f(var);
}
if let Some(init) = initializer {
f(init);
}
}
NodeKind::VariableWithAttributes { variable, .. } => f(variable),
NodeKind::Binary { left, right, .. } => {
f(left);
f(right);
}
NodeKind::Ternary { condition, then_expr, else_expr } => {
f(condition);
f(then_expr);
f(else_expr);
}
NodeKind::Unary { operand, .. } => f(operand),
NodeKind::Assignment { lhs, rhs, .. } => {
f(lhs);
f(rhs);
}
NodeKind::Block { statements } => {
for stmt in statements {
f(stmt);
}
}
NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
f(condition);
f(then_branch);
for (elsif_cond, elsif_body) in elsif_branches {
f(elsif_cond);
f(elsif_body);
}
if let Some(else_body) = else_branch {
f(else_body);
}
}
NodeKind::While { condition, body, continue_block, .. } => {
f(condition);
f(body);
if let Some(cont) = continue_block {
f(cont);
}
}
NodeKind::For { init, condition, update, body, continue_block, .. } => {
if let Some(i) = init {
f(i);
}
if let Some(c) = condition {
f(c);
}
if let Some(u) = update {
f(u);
}
f(body);
if let Some(cont) = continue_block {
f(cont);
}
}
NodeKind::Foreach { variable, list, body, continue_block } => {
f(variable);
f(list);
f(body);
if let Some(cb) = continue_block {
f(cb);
}
}
NodeKind::Given { expr, body } => {
f(expr);
f(body);
}
NodeKind::When { condition, body } => {
f(condition);
f(body);
}
NodeKind::Default { body } => f(body),
NodeKind::StatementModifier { statement, condition, .. } => {
f(statement);
f(condition);
}
NodeKind::LabeledStatement { statement, .. } => f(statement),
NodeKind::Eval { block } => f(block),
NodeKind::Do { block } => f(block),
NodeKind::Defer { block } => f(block),
NodeKind::Try { body, catch_blocks, finally_block } => {
f(body);
for (_, catch_body) in catch_blocks {
f(catch_body);
}
if let Some(finally) = finally_block {
f(finally);
}
}
NodeKind::FunctionCall { args, .. } => {
for arg in args {
f(arg);
}
}
NodeKind::MethodCall { object, args, .. } => {
f(object);
for arg in args {
f(arg);
}
}
NodeKind::IndirectCall { object, args, .. } => {
f(object);
for arg in args {
f(arg);
}
}
NodeKind::Subroutine { prototype, signature, body, .. } => {
if let Some(proto) = prototype {
f(proto);
}
if let Some(sig) = signature {
f(sig);
}
f(body);
}
NodeKind::Method { signature, body, .. } => {
if let Some(sig) = signature {
f(sig);
}
f(body);
}
NodeKind::Return { value } => {
if let Some(v) = value {
f(v);
}
}
NodeKind::Goto { target } => f(target),
NodeKind::Signature { parameters } => {
for param in parameters {
f(param);
}
}
NodeKind::MandatoryParameter { variable } => f(variable),
NodeKind::OptionalParameter { variable, default_value } => {
f(variable);
f(default_value);
}
NodeKind::SlurpyParameter { variable } => f(variable),
NodeKind::NamedParameter { variable } => f(variable),
NodeKind::Match { expr, .. } => f(expr),
NodeKind::Substitution { expr, .. } => f(expr),
NodeKind::Transliteration { expr, .. } => f(expr),
NodeKind::ArrayLiteral { elements } => {
for elem in elements {
f(elem);
}
}
NodeKind::HashLiteral { pairs } => {
for (key, value) in pairs {
f(key);
f(value);
}
}
NodeKind::Package { block, .. } => {
if let Some(b) = block {
f(b);
}
}
NodeKind::PhaseBlock { block, .. } => f(block),
NodeKind::Class { body, .. } => f(body),
NodeKind::Error { partial, .. } => {
if let Some(node) = partial {
f(node);
}
}
NodeKind::Variable { .. }
| NodeKind::Identifier { .. }
| NodeKind::Number { .. }
| NodeKind::String { .. }
| NodeKind::Heredoc { .. }
| NodeKind::Regex { .. }
| NodeKind::Readline { .. }
| NodeKind::Glob { .. }
| NodeKind::Typeglob { .. }
| NodeKind::Diamond
| NodeKind::Ellipsis
| NodeKind::Undef
| NodeKind::Use { .. }
| NodeKind::No { .. }
| NodeKind::Prototype { .. }
| NodeKind::DataSection { .. }
| NodeKind::Format { .. }
| NodeKind::LoopControl { .. }
| NodeKind::MissingExpression
| NodeKind::MissingStatement
| NodeKind::MissingIdentifier
| NodeKind::MissingBlock
| NodeKind::UnknownRest => {}
}
}
pub fn count_nodes(&self) -> usize {
let mut count = 1;
self.for_each_child(|child| {
count += child.count_nodes();
});
count
}
#[inline]
pub fn children(&self) -> Vec<&Node> {
let mut children = Vec::new();
self.for_each_child(|child| children.push(child));
children
}
#[inline]
pub fn child_count(&self) -> usize {
let mut count = 0;
self.for_each_child(|_| count += 1);
count
}
#[inline]
pub fn first_child(&self) -> Option<&Node> {
let mut result = None;
self.for_each_child(|child| {
if result.is_none() {
result = Some(child);
}
});
result
}
#[inline]
pub fn contains_offset(&self, offset: usize) -> bool {
self.location.start <= offset && offset < self.location.end
}
#[inline]
pub fn span_len(&self) -> usize {
self.location.end.saturating_sub(self.location.start)
}
#[inline]
pub fn last_child(&self) -> Option<&Node> {
let mut result = None;
self.for_each_child(|child| {
result = Some(child);
});
result
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeKind {
Program {
statements: Vec<Node>,
},
ExpressionStatement {
expression: Box<Node>,
},
VariableDeclaration {
declarator: String,
variable: Box<Node>,
attributes: Vec<String>,
initializer: Option<Box<Node>>,
},
VariableListDeclaration {
declarator: String,
variables: Vec<Node>,
attributes: Vec<String>,
initializer: Option<Box<Node>>,
},
Variable {
sigil: String, name: String,
},
VariableWithAttributes {
variable: Box<Node>,
attributes: Vec<String>,
},
Assignment {
lhs: Box<Node>,
rhs: Box<Node>,
op: String, },
Binary {
op: String,
left: Box<Node>,
right: Box<Node>,
},
Ternary {
condition: Box<Node>,
then_expr: Box<Node>,
else_expr: Box<Node>,
},
Unary {
op: String,
operand: Box<Node>,
},
Diamond,
Ellipsis,
Undef,
Readline {
filehandle: Option<String>, },
Glob {
pattern: String, },
Typeglob {
name: String,
},
Number {
value: String,
},
String {
value: String,
interpolated: bool,
},
Heredoc {
delimiter: String,
content: String,
interpolated: bool,
indented: bool,
command: bool,
body_span: Option<SourceLocation>,
},
ArrayLiteral {
elements: Vec<Node>,
},
HashLiteral {
pairs: Vec<(Node, Node)>,
},
Block {
statements: Vec<Node>,
},
Eval {
block: Box<Node>,
},
Do {
block: Box<Node>,
},
Defer {
block: Box<Node>,
},
Try {
body: Box<Node>,
catch_blocks: Vec<(Option<String>, Box<Node>)>,
finally_block: Option<Box<Node>>,
},
If {
condition: Box<Node>,
then_branch: Box<Node>,
elsif_branches: Vec<(Box<Node>, Box<Node>)>,
else_branch: Option<Box<Node>>,
},
LabeledStatement {
label: String,
statement: Box<Node>,
},
While {
condition: Box<Node>,
body: Box<Node>,
continue_block: Option<Box<Node>>,
},
Tie {
variable: Box<Node>,
package: Box<Node>,
args: Vec<Node>,
},
Untie {
variable: Box<Node>,
},
For {
init: Option<Box<Node>>,
condition: Option<Box<Node>>,
update: Option<Box<Node>>,
body: Box<Node>,
continue_block: Option<Box<Node>>,
},
Foreach {
variable: Box<Node>,
list: Box<Node>,
body: Box<Node>,
continue_block: Option<Box<Node>>,
},
Given {
expr: Box<Node>,
body: Box<Node>,
},
When {
condition: Box<Node>,
body: Box<Node>,
},
Default {
body: Box<Node>,
},
StatementModifier {
statement: Box<Node>,
modifier: String,
condition: Box<Node>,
},
Subroutine {
name: Option<String>,
name_span: Option<SourceLocation>,
prototype: Option<Box<Node>>,
signature: Option<Box<Node>>,
attributes: Vec<String>,
body: Box<Node>,
},
Prototype {
content: String,
},
Signature {
parameters: Vec<Node>,
},
MandatoryParameter {
variable: Box<Node>,
},
OptionalParameter {
variable: Box<Node>,
default_value: Box<Node>,
},
SlurpyParameter {
variable: Box<Node>,
},
NamedParameter {
variable: Box<Node>,
},
Method {
name: String,
signature: Option<Box<Node>>,
attributes: Vec<String>,
body: Box<Node>,
},
Return {
value: Option<Box<Node>>,
},
LoopControl {
op: String,
label: Option<String>,
},
Goto {
target: Box<Node>,
},
MethodCall {
object: Box<Node>,
method: String,
args: Vec<Node>,
},
FunctionCall {
name: String,
args: Vec<Node>,
},
IndirectCall {
method: String,
object: Box<Node>,
args: Vec<Node>,
},
Regex {
pattern: String,
replacement: Option<String>,
modifiers: String,
has_embedded_code: bool,
},
Match {
expr: Box<Node>,
pattern: String,
modifiers: String,
has_embedded_code: bool,
negated: bool,
},
Substitution {
expr: Box<Node>,
pattern: String,
replacement: String,
modifiers: String,
has_embedded_code: bool,
negated: bool,
},
Transliteration {
expr: Box<Node>,
search: String,
replace: String,
modifiers: String,
negated: bool,
},
Package {
name: String,
name_span: SourceLocation,
block: Option<Box<Node>>,
},
Use {
module: String,
args: Vec<String>,
has_filter_risk: bool,
},
No {
module: String,
args: Vec<String>,
has_filter_risk: bool,
},
PhaseBlock {
phase: String,
phase_span: Option<SourceLocation>,
block: Box<Node>,
},
DataSection {
marker: String,
body: Option<String>,
},
Class {
name: String,
parents: Vec<String>,
body: Box<Node>,
},
Format {
name: String,
body: String,
},
Identifier {
name: String,
},
Error {
message: String,
expected: Vec<TokenKind>,
found: Option<Token>,
partial: Option<Box<Node>>,
},
MissingExpression,
MissingStatement,
MissingIdentifier,
MissingBlock,
UnknownRest,
}
impl NodeKind {
pub fn kind_name(&self) -> &'static str {
match self {
NodeKind::Program { .. } => "Program",
NodeKind::ExpressionStatement { .. } => "ExpressionStatement",
NodeKind::VariableDeclaration { .. } => "VariableDeclaration",
NodeKind::VariableListDeclaration { .. } => "VariableListDeclaration",
NodeKind::Variable { .. } => "Variable",
NodeKind::VariableWithAttributes { .. } => "VariableWithAttributes",
NodeKind::Assignment { .. } => "Assignment",
NodeKind::Binary { .. } => "Binary",
NodeKind::Ternary { .. } => "Ternary",
NodeKind::Unary { .. } => "Unary",
NodeKind::Diamond => "Diamond",
NodeKind::Ellipsis => "Ellipsis",
NodeKind::Undef => "Undef",
NodeKind::Readline { .. } => "Readline",
NodeKind::Glob { .. } => "Glob",
NodeKind::Typeglob { .. } => "Typeglob",
NodeKind::Number { .. } => "Number",
NodeKind::String { .. } => "String",
NodeKind::Heredoc { .. } => "Heredoc",
NodeKind::ArrayLiteral { .. } => "ArrayLiteral",
NodeKind::HashLiteral { .. } => "HashLiteral",
NodeKind::Block { .. } => "Block",
NodeKind::Eval { .. } => "Eval",
NodeKind::Do { .. } => "Do",
NodeKind::Defer { .. } => "Defer",
NodeKind::Try { .. } => "Try",
NodeKind::If { .. } => "If",
NodeKind::LabeledStatement { .. } => "LabeledStatement",
NodeKind::While { .. } => "While",
NodeKind::Tie { .. } => "Tie",
NodeKind::Untie { .. } => "Untie",
NodeKind::For { .. } => "For",
NodeKind::Foreach { .. } => "Foreach",
NodeKind::Given { .. } => "Given",
NodeKind::When { .. } => "When",
NodeKind::Default { .. } => "Default",
NodeKind::StatementModifier { .. } => "StatementModifier",
NodeKind::Subroutine { .. } => "Subroutine",
NodeKind::Prototype { .. } => "Prototype",
NodeKind::Signature { .. } => "Signature",
NodeKind::MandatoryParameter { .. } => "MandatoryParameter",
NodeKind::OptionalParameter { .. } => "OptionalParameter",
NodeKind::SlurpyParameter { .. } => "SlurpyParameter",
NodeKind::NamedParameter { .. } => "NamedParameter",
NodeKind::Method { .. } => "Method",
NodeKind::Return { .. } => "Return",
NodeKind::LoopControl { .. } => "LoopControl",
NodeKind::Goto { .. } => "Goto",
NodeKind::MethodCall { .. } => "MethodCall",
NodeKind::FunctionCall { .. } => "FunctionCall",
NodeKind::IndirectCall { .. } => "IndirectCall",
NodeKind::Regex { .. } => "Regex",
NodeKind::Match { .. } => "Match",
NodeKind::Substitution { .. } => "Substitution",
NodeKind::Transliteration { .. } => "Transliteration",
NodeKind::Package { .. } => "Package",
NodeKind::Use { .. } => "Use",
NodeKind::No { .. } => "No",
NodeKind::PhaseBlock { .. } => "PhaseBlock",
NodeKind::DataSection { .. } => "DataSection",
NodeKind::Class { .. } => "Class",
NodeKind::Format { .. } => "Format",
NodeKind::Identifier { .. } => "Identifier",
NodeKind::Error { .. } => "Error",
NodeKind::MissingExpression => "MissingExpression",
NodeKind::MissingStatement => "MissingStatement",
NodeKind::MissingIdentifier => "MissingIdentifier",
NodeKind::MissingBlock => "MissingBlock",
NodeKind::UnknownRest => "UnknownRest",
}
}
pub const ALL_KIND_NAMES: &[&'static str] = &[
"ArrayLiteral",
"Assignment",
"Binary",
"Block",
"Class",
"DataSection",
"Default",
"Defer",
"Diamond",
"Do",
"Ellipsis",
"Error",
"Eval",
"ExpressionStatement",
"For",
"Foreach",
"Format",
"FunctionCall",
"Given",
"Glob",
"Goto",
"HashLiteral",
"Heredoc",
"Identifier",
"If",
"IndirectCall",
"LabeledStatement",
"LoopControl",
"MandatoryParameter",
"Match",
"Method",
"MethodCall",
"MissingBlock",
"MissingExpression",
"MissingIdentifier",
"MissingStatement",
"NamedParameter",
"No",
"Number",
"OptionalParameter",
"Package",
"PhaseBlock",
"Program",
"Prototype",
"Readline",
"Regex",
"Return",
"Signature",
"SlurpyParameter",
"StatementModifier",
"String",
"Subroutine",
"Substitution",
"Ternary",
"Tie",
"Transliteration",
"Try",
"Typeglob",
"Unary",
"Undef",
"UnknownRest",
"Untie",
"Use",
"Variable",
"VariableDeclaration",
"VariableListDeclaration",
"VariableWithAttributes",
"When",
"While",
];
pub const RECOVERY_KIND_NAMES: &[&'static str] = &[
"Error",
"MissingBlock",
"MissingExpression",
"MissingIdentifier",
"MissingStatement",
"UnknownRest",
];
}
impl fmt::Display for NodeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.kind_name())
}
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_sexp())
}
}
fn format_unary_operator(op: &str) -> String {
match op {
"+" => "unary_+".to_string(),
"-" => "unary_-".to_string(),
"!" => "unary_not".to_string(),
"not" => "unary_not".to_string(),
"~" => "unary_complement".to_string(),
"\\" => "unary_ref".to_string(),
"++" => "unary_++".to_string(),
"--" => "unary_--".to_string(),
"-f" => "unary_-f".to_string(),
"-d" => "unary_-d".to_string(),
"-e" => "unary_-e".to_string(),
"-r" => "unary_-r".to_string(),
"-w" => "unary_-w".to_string(),
"-x" => "unary_-x".to_string(),
"-o" => "unary_-o".to_string(),
"-R" => "unary_-R".to_string(),
"-W" => "unary_-W".to_string(),
"-X" => "unary_-X".to_string(),
"-O" => "unary_-O".to_string(),
"-s" => "unary_-s".to_string(),
"-p" => "unary_-p".to_string(),
"-S" => "unary_-S".to_string(),
"-b" => "unary_-b".to_string(),
"-c" => "unary_-c".to_string(),
"-t" => "unary_-t".to_string(),
"-u" => "unary_-u".to_string(),
"-g" => "unary_-g".to_string(),
"-k" => "unary_-k".to_string(),
"-T" => "unary_-T".to_string(),
"-B" => "unary_-B".to_string(),
"-M" => "unary_-M".to_string(),
"-A" => "unary_-A".to_string(),
"-C" => "unary_-C".to_string(),
"-l" => "unary_-l".to_string(),
"-z" => "unary_-z".to_string(),
"->@*" => "unary_->@*".to_string(),
"->%*" => "unary_->%*".to_string(),
"->$*" => "unary_->$*".to_string(),
"->&*" => "unary_->&*".to_string(),
"->**" => "unary_->**".to_string(),
"defined" => "unary_defined".to_string(),
_ => format!("unary_{}", op.replace(' ', "_")),
}
}
fn format_binary_operator(op: &str) -> String {
match op {
"+" => "binary_+".to_string(),
"-" => "binary_-".to_string(),
"*" => "binary_*".to_string(),
"/" => "binary_/".to_string(),
"%" => "binary_%".to_string(),
"**" => "binary_**".to_string(),
"==" => "binary_==".to_string(),
"!=" => "binary_!=".to_string(),
"<" => "binary_<".to_string(),
">" => "binary_>".to_string(),
"<=" => "binary_<=".to_string(),
">=" => "binary_>=".to_string(),
"<=>" => "binary_<=>".to_string(),
"eq" => "binary_eq".to_string(),
"ne" => "binary_ne".to_string(),
"lt" => "binary_lt".to_string(),
"le" => "binary_le".to_string(),
"gt" => "binary_gt".to_string(),
"ge" => "binary_ge".to_string(),
"cmp" => "binary_cmp".to_string(),
"&&" => "binary_&&".to_string(),
"||" => "binary_||".to_string(),
"and" => "binary_and".to_string(),
"or" => "binary_or".to_string(),
"xor" => "binary_xor".to_string(),
"&" => "binary_&".to_string(),
"|" => "binary_|".to_string(),
"^" => "binary_^".to_string(),
"<<" => "binary_<<".to_string(),
">>" => "binary_>>".to_string(),
"=~" => "binary_=~".to_string(),
"!~" => "binary_!~".to_string(),
"~~" => "binary_~~".to_string(),
"x" => "binary_x".to_string(),
"." => "binary_.".to_string(),
".." => "binary_..".to_string(),
"..." => "binary_...".to_string(),
"isa" => "binary_isa".to_string(),
"=" => "binary_=".to_string(),
"+=" => "binary_+=".to_string(),
"-=" => "binary_-=".to_string(),
"*=" => "binary_*=".to_string(),
"/=" => "binary_/=".to_string(),
"%=" => "binary_%=".to_string(),
"**=" => "binary_**=".to_string(),
".=" => "binary_.=".to_string(),
"&=" => "binary_&=".to_string(),
"|=" => "binary_|=".to_string(),
"^=" => "binary_^=".to_string(),
"<<=" => "binary_<<=".to_string(),
">>=" => "binary_>>=".to_string(),
"&&=" => "binary_&&=".to_string(),
"||=" => "binary_||=".to_string(),
"//=" => "binary_//=".to_string(),
"//" => "binary_//".to_string(),
"->" => "binary_->".to_string(),
"{}" => "binary_{}".to_string(),
"[]" => "binary_[]".to_string(),
"->{}" => "arrow_hash_deref".to_string(),
"->[]" => "arrow_array_deref".to_string(),
_ => format!("binary_{}", op.replace(' ', "_")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
fn all_kind_names_from_variants() -> BTreeSet<&'static str> {
let loc = SourceLocation { start: 0, end: 0 };
let dummy_node = || Node::new(NodeKind::Undef, loc);
let variants: Vec<NodeKind> = vec![
NodeKind::Program { statements: vec![] },
NodeKind::ExpressionStatement { expression: Box::new(dummy_node()) },
NodeKind::VariableDeclaration {
declarator: String::new(),
variable: Box::new(dummy_node()),
attributes: vec![],
initializer: None,
},
NodeKind::VariableListDeclaration {
declarator: String::new(),
variables: vec![],
attributes: vec![],
initializer: None,
},
NodeKind::Variable { sigil: String::new(), name: String::new() },
NodeKind::VariableWithAttributes {
variable: Box::new(dummy_node()),
attributes: vec![],
},
NodeKind::Assignment {
lhs: Box::new(dummy_node()),
rhs: Box::new(dummy_node()),
op: String::new(),
},
NodeKind::Binary {
op: String::new(),
left: Box::new(dummy_node()),
right: Box::new(dummy_node()),
},
NodeKind::Ternary {
condition: Box::new(dummy_node()),
then_expr: Box::new(dummy_node()),
else_expr: Box::new(dummy_node()),
},
NodeKind::Unary { op: String::new(), operand: Box::new(dummy_node()) },
NodeKind::Diamond,
NodeKind::Ellipsis,
NodeKind::Undef,
NodeKind::Readline { filehandle: None },
NodeKind::Glob { pattern: String::new() },
NodeKind::Typeglob { name: String::new() },
NodeKind::Number { value: String::new() },
NodeKind::String { value: String::new(), interpolated: false },
NodeKind::Heredoc {
delimiter: String::new(),
content: String::new(),
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(dummy_node()) },
NodeKind::Do { block: Box::new(dummy_node()) },
NodeKind::Defer { block: Box::new(dummy_node()) },
NodeKind::Try {
body: Box::new(dummy_node()),
catch_blocks: vec![],
finally_block: None,
},
NodeKind::If {
condition: Box::new(dummy_node()),
then_branch: Box::new(dummy_node()),
elsif_branches: vec![],
else_branch: None,
},
NodeKind::LabeledStatement { label: String::new(), statement: Box::new(dummy_node()) },
NodeKind::While {
condition: Box::new(dummy_node()),
body: Box::new(dummy_node()),
continue_block: None,
},
NodeKind::Tie {
variable: Box::new(dummy_node()),
package: Box::new(dummy_node()),
args: vec![],
},
NodeKind::Untie { variable: Box::new(dummy_node()) },
NodeKind::For {
init: None,
condition: None,
update: None,
body: Box::new(dummy_node()),
continue_block: None,
},
NodeKind::Foreach {
variable: Box::new(dummy_node()),
list: Box::new(dummy_node()),
body: Box::new(dummy_node()),
continue_block: None,
},
NodeKind::Given { expr: Box::new(dummy_node()), body: Box::new(dummy_node()) },
NodeKind::When { condition: Box::new(dummy_node()), body: Box::new(dummy_node()) },
NodeKind::Default { body: Box::new(dummy_node()) },
NodeKind::StatementModifier {
statement: Box::new(dummy_node()),
modifier: String::new(),
condition: Box::new(dummy_node()),
},
NodeKind::Subroutine {
name: None,
name_span: None,
prototype: None,
signature: None,
attributes: vec![],
body: Box::new(dummy_node()),
},
NodeKind::Prototype { content: String::new() },
NodeKind::Signature { parameters: vec![] },
NodeKind::MandatoryParameter { variable: Box::new(dummy_node()) },
NodeKind::OptionalParameter {
variable: Box::new(dummy_node()),
default_value: Box::new(dummy_node()),
},
NodeKind::SlurpyParameter { variable: Box::new(dummy_node()) },
NodeKind::NamedParameter { variable: Box::new(dummy_node()) },
NodeKind::Method {
name: String::new(),
signature: None,
attributes: vec![],
body: Box::new(dummy_node()),
},
NodeKind::Return { value: None },
NodeKind::LoopControl { op: String::new(), label: None },
NodeKind::Goto { target: Box::new(dummy_node()) },
NodeKind::MethodCall {
object: Box::new(dummy_node()),
method: String::new(),
args: vec![],
},
NodeKind::FunctionCall { name: String::new(), args: vec![] },
NodeKind::IndirectCall {
method: String::new(),
object: Box::new(dummy_node()),
args: vec![],
},
NodeKind::Regex {
pattern: String::new(),
replacement: None,
modifiers: String::new(),
has_embedded_code: false,
},
NodeKind::Match {
expr: Box::new(dummy_node()),
pattern: String::new(),
modifiers: String::new(),
has_embedded_code: false,
negated: false,
},
NodeKind::Substitution {
expr: Box::new(dummy_node()),
pattern: String::new(),
replacement: String::new(),
modifiers: String::new(),
has_embedded_code: false,
negated: false,
},
NodeKind::Transliteration {
expr: Box::new(dummy_node()),
search: String::new(),
replace: String::new(),
modifiers: String::new(),
negated: false,
},
NodeKind::Package { name: String::new(), name_span: loc, block: None },
NodeKind::Use { module: String::new(), args: vec![], has_filter_risk: false },
NodeKind::No { module: String::new(), args: vec![], has_filter_risk: false },
NodeKind::PhaseBlock {
phase: String::new(),
phase_span: None,
block: Box::new(dummy_node()),
},
NodeKind::DataSection { marker: String::new(), body: None },
NodeKind::Class { name: String::new(), parents: vec![], body: Box::new(dummy_node()) },
NodeKind::Format { name: String::new(), body: String::new() },
NodeKind::Identifier { name: String::new() },
NodeKind::Error {
message: String::new(),
expected: vec![],
found: None,
partial: None,
},
NodeKind::MissingExpression,
NodeKind::MissingStatement,
NodeKind::MissingIdentifier,
NodeKind::MissingBlock,
NodeKind::UnknownRest,
];
variants.iter().map(|v| v.kind_name()).collect()
}
#[test]
fn all_kind_names_is_consistent_with_kind_name() {
let from_enum = all_kind_names_from_variants();
let from_const: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
assert_eq!(
NodeKind::ALL_KIND_NAMES.len(),
from_const.len(),
"ALL_KIND_NAMES contains duplicates"
);
let only_in_enum: Vec<_> = from_enum.difference(&from_const).collect();
let only_in_const: Vec<_> = from_const.difference(&from_enum).collect();
assert!(
only_in_enum.is_empty() && only_in_const.is_empty(),
"ALL_KIND_NAMES is out of sync with NodeKind variants:\n \
in enum but not in ALL_KIND_NAMES: {only_in_enum:?}\n \
in ALL_KIND_NAMES but not in enum: {only_in_const:?}"
);
}
#[test]
fn recovery_kind_names_is_subset_of_all() {
let all: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
let recovery: BTreeSet<&str> = NodeKind::RECOVERY_KIND_NAMES.iter().copied().collect();
assert_eq!(
NodeKind::RECOVERY_KIND_NAMES.len(),
recovery.len(),
"RECOVERY_KIND_NAMES contains duplicates"
);
let not_in_all: Vec<_> = recovery.difference(&all).collect();
assert!(
not_in_all.is_empty(),
"RECOVERY_KIND_NAMES contains entries not in ALL_KIND_NAMES: {not_in_all:?}"
);
}
}