use crate::{
ast::{Node, NodeKind, SourceLocation},
error::{ParseError, ParseOutput, ParseResult, RecoveryKind, RecoverySite},
heredoc_collector::{self, HeredocContent, PendingHeredoc, collect_all},
quote_parser,
token_stream::{Token, TokenKind, TokenStream},
};
use std::collections::{HashSet, VecDeque};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;
fn strip_qw_comments(content: &str) -> String {
content
.lines()
.map(|line| if let Some(pos) = line.find('#') { &line[..pos] } else { line })
.collect::<Vec<_>>()
.join("\n")
}
pub struct Parser<'a> {
tokens: TokenStream<'a>,
recursion_depth: usize,
last_end_position: usize,
in_for_loop_init: bool,
in_class_body: usize,
at_stmt_start: bool,
pending_heredocs: VecDeque<PendingHeredoc>,
custom_attribute_handlers: HashSet<String>,
attribute_handlers_enabled: bool,
src_bytes: &'a [u8],
byte_cursor: usize,
heredoc_start_time: Option<Instant>,
errors: Vec<ParseError>,
cancellation_flag: Option<Arc<AtomicBool>>,
cancellation_check_counter: usize,
}
const MAX_RECURSION_DEPTH: usize = 128;
impl<'a> Parser<'a> {
pub fn new(input: &'a str) -> Self {
Parser {
tokens: TokenStream::new(input),
recursion_depth: 0,
last_end_position: 0,
in_for_loop_init: false,
in_class_body: 0,
at_stmt_start: true,
pending_heredocs: VecDeque::new(),
custom_attribute_handlers: HashSet::new(),
attribute_handlers_enabled: false,
src_bytes: input.as_bytes(),
byte_cursor: 0,
heredoc_start_time: None,
errors: Vec::new(),
cancellation_flag: None,
cancellation_check_counter: 0,
}
}
pub fn new_with_cancellation(input: &'a str, cancellation_flag: Arc<AtomicBool>) -> Self {
let mut p = Parser::new(input);
p.cancellation_flag = Some(cancellation_flag);
p
}
pub fn from_tokens(tokens: Vec<Token>, source: &'a str) -> Self {
Parser {
tokens: TokenStream::from_vec(tokens),
recursion_depth: 0,
last_end_position: 0,
in_for_loop_init: false,
in_class_body: 0,
at_stmt_start: true,
pending_heredocs: VecDeque::new(),
custom_attribute_handlers: HashSet::new(),
attribute_handlers_enabled: false,
src_bytes: source.as_bytes(),
byte_cursor: 0,
heredoc_start_time: None,
errors: Vec::new(),
cancellation_flag: None,
cancellation_check_counter: 0,
}
}
#[inline]
fn check_cancelled(&mut self) -> ParseResult<()> {
self.cancellation_check_counter = self.cancellation_check_counter.wrapping_add(1);
if self.cancellation_check_counter & 63 == 0 {
if let Some(ref flag) = self.cancellation_flag {
if flag.load(Ordering::Relaxed) {
return Err(ParseError::Cancelled);
}
}
}
Ok(())
}
pub fn new_with_recovery_config(input: &'a str, _config: ()) -> Self {
Parser::new(input)
}
pub fn parse(&mut self) -> ParseResult<Node> {
if let Some(ref flag) = self.cancellation_flag {
if flag.load(Ordering::Relaxed) {
return Err(ParseError::Cancelled);
}
}
self.parse_program()
}
pub fn errors(&self) -> &[ParseError] {
&self.errors
}
pub fn parse_with_recovery(&mut self) -> ParseOutput {
let ast = match self.parse() {
Ok(node) => node,
Err(e) => {
if !self.errors.contains(&e) {
self.errors.push(e.clone());
}
Node::new(
NodeKind::Program { statements: vec![] },
SourceLocation { start: 0, end: 0 },
)
}
};
ParseOutput::with_errors(ast, self.errors.clone())
}
}
include!("helpers.rs");
include!("heredoc.rs");
include!("statements.rs");
include!("variables.rs");
include!("control_flow.rs");
include!("declarations.rs");
include!("expressions/mod.rs");
include!("expressions/precedence.rs");
include!("expressions/unary.rs");
include!("expressions/postfix.rs");
include!("expressions/primary.rs");
include!("expressions/calls.rs");
include!("expressions/hashes.rs");
include!("expressions/quotes.rs");
#[cfg(test)]
mod builtin_block_list_tests;
#[cfg(test)]
mod builtin_expansion_tests;
#[cfg(test)]
mod chained_deref_method_tests;
#[cfg(test)]
mod coderef_invocation_tests;
#[cfg(test)]
mod complex_args_tests;
#[cfg(test)]
mod control_flow_expr_tests;
#[cfg(test)]
mod declaration_in_args_tests;
#[cfg(test)]
mod error_recovery_tests;
#[cfg(test)]
mod eval_goto_tests;
#[cfg(test)]
mod for_builtin_block_tests;
#[cfg(test)]
mod format_comprehensive_tests;
#[cfg(test)]
mod format_tests;
#[cfg(test)]
mod forward_declaration_tests;
#[cfg(test)]
mod from_tokens_tests;
#[cfg(test)]
mod glob_assignment_tests;
#[cfg(test)]
mod glob_tests;
#[cfg(test)]
mod hash_vs_block_tests;
#[cfg(test)]
mod heredoc_security_tests;
#[cfg(test)]
mod indirect_call_tests;
#[cfg(test)]
mod indirect_object_tests;
#[cfg(test)]
mod loop_control_tests;
#[cfg(test)]
mod qualified_variable_subscript_tests;
#[cfg(test)]
mod regex_delimiter_tests;
#[cfg(test)]
mod slash_ambiguity_tests;
#[cfg(test)]
mod statement_modifier_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tie_tests;
#[cfg(test)]
mod typed_variable_declaration_tests;
#[cfg(test)]
mod unclosed_block_recovery_tests;
#[cfg(test)]
mod use_overload_tests;
#[cfg(test)]
mod x_repetition_tests;
#[cfg(test)]
mod strip_qw_comments_unit_tests {
use super::strip_qw_comments;
#[test]
fn test_strip_basic() {
let result = strip_qw_comments("foo # comment\n bar");
assert_eq!(result.split_whitespace().collect::<Vec<_>>(), vec!["foo", "bar"]);
}
}