use std::fmt;
use super::super::nfa::types::CharClassChar;
pub use crate::phonetic::common::syllable::{SyllableCondition, SyllableExpr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnicodeNormalization {
NFC,
NFD,
NFKC,
NFKD,
}
impl fmt::Display for UnicodeNormalization {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnicodeNormalization::NFC => write!(f, "NFC"),
UnicodeNormalization::NFD => write!(f, "NFD"),
UnicodeNormalization::NFKC => write!(f, "NFKC"),
UnicodeNormalization::NFKD => write!(f, "NFKD"),
}
}
}
impl std::str::FromStr for UnicodeNormalization {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"NFC" => Ok(UnicodeNormalization::NFC),
"NFD" => Ok(UnicodeNormalization::NFD),
"NFKC" => Ok(UnicodeNormalization::NFKC),
"NFKD" => Ok(UnicodeNormalization::NFKD),
_ => Err(format!("unknown Unicode normalization form: {}", s)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RegexFlags {
pub case_insensitive: Option<bool>,
pub unicode_normalization: Option<UnicodeNormalization>,
pub feature_based: Option<bool>,
pub accent_insensitive: Option<bool>,
pub multiline: Option<bool>,
pub dotall: Option<bool>,
pub local_distance: Option<u8>,
}
impl RegexFlags {
pub fn new() -> Self {
Self::default()
}
pub fn case_insensitive() -> Self {
Self {
case_insensitive: Some(true),
..Default::default()
}
}
pub fn with_normalization(form: UnicodeNormalization) -> Self {
Self {
unicode_normalization: Some(form),
..Default::default()
}
}
pub fn feature_based() -> Self {
Self {
feature_based: Some(true),
..Default::default()
}
}
pub fn accent_insensitive() -> Self {
Self {
accent_insensitive: Some(true),
..Default::default()
}
}
pub fn multiline() -> Self {
Self {
multiline: Some(true),
..Default::default()
}
}
pub fn dotall() -> Self {
Self {
dotall: Some(true),
..Default::default()
}
}
pub fn merge(&self, other: &RegexFlags) -> RegexFlags {
RegexFlags {
case_insensitive: other.case_insensitive.or(self.case_insensitive),
unicode_normalization: other.unicode_normalization.or(self.unicode_normalization),
feature_based: other.feature_based.or(self.feature_based),
accent_insensitive: other.accent_insensitive.or(self.accent_insensitive),
multiline: other.multiline.or(self.multiline),
dotall: other.dotall.or(self.dotall),
local_distance: other.local_distance.or(self.local_distance),
}
}
pub fn is_empty(&self) -> bool {
self.case_insensitive.is_none()
&& self.unicode_normalization.is_none()
&& self.feature_based.is_none()
&& self.accent_insensitive.is_none()
&& self.multiline.is_none()
&& self.dotall.is_none()
&& self.local_distance.is_none()
}
pub fn with_local_distance(distance: u8) -> Self {
Self {
local_distance: Some(distance),
..Default::default()
}
}
}
impl fmt::Display for RegexFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if self.case_insensitive == Some(true) {
parts.push("i".to_string());
} else if self.case_insensitive == Some(false) {
parts.push("-i".to_string());
}
if let Some(norm) = self.unicode_normalization {
parts.push(format!("u:{}", norm));
}
if self.feature_based == Some(true) {
parts.push("f".to_string());
} else if self.feature_based == Some(false) {
parts.push("-f".to_string());
}
if self.accent_insensitive == Some(true) {
parts.push("a".to_string());
} else if self.accent_insensitive == Some(false) {
parts.push("-a".to_string());
}
if self.multiline == Some(true) {
parts.push("m".to_string());
} else if self.multiline == Some(false) {
parts.push("-m".to_string());
}
if self.dotall == Some(true) {
parts.push("s".to_string());
} else if self.dotall == Some(false) {
parts.push("-s".to_string());
}
let flags_str = parts.join("");
if let Some(dist) = self.local_distance {
if flags_str.is_empty() {
write!(f, ";{}", dist)?;
} else {
write!(f, "{};{}", flags_str, dist)?;
}
} else {
write!(f, "{}", flags_str)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Regex {
Empty,
Char(char),
CharClass(CharClassChar),
Any,
Concat(Box<Regex>, Box<Regex>),
Alt(Box<Regex>, Box<Regex>),
Star(Box<Regex>),
Plus(Box<Regex>),
Optional(Box<Regex>),
RepeatExact(Box<Regex>, usize),
RepeatRange(Box<Regex>, usize, Option<usize>),
CapturingGroup(usize, Box<Regex>),
NonCapturingGroup(Box<Regex>),
NamedGroup(String, Box<Regex>),
GroupRef(String),
FlagsGroup {
flags: RegexFlags,
inner: Option<Box<Regex>>,
},
WordBoundary,
StartOfLine,
EndOfLine,
StartOfInput,
EndOfInput,
EndOfInputStrict,
RewriteRule {
pattern: Box<Regex>,
replacement: Box<Regex>,
context: Option<Box<ContextPredicate>>,
weight: f64,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ContextExpr {
Pattern(Regex),
WordBoundary,
And(Box<ContextExpr>, Box<ContextExpr>),
Or(Box<ContextExpr>, Box<ContextExpr>),
Not(Box<ContextExpr>),
}
impl ContextExpr {
pub fn pattern(regex: Regex) -> Self {
ContextExpr::Pattern(regex)
}
pub fn word_boundary() -> Self {
ContextExpr::WordBoundary
}
pub fn and(left: ContextExpr, right: ContextExpr) -> Self {
ContextExpr::And(Box::new(left), Box::new(right))
}
pub fn or(left: ContextExpr, right: ContextExpr) -> Self {
ContextExpr::Or(Box::new(left), Box::new(right))
}
pub fn not(inner: ContextExpr) -> Self {
ContextExpr::Not(Box::new(inner))
}
pub fn size(&self) -> usize {
match self {
ContextExpr::Pattern(regex) => regex.size(),
ContextExpr::WordBoundary => 1,
ContextExpr::And(a, b) | ContextExpr::Or(a, b) => 1 + a.size() + b.size(),
ContextExpr::Not(inner) => 1 + inner.size(),
}
}
}
impl fmt::Display for ContextExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContextExpr::Pattern(regex) => write!(f, "{}", regex),
ContextExpr::WordBoundary => write!(f, "#"),
ContextExpr::And(a, b) => write!(f, "({} & {})", a, b),
ContextExpr::Or(a, b) => write!(f, "({} | {})", a, b),
ContextExpr::Not(inner) => write!(f, "!{}", inner),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContextPredicate {
pub left: Option<ContextExpr>,
pub right: Option<ContextExpr>,
pub syllable: Option<SyllableExpr>,
}
impl ContextPredicate {
pub fn new(left: Option<Regex>, right: Option<Regex>) -> Self {
Self {
left: left.map(ContextExpr::Pattern),
right: right.map(ContextExpr::Pattern),
syllable: None,
}
}
pub fn new_with_exprs(
left: Option<ContextExpr>,
right: Option<ContextExpr>,
syllable: Option<SyllableExpr>,
) -> Self {
Self {
left,
right,
syllable,
}
}
pub fn lookahead(right: Regex) -> Self {
Self {
left: None,
right: Some(ContextExpr::Pattern(right)),
syllable: None,
}
}
pub fn lookbehind(left: Regex) -> Self {
Self {
left: Some(ContextExpr::Pattern(left)),
right: None,
syllable: None,
}
}
pub fn word_start() -> Self {
Self {
left: Some(ContextExpr::WordBoundary),
right: None,
syllable: None,
}
}
pub fn word_end() -> Self {
Self {
left: None,
right: Some(ContextExpr::WordBoundary),
syllable: None,
}
}
pub fn with_syllable(mut self, syllable: SyllableExpr) -> Self {
self.syllable = Some(syllable);
self
}
}
impl Regex {
pub fn empty() -> Self {
Regex::Empty
}
pub fn char(c: char) -> Self {
Regex::Char(c)
}
pub fn literal(s: &str) -> Self {
if s.is_empty() {
return Regex::Empty;
}
let mut chars = s.chars();
let first = chars.next().expect("non-empty string");
let mut result = Regex::Char(first);
for c in chars {
result = Regex::Concat(Box::new(result), Box::new(Regex::Char(c)));
}
result
}
pub fn char_class(class: CharClassChar) -> Self {
Regex::CharClass(class)
}
pub fn any() -> Self {
Regex::Any
}
pub fn concat(a: Regex, b: Regex) -> Self {
Regex::Concat(Box::new(a), Box::new(b))
}
pub fn alt(a: Regex, b: Regex) -> Self {
Regex::Alt(Box::new(a), Box::new(b))
}
pub fn star(inner: Regex) -> Self {
Regex::Star(Box::new(inner))
}
pub fn plus(inner: Regex) -> Self {
Regex::Plus(Box::new(inner))
}
pub fn optional(inner: Regex) -> Self {
Regex::Optional(Box::new(inner))
}
pub fn repeat_exact(inner: Regex, n: usize) -> Self {
Regex::RepeatExact(Box::new(inner), n)
}
pub fn repeat_range(inner: Regex, min: usize, max: Option<usize>) -> Self {
Regex::RepeatRange(Box::new(inner), min, max)
}
pub fn capturing_group(group_num: usize, inner: Regex) -> Self {
Regex::CapturingGroup(group_num, Box::new(inner))
}
pub fn non_capturing_group(inner: Regex) -> Self {
Regex::NonCapturingGroup(Box::new(inner))
}
pub fn named_group(name: impl Into<String>, inner: Regex) -> Self {
Regex::NamedGroup(name.into(), Box::new(inner))
}
pub fn group_ref(name: impl Into<String>) -> Self {
Regex::GroupRef(name.into())
}
pub fn flags_group(flags: RegexFlags, inner: Regex) -> Self {
Regex::FlagsGroup {
flags,
inner: Some(Box::new(inner)),
}
}
pub fn inline_flags(flags: RegexFlags) -> Self {
Regex::FlagsGroup { flags, inner: None }
}
pub fn word_boundary() -> Self {
Regex::WordBoundary
}
pub fn rewrite_rule(
pattern: Regex,
replacement: Regex,
context: Option<ContextPredicate>,
weight: f64,
) -> Self {
Regex::RewriteRule {
pattern: Box::new(pattern),
replacement: Box::new(replacement),
context: context.map(Box::new),
weight,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Regex::Empty)
}
pub fn is_rewrite_rule(&self) -> bool {
matches!(self, Regex::RewriteRule { .. })
}
#[allow(deprecated)]
pub fn size(&self) -> usize {
match self {
Regex::Empty
| Regex::Char(_)
| Regex::Any
| Regex::WordBoundary
| Regex::StartOfLine
| Regex::EndOfLine
| Regex::StartOfInput
| Regex::EndOfInput
| Regex::EndOfInputStrict => 1,
Regex::CharClass(_) => 1,
Regex::GroupRef(_) => 1,
Regex::Concat(a, b) | Regex::Alt(a, b) => 1 + a.size() + b.size(),
Regex::Star(inner)
| Regex::Plus(inner)
| Regex::Optional(inner)
| Regex::NonCapturingGroup(inner)
| Regex::CapturingGroup(_, inner)
| Regex::NamedGroup(_, inner) => 1 + inner.size(),
Regex::RepeatExact(inner, _) | Regex::RepeatRange(inner, _, _) => 1 + inner.size(),
Regex::FlagsGroup { inner, .. } => 1 + inner.as_ref().map_or(0, |i| i.size()),
Regex::RewriteRule {
pattern,
replacement,
context,
..
} => {
let ctx_size = context.as_ref().map_or(0, |c| {
c.left.as_ref().map_or(0, |l| l.size())
+ c.right.as_ref().map_or(0, |r| r.size())
+ c.syllable.as_ref().map_or(0, |s| s.size())
});
1 + pattern.size() + replacement.size() + ctx_size
}
}
}
}
impl fmt::Display for Regex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Regex::Empty => write!(f, ""),
Regex::Char(c) => {
if "\\[](){}|*+?.^$".contains(*c) {
write!(f, "\\{}", c)
} else {
write!(f, "{}", c)
}
}
Regex::CharClass(class) => write!(f, "{}", class),
Regex::Any => write!(f, "."),
Regex::Concat(a, b) => write!(f, "{}{}", a, b),
Regex::Alt(a, b) => write!(f, "({}|{})", a, b),
Regex::Star(inner) => {
if matches!(**inner, Regex::Char(_) | Regex::CharClass(_) | Regex::Any) {
write!(f, "{}*", inner)
} else {
write!(f, "({})*", inner)
}
}
Regex::Plus(inner) => {
if matches!(**inner, Regex::Char(_) | Regex::CharClass(_) | Regex::Any) {
write!(f, "{}+", inner)
} else {
write!(f, "({})+", inner)
}
}
Regex::Optional(inner) => {
if matches!(**inner, Regex::Char(_) | Regex::CharClass(_) | Regex::Any) {
write!(f, "{}?", inner)
} else {
write!(f, "({})?", inner)
}
}
Regex::RepeatExact(inner, n) => {
if matches!(**inner, Regex::Char(_) | Regex::CharClass(_) | Regex::Any) {
write!(f, "{}{{{}}}", inner, n)
} else {
write!(f, "({}){{{}}}", inner, n)
}
}
Regex::RepeatRange(inner, min, max) => {
let quantifier = match max {
Some(max) => format!("{{{},{}}}", min, max),
None => format!("{{{},}}", min),
};
if matches!(**inner, Regex::Char(_) | Regex::CharClass(_) | Regex::Any) {
write!(f, "{}{}", inner, quantifier)
} else {
write!(f, "({}){}", inner, quantifier)
}
}
Regex::CapturingGroup(_, inner) => write!(f, "({})", inner),
Regex::NonCapturingGroup(inner) => write!(f, "(?:{})", inner),
Regex::NamedGroup(name, inner) => write!(f, "(?<{}>{})", name, inner),
Regex::GroupRef(name) => write!(f, "(?&{})", name),
Regex::FlagsGroup { flags, inner } => match inner {
Some(inner) => write!(f, "(?{}:{})", flags, inner),
None => write!(f, "(?{})", flags),
},
Regex::WordBoundary => write!(f, "#"),
Regex::StartOfLine => write!(f, "^"),
Regex::EndOfLine => write!(f, "$"),
Regex::StartOfInput => write!(f, "\\A"),
Regex::EndOfInput => write!(f, "\\Z"),
Regex::EndOfInputStrict => write!(f, "\\z"),
Regex::RewriteRule {
pattern,
replacement,
context,
weight,
} => {
write!(f, "{} -> {}", pattern, replacement)?;
if let Some(ctx) = context {
write!(f, " / ")?;
if let Some(left) = &ctx.left {
write!(f, "{}", left)?;
}
write!(f, "_")?;
if let Some(right) = &ctx.right {
write!(f, "{}", right)?;
}
if let Some(syllable) = &ctx.syllable {
write!(f, " if {}", syllable)?;
}
}
if *weight != 0.0 {
write!(f, " [{:.2}]", weight)?;
}
Ok(())
}
}
}
}
use super::super::nfa::types::CharClass;
#[derive(Debug, Clone, PartialEq)]
pub enum RegexByte {
Empty,
Byte(u8),
ByteClass(CharClass),
Any,
Concat(Box<RegexByte>, Box<RegexByte>),
Alt(Box<RegexByte>, Box<RegexByte>),
Star(Box<RegexByte>),
Plus(Box<RegexByte>),
Optional(Box<RegexByte>),
RepeatExact(Box<RegexByte>, usize),
RepeatRange(Box<RegexByte>, usize, Option<usize>),
CapturingGroup(usize, Box<RegexByte>),
NonCapturingGroup(Box<RegexByte>),
NamedGroup(String, Box<RegexByte>),
GroupRef(String),
FlagsGroup {
flags: RegexFlags,
inner: Option<Box<RegexByte>>,
},
WordBoundary,
StartOfLine,
EndOfLine,
StartOfInput,
EndOfInput,
EndOfInputStrict,
RewriteRule {
pattern: Box<RegexByte>,
replacement: Box<RegexByte>,
context: Option<Box<ContextPredicateByte>>,
weight: f64,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ContextExprByte {
Pattern(RegexByte),
WordBoundary,
And(Box<ContextExprByte>, Box<ContextExprByte>),
Or(Box<ContextExprByte>, Box<ContextExprByte>),
Not(Box<ContextExprByte>),
}
impl ContextExprByte {
pub fn pattern(regex: RegexByte) -> Self {
ContextExprByte::Pattern(regex)
}
pub fn word_boundary() -> Self {
ContextExprByte::WordBoundary
}
pub fn and(left: ContextExprByte, right: ContextExprByte) -> Self {
ContextExprByte::And(Box::new(left), Box::new(right))
}
pub fn or(left: ContextExprByte, right: ContextExprByte) -> Self {
ContextExprByte::Or(Box::new(left), Box::new(right))
}
pub fn not(inner: ContextExprByte) -> Self {
ContextExprByte::Not(Box::new(inner))
}
pub fn size(&self) -> usize {
match self {
ContextExprByte::Pattern(regex) => regex.size(),
ContextExprByte::WordBoundary => 1,
ContextExprByte::And(a, b) | ContextExprByte::Or(a, b) => 1 + a.size() + b.size(),
ContextExprByte::Not(inner) => 1 + inner.size(),
}
}
}
impl fmt::Display for ContextExprByte {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContextExprByte::Pattern(regex) => write!(f, "{}", regex),
ContextExprByte::WordBoundary => write!(f, "#"),
ContextExprByte::And(a, b) => write!(f, "({} & {})", a, b),
ContextExprByte::Or(a, b) => write!(f, "({} | {})", a, b),
ContextExprByte::Not(inner) => write!(f, "!{}", inner),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContextPredicateByte {
pub left: Option<ContextExprByte>,
pub right: Option<ContextExprByte>,
pub syllable: Option<SyllableExpr>,
}
impl ContextPredicateByte {
pub fn new(left: Option<RegexByte>, right: Option<RegexByte>) -> Self {
Self {
left: left.map(ContextExprByte::Pattern),
right: right.map(ContextExprByte::Pattern),
syllable: None,
}
}
pub fn new_with_exprs(
left: Option<ContextExprByte>,
right: Option<ContextExprByte>,
syllable: Option<SyllableExpr>,
) -> Self {
Self {
left,
right,
syllable,
}
}
pub fn lookahead(right: RegexByte) -> Self {
Self {
left: None,
right: Some(ContextExprByte::Pattern(right)),
syllable: None,
}
}
pub fn lookbehind(left: RegexByte) -> Self {
Self {
left: Some(ContextExprByte::Pattern(left)),
right: None,
syllable: None,
}
}
pub fn word_start() -> Self {
Self {
left: Some(ContextExprByte::WordBoundary),
right: None,
syllable: None,
}
}
pub fn word_end() -> Self {
Self {
left: None,
right: Some(ContextExprByte::WordBoundary),
syllable: None,
}
}
pub fn with_syllable(mut self, syllable: SyllableExpr) -> Self {
self.syllable = Some(syllable);
self
}
}
impl RegexByte {
pub fn empty() -> Self {
RegexByte::Empty
}
pub fn byte(b: u8) -> Self {
RegexByte::Byte(b)
}
pub fn literal(s: &[u8]) -> Self {
if s.is_empty() {
return RegexByte::Empty;
}
let mut result = RegexByte::Byte(s[0]);
for &b in &s[1..] {
result = RegexByte::Concat(Box::new(result), Box::new(RegexByte::Byte(b)));
}
result
}
pub fn byte_class(class: CharClass) -> Self {
RegexByte::ByteClass(class)
}
pub fn any() -> Self {
RegexByte::Any
}
pub fn concat(a: RegexByte, b: RegexByte) -> Self {
RegexByte::Concat(Box::new(a), Box::new(b))
}
pub fn alt(a: RegexByte, b: RegexByte) -> Self {
RegexByte::Alt(Box::new(a), Box::new(b))
}
pub fn star(inner: RegexByte) -> Self {
RegexByte::Star(Box::new(inner))
}
pub fn plus(inner: RegexByte) -> Self {
RegexByte::Plus(Box::new(inner))
}
pub fn optional(inner: RegexByte) -> Self {
RegexByte::Optional(Box::new(inner))
}
pub fn repeat_exact(inner: RegexByte, n: usize) -> Self {
RegexByte::RepeatExact(Box::new(inner), n)
}
pub fn repeat_range(inner: RegexByte, min: usize, max: Option<usize>) -> Self {
RegexByte::RepeatRange(Box::new(inner), min, max)
}
pub fn capturing_group(group_num: usize, inner: RegexByte) -> Self {
RegexByte::CapturingGroup(group_num, Box::new(inner))
}
pub fn non_capturing_group(inner: RegexByte) -> Self {
RegexByte::NonCapturingGroup(Box::new(inner))
}
pub fn named_group(name: impl Into<String>, inner: RegexByte) -> Self {
RegexByte::NamedGroup(name.into(), Box::new(inner))
}
pub fn group_ref(name: impl Into<String>) -> Self {
RegexByte::GroupRef(name.into())
}
pub fn flags_group(flags: RegexFlags, inner: RegexByte) -> Self {
RegexByte::FlagsGroup {
flags,
inner: Some(Box::new(inner)),
}
}
pub fn inline_flags(flags: RegexFlags) -> Self {
RegexByte::FlagsGroup { flags, inner: None }
}
pub fn word_boundary() -> Self {
RegexByte::WordBoundary
}
pub fn rewrite_rule(
pattern: RegexByte,
replacement: RegexByte,
context: Option<ContextPredicateByte>,
weight: f64,
) -> Self {
RegexByte::RewriteRule {
pattern: Box::new(pattern),
replacement: Box::new(replacement),
context: context.map(Box::new),
weight,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, RegexByte::Empty)
}
pub fn is_rewrite_rule(&self) -> bool {
matches!(self, RegexByte::RewriteRule { .. })
}
#[allow(deprecated)]
pub fn size(&self) -> usize {
match self {
RegexByte::Empty
| RegexByte::Byte(_)
| RegexByte::Any
| RegexByte::WordBoundary
| RegexByte::StartOfLine
| RegexByte::EndOfLine
| RegexByte::StartOfInput
| RegexByte::EndOfInput
| RegexByte::EndOfInputStrict => 1,
RegexByte::ByteClass(_) => 1,
RegexByte::GroupRef(_) => 1,
RegexByte::Concat(a, b) | RegexByte::Alt(a, b) => 1 + a.size() + b.size(),
RegexByte::Star(inner)
| RegexByte::Plus(inner)
| RegexByte::Optional(inner)
| RegexByte::NonCapturingGroup(inner)
| RegexByte::CapturingGroup(_, inner)
| RegexByte::NamedGroup(_, inner) => 1 + inner.size(),
RegexByte::RepeatExact(inner, _) | RegexByte::RepeatRange(inner, _, _) => {
1 + inner.size()
}
RegexByte::FlagsGroup { inner, .. } => 1 + inner.as_ref().map_or(0, |i| i.size()),
RegexByte::RewriteRule {
pattern,
replacement,
context,
..
} => {
let ctx_size = context.as_ref().map_or(0, |c| {
c.left.as_ref().map_or(0, |l| l.size())
+ c.right.as_ref().map_or(0, |r| r.size())
+ c.syllable.as_ref().map_or(0, |s| s.size())
});
1 + pattern.size() + replacement.size() + ctx_size
}
}
}
}
impl fmt::Display for RegexByte {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RegexByte::Empty => write!(f, ""),
RegexByte::Byte(b) => {
let c = *b as char;
if "\\[](){}|*+?.^$".contains(c) {
write!(f, "\\{}", c)
} else if b.is_ascii_graphic() || *b == b' ' {
write!(f, "{}", c)
} else {
write!(f, "\\x{:02x}", b)
}
}
RegexByte::ByteClass(class) => write!(f, "{}", class),
RegexByte::Any => write!(f, "."),
RegexByte::Concat(a, b) => write!(f, "{}{}", a, b),
RegexByte::Alt(a, b) => write!(f, "({}|{})", a, b),
RegexByte::Star(inner) => {
if matches!(
**inner,
RegexByte::Byte(_) | RegexByte::ByteClass(_) | RegexByte::Any
) {
write!(f, "{}*", inner)
} else {
write!(f, "({})*", inner)
}
}
RegexByte::Plus(inner) => {
if matches!(
**inner,
RegexByte::Byte(_) | RegexByte::ByteClass(_) | RegexByte::Any
) {
write!(f, "{}+", inner)
} else {
write!(f, "({})+", inner)
}
}
RegexByte::Optional(inner) => {
if matches!(
**inner,
RegexByte::Byte(_) | RegexByte::ByteClass(_) | RegexByte::Any
) {
write!(f, "{}?", inner)
} else {
write!(f, "({})?", inner)
}
}
RegexByte::RepeatExact(inner, n) => {
if matches!(
**inner,
RegexByte::Byte(_) | RegexByte::ByteClass(_) | RegexByte::Any
) {
write!(f, "{}{{{}}}", inner, n)
} else {
write!(f, "({}){{{}}}", inner, n)
}
}
RegexByte::RepeatRange(inner, min, max) => {
let quantifier = match max {
Some(max) => format!("{{{},{}}}", min, max),
None => format!("{{{},}}", min),
};
if matches!(
**inner,
RegexByte::Byte(_) | RegexByte::ByteClass(_) | RegexByte::Any
) {
write!(f, "{}{}", inner, quantifier)
} else {
write!(f, "({}){}", inner, quantifier)
}
}
RegexByte::CapturingGroup(_, inner) => write!(f, "({})", inner),
RegexByte::NonCapturingGroup(inner) => write!(f, "(?:{})", inner),
RegexByte::NamedGroup(name, inner) => write!(f, "(?<{}>{})", name, inner),
RegexByte::GroupRef(name) => write!(f, "(?&{})", name),
RegexByte::FlagsGroup { flags, inner } => match inner {
Some(inner) => write!(f, "(?{}:{})", flags, inner),
None => write!(f, "(?{})", flags),
},
RegexByte::WordBoundary => write!(f, "#"),
RegexByte::StartOfLine => write!(f, "^"),
RegexByte::EndOfLine => write!(f, "$"),
RegexByte::StartOfInput => write!(f, "\\A"),
RegexByte::EndOfInput => write!(f, "\\Z"),
RegexByte::EndOfInputStrict => write!(f, "\\z"),
RegexByte::RewriteRule {
pattern,
replacement,
context,
weight,
} => {
write!(f, "{} -> {}", pattern, replacement)?;
if let Some(ctx) = context {
write!(f, " / ")?;
if let Some(left) = &ctx.left {
write!(f, "{}", left)?;
}
write!(f, "_")?;
if let Some(right) = &ctx.right {
write!(f, "{}", right)?;
}
if let Some(syllable) = &ctx.syllable {
write!(f, " if {}", syllable)?;
}
}
if *weight != 0.0 {
write!(f, " [{:.2}]", weight)?;
}
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_regex_literal() {
let r = Regex::literal("phone");
assert!(!r.is_empty());
assert_eq!(r.to_string(), "phone");
}
#[test]
fn test_regex_empty() {
let r = Regex::empty();
assert!(r.is_empty());
assert_eq!(r.to_string(), "");
}
#[test]
fn test_regex_char() {
let r = Regex::char('a');
assert_eq!(r.to_string(), "a");
}
#[test]
fn test_regex_any() {
let r = Regex::any();
assert_eq!(r.to_string(), ".");
}
#[test]
fn test_regex_char_class() {
let class = CharClassChar::from_chars(&['a', 'e', 'i', 'o', 'u']);
let r = Regex::char_class(class);
assert_eq!(r.to_string(), "[aeiou]");
}
#[test]
fn test_regex_concat() {
let a = Regex::char('a');
let b = Regex::char('b');
let r = Regex::concat(a, b);
assert_eq!(r.to_string(), "ab");
}
#[test]
fn test_regex_alt() {
let ph = Regex::literal("ph");
let f = Regex::char('f');
let r = Regex::alt(ph, f);
assert_eq!(r.to_string(), "(ph|f)");
}
#[test]
fn test_regex_star() {
let a = Regex::char('a');
let r = Regex::star(a);
assert_eq!(r.to_string(), "a*");
}
#[test]
fn test_regex_plus() {
let a = Regex::char('a');
let r = Regex::plus(a);
assert_eq!(r.to_string(), "a+");
}
#[test]
fn test_regex_optional() {
let a = Regex::char('a');
let r = Regex::optional(a);
assert_eq!(r.to_string(), "a?");
}
#[test]
fn test_regex_repeat_exact() {
let a = Regex::char('a');
let r = Regex::repeat_exact(a, 3);
assert_eq!(r.to_string(), "a{3}");
}
#[test]
fn test_regex_repeat_range() {
let a = Regex::char('a');
let r = Regex::repeat_range(a.clone(), 2, Some(4));
assert_eq!(r.to_string(), "a{2,4}");
let r2 = Regex::repeat_range(a, 2, None);
assert_eq!(r2.to_string(), "a{2,}");
}
#[test]
fn test_regex_word_boundary() {
let r = Regex::word_boundary();
assert_eq!(r.to_string(), "#");
}
#[test]
fn test_regex_rewrite_rule_simple() {
let r = Regex::rewrite_rule(Regex::literal("ph"), Regex::char('f'), None, 0.0);
assert!(r.is_rewrite_rule());
assert_eq!(r.to_string(), "ph -> f");
}
#[test]
fn test_regex_rewrite_rule_with_context() {
let vowels = CharClassChar::from_chars(&['e', 'i']);
let context = ContextPredicate::lookahead(Regex::char_class(vowels));
let r = Regex::rewrite_rule(Regex::char('c'), Regex::char('s'), Some(context), 0.0);
assert_eq!(r.to_string(), "c -> s / _[ei]");
}
#[test]
fn test_regex_rewrite_rule_with_weight() {
let r = Regex::rewrite_rule(Regex::literal("th"), Regex::char('t'), None, 0.15);
assert_eq!(r.to_string(), "th -> t [0.15]");
}
#[test]
fn test_regex_rewrite_rule_word_end() {
let context = ContextPredicate::word_end();
let r = Regex::rewrite_rule(Regex::char('e'), Regex::empty(), Some(context), 0.0);
assert_eq!(r.to_string(), "e -> / _#");
}
#[test]
fn test_regex_escape_special_chars() {
let r = Regex::char('.');
assert_eq!(r.to_string(), "\\.");
let r2 = Regex::char('*');
assert_eq!(r2.to_string(), "\\*");
let r3 = Regex::char('[');
assert_eq!(r3.to_string(), "\\[");
}
#[test]
fn test_regex_size() {
let r = Regex::literal("phone");
assert_eq!(r.size(), 9);
let r2 = Regex::star(Regex::char('a'));
assert_eq!(r2.size(), 2);
let r3 = Regex::alt(Regex::char('a'), Regex::char('b'));
assert_eq!(r3.size(), 3); }
#[test]
fn test_regex_byte_literal() {
let r = RegexByte::literal(b"phone");
assert!(!r.is_empty());
assert_eq!(r.to_string(), "phone");
}
#[test]
fn test_regex_byte_empty() {
let r = RegexByte::empty();
assert!(r.is_empty());
assert_eq!(r.to_string(), "");
}
#[test]
fn test_regex_byte_rewrite_rule() {
let r =
RegexByte::rewrite_rule(RegexByte::literal(b"ph"), RegexByte::byte(b'f'), None, 0.0);
assert!(r.is_rewrite_rule());
assert_eq!(r.to_string(), "ph -> f");
}
#[test]
fn test_context_predicate_new() {
let left = Regex::char('a');
let right = Regex::char('b');
let ctx = ContextPredicate::new(Some(left), Some(right));
assert!(ctx.left.is_some());
assert!(ctx.right.is_some());
}
#[test]
fn test_context_predicate_lookahead() {
let right = Regex::char('b');
let ctx = ContextPredicate::lookahead(right);
assert!(ctx.left.is_none());
assert!(ctx.right.is_some());
}
#[test]
fn test_context_predicate_lookbehind() {
let left = Regex::char('a');
let ctx = ContextPredicate::lookbehind(left);
assert!(ctx.left.is_some());
assert!(ctx.right.is_none());
}
}