use std::convert::Infallible;
use boreal_parser::regex::{AssertionKind, Node};
use boreal_parser::VariableFlags;
use crate::regex::{regex_ast_to_string, visit, Regex, VisitAction, Visitor};
use super::atom::AtomsDetails;
use super::{CompiledVariable, MatcherType, VariableCompilationError};
pub(super) fn compile_regex(
ast: &Node,
mut case_insensitive: bool,
dot_all: bool,
flags: VariableFlags,
) -> Result<CompiledVariable, VariableCompilationError> {
if flags.contains(VariableFlags::NOCASE) {
case_insensitive = true;
}
let AtomsDetails {
mut literals,
pre_ast,
post_ast,
} = super::atom::get_atoms_details(ast).map_err(|e| match e {
super::atom::AtomsExtractionError => VariableCompilationError::AtomsExtractionError,
})?;
let use_ac = !literals.is_empty()
&& literals.iter().all(|lit| lit.len() >= 2)
&& visit(ast, AcCompatibility::default()).unwrap_or_else(|e| match e {});
let mut has_wide_word_boundaries = false;
let matcher_type = if use_ac {
let pre = match pre_ast {
Some(ast) => {
let (pre, has_ww_boundaries) = convert_ast_to_string_with_flags(&ast, flags)?;
has_wide_word_boundaries |= has_ww_boundaries;
Some(pre)
}
None => None,
};
let post = match post_ast {
Some(ast) => {
let (post, has_ww_boundaries) = convert_ast_to_string_with_flags(&ast, flags)?;
has_wide_word_boundaries |= has_ww_boundaries;
Some(post)
}
None => None,
};
apply_ascii_wide_flags_on_literals(&mut literals, flags);
MatcherType::Atomized {
left_validator: compile_validator(pre, case_insensitive, dot_all)?,
right_validator: compile_validator(post, case_insensitive, dot_all)?,
}
} else {
let (expr, has_ww_boundaries) = convert_ast_to_string_with_flags(ast, flags)?;
has_wide_word_boundaries |= has_ww_boundaries;
if literals.iter().any(|lit| lit.len() < 2) {
literals.clear();
} else {
apply_ascii_wide_flags_on_literals(&mut literals, flags);
}
MatcherType::Raw(compile_regex_expr(&expr, case_insensitive, dot_all)?)
};
let non_wide_regex = if has_wide_word_boundaries {
let expr = regex_ast_to_string(ast);
Some(compile_regex_expr(&expr, case_insensitive, dot_all)?)
} else {
None
};
Ok(CompiledVariable {
literals,
matcher_type,
non_wide_regex,
})
}
struct AcCompatibility(bool);
impl Default for AcCompatibility {
fn default() -> Self {
Self(true)
}
}
impl Visitor for AcCompatibility {
type Output = bool;
type Err = Infallible;
fn visit_pre(&mut self, node: &Node) -> Result<VisitAction, Self::Err> {
match node {
Node::Assertion(AssertionKind::StartLine) | Node::Assertion(AssertionKind::EndLine) => {
self.0 = false;
}
Node::Repetition { greedy: true, .. } => {
self.0 = false;
}
_ => (),
}
Ok(VisitAction::Continue)
}
fn finish(self) -> Result<Self::Output, Self::Err> {
Ok(self.0)
}
}
fn compile_validator(
expr: Option<String>,
case_insensitive: bool,
dot_all: bool,
) -> Result<Option<Regex>, VariableCompilationError> {
match expr {
Some(expr) => Ok(Some(compile_regex_expr(&expr, case_insensitive, dot_all)?)),
None => Ok(None),
}
}
fn apply_ascii_wide_flags_on_literals(literals: &mut Vec<Vec<u8>>, flags: VariableFlags) {
if !flags.contains(VariableFlags::WIDE) {
return;
}
if flags.contains(VariableFlags::ASCII) {
let wide_literals: Vec<_> = literals.iter().map(|v| widen_literal(v)).collect();
literals.extend(wide_literals);
} else {
for lit in literals {
*lit = widen_literal(lit);
}
}
}
fn widen_literal(literal: &[u8]) -> Vec<u8> {
let mut new_lit = Vec::with_capacity(literal.len() * 2);
for b in literal {
new_lit.push(*b);
new_lit.push(0);
}
new_lit
}
fn convert_ast_to_string_with_flags(
ast: &Node,
flags: VariableFlags,
) -> Result<(String, bool), VariableCompilationError> {
if flags.contains(VariableFlags::WIDE) {
let (wide_ast, has_wide_word_boundaries) = visit(ast, AstWidener::new())?;
let expr = if flags.contains(VariableFlags::ASCII) {
format!(
"{}|{}",
regex_ast_to_string(ast),
regex_ast_to_string(&wide_ast),
)
} else {
regex_ast_to_string(&wide_ast)
};
Ok((expr, has_wide_word_boundaries))
} else {
Ok((regex_ast_to_string(ast), false))
}
}
fn compile_regex_expr(
expr: &str,
case_insensitive: bool,
dot_all: bool,
) -> Result<Regex, VariableCompilationError> {
Regex::from_str(expr, case_insensitive, dot_all).map_err(VariableCompilationError::Regex)
}
#[derive(Debug)]
struct AstWidener {
node: Option<Node>,
stack: Vec<StackLevel>,
has_word_boundaries: bool,
}
#[derive(Debug)]
struct StackLevel {
nodes: Vec<Node>,
in_concat: bool,
}
impl StackLevel {
fn new(in_concat: bool) -> Self {
Self {
nodes: Vec::new(),
in_concat,
}
}
fn push(&mut self, node: Node) {
self.nodes.push(node);
}
}
impl AstWidener {
fn new() -> Self {
Self {
node: None,
stack: Vec::new(),
has_word_boundaries: false,
}
}
fn add(&mut self, node: Node) -> Result<(), VariableCompilationError> {
if self.stack.is_empty() {
match self.node.replace(node) {
Some(_) => Err(VariableCompilationError::WidenError),
None => Ok(()),
}
} else {
let pos = self.stack.len() - 1;
self.stack[pos].push(node);
Ok(())
}
}
fn add_wide(&mut self, node: Node) -> Result<(), VariableCompilationError> {
let nul_byte = Node::Literal(b'\0');
if self.stack.is_empty() {
match self.node.replace(Node::Concat(vec![node, nul_byte])) {
Some(_) => Err(VariableCompilationError::WidenError),
None => Ok(()),
}
} else {
let pos = self.stack.len() - 1;
let level = &mut self.stack[pos];
if level.in_concat {
level.nodes.push(node);
level.nodes.push(nul_byte);
} else {
level
.nodes
.push(Node::Group(Box::new(Node::Concat(vec![node, nul_byte]))));
}
Ok(())
}
}
fn pop(&mut self) -> Option<Vec<Node>> {
self.stack.pop().map(|v| v.nodes)
}
}
impl Visitor for AstWidener {
type Output = (Node, bool);
type Err = VariableCompilationError;
fn finish(self) -> Result<(Node, bool), Self::Err> {
match self.node {
Some(v) => Ok((v, self.has_word_boundaries)),
None => Err(VariableCompilationError::WidenError),
}
}
fn visit_pre(&mut self, node: &Node) -> Result<VisitAction, Self::Err> {
match node {
Node::Dot | Node::Empty | Node::Literal(_) | Node::Class(_) | Node::Assertion(_) => (),
Node::Repetition { .. } | Node::Group(_) | Node::Alternation(_) => {
self.stack.push(StackLevel::new(false));
}
Node::Concat(_) => {
self.stack.push(StackLevel::new(true));
}
}
Ok(VisitAction::Continue)
}
fn visit_post(&mut self, node: &Node) -> Result<(), Self::Err> {
match node {
Node::Empty => self.add(Node::Empty),
Node::Dot => self.add_wide(Node::Dot),
Node::Literal(lit) => self.add_wide(Node::Literal(*lit)),
Node::Class(cls) => self.add_wide(Node::Class(cls.clone())),
Node::Assertion(AssertionKind::StartLine) | Node::Assertion(AssertionKind::EndLine) => {
self.add(node.clone())
}
Node::Assertion(AssertionKind::WordBoundary)
| Node::Assertion(AssertionKind::NonWordBoundary) => {
self.has_word_boundaries = true;
self.add(Node::Empty)
}
Node::Repetition {
node: _,
kind,
greedy,
} => {
let node = self
.pop()
.and_then(|mut v| v.pop())
.ok_or(VariableCompilationError::WidenError)?;
self.add(Node::Repetition {
kind: kind.clone(),
greedy: *greedy,
node: Box::new(node),
})
}
Node::Group(_) => {
let node = self
.pop()
.and_then(|mut v| v.pop())
.ok_or(VariableCompilationError::WidenError)?;
self.add(Node::Group(Box::new(node)))
}
Node::Concat(_) => {
let vec = self.pop().ok_or(VariableCompilationError::WidenError)?;
self.add(Node::Concat(vec))
}
Node::Alternation(_) => {
let vec = self.pop().ok_or(VariableCompilationError::WidenError)?;
self.add(Node::Alternation(vec))
}
}
}
}
#[cfg(test)]
mod tests {
use crate::test_helpers::test_type_traits_non_clonable;
use super::*;
#[test]
fn test_types_traits() {
test_type_traits_non_clonable(AstWidener::new());
test_type_traits_non_clonable(StackLevel::new(false));
}
}