use crate::pratt_parser::PrattParser;
use pest::{
Parser,
iterators::{Pair, Pairs},
};
use pest_derive::Parser;
use regex::Regex;
use stacker;
use std::sync::{Arc, OnceLock};
#[derive(Parser)]
#[grammar = "grammar.pest"]
pub struct PerlParser;
#[derive(Debug, Clone, PartialEq)]
pub enum AstNode {
Program(Vec<AstNode>),
Statement(Box<AstNode>),
Block(Vec<AstNode>),
VariableDeclaration {
scope: Arc<str>,
variables: Vec<AstNode>,
initializer: Option<Box<AstNode>>,
},
SubDeclaration {
name: Arc<str>,
prototype: Option<Arc<str>>,
attributes: Vec<Arc<str>>,
body: Box<AstNode>,
},
FormatDeclaration {
name: Arc<str>,
format_lines: Vec<Arc<str>>,
},
PackageDeclaration {
name: Arc<str>,
version: Option<Arc<str>>,
block: Option<Box<AstNode>>,
},
UseStatement {
module: Arc<str>,
version: Option<Arc<str>>,
import_list: Vec<Arc<str>>,
},
RequireStatement {
module: Arc<str>,
},
IfStatement {
condition: Box<AstNode>,
then_block: Box<AstNode>,
elsif_clauses: Vec<(AstNode, AstNode)>,
else_block: Option<Box<AstNode>>,
},
UnlessStatement {
condition: Box<AstNode>,
block: Box<AstNode>,
else_block: Option<Box<AstNode>>,
},
GivenStatement {
expression: Box<AstNode>,
when_clauses: Vec<(AstNode, AstNode)>,
default_block: Option<Box<AstNode>>,
},
WhileStatement {
label: Option<Arc<str>>,
condition: Box<AstNode>,
block: Box<AstNode>,
},
UntilStatement {
label: Option<Arc<str>>,
condition: Box<AstNode>,
block: Box<AstNode>,
},
ForStatement {
label: Option<Arc<str>>,
init: Option<Box<AstNode>>,
condition: Option<Box<AstNode>>,
update: Option<Box<AstNode>>,
block: Box<AstNode>,
},
ForeachStatement {
label: Option<Arc<str>>,
variable: Option<Box<AstNode>>,
list: Box<AstNode>,
block: Box<AstNode>,
},
BinaryOp {
op: Arc<str>,
left: Box<AstNode>,
right: Box<AstNode>,
},
UnaryOp {
op: Arc<str>,
operand: Box<AstNode>,
},
TernaryOp {
condition: Box<AstNode>,
true_expr: Box<AstNode>,
false_expr: Box<AstNode>,
},
PostfixDereference {
expr: Box<AstNode>,
deref_type: Arc<str>,
},
Dereference {
expr: Box<AstNode>,
deref_type: Arc<str>,
},
TypeglobSlotAccess {
typeglob: Box<AstNode>,
slot: Arc<str>,
},
Assignment {
target: Box<AstNode>,
op: Arc<str>,
value: Box<AstNode>,
},
FunctionCall {
function: Box<AstNode>,
args: Vec<AstNode>,
},
MethodCall {
object: Box<AstNode>,
method: Arc<str>,
args: Vec<AstNode>,
},
BuiltinListOp {
name: Arc<str>,
args: Vec<AstNode>,
},
ArrayAccess {
array: Box<AstNode>,
index: Box<AstNode>,
},
HashAccess {
hash: Box<AstNode>,
key: Box<AstNode>,
},
ScalarVariable(Arc<str>),
ArrayVariable(Arc<str>),
HashVariable(Arc<str>),
TypeglobVariable(Arc<str>),
ScalarReference(Arc<str>),
ArrayReference(Arc<str>),
HashReference(Arc<str>),
SubroutineReference(Arc<str>),
GlobReference(Arc<str>),
ArrayElement {
array: Arc<str>,
index: Box<AstNode>,
},
HashElement {
hash: Arc<str>,
key: Box<AstNode>,
},
Number(Arc<str>),
String(Arc<str>),
Identifier(Arc<str>),
SpecialLiteral(Arc<str>),
Bareword(Arc<str>),
EmptyExpression,
Regex {
pattern: Arc<str>,
flags: Arc<str>,
named_groups: Vec<Arc<str>>,
},
Substitution {
pattern: Arc<str>,
replacement: Arc<str>,
flags: Arc<str>,
},
Transliteration {
search_list: Arc<str>,
replace_list: Arc<str>,
flags: Arc<str>,
},
ReturnStatement {
value: Option<Box<AstNode>>,
},
LastStatement {
label: Option<Arc<str>>,
},
NextStatement {
label: Option<Arc<str>>,
},
TieStatement {
variable: Box<AstNode>,
class: Box<AstNode>,
args: Vec<AstNode>,
},
UntieStatement {
variable: Box<AstNode>,
},
TiedExpression {
variable: Box<AstNode>,
},
Comment(Arc<str>),
Label(Arc<str>),
LabeledBlock {
label: Arc<str>,
block: Box<AstNode>,
},
AnonymousSub {
prototype: Option<Arc<str>>,
body: Box<AstNode>,
},
List(Vec<AstNode>),
ArrayRef(Vec<AstNode>),
HashRef(Vec<AstNode>),
BeginBlock(Box<AstNode>),
EndBlock(Box<AstNode>),
CheckBlock(Box<AstNode>),
InitBlock(Box<AstNode>),
UnitcheckBlock(Box<AstNode>),
QwList(Vec<Arc<str>>),
QqString(Arc<str>),
QxString(Arc<str>),
QrRegex {
pattern: Arc<str>,
flags: Arc<str>,
named_groups: Vec<Arc<str>>,
},
InterpolatedString(Vec<AstNode>),
Heredoc {
marker: Arc<str>,
indented: bool,
quoted: bool,
content: Arc<str>,
},
Glob(Arc<str>),
Readline {
filehandle: Option<Arc<str>>,
},
DoBlock(Box<AstNode>),
EvalBlock(Box<AstNode>),
EvalString(Box<AstNode>),
GotoStatement {
target: Arc<str>,
},
DataSection(Arc<str>),
EndSection(Arc<str>),
Pod(Arc<str>),
TryCatch {
try_block: Box<AstNode>,
catch_clauses: Vec<(Option<Arc<str>>, AstNode)>, finally_block: Option<Box<AstNode>>,
},
DeferStatement(Box<AstNode>),
ErrorNode {
message: Arc<str>,
content: Arc<str>,
},
ClassDeclaration {
name: Arc<str>,
version: Option<Arc<str>>,
superclass: Option<Arc<str>>,
body: Vec<AstNode>,
},
FieldDeclaration {
name: Arc<str>,
attributes: Vec<Arc<str>>,
default: Option<Box<AstNode>>,
},
MethodDeclaration {
name: Arc<str>,
signature: Option<Arc<str>>,
attributes: Vec<Arc<str>>,
body: Box<AstNode>,
},
RoleDeclaration {
name: Arc<str>,
body: Box<AstNode>,
},
}
pub struct PureRustPerlParser {
_pratt_parser: PrattParser,
}
impl PureRustPerlParser {
pub fn new() -> Self {
Self { _pratt_parser: PrattParser::new() }
}
#[inline(always)]
pub fn parse(&mut self, source: &str) -> Result<AstNode, Box<dyn std::error::Error>> {
let normalized = Self::normalize_source(source);
match <PerlParser as Parser<Rule>>::parse(Rule::program, &normalized) {
Ok(pairs) => self.build_ast(pairs),
Err(e) => {
self.parse_with_recovery(&normalized, e)
}
}
}
fn normalize_source(source: &str) -> String {
static LOOP_DECL_RE: OnceLock<Option<Regex>> = OnceLock::new();
static SIMPLE_SCALAR_DEREF_RE: OnceLock<Option<Regex>> = OnceLock::new();
static ASSIGN_BITNOT_RE: OnceLock<Option<Regex>> = OnceLock::new();
let Some(loop_decl_re) = LOOP_DECL_RE
.get_or_init(|| {
Regex::new(
r"\b(?P<kw>for(?:each)?)\s+(?:my|our|local|state)\s+(?P<var>\$[A-Za-z_][A-Za-z0-9_:]*)\s*(?P<paren>\()",
)
.ok()
})
.as_ref()
else {
return source.to_string();
};
let Some(scalar_deref_re) = SIMPLE_SCALAR_DEREF_RE
.get_or_init(|| Regex::new(r"\$\$(?P<name>[A-Za-z_][A-Za-z0-9_:]*)").ok())
.as_ref()
else {
return source.to_string();
};
let Some(assign_bitnot_re) = ASSIGN_BITNOT_RE
.get_or_init(|| Regex::new(r"=\s+~\s*(?P<expr>\$[A-Za-z_][A-Za-z0-9_:]*)").ok())
.as_ref()
else {
return source.to_string();
};
let normalized_loops = loop_decl_re.replace_all(source, "$kw $var $paren").into_owned();
let normalized_derefs = scalar_deref_re
.replace_all(&normalized_loops, |caps: ®ex::Captures<'_>| {
let variable = format!("${}", &caps["name"]);
format!("${{{}}}", variable)
})
.into_owned();
assign_bitnot_re
.replace_all(&normalized_derefs, |caps: ®ex::Captures<'_>| {
format!("= bitnot({})", &caps["expr"])
})
.into_owned()
}
fn parse_with_recovery(
&mut self,
source: &str,
original_error: pest::error::Error<Rule>,
) -> Result<AstNode, Box<dyn std::error::Error>> {
let mut statements = Vec::new();
let lines: Vec<&str> = source.lines().collect();
let mut current_block = String::new();
let mut brace_count: i32 = 0;
let mut in_single_quote = false;
let mut in_double_quote = false;
for line in lines {
current_block.push_str(line);
current_block.push('\n');
{
let mut escaped = false;
for ch in line.chars() {
if escaped {
escaped = false;
continue;
}
if ch == '\\' {
escaped = true;
continue;
}
match ch {
'\'' if !in_double_quote => in_single_quote = !in_single_quote,
'"' if !in_single_quote => in_double_quote = !in_double_quote,
'{' if !in_single_quote && !in_double_quote => brace_count += 1,
'}' if !in_single_quote && !in_double_quote => brace_count -= 1,
_ => {}
}
}
}
if (brace_count == 0 && (line.trim().ends_with(';') || line.trim().ends_with('}')))
|| line.trim().is_empty()
{
let trimmed = current_block.trim();
if !trimmed.is_empty() && !trimmed.starts_with('#') {
let with_semi = if !trimmed.ends_with(';') && !trimmed.ends_with('}') {
format!("{};", trimmed)
} else {
trimmed.to_string()
};
if let Ok(pairs) =
<PerlParser as Parser<Rule>>::parse(Rule::statements, &with_semi)
{
for pair in pairs {
for inner_pair in pair.into_inner() {
if let Some(node) = self.build_node(inner_pair).unwrap_or(None) {
statements.push(node);
}
}
}
current_block.clear();
in_single_quote = false;
in_double_quote = false;
} else {
current_block.clear();
in_single_quote = false;
in_double_quote = false;
}
} else if trimmed.starts_with('#') {
statements.push(AstNode::Comment(Arc::from(trimmed)));
current_block.clear();
}
}
}
if statements.is_empty() {
Err(Box::new(original_error))
} else {
Ok(AstNode::Program(statements))
}
}
fn extract_named_groups(&self, pattern: &str) -> Vec<Arc<str>> {
let mut groups = Vec::new();
let chars: Vec<char> = pattern.chars().collect();
let mut i = 0;
while i < chars.len() {
if i + 3 < chars.len() && chars[i..i + 3] == ['(', '?', '<'] {
i += 3;
let mut name = String::new();
while i < chars.len() && chars[i] != '>' {
name.push(chars[i]);
i += 1;
}
if !name.is_empty() {
groups.push(Arc::from(name));
}
}
i += 1;
}
groups
}
pub fn build_ast(&mut self, pairs: Pairs<Rule>) -> Result<AstNode, Box<dyn std::error::Error>> {
let mut nodes = Vec::new();
for pair in pairs {
if let Some(node) = self.build_node(pair)? {
nodes.push(node);
}
}
if nodes.len() == 1 {
nodes.pop().ok_or_else(|| "Empty nodes".into())
} else {
Ok(AstNode::Program(nodes))
}
}
#[inline]
pub fn build_node(
&mut self,
pair: Pair<Rule>,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
const STACK_RED_ZONE: usize = 512 * 1024; const STACK_SIZE: usize = 8 * 1024 * 1024; stacker::maybe_grow(STACK_RED_ZONE, STACK_SIZE, || self.build_node_impl(pair))
}
#[inline]
fn build_node_impl(
&mut self,
pair: Pair<Rule>,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
match pair.as_rule() {
Rule::simple_assignment => {
let mut inner = pair.into_inner();
let var_pair = inner.next().ok_or("Missing variable in simple assignment")?;
let var = self.build_node(var_pair)?.ok_or("Failed to build variable node")?;
let val_pair = inner.next().ok_or("Missing value in simple assignment")?;
let value = self.build_node(val_pair)?.ok_or("Failed to build value node")?;
Ok(Some(AstNode::Assignment {
target: Box::new(var),
op: Arc::from("="),
value: Box::new(value),
}))
}
Rule::simple_method_call => {
let text = pair.as_str();
let parts: Vec<&str> = text.split("->").collect();
if parts.len() == 2 {
let var_part = parts[0].trim();
let method_part = parts[1].trim();
let method_name = method_part.split('(').next().unwrap_or("").trim();
let object = if var_part.starts_with('$') {
AstNode::ScalarVariable(Arc::from(var_part))
} else {
AstNode::Identifier(Arc::from(var_part))
};
Ok(Some(AstNode::MethodCall {
object: Box::new(object),
method: Arc::from(method_name),
args: vec![],
}))
} else {
Ok(None)
}
}
Rule::simple_function_call => {
let text = pair.as_str();
let parts: Vec<&str> = text.split('(').collect();
if parts.len() >= 2 {
let func_name = parts[0].trim();
let func = AstNode::Identifier(Arc::from(func_name));
let args = if let Some(arg_part) = parts.get(1) {
let arg_text = arg_part.trim_end_matches(");").trim_end_matches(';').trim();
if !arg_text.is_empty() {
let arg = if arg_text.starts_with('$') {
AstNode::ScalarVariable(Arc::from(arg_text))
} else if arg_text.starts_with('"') || arg_text.starts_with('\'') {
AstNode::String(Arc::from(arg_text))
} else if arg_text.chars().all(|c| c.is_numeric() || c == '.') {
AstNode::Number(Arc::from(arg_text))
} else {
AstNode::Identifier(Arc::from(arg_text))
};
vec![arg]
} else {
vec![]
}
} else {
vec![]
};
Ok(Some(AstNode::FunctionCall { function: Box::new(func), args }))
} else {
Ok(None)
}
}
Rule::program => {
let mut statements = Vec::new();
for inner in pair.into_inner() {
if let Some(node) = self.build_node(inner)? {
statements.push(node);
}
}
Ok(Some(AstNode::Program(statements)))
}
Rule::statements => {
let mut statements = Vec::new();
for inner in pair.into_inner() {
if let Some(node) = self.build_node(inner)? {
statements.push(node);
}
}
Ok(Some(AstNode::Program(statements)))
}
Rule::statement => {
let inner = pair.into_inner().next().ok_or("Empty statement")?;
self.build_node(inner)
}
Rule::modified_statement => {
let mut inner = pair.into_inner();
let expr_pair = inner.next().ok_or("Missing expression in modified statement")?;
let expr = self.build_node(expr_pair)?;
let modifier = inner.next().ok_or("Missing modifier in modified statement")?;
let modifier_str = modifier.as_str();
let (modifier_type, condition_expr) =
if let Some(rest) = modifier_str.strip_prefix("if ") {
("if", rest)
} else if let Some(rest) = modifier_str.strip_prefix("unless ") {
("unless", rest)
} else if let Some(rest) = modifier_str.strip_prefix("while ") {
("while", rest)
} else if let Some(rest) = modifier_str.strip_prefix("until ") {
("until", rest)
} else if let Some(rest) = modifier_str.strip_prefix("for ") {
("for", rest)
} else if let Some(rest) = modifier_str.strip_prefix("foreach ") {
("foreach", rest)
} else {
return Ok(expr);
};
let condition = Some(AstNode::Identifier(Arc::from(condition_expr.trim())));
if let (Some(expr), Some(condition)) = (expr, condition) {
match modifier_type {
"if" => Ok(Some(AstNode::IfStatement {
condition: Box::new(condition),
then_block: Box::new(expr),
elsif_clauses: Vec::new(),
else_block: None,
})),
"unless" => Ok(Some(AstNode::UnlessStatement {
condition: Box::new(condition),
block: Box::new(expr),
else_block: None,
})),
"while" => Ok(Some(AstNode::WhileStatement {
label: None,
condition: Box::new(condition),
block: Box::new(expr),
})),
"until" => Ok(Some(AstNode::UntilStatement {
label: None,
condition: Box::new(condition),
block: Box::new(expr),
})),
"for" | "foreach" => Ok(Some(AstNode::ForStatement {
init: None,
condition: Some(Box::new(condition)),
update: None,
block: Box::new(expr),
label: None,
})),
_ => Ok(Some(AstNode::Statement(Box::new(expr)))),
}
} else {
Ok(None)
}
}
Rule::expression_statement => {
let inner = pair.into_inner().next().ok_or("Empty expression statement")?;
if let Some(expr) = self.build_node(inner)? {
Ok(Some(AstNode::Statement(Box::new(expr))))
} else {
Ok(None)
}
}
Rule::declaration_statement => {
let inner = pair.into_inner().next().ok_or("Empty declaration statement")?;
self.build_node(inner)
}
Rule::variable_declaration => {
let mut inner = pair.into_inner();
let scope_pair = inner.next().ok_or("Missing scope in variable declaration")?;
let scope = Arc::from(scope_pair.as_str());
let mut variables = Vec::new();
let mut initializer = None;
for p in inner {
match p.as_rule() {
Rule::variable_list => {
for var in p.into_inner() {
if let Some(v) = self.build_node(var)? {
variables.push(v);
}
}
}
Rule::expression => {
initializer = self.build_node(p)?.map(Box::new);
}
_ => {}
}
}
Ok(Some(AstNode::VariableDeclaration { scope, variables, initializer }))
}
Rule::sub_declaration => {
let inner = pair.into_inner();
let mut _sub_modifier = None;
let mut name = Arc::from("");
let mut prototype = None;
let mut attributes = Vec::new();
let mut body = None;
for p in inner {
match p.as_rule() {
Rule::sub_modifier => {
_sub_modifier = Some(p.as_str().to_string());
}
Rule::identifier => {
name = Arc::from(p.as_str());
}
Rule::prototype => {
prototype = Some(Arc::from(p.as_str()));
}
Rule::signature => {
prototype = Some(Arc::from(p.as_str()));
}
Rule::attributes => {
for attr in p.into_inner() {
attributes.push(Arc::from(attr.as_str()));
}
}
Rule::block => {
body = self.build_node(p)?.map(Box::new);
}
_ => {}
}
}
Ok(Some(AstNode::SubDeclaration {
name,
prototype,
attributes,
body: body.unwrap_or_else(|| Box::new(AstNode::Block(vec![]))),
}))
}
Rule::glob => {
let inner = pair.into_inner().next().ok_or("Empty glob")?; Ok(Some(AstNode::Glob(Arc::from(inner.as_str()))))
}
Rule::readline => {
let mut filehandle = None;
for p in pair.into_inner() {
if p.as_rule() == Rule::filehandle {
filehandle = Some(Arc::from(p.as_str()));
}
}
Ok(Some(AstNode::Readline { filehandle }))
}
Rule::format_declaration => {
let inner = pair.into_inner();
let mut name = Arc::from("");
let mut format_lines = Vec::new();
for p in inner {
match p.as_rule() {
Rule::format_name => {
name = Arc::from(p.as_str());
}
Rule::format_lines => {
for line in p.into_inner() {
if line.as_rule() == Rule::format_line {
format_lines.push(Arc::from(line.as_str()));
}
}
}
_ => {}
}
}
Ok(Some(AstNode::FormatDeclaration { name, format_lines }))
}
Rule::if_statement => {
let mut inner = pair.into_inner();
let cond_pair = inner.next().ok_or("Missing condition in if statement")?;
let condition =
Box::new(self.build_node(cond_pair)?.ok_or("Failed to build condition node")?);
let block_pair = inner.next().ok_or("Missing block in if statement")?;
let then_block =
Box::new(self.build_node(block_pair)?.ok_or("Failed to build block node")?);
let mut elsif_clauses = Vec::new();
let mut else_block = None;
for p in inner {
match p.as_rule() {
Rule::elsif_clause => {
let mut elsif_inner = p.into_inner();
let cond_pair =
elsif_inner.next().ok_or("Missing condition in elsif")?;
let cond = self
.build_node(cond_pair)?
.ok_or("Failed to build elsif condition")?;
let block_pair = elsif_inner.next().ok_or("Missing block in elsif")?;
let block = self
.build_node(block_pair)?
.ok_or("Failed to build elsif block")?;
elsif_clauses.push((cond, block));
}
Rule::else_clause => {
let mut else_inner = p.into_inner();
let else_pair = else_inner.next().ok_or("Missing block in else")?;
else_block = self.build_node(else_pair)?.map(Box::new);
}
_ => {}
}
}
Ok(Some(AstNode::IfStatement { condition, then_block, elsif_clauses, else_block }))
}
Rule::tie_statement => {
let mut inner = pair.into_inner();
let var_pair = inner.next().ok_or("Missing variable in tie statement")?;
let variable =
Box::new(self.build_node(var_pair)?.ok_or("Failed to build variable node")?);
let class_pair = inner.next().ok_or("Missing class in tie statement")?;
let class =
Box::new(self.build_node(class_pair)?.ok_or("Failed to build class node")?);
let mut args = Vec::new();
for arg in inner {
if let Some(node) = self.build_node(arg)? {
args.push(node);
}
}
Ok(Some(AstNode::TieStatement { variable, class, args }))
}
Rule::untie_statement => {
let mut inner = pair.into_inner();
let var_pair = inner.next().ok_or("Missing variable in untie statement")?;
let variable =
Box::new(self.build_node(var_pair)?.ok_or("Failed to build variable node")?);
Ok(Some(AstNode::UntieStatement { variable }))
}
Rule::tied_statement => {
let mut inner = pair.into_inner();
let var_pair = inner.next().ok_or("Missing variable in tied statement")?;
let variable =
Box::new(self.build_node(var_pair)?.ok_or("Failed to build variable node")?);
Ok(Some(AstNode::TiedExpression { variable }))
}
Rule::given_statement => {
let mut inner = pair.into_inner();
let expr_pair = inner.next().ok_or("Missing expression in given statement")?;
let expression =
Box::new(self.build_node(expr_pair)?.ok_or("Failed to build expression node")?);
let given_block = inner.next().ok_or("Missing block in given statement")?;
let mut when_clauses = Vec::new();
let mut default_block = None;
for p in given_block.into_inner() {
match p.as_rule() {
Rule::when_clause => {
let mut when_inner = p.into_inner();
let when_cond_pair =
when_inner.next().ok_or("Missing condition in when clause")?;
let cond_inner_pair =
when_cond_pair.into_inner().next().ok_or("Empty when condition")?;
let cond = self
.build_node(cond_inner_pair)?
.ok_or("Failed to build when condition")?;
let block_pair =
when_inner.next().ok_or("Missing block in when clause")?;
let block =
self.build_node(block_pair)?.ok_or("Failed to build when block")?;
when_clauses.push((cond, block));
}
Rule::default_clause => {
let mut default_inner = p.into_inner();
let default_pair =
default_inner.next().ok_or("Missing block in default clause")?;
default_block = Some(Box::new(
self.build_node(default_pair)?
.ok_or("Failed to build default block")?,
));
}
_ => {}
}
}
Ok(Some(AstNode::GivenStatement { expression, when_clauses, default_block }))
}
Rule::block => {
let mut statements = Vec::new();
for inner in pair.into_inner() {
if inner.as_rule() == Rule::statements {
for stmt in inner.into_inner() {
if let Some(node) = self.build_node(stmt)? {
statements.push(node);
}
}
}
}
Ok(Some(AstNode::Block(statements)))
}
Rule::anonymous_sub => {
let inner = pair.into_inner();
let mut prototype = None;
let mut attributes: Vec<Arc<str>> = Vec::new();
let mut body = None;
for p in inner {
match p.as_rule() {
Rule::prototype => {
prototype = Some(Arc::from(p.as_str()));
}
Rule::attributes => {
for attr in p.into_inner() {
attributes.push(Arc::from(attr.as_str()));
}
}
Rule::block => {
body = self.build_node(p)?.map(Box::new);
}
_ => {}
}
}
Ok(Some(AstNode::AnonymousSub {
prototype,
body: body.unwrap_or_else(|| Box::new(AstNode::Block(vec![]))),
}))
}
Rule::expression => self.build_expression(pair),
Rule::logical_or_expression => {
self.build_binary_expression(pair, Rule::logical_or_expression)
}
Rule::logical_xor_expression => {
self.build_binary_expression(pair, Rule::logical_xor_expression)
}
Rule::defined_or_expression => {
self.build_binary_expression(pair, Rule::defined_or_expression)
}
Rule::logical_and_expression => {
self.build_binary_expression(pair, Rule::logical_and_expression)
}
Rule::equality_expression => {
self.build_binary_expression(pair, Rule::equality_expression)
}
Rule::relational_expression => {
self.build_binary_expression(pair, Rule::relational_expression)
}
Rule::isa_expression => self.build_binary_expression(pair, Rule::isa_expression),
Rule::bitwise_expression => {
self.build_binary_expression(pair, Rule::bitwise_expression)
}
Rule::bitwise_string_expression => {
self.build_binary_expression(pair, Rule::bitwise_string_expression)
}
Rule::range_expression => self.build_binary_expression(pair, Rule::range_expression),
Rule::additive_expression => {
self.build_binary_expression(pair, Rule::additive_expression)
}
Rule::multiplicative_expression => {
self.build_binary_expression(pair, Rule::multiplicative_expression)
}
Rule::exponential_expression => {
self.build_binary_expression(pair, Rule::exponential_expression)
}
Rule::assignment_expression => {
let mut inner = pair.into_inner();
if let (Some(target_pair), Some(op_pair), Some(value_pair)) =
(inner.next(), inner.next(), inner.next())
{
let target =
Box::new(self.build_node(target_pair)?.unwrap_or(AstNode::EmptyExpression));
let op_str = op_pair.as_str();
let op = if op_str == "_DIV_=" {
Arc::from("/=")
} else if op_str.contains("_DIV_") {
Arc::from(op_str.replace("_DIV_", "/"))
} else {
Arc::from(op_str)
};
let value =
Box::new(self.build_node(value_pair)?.unwrap_or(AstNode::EmptyExpression));
Ok(Some(AstNode::Assignment { target, op, value }))
} else {
Ok(None)
}
}
Rule::unary_expression => {
let mut inner = pair.into_inner();
let first = inner.next().ok_or("Empty unary expression")?;
match first.as_rule() {
Rule::postfix_expression | Rule::reference => {
self.build_node(first)
}
Rule::file_test_operator => {
let op = Arc::from(first.as_str());
if let Some(next_pair) = inner.next() {
if let Some(operand_node) = self.build_node(next_pair)? {
let operand = Box::new(operand_node);
Ok(Some(AstNode::UnaryOp { op, operand }))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
_ => {
let op = Arc::from(first.as_str());
if let Some(next_pair) = inner.next() {
if let Some(operand_node) = self.build_node(next_pair)? {
let operand = Box::new(operand_node);
Ok(Some(AstNode::UnaryOp { op, operand }))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
}
}
Rule::postfix_expression => {
let mut inner = pair.into_inner();
let expr_pair = inner.next().ok_or("Empty postfix expression")?;
let mut expr =
self.build_node(expr_pair)?.ok_or("Failed to build base expression")?;
for postfix_op in inner {
if postfix_op.as_rule() == Rule::postfix_dereference {
let op_inner =
postfix_op.into_inner().next().ok_or("Empty postfix dereference")?;
match op_inner.as_rule() {
Rule::postfix_dereference => {
let deref_str = op_inner.as_str();
let deref_type = deref_str.strip_prefix("->").unwrap_or(deref_str);
expr = AstNode::PostfixDereference {
expr: Box::new(expr),
deref_type: Arc::from(deref_type),
};
}
Rule::method_call => {
let method_inner = op_inner.into_inner();
let mut method = Arc::from("");
let mut args = Vec::new();
for p in method_inner {
match p.as_rule() {
Rule::method_name => {
method = Arc::from(p.as_str());
}
Rule::function_args => {
if let Some(arg_list) = p.into_inner().next() {
args = self.parse_arg_list(arg_list)?;
}
}
_ => {}
}
}
expr = AstNode::MethodCall { object: Box::new(expr), method, args };
}
Rule::typeglob_slot_access => {
let slot_pair = op_inner
.into_inner()
.next()
.ok_or("Empty typeglob slot access")?;
let slot = Arc::from(slot_pair.as_str());
expr =
AstNode::TypeglobSlotAccess { typeglob: Box::new(expr), slot };
}
Rule::array_access => {
let index_pair =
op_inner.into_inner().next().ok_or("Empty array access")?;
let index_expr = self
.build_node(index_pair)?
.ok_or("Failed to build array index node")?;
expr = AstNode::ArrayAccess {
array: Box::new(expr),
index: Box::new(index_expr),
};
}
Rule::hash_access => {
let key_pair =
op_inner.into_inner().next().ok_or("Empty hash access")?;
let key_expr = self
.build_node(key_pair)?
.ok_or("Failed to build hash key node")?;
expr = AstNode::HashAccess {
hash: Box::new(expr),
key: Box::new(key_expr),
};
}
Rule::function_call => {
let args = if let Some(args_pair) = op_inner.into_inner().next() {
self.parse_arg_list(args_pair)?
} else {
Vec::new()
};
expr = AstNode::FunctionCall { function: Box::new(expr), args };
}
_ => {}
}
}
}
Ok(Some(expr))
}
Rule::scalar_variable => Ok(Some(AstNode::ScalarVariable(Arc::from(pair.as_str())))),
Rule::array_variable => Ok(Some(AstNode::ArrayVariable(Arc::from(pair.as_str())))),
Rule::hash_variable => Ok(Some(AstNode::HashVariable(Arc::from(pair.as_str())))),
Rule::typeglob_variable => {
Ok(Some(AstNode::TypeglobVariable(Arc::from(pair.as_str()))))
}
Rule::number => Ok(Some(AstNode::Number(Arc::from(pair.as_str())))),
Rule::identifier => Ok(Some(AstNode::Identifier(Arc::from(pair.as_str())))),
Rule::qualified_name => Ok(Some(AstNode::Identifier(Arc::from(pair.as_str())))),
Rule::qualified_name_or_identifier => {
Ok(Some(AstNode::Identifier(Arc::from(pair.as_str()))))
}
Rule::class_method_call => {
let inner = pair.into_inner();
let mut parts = Vec::new();
let mut args = Vec::new();
for p in inner {
match p.as_rule() {
Rule::identifier => {
parts.push(p.as_str());
}
Rule::method_name => {
parts.push(p.as_str());
}
Rule::function_args => {
if let Some(arg_list) = p.into_inner().next() {
for arg_pair in arg_list.into_inner() {
if let Ok(Some(arg)) = self.build_node(arg_pair) {
args.push(arg);
}
}
}
}
_ => {}
}
}
if parts.len() >= 2 {
let method_name = Arc::from(parts.pop().ok_or("Empty parts list")?);
let class_name = parts.join("::");
let class_node = AstNode::Identifier(Arc::from(class_name));
Ok(Some(AstNode::MethodCall {
object: Box::new(class_node),
method: method_name,
args,
}))
} else {
Ok(Some(AstNode::Identifier(Arc::from(""))))
}
}
Rule::user_function_call => {
let inner = pair.into_inner();
let mut name = Arc::from("");
let mut args = Vec::new();
for p in inner {
match p.as_rule() {
Rule::identifier => {
name = Arc::from(p.as_str());
}
Rule::list_op_args => {
for arg_pair in p.into_inner() {
if arg_pair.as_rule() == Rule::list_op_arg {
for expr_pair in arg_pair.into_inner() {
if let Ok(Some(arg)) = self.build_node(expr_pair) {
args.push(arg);
}
}
}
}
}
_ => {}
}
}
Ok(Some(AstNode::FunctionCall {
function: Box::new(AstNode::Identifier(name)),
args,
}))
}
Rule::builtin_list_op => {
let inner = pair.into_inner();
let mut name = Arc::from("");
let mut args = Vec::new();
for p in inner {
match p.as_rule() {
Rule::builtin_list_op_name => {
name = Arc::from(p.as_str().trim());
}
Rule::list_op_args => {
for arg_pair in p.into_inner() {
if arg_pair.as_rule() == Rule::list_op_arg {
for expr_pair in arg_pair.into_inner() {
if let Ok(Some(arg)) = self.build_node(expr_pair) {
args.push(arg);
}
}
}
}
}
_ => {}
}
}
Ok(Some(AstNode::BuiltinListOp { name, args }))
}
Rule::special_literal => Ok(Some(AstNode::SpecialLiteral(Arc::from(pair.as_str())))),
Rule::string => {
let inner_pairs: Vec<_> = pair.into_inner().collect();
if let Some(inner) = inner_pairs.into_iter().next() {
self.build_node(inner)
} else {
Ok(Some(AstNode::String(Arc::from(""))))
}
}
Rule::single_quoted_string => Ok(Some(AstNode::String(Arc::from(pair.as_str())))),
Rule::double_quoted_string => {
let mut parts = Vec::new();
if let Some(content_pair) = pair.into_inner().next() {
for part in content_pair.into_inner() {
for inner in part.into_inner() {
match inner.as_rule() {
Rule::double_string_chars => {
parts.push(AstNode::String(Arc::from(inner.as_str())));
}
Rule::interpolation => {
if let Ok(Some(interp_node)) = self.build_node(inner) {
parts.push(interp_node);
}
}
_ => {}
}
}
}
if parts.is_empty() {
Ok(Some(AstNode::String(Arc::from(""))))
} else if parts.len() == 1 && matches!(parts[0], AstNode::String(_)) {
Ok(Some(parts.into_iter().next().ok_or("Empty parts list")?))
} else {
Ok(Some(AstNode::InterpolatedString(parts)))
}
} else {
Ok(Some(AstNode::String(Arc::from(""))))
}
}
Rule::q_string => {
let content = pair.as_str();
if content.contains("__HEREDOC__") {
if let Some(start_idx) = content.find("{__HEREDOC__")
&& let Some(end_idx) = content.rfind("__HEREDOC__}")
{
let heredoc_content = &content[start_idx + 12..end_idx];
return Ok(Some(AstNode::String(Arc::from(heredoc_content))));
}
}
Ok(Some(AstNode::String(Arc::from(content))))
}
Rule::qq_string => {
let content = pair.as_str();
if content.contains("__HEREDOC__") {
if let Some(start_idx) = content.find("{__HEREDOC__")
&& let Some(end_idx) = content.rfind("__HEREDOC__}")
{
let heredoc_content = &content[start_idx + 12..end_idx];
return Ok(Some(AstNode::QqString(Arc::from(heredoc_content))));
}
}
Ok(Some(AstNode::QqString(Arc::from(content))))
}
Rule::qx_string => {
Ok(Some(AstNode::QxString(Arc::from(pair.as_str()))))
}
Rule::backtick_string => {
Ok(Some(AstNode::QxString(Arc::from(pair.as_str()))))
}
Rule::heredoc_placeholder => {
Ok(Some(AstNode::String(Arc::from(pair.as_str()))))
}
Rule::heredoc => {
let inner = pair.into_inner();
let mut indented = false;
let mut marker = Arc::from("");
let mut quoted = false;
for p in inner {
match p.as_rule() {
Rule::heredoc_indented => {
indented = true;
}
Rule::heredoc_delimiter => {
let delimiter_str = p.as_str();
let delimiter_inner = p.into_inner().next();
if let Some(d) = delimiter_inner {
match d.as_rule() {
Rule::heredoc_single_quoted => {
quoted = true;
marker = Arc::from(d.as_str().trim_matches('\''));
}
Rule::heredoc_double_quoted => {
marker = Arc::from(d.as_str().trim_matches('"'));
}
Rule::heredoc_backtick => {
marker = Arc::from(d.as_str().trim_matches('`'));
}
Rule::heredoc_escaped => {
marker = Arc::from(d.as_str().trim_start_matches('\\'));
}
Rule::bare_heredoc_delimiter => {
marker = Arc::from(d.as_str());
}
_ => {}
}
} else {
marker = Arc::from(delimiter_str);
}
}
_ => {}
}
}
Ok(Some(AstNode::Heredoc {
marker,
indented,
quoted,
content: Arc::from(""), }))
}
Rule::list => {
let mut elements = Vec::new();
for inner in pair.into_inner() {
if inner.as_rule() == Rule::list_elements {
for elem in inner.into_inner() {
if let Some(node) = self.build_node(elem)? {
elements.push(node);
}
}
}
}
Ok(Some(AstNode::List(elements)))
}
Rule::array_ref => {
let mut elements = Vec::new();
for inner in pair.into_inner() {
if inner.as_rule() == Rule::list_elements {
for elem in inner.into_inner() {
if let Some(node) = self.build_node(elem)? {
elements.push(node);
}
}
}
}
Ok(Some(AstNode::ArrayRef(elements)))
}
Rule::hash_ref => {
let mut elements = Vec::new();
for inner in pair.into_inner() {
if inner.as_rule() == Rule::hash_elements {
for elem in inner.into_inner() {
if let Some(node) = self.build_node(elem)? {
elements.push(node);
}
}
}
}
Ok(Some(AstNode::HashRef(elements)))
}
Rule::begin_block => {
let inner = pair.into_inner().next().ok_or("Empty begin block")?; let block = self.build_node(inner)?.map(Box::new);
Ok(block.map(AstNode::BeginBlock))
}
Rule::end_block => {
let inner = pair.into_inner().next().ok_or("Empty end block")?; let block = self.build_node(inner)?.map(Box::new);
Ok(block.map(AstNode::EndBlock))
}
Rule::check_block => {
let inner = pair.into_inner().next().ok_or("Empty check block")?; let block = self.build_node(inner)?.map(Box::new);
Ok(block.map(AstNode::CheckBlock))
}
Rule::init_block => {
let inner = pair.into_inner().next().ok_or("Empty init block")?; let block = self.build_node(inner)?.map(Box::new);
Ok(block.map(AstNode::InitBlock))
}
Rule::unitcheck_block => {
let inner = pair.into_inner().next().ok_or("Empty unitcheck block")?; let block = self.build_node(inner)?.map(Box::new);
Ok(block.map(AstNode::UnitcheckBlock))
}
Rule::qw_list => {
let mut words = Vec::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::qw_paren_items
| Rule::qw_bracket_items
| Rule::qw_brace_items
| Rule::qw_angle_items
| Rule::qw_delimited_items => {
let content = inner.as_str();
words.extend(content.split_whitespace().map(Arc::from));
}
_ => {}
}
}
Ok(Some(AstNode::QwList(words)))
}
Rule::do_block => {
let inner = pair.into_inner().next().ok_or("Empty do block")?;
let expr = self.build_node(inner)?.map(Box::new);
Ok(expr.map(AstNode::DoBlock))
}
Rule::eval_statement => {
let inner = pair.into_inner().next().ok_or("Empty eval statement")?;
let expr = self.build_node(inner)?;
Ok(expr.map(|e| match e {
AstNode::Block(_) => AstNode::EvalBlock(Box::new(e)),
_ => AstNode::EvalString(Box::new(e)),
}))
}
Rule::goto_statement => {
let inner = pair.into_inner().next().ok_or("Empty goto statement")?;
let target = match inner.as_rule() {
Rule::goto_target => Arc::from(inner.as_str()),
_ => {
if let Some(expr) = self.build_node(inner)? {
Arc::from(format!("{:?}", expr)) } else {
Arc::from("")
}
}
};
Ok(Some(AstNode::GotoStatement { target }))
}
Rule::try_catch_statement => {
let mut inner = pair.into_inner();
let try_pair = inner.next().ok_or("Missing try block")?;
let try_block =
Box::new(self.build_node(try_pair)?.ok_or("Failed to build try block")?);
let mut catch_clauses = Vec::new();
let mut finally_block = None;
for p in inner {
match p.as_rule() {
Rule::catch_clause => {
let catch_inner = p.into_inner();
let mut param = None;
let mut block = None;
for cp in catch_inner {
match cp.as_rule() {
Rule::catch_parameter => {
param = Some(Arc::from(cp.as_str()));
}
Rule::block => {
block = Some(
self.build_node(cp)?
.ok_or("Failed to build catch block")?,
);
}
_ => {}
}
}
if let Some(b) = block {
catch_clauses.push((param, b));
}
}
Rule::finally_clause => {
let mut finally_inner = p.into_inner();
if let Some(block_pair) = finally_inner.next() {
finally_block = Some(Box::new(
self.build_node(block_pair)?
.ok_or("Failed to build finally block")?,
));
}
}
_ => {}
}
}
Ok(Some(AstNode::TryCatch { try_block, catch_clauses, finally_block }))
}
Rule::defer_statement => {
let mut inner = pair.into_inner();
let block_pair = inner.next().ok_or("Missing block in defer statement")?;
let block =
Box::new(self.build_node(block_pair)?.ok_or("Failed to build defer block")?);
Ok(Some(AstNode::DeferStatement(block)))
}
Rule::class_declaration => {
let mut inner = pair.into_inner();
let name_pair = inner.next().ok_or("Missing class name")?;
let name = Arc::from(name_pair.as_str());
let mut version = None;
let mut superclass = None;
let mut body = Vec::new();
for p in inner {
match p.as_rule() {
Rule::version => {
version = Some(Arc::from(p.as_str()));
}
Rule::superclass => {
let super_pair =
p.into_inner().next().ok_or("Missing superclass name")?;
superclass = Some(Arc::from(super_pair.as_str()));
}
Rule::class_body => {
for member in p.into_inner() {
if let Some(node) = self.build_node(member)? {
body.push(node);
}
}
}
_ => {}
}
}
Ok(Some(AstNode::ClassDeclaration { name, version, superclass, body }))
}
Rule::method_declaration => {
let mut inner = pair.into_inner();
let name_pair = inner.next().ok_or("Missing method name")?;
let name = Arc::from(name_pair.as_str());
let mut signature = None;
let mut attributes = Vec::new();
let mut body = None;
for p in inner {
match p.as_rule() {
Rule::signature => {
signature = Some(Arc::from(p.as_str()));
}
Rule::attributes => {
for attr in p.into_inner() {
attributes.push(Arc::from(attr.as_str()));
}
}
Rule::block => {
body = Some(Box::new(
self.build_node(p)?.ok_or("Failed to build method body")?,
));
}
_ => {}
}
}
Ok(Some(AstNode::MethodDeclaration {
name,
signature,
attributes,
body: body.unwrap_or_else(|| Box::new(AstNode::Block(vec![]))),
}))
}
Rule::field_declaration => {
let mut inner = pair.into_inner();
let name_pair = inner.next().ok_or("Missing field name")?;
let name = Arc::from(name_pair.as_str());
let mut attributes = Vec::new();
let mut default = None;
for p in inner {
match p.as_rule() {
Rule::field_attributes => {
for attr in p.into_inner() {
attributes.push(Arc::from(attr.as_str()));
}
}
Rule::default_value => {
if let Some(expr) = p.into_inner().next() {
default = Some(Box::new(
self.build_node(expr)?
.ok_or("Failed to build field default value")?,
));
}
}
_ => {}
}
}
Ok(Some(AstNode::FieldDeclaration { name, attributes, default }))
}
Rule::return_statement => {
let mut inner = pair.into_inner();
if let Some(expr_pair) = inner.next() {
if expr_pair.as_rule() != Rule::semicolon {
let expr = self.build_node(expr_pair)?;
Ok(Some(AstNode::ReturnStatement { value: expr.map(Box::new) }))
} else {
Ok(Some(AstNode::ReturnStatement { value: None }))
}
} else {
Ok(Some(AstNode::ReturnStatement { value: None }))
}
}
Rule::pod_section => Ok(Some(AstNode::Pod(Arc::from(pair.as_str())))),
Rule::data_section => Ok(Some(AstNode::DataSection(Arc::from(pair.as_str())))),
Rule::end_section => Ok(Some(AstNode::EndSection(Arc::from(pair.as_str())))),
Rule::labeled_block => {
let mut inner = pair.into_inner();
let label_pair = inner.next().ok_or("Missing label in labeled block")?;
let label = Arc::from(label_pair.as_str().trim_end_matches(':'));
let block_pair = inner.next().ok_or("Missing block in labeled block")?;
let block = self.build_node(block_pair)?.map(Box::new);
Ok(block.map(|b| AstNode::LabeledBlock { label, block: b }))
}
Rule::comment => Ok(Some(AstNode::Comment(Arc::from(pair.as_str())))),
Rule::semicolon | Rule::WHITESPACE => Ok(None),
Rule::standalone_expression => {
let inner = pair.into_inner().next().ok_or("Empty standalone expression")?;
self.build_node(inner)
}
Rule::regex => {
let mut inner = pair.into_inner();
if let Some(first) = inner.next() {
match first.as_rule() {
Rule::match_regex => {
let mut match_inner = first.into_inner();
let pattern = match_inner
.next()
.map(|p| Arc::from(p.as_str()))
.unwrap_or_else(|| Arc::from(""));
let flags = match_inner
.next()
.map(|p| Arc::from(p.as_str()))
.unwrap_or_else(|| Arc::from(""));
let named_groups = self.extract_named_groups(&pattern);
Ok(Some(AstNode::Regex { pattern, flags, named_groups }))
}
_ => {
let pattern = Arc::from(first.as_str());
let flags = inner
.next()
.map(|p| Arc::from(p.as_str()))
.unwrap_or_else(|| Arc::from(""));
let named_groups = self.extract_named_groups(&pattern);
Ok(Some(AstNode::Regex { pattern, flags, named_groups }))
}
}
} else {
Ok(Some(AstNode::Regex {
pattern: Arc::from(""),
flags: Arc::from(""),
named_groups: Vec::new(),
}))
}
}
Rule::substitution => {
let inner = pair.into_inner();
let mut pattern = Arc::from("");
let mut replacement = Arc::from("");
let mut flags = Arc::from("");
for p in inner {
match p.as_rule() {
Rule::sub_pattern => {
pattern = Arc::from(p.as_str());
}
Rule::replacement => {
replacement = Arc::from(p.as_str());
}
Rule::regex_flags => {
flags = Arc::from(p.as_str());
}
_ => {}
}
}
Ok(Some(AstNode::Substitution { pattern, replacement, flags }))
}
Rule::transliteration => {
let inner = pair.into_inner();
let mut search_list = Arc::from("");
let mut replace_list = Arc::from("");
let mut flags = Arc::from("");
for p in inner {
match p.as_rule() {
Rule::search_list => {
search_list = Arc::from(p.as_str());
}
Rule::replace_list => {
replace_list = Arc::from(p.as_str());
}
Rule::trans_flags => {
flags = Arc::from(p.as_str());
}
_ => {}
}
}
Ok(Some(AstNode::Transliteration { search_list, replace_list, flags }))
}
Rule::while_statement => {
let inner = pair.into_inner();
let mut label = None;
let mut condition = None;
let mut block = None;
for p in inner {
match p.as_rule() {
Rule::label => {
label = Some(Arc::from(p.as_str().trim_end_matches(':')));
}
Rule::expression => {
condition = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
Rule::block => {
block = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
_ => {}
}
}
Ok(Some(AstNode::WhileStatement {
label,
condition: condition.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
block: block.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
}))
}
Rule::until_statement => {
let inner = pair.into_inner();
let mut label = None;
let mut condition = None;
let mut block = None;
for p in inner {
match p.as_rule() {
Rule::label => {
label = Some(Arc::from(p.as_str().trim_end_matches(':')));
}
Rule::expression => {
condition = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
Rule::block => {
block = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
_ => {}
}
}
Ok(Some(AstNode::UntilStatement {
label,
condition: condition.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
block: block.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
}))
}
Rule::unless_statement => {
let mut inner = pair.into_inner();
let cond_pair = inner.next().ok_or("Missing condition in unless statement")?;
let condition =
Box::new(self.build_node(cond_pair)?.unwrap_or(AstNode::EmptyExpression));
let block_pair = inner.next().ok_or("Missing block in unless statement")?;
let block =
Box::new(self.build_node(block_pair)?.unwrap_or(AstNode::EmptyExpression));
let mut else_block = None;
if let Some(else_clause) = inner.next()
&& else_clause.as_rule() == Rule::else_clause
{
let mut else_inner = else_clause.into_inner();
if let Some(else_block_pair) = else_inner.next() {
else_block = Some(Box::new(
self.build_node(else_block_pair)?.unwrap_or(AstNode::EmptyExpression),
));
}
}
Ok(Some(AstNode::UnlessStatement { condition, block, else_block }))
}
Rule::foreach_statement => {
let inner = pair.into_inner();
let mut label = None;
let mut variable = None;
let mut list = None;
let mut block = None;
let mut declarator = None;
for p in inner {
match p.as_rule() {
Rule::label => {
label = Some(Arc::from(p.as_str().trim_end_matches(':')));
}
Rule::loop_variable_declarator => {
declarator = Some(Arc::from(p.as_str()));
}
Rule::variable => {
variable = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
Rule::expression => {
list = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
Rule::block => {
block = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
_ => {}
}
}
let final_variable = if let Some(decl) = declarator {
variable.map(|var| {
Box::new(AstNode::VariableDeclaration {
scope: decl,
variables: vec![*var],
initializer: None,
})
})
} else {
variable
};
Ok(Some(AstNode::ForeachStatement {
label,
variable: final_variable,
list: list.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
block: block.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
}))
}
Rule::for_statement => {
let inner = pair.into_inner();
let mut label = None;
let mut init = None;
let mut condition = None;
let mut update = None;
let mut block = None;
let mut variable = None;
let mut list = None;
let mut declarator = None;
let is_c_style = inner.clone().any(|p| p.as_rule() == Rule::for_init);
for p in inner {
match p.as_rule() {
Rule::label => {
label = Some(Arc::from(p.as_str().trim_end_matches(':')));
}
Rule::for_init => {
init = self.build_node(p)?.map(Box::new);
}
Rule::assignment_expression => {
if init.is_none() && condition.is_none() {
init = self.build_node(p)?.map(Box::new);
}
}
Rule::expression => {
if is_c_style {
if condition.is_none() {
condition = self.build_node(p)?.map(Box::new);
} else if update.is_none() {
update = self.build_node(p)?.map(Box::new);
}
} else {
list = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
}
Rule::variable => {
variable = Some(Box::new(
self.build_node(p)?.unwrap_or(AstNode::EmptyExpression),
));
}
Rule::loop_variable_declarator => {
declarator = Some(Arc::from(p.as_str()));
}
Rule::block => {
block = self.build_node(p)?.map(Box::new);
}
_ => {}
}
}
if is_c_style || init.is_some() || condition.is_some() || update.is_some() {
Ok(Some(AstNode::ForStatement {
label,
init,
condition,
update,
block: block.unwrap_or_else(|| Box::new(AstNode::Block(vec![]))),
}))
} else {
let final_variable = if let Some(decl) = declarator {
variable.map(|var| {
Box::new(AstNode::VariableDeclaration {
scope: decl,
variables: vec![*var],
initializer: None,
})
})
} else {
variable
};
Ok(Some(AstNode::ForeachStatement {
label,
variable: final_variable,
list: list.unwrap_or_else(|| Box::new(AstNode::EmptyExpression)),
block: block.unwrap_or_else(|| Box::new(AstNode::Block(vec![]))),
}))
}
}
Rule::package_declaration => {
let inner = pair.into_inner();
let mut name = Arc::from("");
let mut version = None;
let mut block = None;
for p in inner {
match p.as_rule() {
Rule::qualified_name => name = Arc::from(p.as_str()),
Rule::version => version = Some(Arc::from(p.as_str())),
Rule::block => block = self.build_node(p)?.map(Box::new),
Rule::semicolon => {}
_ => {}
}
}
Ok(Some(AstNode::PackageDeclaration { name, version, block }))
}
Rule::use_statement => {
let inner = pair.into_inner();
let mut module = Arc::from("");
let mut version = None;
let mut import_list = Vec::new();
for p in inner {
match p.as_rule() {
Rule::module_name => module = Arc::from(p.as_str()),
Rule::version => version = Some(Arc::from(p.as_str())),
Rule::import_list => {
for item in p.into_inner() {
if item.as_rule() == Rule::import_items {
for import_item in item.into_inner() {
if import_item.as_rule() == Rule::import_item {
import_list.push(Arc::from(import_item.as_str()));
}
}
}
}
}
_ => {}
}
}
Ok(Some(AstNode::UseStatement { module, version, import_list }))
}
Rule::require_statement => {
let inner = pair.into_inner();
let mut module = Arc::from("");
for p in inner {
match p.as_rule() {
Rule::module_name => module = Arc::from(p.as_str()),
Rule::expression => {
if let Some(expr) = self.build_node(p)? {
module = Arc::from(Self::node_to_sexp(&expr));
}
}
_ => {}
}
}
Ok(Some(AstNode::RequireStatement { module }))
}
Rule::interpolation => {
let inner = pair.into_inner().next().ok_or("Empty interpolation")?;
self.build_node(inner)
}
Rule::complex_scalar_interpolation => {
let inner = pair.into_inner().next().ok_or("Empty complex scalar interpolation")?;
self.build_node(inner)
}
Rule::complex_array_interpolation => {
let inner = pair.into_inner().next().ok_or("Empty complex array interpolation")?;
self.build_node(inner)
}
Rule::reference => {
let inner = pair.into_inner().next().ok_or("Empty reference")?;
self.build_node(inner)
}
Rule::dereference => {
let inner = pair.into_inner().next().ok_or("Empty dereference")?;
self.build_node(inner)
}
Rule::scalar_reference => {
Ok(Some(AstNode::ScalarReference(Arc::from(pair.as_str()))))
}
Rule::array_reference => {
Ok(Some(AstNode::ArrayReference(Arc::from(pair.as_str()))))
}
Rule::hash_reference => {
Ok(Some(AstNode::HashReference(Arc::from(pair.as_str()))))
}
Rule::subroutine_reference => {
Ok(Some(AstNode::SubroutineReference(Arc::from(pair.as_str()))))
}
Rule::glob_reference => {
Ok(Some(AstNode::GlobReference(Arc::from(pair.as_str()))))
}
Rule::scalar_dereference => self.build_dereference_node(pair, "$"),
Rule::array_dereference => self.build_dereference_node(pair, "@"),
Rule::hash_dereference => self.build_dereference_node(pair, "%"),
Rule::code_dereference => self.build_dereference_node(pair, "&"),
Rule::glob_dereference => self.build_dereference_node(pair, "*"),
Rule::primary_expression => {
let inner: Vec<_> = pair.into_inner().collect();
if inner.is_empty() {
Ok(None)
} else if inner.len() == 1 {
let first = inner.into_iter().next().ok_or("Expected exactly one element")?;
match first.as_rule() {
Rule::expression => {
self.build_node(first)
}
_ => self.build_node(first),
}
} else {
let mut result = None;
for p in inner {
if p.as_str() == "(" || p.as_str() == ")" {
continue;
}
if let Some(node) = self.build_node(p)? {
result = Some(node);
}
}
Ok(result)
}
}
_ => {
let inner: Vec<_> = pair.into_inner().collect();
if inner.is_empty() {
Ok(None)
} else if inner.len() == 1 {
let first = inner.into_iter().next().ok_or("Expected exactly one element")?;
self.build_node(first)
} else {
let mut nodes = Vec::new();
for p in inner {
if let Some(node) = self.build_node(p)? {
nodes.push(node);
}
}
if nodes.is_empty() {
Ok(None)
} else if nodes.len() == 1 {
Ok(nodes.into_iter().next())
} else {
Ok(Some(AstNode::List(nodes)))
}
}
}
}
}
fn build_expression(
&mut self,
pair: Pair<Rule>,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
let inner = pair.into_inner().next().ok_or("Empty expression")?;
match inner.as_rule() {
Rule::assignment_expression => self.build_node(inner),
Rule::ternary_expression => self.build_ternary_expression(inner),
_ => self.build_node(inner),
}
}
#[inline]
fn build_ternary_expression(
&mut self,
pair: Pair<Rule>,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
let inner: Vec<_> = pair.into_inner().collect();
if inner.len() == 1 {
let first = inner.into_iter().next().ok_or("Expected exactly one element")?;
self.build_node(first)
} else if inner.len() == 3 {
let condition = Box::new(
self.build_node(inner[0].clone())?.ok_or("Failed to build ternary condition")?,
);
let then_expr = Box::new(
self.build_node(inner[1].clone())?
.ok_or("Failed to build ternary then expression")?,
);
let else_expr = Box::new(
self.build_node(inner[2].clone())?
.ok_or("Failed to build ternary else expression")?,
);
Ok(Some(AstNode::TernaryOp { condition, true_expr: then_expr, false_expr: else_expr }))
} else {
let first = inner.into_iter().next().ok_or("Expected exactly one element")?;
self.build_node(first)
}
}
#[inline]
fn build_binary_expression(
&mut self,
pair: Pair<Rule>,
_op_rule: Rule,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
let inner: Vec<_> = pair.into_inner().collect();
if inner.len() == 1 {
let first = inner.into_iter().next().ok_or("Expected exactly one element")?;
self.build_node(first)
} else if inner.len() >= 3 {
self.build_binary_expr_with_precedence(inner)
} else {
let first = inner.into_iter().next().ok_or("Expected exactly one element")?;
self.build_node(first)
}
}
fn build_binary_expr_with_precedence(
&mut self,
pairs: Vec<Pair<Rule>>,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
if pairs.is_empty() {
return Ok(None);
}
let mut result = self.build_node(pairs[0].clone())?.unwrap_or(AstNode::EmptyExpression);
let mut i = 1;
while i < pairs.len() - 1 {
let op_str = pairs[i].as_str();
let op = Arc::from(if op_str == "_DIV_" { "/" } else { op_str });
let right = self.build_node(pairs[i + 1].clone())?.unwrap_or(AstNode::EmptyExpression);
result = AstNode::BinaryOp { op, left: Box::new(result), right: Box::new(right) };
i += 2;
}
Ok(Some(result))
}
fn _apply_precedence(&self, left: AstNode, op: Arc<str>, right: AstNode, _prec: u8) -> AstNode {
AstNode::BinaryOp { op, left: Box::new(left), right: Box::new(right) }
}
fn parse_arg_list(
&mut self,
pair: Pair<Rule>,
) -> Result<Vec<AstNode>, Box<dyn std::error::Error>> {
let mut args = Vec::new();
for arg in pair.into_inner() {
if let Some(node) = self.build_node(arg)? {
args.push(node);
}
}
Ok(args)
}
fn build_dereference_node(
&mut self,
pair: Pair<Rule>,
deref_type: &'static str,
) -> Result<Option<AstNode>, Box<dyn std::error::Error>> {
let mut inner = pair.into_inner();
let expr = if let Some(inner_pair) = inner.next() {
match inner_pair.as_rule() {
Rule::expression => {
Box::new(self.build_node(inner_pair)?.unwrap_or(AstNode::EmptyExpression))
}
Rule::variable_name => {
let variable = Arc::<str>::from(format!("${}", inner_pair.as_str()));
Box::new(AstNode::ScalarVariable(variable))
}
_ => Box::new(self.build_node(inner_pair)?.unwrap_or(AstNode::EmptyExpression)),
}
} else {
Box::new(AstNode::EmptyExpression)
};
Ok(Some(AstNode::Dereference { expr, deref_type: Arc::from(deref_type) }))
}
pub fn to_sexp(&self, node: &AstNode) -> String {
Self::node_to_sexp(node)
}
pub fn node_to_sexp(node: &AstNode) -> String {
match node {
AstNode::Program(children) => {
let mut flat_children = vec![];
for c in children {
let sexp = Self::node_to_sexp(c);
if sexp.starts_with("(source_file ") {
let inner = sexp.trim_start_matches("(source_file ").trim_end_matches(")");
flat_children.push(inner.to_string());
} else {
flat_children.push(sexp);
}
}
if flat_children.is_empty() {
"(source_file)".to_string()
} else {
format!("(source_file {})", flat_children.join(" "))
}
}
AstNode::Statement(expr) => Self::node_to_sexp(expr),
AstNode::Block(statements) => {
let stmt_sexps: Vec<String> = statements.iter().map(Self::node_to_sexp).collect();
format!("(block {})", stmt_sexps.join(" "))
}
AstNode::VariableDeclaration { scope, variables, initializer } => {
let var_sexps: Vec<String> = variables.iter().map(Self::node_to_sexp).collect();
if let Some(init) = initializer {
format!(
"(variable_declaration {} {} = {})",
scope,
var_sexps.join(" "),
Self::node_to_sexp(init)
)
} else {
format!("(variable_declaration {} {})", scope, var_sexps.join(" "))
}
}
AstNode::SubDeclaration { name, prototype, body, .. } => {
let mut parts = vec![format!("(identifier {})", name)];
if let Some(proto) = prototype {
parts.push(format!("(signature {})", proto));
}
parts.push(Self::node_to_sexp(body));
format!("(subroutine {})", parts.join(" "))
}
AstNode::AnonymousSub { body, .. } => {
format!("(anonymous_subroutine {})", Self::node_to_sexp(body))
}
AstNode::FormatDeclaration { name, format_lines } => {
let lines_sexp = format_lines
.iter()
.map(|line| format!("(format_line \"{}\")", line.replace("\"", "\\\"")))
.collect::<Vec<_>>()
.join(" ");
if name.is_empty() {
format!("(format_declaration {})", lines_sexp)
} else {
format!("(format_declaration (identifier {}) {})", name, lines_sexp)
}
}
AstNode::IfStatement { condition, then_block, .. } => {
format!(
"(if_statement {} {})",
Self::node_to_sexp(condition),
Self::node_to_sexp(then_block)
)
}
AstNode::GivenStatement { expression, when_clauses, default_block } => {
let mut result = format!("(given_statement {}", Self::node_to_sexp(expression));
for (cond, block) in when_clauses {
result.push_str(&format!(
" (when_clause {} {})",
Self::node_to_sexp(cond),
Self::node_to_sexp(block)
));
}
if let Some(default) = default_block {
result.push_str(&format!(" (default_clause {})", Self::node_to_sexp(default)));
}
result.push(')');
result
}
AstNode::TieStatement { variable, class, args } => {
let args_str = args.iter().map(Self::node_to_sexp).collect::<Vec<_>>().join(" ");
format!(
"(tie_statement {} {} {})",
Self::node_to_sexp(variable),
Self::node_to_sexp(class),
args_str
)
}
AstNode::UntieStatement { variable } => {
format!("(untie_statement {})", Self::node_to_sexp(variable))
}
AstNode::TiedExpression { variable } => {
format!("(tied_expression {})", Self::node_to_sexp(variable))
}
AstNode::PostfixDereference { expr, deref_type } => {
format!("(postfix_deref {} {})", Self::node_to_sexp(expr), deref_type)
}
AstNode::Dereference { expr, deref_type } => {
format!("(dereference {} {})", Self::node_to_sexp(expr), deref_type)
}
AstNode::TypeglobSlotAccess { typeglob, slot } => {
format!("(typeglob_slot_access {} {})", Self::node_to_sexp(typeglob), slot)
}
AstNode::ArrayAccess { array, index } => {
format!(
"(array_access {} {})",
Self::node_to_sexp(array),
Self::node_to_sexp(index)
)
}
AstNode::HashAccess { hash, key } => {
format!("(hash_access {} {})", Self::node_to_sexp(hash), Self::node_to_sexp(key))
}
AstNode::MethodCall { object, method, args } => {
let args_str = if args.is_empty() {
"( )".to_string()
} else {
format!(
"( {} )",
args.iter().map(Self::node_to_sexp).collect::<Vec<_>>().join(" ")
)
};
format!(
"(method_call_expression {} -> (method {}) {})",
Self::node_to_sexp(object),
method,
args_str
)
}
AstNode::Assignment { target, op, value } => {
format!(
"(assignment {} ({}) {})",
Self::node_to_sexp(target),
op,
Self::node_to_sexp(value)
)
}
AstNode::FunctionCall { function, args } => {
let args_str = if args.is_empty() {
"".to_string()
} else {
format!(
" {}",
args.iter().map(Self::node_to_sexp).collect::<Vec<_>>().join(" ")
)
};
format!("(function_call {}{})", Self::node_to_sexp(function), args_str)
}
AstNode::BuiltinListOp { name, args } => {
let args_str = if args.is_empty() {
"".to_string()
} else {
format!(
" {}",
args.iter().map(Self::node_to_sexp).collect::<Vec<_>>().join(" ")
)
};
format!("(function_call (identifier {}){})", name, args_str)
}
AstNode::BinaryOp { op, left, right } => {
format!(
"(binary_expression {} ({}) {})",
Self::node_to_sexp(left),
op,
Self::node_to_sexp(right)
)
}
AstNode::ScalarVariable(name) => {
format!("(scalar_variable {})", name)
}
AstNode::ArrayVariable(name) => {
format!("(array_variable {})", name)
}
AstNode::HashVariable(name) => {
format!("(hash_variable {})", name)
}
AstNode::TypeglobVariable(name) => {
format!("(typeglob_variable {})", name)
}
AstNode::ScalarReference(name) => {
format!("(scalar_reference {})", name)
}
AstNode::ArrayReference(name) => {
format!("(array_reference {})", name)
}
AstNode::HashReference(name) => {
format!("(hash_reference {})", name)
}
AstNode::SubroutineReference(name) => {
format!("(subroutine_reference {})", name)
}
AstNode::GlobReference(name) => {
format!("(glob_reference {})", name)
}
AstNode::Number(value) => {
format!("(number {})", value)
}
AstNode::String(value) => {
format!("(string_literal {})", value)
}
AstNode::Bareword(value) => {
format!("(bareword {})", value)
}
AstNode::Regex { pattern, flags, .. } => {
if flags.is_empty() {
format!("(regex /{}/ )", pattern)
} else {
format!("(regex /{}/{} )", pattern, flags)
}
}
AstNode::InterpolatedString(parts) => {
let parts_str = parts.iter().map(Self::node_to_sexp).collect::<Vec<_>>().join(" ");
format!("(interpolated_string {})", parts_str)
}
AstNode::Identifier(name) => {
format!("(identifier {})", name)
}
AstNode::SpecialLiteral(name) => {
format!("(special_literal {})", name)
}
AstNode::EmptyExpression => "(empty_expression)".to_string(),
AstNode::Comment(content) => {
format!("(comment {})", content)
}
AstNode::List(items) => {
let item_sexps: Vec<String> = items.iter().map(Self::node_to_sexp).collect();
item_sexps.join(" ")
}
AstNode::ArrayRef(items) => {
let item_sexps: Vec<String> = items.iter().map(Self::node_to_sexp).collect();
format!("(array_ref {})", item_sexps.join(" "))
}
AstNode::HashRef(items) => {
let item_sexps: Vec<String> = items.iter().map(Self::node_to_sexp).collect();
format!("(hash_ref {})", item_sexps.join(" "))
}
AstNode::WhileStatement { label, condition, block } => {
let label_str =
if let Some(l) = label { format!(" (label {})", l) } else { String::new() };
format!(
"(while_statement{} {} {})",
label_str,
Self::node_to_sexp(condition),
Self::node_to_sexp(block)
)
}
AstNode::UntilStatement { label, condition, block } => {
let label_str =
if let Some(l) = label { format!(" (label {})", l) } else { String::new() };
format!(
"(until_statement{} {} {})",
label_str,
Self::node_to_sexp(condition),
Self::node_to_sexp(block)
)
}
AstNode::UnlessStatement { condition, block, else_block } => {
let else_str = if let Some(e) = else_block {
format!(" (else {})", Self::node_to_sexp(e))
} else {
String::new()
};
format!(
"(unless_statement {} {}{}",
Self::node_to_sexp(condition),
Self::node_to_sexp(block),
else_str
)
}
AstNode::ForStatement { init, condition, update, block, .. } => {
let mut parts = vec![];
if let Some(i) = init {
parts.push(format!("(init {})", Self::node_to_sexp(i)));
}
if let Some(c) = condition {
parts.push(format!("(condition {})", Self::node_to_sexp(c)));
}
if let Some(u) = update {
parts.push(format!("(update {})", Self::node_to_sexp(u)));
}
parts.push(format!("(body {})", Self::node_to_sexp(block)));
format!("(for_statement {})", parts.join(" "))
}
AstNode::ForeachStatement { label, variable, list, block } => {
let label_str =
if let Some(l) = label { format!(" (label {})", l) } else { String::new() };
let var_str = if let Some(v) = variable {
format!(" (variable {})", Self::node_to_sexp(v))
} else {
String::new()
};
format!(
"(foreach_statement{}{} {} {})",
label_str,
var_str,
Self::node_to_sexp(list),
Self::node_to_sexp(block)
)
}
AstNode::PackageDeclaration { name, version, block } => {
let mut parts = vec![format!("(name {})", name)];
if let Some(v) = version {
parts.push(format!("(version {})", v));
}
if let Some(b) = block {
parts.push(format!("(body {})", Self::node_to_sexp(b)));
}
format!("(package_declaration {})", parts.join(" "))
}
AstNode::UseStatement { module, version, import_list } => {
let mut parts = vec![format!("use (package {})", module)];
if let Some(v) = version {
parts.push(format!("(version {})", v));
}
if !import_list.is_empty() {
parts.push(format!("(import_list {})", import_list.join(" ")));
}
parts.push(";".to_string());
format!("(use_statement {})", parts.join(" "))
}
AstNode::RequireStatement { module } => {
format!("(require_statement require (package {}) ;)", module)
}
AstNode::Substitution { pattern, replacement, flags } => {
if flags.is_empty() {
format!("(substitution s/{}/{}/ )", pattern, replacement)
} else {
format!("(substitution s/{}/{}/{} )", pattern, replacement, flags)
}
}
AstNode::Transliteration { search_list, replace_list, flags } => {
if flags.is_empty() {
format!("(transliteration tr/{}/{}/ )", search_list, replace_list)
} else {
format!("(transliteration tr/{}/{}/{} )", search_list, replace_list, flags)
}
}
AstNode::BeginBlock(block) => {
format!("(begin_block {})", Self::node_to_sexp(block))
}
AstNode::EndBlock(block) => {
format!("(end_block {})", Self::node_to_sexp(block))
}
AstNode::CheckBlock(block) => {
format!("(check_block {})", Self::node_to_sexp(block))
}
AstNode::InitBlock(block) => {
format!("(init_block {})", Self::node_to_sexp(block))
}
AstNode::UnitcheckBlock(block) => {
format!("(unitcheck_block {})", Self::node_to_sexp(block))
}
AstNode::QwList(words) => {
let word_list =
words.iter().map(|w| format!("(word {})", w)).collect::<Vec<_>>().join(" ");
format!("(qw_list {})", word_list)
}
AstNode::DoBlock(expr) => {
format!("(do_block {})", Self::node_to_sexp(expr))
}
AstNode::EvalBlock(block) => {
format!("(eval_block {})", Self::node_to_sexp(block))
}
AstNode::EvalString(expr) => {
format!("(eval_string {})", Self::node_to_sexp(expr))
}
AstNode::GotoStatement { target } => {
format!("(goto_statement {})", target)
}
AstNode::ReturnStatement { value } => {
if let Some(v) = value {
format!("(return_statement {})", Self::node_to_sexp(v))
} else {
"(return_statement)".to_string()
}
}
AstNode::LabeledBlock { label, block } => {
format!("(labeled_block {} {})", label, Self::node_to_sexp(block))
}
AstNode::Heredoc { marker, indented, quoted, content } => {
let flags = format!(
"{}{}",
if *indented { "~" } else { "" },
if *quoted { "'" } else { "" }
);
format!("(heredoc {} {} \"{}\")", marker, flags, content.escape_default())
}
AstNode::Pod(content) => {
format!("(pod {})", content)
}
AstNode::DataSection(content) => {
format!("(data_section {})", content)
}
AstNode::EndSection(content) => {
format!("(end_section {})", content)
}
AstNode::Glob(pattern) => {
format!("(glob <{}>)", pattern)
}
AstNode::QqString(content) => {
format!("(string_literal {})", content)
}
AstNode::QxString(content) => {
format!("(command_substitution {})", content)
}
AstNode::Readline { filehandle } => {
if let Some(fh) = filehandle {
format!("(readline <{}>)", fh)
} else {
"(readline <>)".to_string()
}
}
AstNode::TryCatch { try_block, catch_clauses, finally_block } => {
let mut result = format!("(try_catch_statement {}", Self::node_to_sexp(try_block));
for (param, block) in catch_clauses {
if let Some(p) = param {
result.push_str(&format!(" (catch ({}) {})", p, Self::node_to_sexp(block)));
} else {
result.push_str(&format!(" (catch {})", Self::node_to_sexp(block)));
}
}
if let Some(finally) = finally_block {
result.push_str(&format!(" (finally {})", Self::node_to_sexp(finally)));
}
result.push(')');
result
}
AstNode::DeferStatement(block) => {
format!("(defer_statement {})", Self::node_to_sexp(block))
}
AstNode::ClassDeclaration { name, version, superclass, body } => {
let mut result = format!("(class_declaration {}", name);
if let Some(v) = version {
result.push_str(&format!(" (version {})", v));
}
if let Some(s) = superclass {
result.push_str(&format!(" (superclass {})", s));
}
for member in body {
result.push_str(&format!(" {}", Self::node_to_sexp(member)));
}
result.push(')');
result
}
AstNode::MethodDeclaration { name, signature, attributes, body } => {
let mut result = format!("(method_declaration {}", name);
if let Some(sig) = signature {
result.push_str(&format!(" (signature {})", sig));
}
if !attributes.is_empty() {
result.push_str(&format!(" (attributes {})", attributes.join(" ")));
}
result.push_str(&format!(" {}", Self::node_to_sexp(body)));
result.push(')');
result
}
AstNode::FieldDeclaration { name, attributes, default } => {
let mut result = format!("(field_declaration {}", name);
if !attributes.is_empty() {
result.push_str(&format!(" (attributes {})", attributes.join(" ")));
}
if let Some(d) = default {
result.push_str(&format!(" (default {})", Self::node_to_sexp(d)));
}
result.push(')');
result
}
AstNode::RoleDeclaration { name, body } => {
format!("(role_declaration {} {})", name, Self::node_to_sexp(body))
}
_ => format!("(unhandled_node {:?})", node),
}
}
}
impl Default for PureRustPerlParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use perl_tdd_support::must;
#[test]
fn test_basic_parsing() {
let mut parser = PureRustPerlParser::new();
let source = "$var";
let result = parser.parse(source);
assert!(result.is_ok());
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("AST: {:?}", ast);
println!("S-expression: {}", sexp);
}
#[test]
fn test_variable_parsing() {
use perl_tdd_support::must;
let mut parser = PureRustPerlParser::new();
let source = "$scalar @array %hash";
let result = parser.parse(source);
assert!(result.is_ok());
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("S-expression: {}", sexp);
}
#[test]
fn test_assignment_parsing() {
let mut parser = PureRustPerlParser::new();
let source = "my $var = 42;";
let result = parser.parse(source);
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("Success! AST: {:?}", ast);
println!("S-expression: {}", sexp);
}
#[test]
fn test_function_declaration() {
let mut parser = PureRustPerlParser::new();
let source = "sub hello { print 'Hello'; }";
let result = parser.parse(source);
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("S-expression: {}", sexp);
}
#[test]
fn test_if_statement() {
let mut parser = PureRustPerlParser::new();
let source = "if ($x > 0) { print 'positive'; }";
let result = parser.parse(source);
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("S-expression: {}", sexp);
}
#[test]
fn test_regression_percent_string_in_if_assignment() {
let mut parser = PureRustPerlParser::new();
let source = r#"if ($a > 0) { $a = "%"; }"#;
let result = parser.parse(source);
assert!(result.is_ok(), "Failed to parse regression input: {source}");
}
#[test]
fn test_array_assignment() {
let mut parser = PureRustPerlParser::new();
let source = "@array = (1, 2, 3);";
let result = parser.parse(source);
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("Array assignment AST: {:?}", ast);
println!("S-expression: {}", sexp);
}
#[test]
fn test_hash_assignment() {
let mut parser = PureRustPerlParser::new();
let source = "%hash = (a => 1, b => 2);";
let result = parser.parse(source);
let ast = must(result);
let sexp = parser.to_sexp(&ast);
println!("Hash assignment AST: {:?}", ast);
println!("S-expression: {}", sexp);
}
}