use std::collections::HashMap;
use super::context::{BoundaryKind, ContextPattern, ContextPatternChar};
use super::optimizer::{NfaOptimizerChar, OptimizationConfig};
use super::thompson::{ThompsonBuilder, ThompsonBuilderChar};
use super::{NFAChar, NFA};
use crate::phonetic::regex::ast::{
ContextExpr, ContextExprByte, Regex, RegexByte, RegexFlags, UnicodeNormalization,
};
use crate::phonetic::regex::error::{ParseError, ParseErrorKind, ParseResult, Position};
use crate::phonetic::regex::transform::apply_flags;
#[derive(Debug, Clone)]
pub struct CompileResultChar {
pub nfa: NFAChar,
pub unicode_normalization: Option<UnicodeNormalization>,
pub multiline: bool,
pub dotall: bool,
pub local_distance: Option<u8>,
}
pub fn compile(regex: &Regex) -> ParseResult<NFAChar> {
let mut compiler = NFACompilerChar::new();
compiler.compile(regex)
}
pub fn compile_with_flags(regex: &Regex) -> ParseResult<CompileResultChar> {
let mut compiler = NFACompilerChar::new();
compiler.compile_with_flags(regex)
}
pub fn compile_bytes(regex: &RegexByte) -> ParseResult<NFA> {
let mut compiler = NFACompilerByte::new();
compiler.compile(regex)
}
#[derive(Debug, Clone)]
pub struct CompiledRewriteChar {
pub source: NFAChar,
pub replacement: Vec<char>,
pub left_context: Option<ContextPatternChar>,
pub right_context: Option<ContextPatternChar>,
pub weight: f64,
}
#[derive(Debug, Clone)]
pub struct CompiledRewrite {
pub source: NFA,
pub replacement: Vec<u8>,
pub left_context: Option<ContextPattern>,
pub right_context: Option<ContextPattern>,
pub weight: f64,
}
pub fn compile_rewrite(regex: &Regex) -> ParseResult<CompiledRewriteChar> {
let mut compiler = NFACompilerChar::new();
compiler.compile_rewrite(regex)
}
pub fn compile_rewrite_bytes(regex: &RegexByte) -> ParseResult<CompiledRewrite> {
let mut compiler = NFACompilerByte::new();
compiler.compile_rewrite(regex)
}
#[derive(Debug)]
enum CompileWork<'a> {
Compile(&'a Regex),
DoConcatenate,
DoAlternation,
DoStar,
DoPlus,
DoOptional,
DoRepeatExact(usize),
DoRepeatRange(usize, Option<usize>),
}
pub struct NFACompilerChar {
builder: ThompsonBuilderChar,
symbols: HashMap<String, Vec<char>>,
flags: RegexFlags,
use_trampolining: bool,
optimization: Option<OptimizationConfig>,
}
impl NFACompilerChar {
pub fn new() -> Self {
Self {
builder: ThompsonBuilderChar::new(),
symbols: HashMap::new(),
flags: RegexFlags::default(),
use_trampolining: false,
optimization: Some(OptimizationConfig::full()),
}
}
pub fn with_trampolining(mut self) -> Self {
self.use_trampolining = true;
self
}
pub fn set_trampolining(&mut self, enabled: bool) {
self.use_trampolining = enabled;
}
pub fn is_trampolining(&self) -> bool {
self.use_trampolining
}
pub fn with_optimization(mut self, config: OptimizationConfig) -> Self {
self.optimization = Some(config);
self
}
pub fn without_optimization(mut self) -> Self {
self.optimization = None;
self
}
pub fn set_optimization(&mut self, config: Option<OptimizationConfig>) {
self.optimization = config;
}
pub fn is_optimization_enabled(&self) -> bool {
self.optimization.is_some()
}
pub fn optimization_config(&self) -> Option<&OptimizationConfig> {
self.optimization.as_ref()
}
pub fn add_symbol(&mut self, name: impl Into<String>, chars: Vec<char>) {
self.symbols.insert(name.into(), chars);
}
pub fn set_flags(&mut self, flags: RegexFlags) {
self.flags = flags;
}
pub fn flags(&self) -> &RegexFlags {
&self.flags
}
pub fn get_symbol(&self, name: &str) -> Option<&Vec<char>> {
self.symbols.get(name)
}
pub fn compile(&mut self, regex: &Regex) -> ParseResult<NFAChar> {
let transform_result = apply_flags(regex);
let nfa = self.compile_regex(&transform_result.regex)?;
let mut nfa = if let Some(ref config) = self.optimization {
let optimizer = NfaOptimizerChar::new(config.clone());
let (optimized, _stats) = optimizer.optimize(nfa);
optimized
} else {
nfa
};
nfa.finalize();
Ok(nfa)
}
pub fn compile_with_flags(&mut self, regex: &Regex) -> ParseResult<CompileResultChar> {
let transform_result = apply_flags(regex);
let nfa = self.compile_regex(&transform_result.regex)?;
let nfa = if let Some(ref config) = self.optimization {
let optimizer = NfaOptimizerChar::new(config.clone());
let (optimized, _stats) = optimizer.optimize(nfa);
optimized
} else {
nfa
};
let mut nfa = nfa;
nfa.finalize();
Ok(CompileResultChar {
nfa,
unicode_normalization: transform_result.unicode_normalization,
multiline: transform_result.multiline,
dotall: transform_result.dotall,
local_distance: transform_result.local_distance,
})
}
pub fn compile_rewrite(&mut self, regex: &Regex) -> ParseResult<CompiledRewriteChar> {
match regex {
Regex::RewriteRule {
pattern,
replacement,
context,
weight,
} => {
let source = self.compile_regex(pattern)?;
let mut source = if let Some(ref config) = self.optimization {
let optimizer = NfaOptimizerChar::new(config.clone());
let (optimized, _stats) = optimizer.optimize(source);
optimized
} else {
source
};
source.finalize();
let replacement_chars = self.regex_to_literal(replacement)?;
let (left_context, right_context) = if let Some(ctx) = context {
(
ctx.left
.as_ref()
.map(|l| self.compile_context_expr(l))
.transpose()?,
ctx.right
.as_ref()
.map(|r| self.compile_context_expr(r))
.transpose()?,
)
} else {
(None, None)
};
Ok(CompiledRewriteChar {
source,
replacement: replacement_chars,
left_context,
right_context,
weight: *weight,
})
}
_ => Err(ParseError::new(
ParseErrorKind::InvalidRewriteRule("expected rewrite rule".to_string()),
Position::start(),
)),
}
}
fn compile_context_expr(&mut self, expr: &ContextExpr) -> ParseResult<ContextPatternChar> {
match expr {
ContextExpr::Pattern(regex) => {
let nfa = self.compile_regex(regex)?;
let mut nfa = if let Some(ref config) = self.optimization {
let optimizer = NfaOptimizerChar::new(config.clone());
let (optimized, _stats) = optimizer.optimize(nfa);
optimized
} else {
nfa
};
nfa.finalize();
Ok(ContextPatternChar::Nfa(nfa))
}
ContextExpr::WordBoundary => {
Ok(ContextPatternChar::Boundary(BoundaryKind::WordStart))
}
ContextExpr::And(a, b) => {
let left = self.compile_context_expr(a)?;
let right = self.compile_context_expr(b)?;
Ok(ContextPatternChar::And(Box::new(left), Box::new(right)))
}
ContextExpr::Or(a, b) => {
let left = self.compile_context_expr(a)?;
let right = self.compile_context_expr(b)?;
Ok(ContextPatternChar::Or(Box::new(left), Box::new(right)))
}
ContextExpr::Not(inner) => {
let pattern = self.compile_context_expr(inner)?;
Ok(ContextPatternChar::Not(Box::new(pattern)))
}
}
}
fn compile_regex(&mut self, regex: &Regex) -> ParseResult<NFAChar> {
if self.use_trampolining {
self.compile_regex_trampolined(regex)
} else {
self.compile_regex_recursive(regex)
}
}
#[allow(deprecated)]
fn compile_regex_recursive(&mut self, regex: &Regex) -> ParseResult<NFAChar> {
match regex {
Regex::Empty => Ok(self.builder.epsilon()),
Regex::Char(c) => Ok(self.builder.single_char(*c)),
Regex::CharClass(class) => Ok(self.builder.char_class(class.clone())),
Regex::Any => Ok(self.builder.any_char()),
Regex::Concat(a, b) => {
let nfa_a = self.compile_regex_recursive(a)?;
let nfa_b = self.compile_regex_recursive(b)?;
Ok(self.builder.concatenate(nfa_a, nfa_b))
}
Regex::Alt(a, b) => {
let nfa_a = self.compile_regex_recursive(a)?;
let nfa_b = self.compile_regex_recursive(b)?;
Ok(self.builder.alternation(nfa_a, nfa_b))
}
Regex::Star(inner) => {
let nfa = self.compile_regex_recursive(inner)?;
Ok(self.builder.kleene_star(nfa))
}
Regex::Plus(inner) => {
let nfa = self.compile_regex_recursive(inner)?;
Ok(self.builder.kleene_plus(nfa))
}
Regex::Optional(inner) => {
let nfa = self.compile_regex_recursive(inner)?;
Ok(self.builder.optional(nfa))
}
Regex::RepeatExact(inner, n) => {
let nfa = self.compile_regex_recursive(inner)?;
Ok(self.builder.repeat_exact(nfa, *n))
}
Regex::RepeatRange(inner, min, max) => {
let nfa = self.compile_regex_recursive(inner)?;
Ok(self.builder.repeat_range(nfa, *min, *max))
}
Regex::CapturingGroup(_, inner) => {
self.compile_regex_recursive(inner)
}
Regex::NonCapturingGroup(inner) => self.compile_regex_recursive(inner),
Regex::NamedGroup(_, inner) => {
self.compile_regex_recursive(inner)
}
Regex::GroupRef(name) => {
Err(ParseError::new(
ParseErrorKind::InvalidGroupReference(format!(
"group reference (?&{}) cannot be compiled - subroutine expansion not yet implemented",
name
)),
Position::start(),
))
}
Regex::FlagsGroup { inner, .. } => {
match inner {
Some(inner_regex) => self.compile_regex_recursive(inner_regex),
None => Ok(self.builder.epsilon()),
}
}
Regex::WordBoundary => {
Ok(self.builder.epsilon())
}
Regex::StartOfLine => Ok(self.builder.start_of_line()),
Regex::EndOfLine => Ok(self.builder.end_of_line()),
Regex::StartOfInput => Ok(self.builder.start_of_input()),
Regex::EndOfInput => Ok(self.builder.end_of_input()),
Regex::EndOfInputStrict => Ok(self.builder.end_of_input_strict()),
Regex::RewriteRule { pattern, .. } => {
self.compile_regex_recursive(pattern)
}
}
}
#[allow(deprecated)]
fn compile_regex_trampolined(&mut self, regex: &Regex) -> ParseResult<NFAChar> {
let mut work_stack: Vec<CompileWork<'_>> = vec![CompileWork::Compile(regex)];
let mut value_stack: Vec<NFAChar> = Vec::new();
while let Some(work) = work_stack.pop() {
match work {
CompileWork::Compile(node) => {
self.process_compile_node(node, &mut work_stack, &mut value_stack)?;
}
CompileWork::DoConcatenate => {
let b = value_stack.pop().expect("second operand for concat");
let a = value_stack.pop().expect("first operand for concat");
value_stack.push(self.builder.concatenate(a, b));
}
CompileWork::DoAlternation => {
let b = value_stack.pop().expect("second operand for alt");
let a = value_stack.pop().expect("first operand for alt");
value_stack.push(self.builder.alternation(a, b));
}
CompileWork::DoStar => {
let inner = value_stack.pop().expect("operand for star");
value_stack.push(self.builder.kleene_star(inner));
}
CompileWork::DoPlus => {
let inner = value_stack.pop().expect("operand for plus");
value_stack.push(self.builder.kleene_plus(inner));
}
CompileWork::DoOptional => {
let inner = value_stack.pop().expect("operand for optional");
value_stack.push(self.builder.optional(inner));
}
CompileWork::DoRepeatExact(n) => {
let inner = value_stack.pop().expect("operand for repeat");
value_stack.push(self.builder.repeat_exact(inner, n));
}
CompileWork::DoRepeatRange(min, max) => {
let inner = value_stack.pop().expect("operand for repeat range");
value_stack.push(self.builder.repeat_range(inner, min, max));
}
}
}
value_stack.pop().ok_or_else(|| {
ParseError::new(
ParseErrorKind::InternalError("empty value stack after compilation".into()),
Position::start(),
)
})
}
#[allow(deprecated)]
fn process_compile_node<'a>(
&mut self,
node: &'a Regex,
work_stack: &mut Vec<CompileWork<'a>>,
value_stack: &mut Vec<NFAChar>,
) -> ParseResult<()> {
match node {
Regex::Empty => value_stack.push(self.builder.epsilon()),
Regex::Char(c) => value_stack.push(self.builder.single_char(*c)),
Regex::CharClass(class) => value_stack.push(self.builder.char_class(class.clone())),
Regex::Any => value_stack.push(self.builder.any_char()),
Regex::WordBoundary => value_stack.push(self.builder.epsilon()),
Regex::StartOfLine => value_stack.push(self.builder.start_of_line()),
Regex::EndOfLine => value_stack.push(self.builder.end_of_line()),
Regex::StartOfInput => value_stack.push(self.builder.start_of_input()),
Regex::EndOfInput => value_stack.push(self.builder.end_of_input()),
Regex::EndOfInputStrict => value_stack.push(self.builder.end_of_input_strict()),
Regex::Concat(a, b) => {
work_stack.push(CompileWork::DoConcatenate);
work_stack.push(CompileWork::Compile(b));
work_stack.push(CompileWork::Compile(a));
}
Regex::Alt(a, b) => {
work_stack.push(CompileWork::DoAlternation);
work_stack.push(CompileWork::Compile(b));
work_stack.push(CompileWork::Compile(a));
}
Regex::Star(inner) => {
work_stack.push(CompileWork::DoStar);
work_stack.push(CompileWork::Compile(inner));
}
Regex::Plus(inner) => {
work_stack.push(CompileWork::DoPlus);
work_stack.push(CompileWork::Compile(inner));
}
Regex::Optional(inner) => {
work_stack.push(CompileWork::DoOptional);
work_stack.push(CompileWork::Compile(inner));
}
Regex::RepeatExact(inner, n) => {
work_stack.push(CompileWork::DoRepeatExact(*n));
work_stack.push(CompileWork::Compile(inner));
}
Regex::RepeatRange(inner, min, max) => {
work_stack.push(CompileWork::DoRepeatRange(*min, *max));
work_stack.push(CompileWork::Compile(inner));
}
Regex::CapturingGroup(_, inner)
| Regex::NonCapturingGroup(inner)
| Regex::NamedGroup(_, inner) => {
work_stack.push(CompileWork::Compile(inner));
}
Regex::FlagsGroup {
inner: Some(inner_regex),
..
} => {
work_stack.push(CompileWork::Compile(inner_regex));
}
Regex::FlagsGroup { inner: None, .. } => {
value_stack.push(self.builder.epsilon());
}
Regex::RewriteRule { pattern, .. } => {
work_stack.push(CompileWork::Compile(pattern));
}
Regex::GroupRef(name) => {
return Err(ParseError::new(
ParseErrorKind::InvalidGroupReference(format!(
"group reference (?&{}) cannot be compiled - subroutine expansion not yet implemented",
name
)),
Position::start(),
));
}
}
Ok(())
}
#[allow(deprecated)]
fn regex_to_literal(&self, regex: &Regex) -> ParseResult<Vec<char>> {
match regex {
Regex::Empty => Ok(Vec::new()),
Regex::Char(c) => Ok(vec![*c]),
Regex::Concat(a, b) => {
let mut chars = self.regex_to_literal(a)?;
chars.extend(self.regex_to_literal(b)?);
Ok(chars)
}
Regex::CapturingGroup(_, inner)
| Regex::NonCapturingGroup(inner)
| Regex::NamedGroup(_, inner) => self.regex_to_literal(inner),
Regex::FlagsGroup {
inner: Some(inner_regex),
..
} => self.regex_to_literal(inner_regex),
Regex::FlagsGroup { inner: None, .. } => Ok(Vec::new()),
_ => Err(ParseError::new(
ParseErrorKind::InvalidRewriteRule(
"replacement must be a literal string".to_string(),
),
Position::start(),
)),
}
}
}
impl Default for NFACompilerChar {
fn default() -> Self {
Self::new()
}
}
pub struct NFACompilerByte {
builder: ThompsonBuilder,
}
impl NFACompilerByte {
pub fn new() -> Self {
Self {
builder: ThompsonBuilder::new(),
}
}
pub fn compile(&mut self, regex: &RegexByte) -> ParseResult<NFA> {
let mut nfa = self.compile_regex(regex)?;
nfa.finalize();
Ok(nfa)
}
pub fn compile_rewrite(&mut self, regex: &RegexByte) -> ParseResult<CompiledRewrite> {
match regex {
RegexByte::RewriteRule {
pattern,
replacement,
context,
weight,
} => {
let mut source = self.compile_regex(pattern)?;
source.finalize();
let replacement_bytes = self.regex_to_literal(replacement)?;
let (left_context, right_context) = if let Some(ctx) = context {
(
ctx.left
.as_ref()
.map(|l| self.compile_context_expr(l))
.transpose()?,
ctx.right
.as_ref()
.map(|r| self.compile_context_expr(r))
.transpose()?,
)
} else {
(None, None)
};
Ok(CompiledRewrite {
source,
replacement: replacement_bytes,
left_context,
right_context,
weight: *weight,
})
}
_ => Err(ParseError::new(
ParseErrorKind::InvalidRewriteRule("expected rewrite rule".to_string()),
Position::start(),
)),
}
}
fn compile_context_expr(&mut self, expr: &ContextExprByte) -> ParseResult<ContextPattern> {
match expr {
ContextExprByte::Pattern(regex) => {
let mut nfa = self.compile_regex(regex)?;
nfa.finalize();
Ok(ContextPattern::Nfa(nfa))
}
ContextExprByte::WordBoundary => Ok(ContextPattern::Boundary(BoundaryKind::WordStart)),
ContextExprByte::And(a, b) => {
let left = self.compile_context_expr(a)?;
let right = self.compile_context_expr(b)?;
Ok(ContextPattern::And(Box::new(left), Box::new(right)))
}
ContextExprByte::Or(a, b) => {
let left = self.compile_context_expr(a)?;
let right = self.compile_context_expr(b)?;
Ok(ContextPattern::Or(Box::new(left), Box::new(right)))
}
ContextExprByte::Not(inner) => {
let pattern = self.compile_context_expr(inner)?;
Ok(ContextPattern::Not(Box::new(pattern)))
}
}
}
#[allow(deprecated)]
fn compile_regex(&mut self, regex: &RegexByte) -> ParseResult<NFA> {
match regex {
RegexByte::Empty => Ok(self.builder.epsilon()),
RegexByte::Byte(b) => Ok(self.builder.single_byte(*b)),
RegexByte::ByteClass(class) => Ok(self.builder.byte_class(class.clone())),
RegexByte::Any => Ok(self.builder.any_byte()),
RegexByte::Concat(a, b) => {
let nfa_a = self.compile_regex(a)?;
let nfa_b = self.compile_regex(b)?;
Ok(self.builder.concatenate(nfa_a, nfa_b))
}
RegexByte::Alt(a, b) => {
let nfa_a = self.compile_regex(a)?;
let nfa_b = self.compile_regex(b)?;
Ok(self.builder.alternation(nfa_a, nfa_b))
}
RegexByte::Star(inner) => {
let nfa = self.compile_regex(inner)?;
Ok(self.builder.kleene_star(nfa))
}
RegexByte::Plus(inner) => {
let nfa = self.compile_regex(inner)?;
Ok(self.builder.kleene_plus(nfa))
}
RegexByte::Optional(inner) => {
let nfa = self.compile_regex(inner)?;
Ok(self.builder.optional(nfa))
}
RegexByte::RepeatExact(inner, n) => {
let nfa = self.compile_regex(inner)?;
Ok(self.builder.repeat_exact(nfa, *n))
}
RegexByte::RepeatRange(inner, min, max) => {
let nfa = self.compile_regex(inner)?;
Ok(self.builder.repeat_range(nfa, *min, *max))
}
RegexByte::CapturingGroup(_, inner) => {
self.compile_regex(inner)
}
RegexByte::NonCapturingGroup(inner) => {
self.compile_regex(inner)
}
RegexByte::NamedGroup(_, inner) => {
self.compile_regex(inner)
}
RegexByte::GroupRef(name) => {
Err(ParseError::new(
ParseErrorKind::InvalidGroupReference(format!(
"group reference (?&{}) cannot be compiled - subroutine expansion not yet implemented",
name
)),
Position::start(),
))
}
RegexByte::FlagsGroup { inner, .. } => {
match inner {
Some(inner_regex) => self.compile_regex(inner_regex),
None => Ok(self.builder.epsilon()),
}
}
RegexByte::WordBoundary => {
Ok(self.builder.epsilon())
}
RegexByte::StartOfLine => Ok(self.builder.start_of_line()),
RegexByte::EndOfLine => Ok(self.builder.end_of_line()),
RegexByte::StartOfInput => Ok(self.builder.start_of_input()),
RegexByte::EndOfInput => Ok(self.builder.end_of_input()),
RegexByte::EndOfInputStrict => Ok(self.builder.end_of_input_strict()),
RegexByte::RewriteRule { pattern, .. } => {
self.compile_regex(pattern)
}
}
}
#[allow(deprecated)]
fn regex_to_literal(&self, regex: &RegexByte) -> ParseResult<Vec<u8>> {
match regex {
RegexByte::Empty => Ok(Vec::new()),
RegexByte::Byte(b) => Ok(vec![*b]),
RegexByte::Concat(a, b) => {
let mut bytes = self.regex_to_literal(a)?;
bytes.extend(self.regex_to_literal(b)?);
Ok(bytes)
}
RegexByte::CapturingGroup(_, inner)
| RegexByte::NonCapturingGroup(inner)
| RegexByte::NamedGroup(_, inner) => self.regex_to_literal(inner),
RegexByte::FlagsGroup {
inner: Some(inner_regex),
..
} => self.regex_to_literal(inner_regex),
RegexByte::FlagsGroup { inner: None, .. } => Ok(Vec::new()),
_ => Err(ParseError::new(
ParseErrorKind::InvalidRewriteRule(
"replacement must be a literal string".to_string(),
),
Position::start(),
)),
}
}
}
impl Default for NFACompilerByte {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::phonetic::regex::{parse, parse_rule};
#[test]
fn test_compile_literal() {
let regex = parse("phone").expect("test: parse phone");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("phone"));
assert!(!nfa.accepts("fone"));
}
#[test]
fn test_compile_alternation() {
let regex = parse("ph|f").expect("test: parse ph|f");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("ph"));
assert!(nfa.accepts("f"));
assert!(!nfa.accepts("g"));
}
#[test]
fn test_compile_group() {
let regex = parse("(ph|f)one").expect("test: parse (ph|f)one");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("phone"));
assert!(nfa.accepts("fone"));
assert!(!nfa.accepts("bone"));
}
#[test]
fn test_compile_star() {
let regex = parse("a*").expect("test: parse a*");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts(""));
assert!(nfa.accepts("a"));
assert!(nfa.accepts("aaa"));
assert!(!nfa.accepts("b"));
}
#[test]
fn test_compile_plus() {
let regex = parse("a+").expect("test: parse a+");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(!nfa.accepts(""));
assert!(nfa.accepts("a"));
assert!(nfa.accepts("aaa"));
}
#[test]
fn test_compile_optional() {
let regex = parse("a?b").expect("test: parse a?b");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("b"));
assert!(nfa.accepts("ab"));
assert!(!nfa.accepts("aab"));
}
#[test]
fn test_compile_char_class() {
let regex = parse("[aeiou]").expect("test: parse [aeiou]");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("a"));
assert!(nfa.accepts("e"));
assert!(!nfa.accepts("b"));
}
#[test]
fn test_compile_any() {
let regex = parse("a.c").expect("test: parse a.c");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("abc"));
assert!(nfa.accepts("axc"));
assert!(!nfa.accepts("ac"));
}
#[test]
fn test_compile_repeat_exact() {
let regex = parse("a{3}").expect("test: parse a{3}");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(!nfa.accepts("aa"));
assert!(nfa.accepts("aaa"));
assert!(!nfa.accepts("aaaa"));
}
#[test]
fn test_compile_repeat_range() {
let regex = parse("a{2,4}").expect("test: parse a{2,4}");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(!nfa.accepts("a"));
assert!(nfa.accepts("aa"));
assert!(nfa.accepts("aaa"));
assert!(nfa.accepts("aaaa"));
assert!(!nfa.accepts("aaaaa"));
}
#[test]
fn test_compile_rewrite_rule_simple() {
let regex = parse_rule("ph -> f").expect("test: parse rule ph -> f");
let rewrite = compile_rewrite(®ex).expect("test: compile_rewrite");
assert!(rewrite.source.accepts("ph"));
assert_eq!(rewrite.replacement, vec!['f']);
assert!(rewrite.left_context.is_none());
assert!(rewrite.right_context.is_none());
}
#[test]
fn test_compile_rewrite_rule_with_context() {
let regex = parse_rule("c -> s / _[ei]").expect("test: parse rule c -> s / _[ei]");
let rewrite = compile_rewrite(®ex).expect("test: compile_rewrite");
assert!(rewrite.source.accepts("c"));
assert_eq!(rewrite.replacement, vec!['s']);
assert!(rewrite.left_context.is_none());
assert!(rewrite.right_context.is_some());
let right = rewrite
.right_context
.expect("test: right_context is_some checked above");
assert!(right.accepts("e"));
assert!(right.accepts("i"));
assert!(!right.accepts("a"));
}
#[test]
fn test_compile_rewrite_rule_empty_replacement() {
let regex = parse_rule("e -> / _#").expect("test: parse rule e -> / _#");
let rewrite = compile_rewrite(®ex).expect("test: compile_rewrite");
assert!(rewrite.source.accepts("e"));
assert!(rewrite.replacement.is_empty());
}
#[test]
fn test_compile_complex_pattern() {
let regex = parse("(ph|f)one[s]?").expect("test: parse (ph|f)one[s]?");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.accepts("phone"));
assert!(nfa.accepts("phones"));
assert!(nfa.accepts("fone"));
assert!(nfa.accepts("fones"));
assert!(!nfa.accepts("bone"));
}
#[test]
fn test_compile_bytes_literal() {
let regex = crate::phonetic::regex::parse_bytes(b"phone").expect("test: parse_bytes phone");
let nfa = compile_bytes(®ex).expect("test: compile_bytes nfa");
assert!(nfa.accepts(b"phone"));
assert!(!nfa.accepts(b"fone"));
}
#[test]
fn test_compile_bytes_alternation() {
let regex = crate::phonetic::regex::parse_bytes(b"ph|f").expect("test: parse_bytes ph|f");
let nfa = compile_bytes(®ex).expect("test: compile_bytes nfa");
assert!(nfa.accepts(b"ph"));
assert!(nfa.accepts(b"f"));
}
#[test]
fn test_compile_bytes_rewrite() {
let regex = crate::phonetic::regex::parse_rule_bytes(b"ph -> f")
.expect("test: parse_rule_bytes ph -> f");
let rewrite = compile_rewrite_bytes(®ex).expect("test: compile_rewrite_bytes");
assert!(rewrite.source.accepts(b"ph"));
assert_eq!(rewrite.replacement, vec![b'f']);
}
#[test]
fn test_compile_start_of_line() {
let regex = parse("^hello").expect("test: parse ^hello");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 3);
}
#[test]
fn test_compile_end_of_line() {
let regex = parse("hello$").expect("test: parse hello$");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 3);
}
#[test]
fn test_compile_anchored_pattern() {
let regex = parse("^hello$").expect("test: parse ^hello$");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 4);
}
#[test]
fn test_compile_start_of_input() {
let regex = parse(r"\Ahello").expect("test: parse \\Ahello");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 3);
}
#[test]
fn test_compile_end_of_input() {
let regex = parse(r"hello\Z").expect("test: parse hello\\Z");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 3);
}
#[test]
fn test_compile_strict_end_of_input() {
let regex = parse(r"hello\z").expect("test: parse hello\\z");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 3);
}
#[test]
fn test_compile_multiline_flags() {
let regex = parse("(?m)^line$").expect("test: parse (?m)^line$");
let nfa = compile(®ex).expect("test: compile nfa");
assert!(nfa.state_count() >= 3);
}
#[test]
fn test_trampolining_api() {
let mut compiler = NFACompilerChar::new();
assert!(!compiler.is_trampolining(), "default should be recursive");
compiler.set_trampolining(true);
assert!(
compiler.is_trampolining(),
"should be trampolining after set"
);
compiler.set_trampolining(false);
assert!(
!compiler.is_trampolining(),
"should be recursive after unset"
);
let compiler2 = NFACompilerChar::new().with_trampolining();
assert!(
compiler2.is_trampolining(),
"builder should enable trampolining"
);
}
#[test]
fn test_trampolined_matches_recursive_simple() {
let patterns = ["a", "ab", "a|b", "a*", "a+", "a?", "[aeiou]"];
for pattern in patterns {
let regex = parse(pattern).expect("parse");
let nfa_recursive = compile(®ex).expect("recursive compile");
let mut compiler = NFACompilerChar::new().with_trampolining();
let nfa_trampolined = compiler.compile(®ex).expect("trampolined compile");
assert_eq!(
nfa_recursive.state_count(),
nfa_trampolined.state_count(),
"state count mismatch for pattern '{}'",
pattern
);
let test_inputs = ["", "a", "b", "ab", "aaa", "aeiou"];
for input in test_inputs {
assert_eq!(
nfa_recursive.accepts(input),
nfa_trampolined.accepts(input),
"behavior mismatch for pattern '{}' on input '{}'",
pattern,
input
);
}
}
}
#[test]
fn test_trampolined_matches_recursive_complex() {
let patterns = [
"(ab)+",
"a{2,5}",
"(?:a|b)*c",
"(ph|f)one",
"[a-z]+",
"a{3}",
];
for pattern in patterns {
let regex = parse(pattern).expect("parse");
let nfa_recursive = compile(®ex).expect("recursive compile");
let mut compiler = NFACompilerChar::new().with_trampolining();
let nfa_trampolined = compiler.compile(®ex).expect("trampolined compile");
assert_eq!(
nfa_recursive.state_count(),
nfa_trampolined.state_count(),
"state count mismatch for pattern '{}'",
pattern
);
}
}
#[test]
fn test_deep_nesting_with_trampolining() {
let depth = 50; let mut pattern = "a".to_string();
for _ in 0..depth {
pattern = format!("({})", pattern);
}
let regex = parse(&pattern).expect("parse deeply nested pattern");
let mut compiler = NFACompilerChar::new().with_trampolining();
let nfa = compiler
.compile(®ex)
.expect("trampolined compile of deep pattern");
assert!(nfa.accepts("a"), "should accept 'a'");
assert!(!nfa.accepts("b"), "should reject 'b'");
assert!(!nfa.accepts("aa"), "should reject 'aa'");
let nfa_recursive = compile(®ex).expect("recursive compile");
assert_eq!(nfa.state_count(), nfa_recursive.state_count());
}
#[test]
fn test_deep_alternation_with_trampolining() {
let depth = 40;
let mut pattern = "a".to_string();
for i in 0..depth {
let c = char::from(b'a' + ((i + 1) % 26) as u8);
pattern = format!("({}|{})", pattern, c);
}
let regex = parse(&pattern).expect("parse deeply nested alternation");
let mut compiler = NFACompilerChar::new().with_trampolining();
let nfa = compiler
.compile(®ex)
.expect("trampolined compile of deep alternation");
assert!(nfa.accepts("a"));
assert!(nfa.accepts("b"));
assert!(nfa.accepts("c"));
let nfa_recursive = compile(®ex).expect("recursive compile");
assert_eq!(nfa.state_count(), nfa_recursive.state_count());
}
#[test]
fn test_deep_concat_with_trampolining() {
let depth = 100;
let mut pattern = String::new();
for _ in 0..depth {
pattern.push_str("(?:a)");
}
let regex = parse(&pattern).expect("parse deeply concatenated pattern");
let mut compiler = NFACompilerChar::new().with_trampolining();
let nfa = compiler
.compile(®ex)
.expect("trampolined compile of deep concat");
let expected: String = std::iter::repeat('a').take(depth).collect();
assert!(nfa.accepts(&expected), "should accept {} 'a' chars", depth);
let too_short: String = std::iter::repeat('a').take(depth - 1).collect();
assert!(
!nfa.accepts(&too_short),
"should reject {} 'a' chars",
depth - 1
);
let nfa_recursive = compile(®ex).expect("recursive compile");
assert_eq!(nfa.state_count(), nfa_recursive.state_count());
}
#[test]
fn test_recursive_mode_still_works() {
let patterns = ["hello", "world|earth", "a+b*c?", "[0-9]+"];
for pattern in patterns {
let regex = parse(pattern).expect("parse");
let nfa = compile(®ex).expect("recursive compile");
assert!(nfa.state_count() > 0);
}
}
}