impl TranslatorBuilder { /// Create a new translator builder with a default c onfiguration. pubfn new() -> TranslatorBuilder {
TranslatorBuilder {
utf8: true,
line_terminator: b'\n',
flags: Flags::default(),
}
}
/// Build a translator using the current configuration. pubfn build(&self) -> Translator {
Translator {
stack: RefCell::new(vec![]),
flags: Cell::new(self.flags),
utf8: self.utf8,
line_terminator: self.line_terminator,
}
}
/// When disabled, translation will permit the construction of a regular /// expression that may match invalid UTF-8. /// /// When enabled (the default), the translator is guaranteed to produce an /// expression that, for non-empty matches, will only ever produce spans /// that are entirely valid UTF-8 (otherwise, the translator will return an /// error). /// /// Perhaps surprisingly, when UTF-8 is enabled, an empty regex or even /// a negated ASCII word boundary (uttered as `(?-u:\B)` in the concrete /// syntax) will be allowed even though they can produce matches that split /// a UTF-8 encoded codepoint. This only applies to zero-width or "empty" /// matches, and it is expected that the regex engine itself must handle /// these cases if necessary (perhaps by suppressing any zero-width matches /// that split a codepoint). pubfn utf8(&mutself, yes: bool) -> &mut TranslatorBuilder { self.utf8 = yes; self
}
/// Sets the line terminator for use with `(?u-s:.)` and `(?-us:.)`. /// /// Namely, instead of `.` (by default) matching everything except for `\n`, /// this will cause `.` to match everything except for the byte given. /// /// If `.` is used in a context where Unicode mode is enabled and this byte /// isn't ASCII, then an error will be returned. When Unicode mode is /// disabled, then any byte is permitted, but will return an error if UTF-8 /// mode is enabled and it is a non-ASCII byte. /// /// In short, any ASCII value for a line terminator is always okay. But a /// non-ASCII byte might result in an error depending on whether Unicode /// mode or UTF-8 mode are enabled. /// /// Note that if `R` mode is enabled then it always takes precedence and /// the line terminator will be treated as `\r` and `\n` simultaneously. /// /// Note also that this *doesn't* impact the look-around assertions /// `(?m:^)` and `(?m:$)`. That's usually controlled by additional /// configuration in the regex engine itself. pubfn line_terminator(&mutself, byte: u8) -> &mut TranslatorBuilder { self.line_terminator = byte; self
}
/// Enable or disable the case insensitive flag (`i`) by default. pubfn case_insensitive(&mutself, yes: bool) -> &mut TranslatorBuilder { self.flags.case_insensitive = if yes { Some(true) } else { None }; self
}
/// Enable or disable the multi-line matching flag (`m`) by default. pubfn multi_line(&mutself, yes: bool) -> &mut TranslatorBuilder { self.flags.multi_line = if yes { Some(true) } else { None }; self
}
/// Enable or disable the "dot matches any character" flag (`s`) by /// default. pubfn dot_matches_new_line(
&mutself,
yes: bool,
) -> &mut TranslatorBuilder { self.flags.dot_matches_new_line = if yes { Some(true) } else { None }; self
}
/// Enable or disable the CRLF mode flag (`R`) by default. pubfn crlf(&mutself, yes: bool) -> &mut TranslatorBuilder { self.flags.crlf = if yes { Some(true) } else { None }; self
}
/// Enable or disable the "swap greed" flag (`U`) by default. pubfn swap_greed(&mutself, yes: bool) -> &mut TranslatorBuilder { self.flags.swap_greed = if yes { Some(true) } else { None }; self
}
/// Enable or disable the Unicode flag (`u`) by default. pubfn unicode(&mutself, yes: bool) -> &mutTranslatorBuilder { self.flags.unicode = if yes { None } else { Some(false) }; self
}
}
/// A translator maps abstract syntax to a high level intermediate /// representation. /// /// A translator may be benefit from reuse. That is, a translator can translate /// many abstract syntax trees. /// /// A `Translator` can be configured in more detail via a /// [`TranslatorBuilder`]. #[derive(Clone, Debug)] pubstruct Translator { /// Our call stack, but on the heap.
stack: RefCell<Vec<HirFrame>>, /// The current flag settings.
flags: Cell<Flags>, /// Whether we're allowed to produce HIR that can match arbitrary bytes.
utf8: bool, /// The line terminator to use for `.`.
line_terminator: u8,
}
impl Translator { /// Create a new translator using the default configuration. pubfn new() -> Translator {
TranslatorBuilder::new().build()
}
/// Translate the given abstract syntax tree (AST) into a high level /// intermediate representation (HIR). /// /// If there was a problem doing the translation, then an HIR-specific /// error is returned. /// /// The original pattern string used to produce the `Ast` *must* also be /// provided. The translator does not use the pattern string during any /// correct translation, but is used for error reporting. pubfn translate(&mutself, pattern: &str, ast: &Ast) -> Result<Hir> {
ast::visit(ast, TranslatorI::new(self, pattern))
}
}
/// An HirFrame is a single stack frame, represented explicitly, which is /// created for each item in the Ast that we traverse. /// /// Note that technically, this type doesn't represent our entire stack /// frame. In particular, the Ast visitor represents any state associated with /// traversing the Ast itself. #[derive(Clone, Debug)] enum HirFrame { /// An arbitrary HIR expression. These get pushed whenever we hit a base /// case in the Ast. They get popped after an inductive (i.e., recursive) /// step is complete.
Expr(Hir), /// A literal that is being constructed, character by character, from the /// AST. We need this because the AST gives each individual character its /// own node. So as we see characters, we peek at the top-most HirFrame. /// If it's a literal, then we add to it. Otherwise, we push a new literal. /// When it comes time to pop it, we convert it to an Hir via Hir::literal.
Literal(Vec<u8>), /// A Unicode character class. This frame is mutated as we descend into /// the Ast of a character class (which is itself its own mini recursive /// structure).
ClassUnicode(hir::ClassUnicode), /// A byte-oriented character class. This frame is mutated as we descend /// into the Ast of a character class (which is itself its own mini /// recursive structure). /// /// Byte character classes are created when Unicode mode (`u`) is disabled. /// If `utf8` is enabled (the default), then a byte character is only /// permitted to match ASCII text.
ClassBytes(hir::ClassBytes), /// This is pushed whenever a repetition is observed. After visiting every /// sub-expression in the repetition, the translator's stack is expected to /// have this sentinel at the top. /// /// This sentinel only exists to stop other things (like flattening /// literals) from reaching across repetition operators.
Repetition, /// This is pushed on to the stack upon first seeing any kind of capture, /// indicated by parentheses (including non-capturing groups). It is popped /// upon leaving a group.
Group { /// The old active flags when this group was opened. /// /// If this group sets flags, then the new active flags are set to the /// result of merging the old flags with the flags introduced by this /// group. If the group doesn't set any flags, then this is simply /// equivalent to whatever flags were set when the group was opened. /// /// When this group is popped, the active flags should be restored to /// the flags set here. /// /// The "active" flags correspond to whatever flags are set in the /// Translator.
old_flags: Flags,
}, /// This is pushed whenever a concatenation is observed. After visiting /// every sub-expression in the concatenation, the translator's stack is /// popped until it sees a Concat frame.
Concat, /// This is pushed whenever an alternation is observed. After visiting /// every sub-expression in the alternation, the translator's stack is /// popped until it sees an Alternation frame.
Alternation, /// This is pushed immediately before each sub-expression in an /// alternation. This separates the branches of an alternation on the /// stack and prevents literal flattening from reaching across alternation /// branches. /// /// It is popped after each expression in a branch until an 'Alternation' /// frame is observed when doing a post visit on an alternation.
AlternationBranch,
}
impl HirFrame { /// Assert that the current stack frame is an Hir expression and return it. fn unwrap_expr(self) -> Hir { matchself {
HirFrame::Expr(expr) => expr,
HirFrame::Literal(lit) => Hir::literal(lit),
_ => panic!("tried to unwrap expr from HirFrame, got: {:?}", self),
}
}
/// Assert that the current stack frame is a Unicode class expression and /// return it. fn unwrap_class_unicode(self) -> hir::ClassUnicode { matchself {
HirFrame::ClassUnicode(cls) => cls,
_ => panic!( "tried to unwrap Unicode class \
from HirFrame, got: {:?}", self
),
}
}
/// Assert that the current stack frame is a byte class expression and /// return it. fn unwrap_class_bytes(self) -> hir::ClassBytes { matchself {
HirFrame::ClassBytes(cls) => cls,
_ => panic!( "tried to unwrap byte class \
from HirFrame, got: {:?}", self
),
}
}
/// Assert that the current stack frame is a repetition sentinel. If it /// isn't, then panic. fn unwrap_repetition(self) { matchself {
HirFrame::Repetition => {}
_ => {
panic!( "tried to unwrap repetition from HirFrame, got: {:?}", self
)
}
}
}
/// Assert that the current stack frame is a group indicator and return /// its corresponding flags (the flags that were active at the time the /// group was entered). fn unwrap_group(self) -> Flags { matchself {
HirFrame::Group { old_flags } => old_flags,
_ => {
panic!("tried to unwrap group from HirFrame, got: {:?}", self)
}
}
}
/// Assert that the current stack frame is an alternation pipe sentinel. If /// it isn't, then panic. fn unwrap_alternation_pipe(self) { matchself {
HirFrame::AlternationBranch => {}
_ => {
panic!( "tried to unwrap alt pipe from HirFrame, got: {:?}", self
)
}
}
}
}
impl<'t, 'p> Visitor for TranslatorI<'t, 'p> { type Output = Hir; type Err = Error;
fn finish(self) -> Result<Hir> { // ... otherwise, we should have exactly one HIR on the stack.
assert_eq!(self.trans().stack.borrow().len(), 1);
Ok(self.pop().unwrap().unwrap_expr())
}
fn visit_class_set_item_pre(
&mutself,
ast: &ast::ClassSetItem,
) -> Result<()> { match *ast {
ast::ClassSetItem::Bracketed(_) => { ifself.flags().unicode() { let cls = hir::ClassUnicode::empty(); self.push(HirFrame::ClassUnicode(cls));
} else { let cls = hir::ClassBytes::empty(); self.push(HirFrame::ClassBytes(cls));
}
} // We needn't handle the Union case here since the visitor will // do it for us.
_ => {}
}
Ok(())
}
/// The internal implementation of a translator. /// /// This type is responsible for carrying around the original pattern string, /// which is not tied to the internal state of a translator. /// /// A TranslatorI exists for the time it takes to translate a single Ast. #[derive(Clone, Debug)] struct TranslatorI<'t, 'p> {
trans: &'t Translator,
pattern: &'p str,
}
/// Return a reference to the underlying translator. fn trans(&self) -> &Translator {
&self.trans
}
/// Push the given frame on to the call stack. fn push(&self, frame: HirFrame) { self.trans().stack.borrow_mut().push(frame);
}
/// Push the given literal char on to the call stack. /// /// If the top-most element of the stack is a literal, then the char /// is appended to the end of that literal. Otherwise, a new literal /// containing just the given char is pushed to the top of the stack. fn push_char(&self, ch: char) { letmut buf = [0; 4]; let bytes = ch.encode_utf8(&mut buf).as_bytes(); letmut stack = self.trans().stack.borrow_mut(); iflet Some(HirFrame::Literal(refmut literal)) = stack.last_mut() {
literal.extend_from_slice(bytes);
} else {
stack.push(HirFrame::Literal(bytes.to_vec()));
}
}
/// Push the given literal byte on to the call stack. /// /// If the top-most element of the stack is a literal, then the byte /// is appended to the end of that literal. Otherwise, a new literal /// containing just the given byte is pushed to the top of the stack. fn push_byte(&self, byte: u8) { letmut stack = self.trans().stack.borrow_mut(); iflet Some(HirFrame::Literal(refmut literal)) = stack.last_mut() {
literal.push(byte);
} else {
stack.push(HirFrame::Literal(vec![byte]));
}
}
/// Pop the top of the call stack. If the call stack is empty, return None. fn pop(&self) -> Option<HirFrame> { self.trans().stack.borrow_mut().pop()
}
/// Pop an HIR expression from the top of the stack for a concatenation. /// /// This returns None if the stack is empty or when a concat frame is seen. /// Otherwise, it panics if it could not find an HIR expression. fn pop_concat_expr(&self) -> Option<Hir> { let frame = self.pop()?; match frame {
HirFrame::Concat => None,
HirFrame::Expr(expr) => Some(expr),
HirFrame::Literal(lit) => Some(Hir::literal(lit)),
HirFrame::ClassUnicode(_) => {
unreachable!("expected expr or concat, got Unicode class")
}
HirFrame::ClassBytes(_) => {
unreachable!("expected expr or concat, got byte class")
}
HirFrame::Repetition => {
unreachable!("expected expr or concat, got repetition")
}
HirFrame::Group { .. } => {
unreachable!("expected expr or concat, got group")
}
HirFrame::Alternation => {
unreachable!("expected expr or concat, got alt marker")
}
HirFrame::AlternationBranch => {
unreachable!("expected expr or concat, got alt branch marker")
}
}
}
/// Pop an HIR expression from the top of the stack for an alternation. /// /// This returns None if the stack is empty or when an alternation frame is /// seen. Otherwise, it panics if it could not find an HIR expression. fn pop_alt_expr(&self) -> Option<Hir> { let frame = self.pop()?; match frame {
HirFrame::Alternation => None,
HirFrame::Expr(expr) => Some(expr),
HirFrame::Literal(lit) => Some(Hir::literal(lit)),
HirFrame::ClassUnicode(_) => {
unreachable!("expected expr or alt, got Unicode class")
}
HirFrame::ClassBytes(_) => {
unreachable!("expected expr or alt, got byte class")
}
HirFrame::Repetition => {
unreachable!("expected expr or alt, got repetition")
}
HirFrame::Group { .. } => {
unreachable!("expected expr or alt, got group")
}
HirFrame::Concat => {
unreachable!("expected expr or alt, got concat marker")
}
HirFrame::AlternationBranch => {
unreachable!("expected expr or alt, got alt branch marker")
}
}
}
/// Create a new error with the given span and error type. fn error(&self, span: Span, kind: ErrorKind) -> Error {
Error { kind, pattern: self.pattern.to_string(), span }
}
/// Return a copy of the active flags. fn flags(&self) -> Flags { self.trans().flags.get()
}
/// Set the flags of this translator from the flags set in the given AST. /// Then, return the old flags. fn set_flags(&self, ast_flags: &ast::Flags) -> Flags { let old_flags = self.flags(); letmut new_flags = Flags::from_ast(ast_flags);
new_flags.merge(&old_flags); self.trans().flags.set(new_flags);
old_flags
}
/// Convert an Ast literal to its scalar representation. /// /// When Unicode mode is enabled, then this always succeeds and returns a /// `char` (Unicode scalar value). /// /// When Unicode mode is disabled, then a `char` will still be returned /// whenever possible. A byte is returned only when invalid UTF-8 is /// allowed and when the byte is not ASCII. Otherwise, a non-ASCII byte /// will result in an error when invalid UTF-8 is not allowed. fn ast_literal_to_scalar(
&self,
lit: &ast::Literal,
) -> Result<Either<char, u8>> { ifself.flags().unicode() { return Ok(Either::Left(lit.c));
} let byte = match lit.byte() {
None => return Ok(Either::Left(lit.c)),
Some(byte) => byte,
}; if byte <= 0x7F { return Ok(Either::Left(char::try_from(byte).unwrap()));
} ifself.trans().utf8 { return Err(self.error(lit.span, ErrorKind::InvalidUtf8));
}
Ok(Either::Right(byte))
}
fn case_fold_char(&self, span: Span, c: char) -> Result<Option<Hir>> { if !self.flags().case_insensitive() { return Ok(None);
} ifself.flags().unicode() { // If case folding won't do anything, then don't bother trying. let map = unicode::SimpleCaseFolder::new()
.map(|f| f.overlaps(c, c))
.map_err(|_| { self.error(span, ErrorKind::UnicodeCaseUnavailable)
})?; if !map { return Ok(None);
} letmut cls =
hir::ClassUnicode::new(vec![hir::ClassUnicodeRange::new(
c, c,
)]);
cls.try_case_fold_simple().map_err(|_| { self.error(span, ErrorKind::UnicodeCaseUnavailable)
})?;
Ok(Some(Hir::class(hir::Class::Unicode(cls))))
} else { if c.len_utf8() > 1 { return Err(self.error(span, ErrorKind::UnicodeNotAllowed));
} // If case folding won't do anything, then don't bother trying. match c { 'A'..='Z' | 'a'..='z' => {}
_ => return Ok(None),
} letmut cls =
hir::ClassBytes::new(vec![hir::ClassBytesRange::new( // OK because 'c.len_utf8() == 1' which in turn implies // that 'c' is ASCII.
u8::try_from(c).unwrap(),
u8::try_from(c).unwrap(),
)]);
cls.case_fold_simple();
Ok(Some(Hir::class(hir::Class::Bytes(cls))))
}
}
fn hir_dot(&self, span: Span) -> Result<Hir> { let (utf8, lineterm, flags) =
(self.trans().utf8, self.trans().line_terminator, self.flags()); if utf8 && (!flags.unicode() || !lineterm.is_ascii()) { return Err(self.error(span, ErrorKind::InvalidUtf8));
} let dot = if flags.dot_matches_new_line() { if flags.unicode() {
hir::Dot::AnyChar
} else {
hir::Dot::AnyByte
}
} else { if flags.unicode() { if flags.crlf() {
hir::Dot::AnyCharExceptCRLF
} else { if !lineterm.is_ascii() { return Err( self.error(span, ErrorKind::InvalidLineTerminator)
);
}
hir::Dot::AnyCharExcept(char::from(lineterm))
}
} else { if flags.crlf() {
hir::Dot::AnyByteExceptCRLF
} else {
hir::Dot::AnyByteExcept(lineterm)
}
}
};
Ok(Hir::dot(dot))
}
fn hir_capture(&self, group: &ast::Group, expr: Hir) -> Hir { let (index, name) = match group.kind {
ast::GroupKind::CaptureIndex(index) => (index, None),
ast::GroupKind::CaptureName { ref name, .. } => {
(name.index, Some(name.name.clone().into_boxed_str()))
} // The HIR doesn't need to use non-capturing groups, since the way // in which the data type is defined handles this automatically.
ast::GroupKind::NonCapturing(_) => return expr,
};
Hir::capture(hir::Capture { index, name, sub: Box::new(expr) })
}
assert!(self.flags().unicode()); let result = match ast_class.kind {
Digit => unicode::perl_digit(),
Space => unicode::perl_space(),
Word => unicode::perl_word(),
}; letmut class = self.convert_unicode_class_error(&ast_class.span, result)?; // We needn't apply case folding here because the Perl Unicode classes // are already closed under Unicode simple case folding. if ast_class.negated {
class.negate();
}
Ok(class)
}
assert!(!self.flags().unicode()); letmut class = match ast_class.kind {
Digit => hir_ascii_class_bytes(&ast::ClassAsciiKind::Digit),
Space => hir_ascii_class_bytes(&ast::ClassAsciiKind::Space),
Word => hir_ascii_class_bytes(&ast::ClassAsciiKind::Word),
}; // We needn't apply case folding here because the Perl ASCII classes // are already closed (under ASCII case folding). if ast_class.negated {
class.negate();
} // Negating a Perl byte class is likely to cause it to match invalid // UTF-8. That's only OK if the translator is configured to allow such // things. ifself.trans().utf8 && !class.is_ascii() { return Err(self.error(ast_class.span, ErrorKind::InvalidUtf8));
}
Ok(class)
}
/// Converts the given Unicode specific error to an HIR translation error. /// /// The span given should approximate the position at which an error would /// occur. fn convert_unicode_class_error(
&self,
span: &Span,
result: core::result::Result<hir::ClassUnicode, unicode::Error>,
) -> Result<hir::ClassUnicode> {
result.map_err(|err| { let sp = span.clone(); match err {
unicode::Error::PropertyNotFound => { self.error(sp, ErrorKind::UnicodePropertyNotFound)
}
unicode::Error::PropertyValueNotFound => { self.error(sp, ErrorKind::UnicodePropertyValueNotFound)
}
unicode::Error::PerlClassNotFound => { self.error(sp, ErrorKind::UnicodePerlClassNotFound)
}
}
})
}
fn unicode_fold_and_negate(
&self,
span: &Span,
negated: bool,
class: &mut hir::ClassUnicode,
) -> Result<()> { // Note that we must apply case folding before negation! // Consider `(?i)[^x]`. If we applied negation first, then // the result would be the character class that matched any // Unicode scalar value. ifself.flags().case_insensitive() {
class.try_case_fold_simple().map_err(|_| { self.error(span.clone(), ErrorKind::UnicodeCaseUnavailable)
})?;
} if negated {
class.negate();
}
Ok(())
}
fn bytes_fold_and_negate(
&self,
span: &Span,
negated: bool,
class: &mut hir::ClassBytes,
) -> Result<()> { // Note that we must apply case folding before negation! // Consider `(?i)[^x]`. If we applied negation first, then // the result would be the character class that matched any // Unicode scalar value. ifself.flags().case_insensitive() {
class.case_fold_simple();
} if negated {
class.negate();
} ifself.trans().utf8 && !class.is_ascii() { return Err(self.error(span.clone(), ErrorKind::InvalidUtf8));
}
Ok(())
}
/// Return a scalar byte value suitable for use as a literal in a byte /// character class. fn class_literal_byte(&self, ast: &ast::Literal) -> Result<u8> { matchself.ast_literal_to_scalar(ast)? {
Either::Right(byte) => Ok(byte),
Either::Left(ch) => { let cp = u32::from(ch); if cp <= 0x7F {
Ok(u8::try_from(cp).unwrap())
} else { // We can't feasibly support Unicode in // byte oriented classes. Byte classes don't // do Unicode case folding.
Err(self.error(ast.span, ErrorKind::UnicodeNotAllowed))
}
}
}
}
}
/// A translator's representation of a regular expression's flags at any given /// moment in time. /// /// Each flag can be in one of three states: absent, present but disabled or /// present but enabled. #[derive(Clone, Copy, Debug, Default)] struct Flags {
case_insensitive: Option<bool>,
multi_line: Option<bool>,
dot_matches_new_line: Option<bool>,
swap_greed: Option<bool>,
unicode: Option<bool>,
crlf: Option<bool>, // Note that `ignore_whitespace` is omitted here because it is handled // entirely in the parser.
}
// We create these errors to compare with real hir::Errors in the tests. // We define equality between TestError and hir::Error to disregard the // pattern string in hir::Error, which is annoying to provide in tests. #[derive(Clone, Debug)] struct TestError {
span: Span,
kind: hir::ErrorKind,
}
#[test] fn cat_alt() { let a = || hir_look(hir::Look::Start); let b = || hir_look(hir::Look::End); let c = || hir_look(hir::Look::WordUnicode); let d = || hir_look(hir::Look::WordUnicodeNegate);
// Tests the HIR transformation of things like '[a-z]|[A-Z]' into // '[A-Za-z]'. In other words, an alternation of just classes is always // equivalent to a single class corresponding to the union of the branches // in that class. (Unless some branches match invalid UTF-8 and others // match non-ASCII Unicode.) #[test] fn cat_class_flattened() {
assert_eq!(t(r"[a-z]|[A-Z]"), hir_uclass(&[('A', 'Z'), ('a', 'z')])); // Combining all of the letter properties should give us the one giant // letter property. #[cfg(feature = "unicode-gencat")]
assert_eq!(
t(r"(?x)
\p{Lowercase_Letter}
|\p{Uppercase_Letter}
|\p{Titlecase_Letter}
|\p{Modifier_Letter}
|\p{Other_Letter} "),
hir_uclass_query(ClassQuery::Binary("letter"))
); // Byte classes that can truly match invalid UTF-8 cannot be combined // with Unicode classes.
assert_eq!(
t_bytes(r"[Δδ]|(?-u:[\x90-\xFF])|[Λλ]"),
hir_alt(vec![
hir_uclass(&[('Δ', 'Δ'), ('δ', 'δ')]),
hir_bclass(&[(b'\x90', b'\xFF')]),
hir_uclass(&[('Λ', 'Λ'), ('λ', 'λ')]),
])
); // Byte classes on their own can be combined, even if some are ASCII // and others are invalid UTF-8.
assert_eq!(
t_bytes(r"[a-z]|(?-u:[\x90-\xFF])|[A-Z]"),
hir_bclass(&[(b'A', b'Z'), (b'a', b'z'), (b'\x90', b'\xFF')]),
);
}
// In `[a^]`, `^` does not need to be escaped, so it makes sense that // `^` is also allowed to be unescaped after `&&`.
assert_eq!(t(r"[\^&&^]"), hir_uclass(&[('^', '^')])); // `]` needs to be escaped after `&&` since it's not at start of class.
assert_eq!(t(r"[]&&\]]"), hir_uclass(&[(']', ']')]));
assert_eq!(t(r"[-&&-]"), hir_uclass(&[('-', '-')]));
assert_eq!(t(r"[\&&&&]"), hir_uclass(&[('&', '&')]));
assert_eq!(t(r"[\&&&\&]"), hir_uclass(&[('&', '&')])); // Test precedence.
assert_eq!(
t(r"[a-w&&[^c-g]z]"),
hir_uclass(&[('a', 'b'), ('h', 'w')])
);
}
#[test] fn analysis_is_all_assertions() { // Positive examples. let p = props(r"\b");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"\B");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"^");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"$");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"\A");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"\z");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"$^\z\A\b\B");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"$|^|\z|\A|\b|\B");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"^$|$^");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
let p = props(r"((\b)+())*^");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(0));
// Negative examples. let p = props(r"^a");
assert!(!p.look_set().is_empty());
assert_eq!(p.minimum_len(), Some(1));
}
#[test] fn analysis_look_set_prefix_any() { let p = props(r"(?-u)(?i:(?:\b|_)win(?:32|64|dows)?(?:\b|_))");
assert!(p.look_set_prefix_any().contains(Look::WordAscii));
}
#[test] fn analysis_is_anchored() { let is_start = |p| props(p).look_set_prefix().contains(Look::Start); let is_end = |p| props(p).look_set_suffix().contains(Look::End);
// This tests that the smart Hir::repetition constructors does some basic // simplifications. #[test] fn smart_repetition() {
assert_eq!(t(r"a{0}"), Hir::empty());
assert_eq!(t(r"a{1}"), hir_lit("a"));
assert_eq!(t(r"\B{32111}"), hir_look(hir::Look::WordUnicodeNegate));
}
// This tests that the smart Hir::concat constructor simplifies the given // exprs in a way we expect. #[test] fn smart_concat() {
assert_eq!(t(""), Hir::empty());
assert_eq!(t("(?:)"), Hir::empty());
assert_eq!(t("abc"), hir_lit("abc"));
assert_eq!(t("(?:foo)(?:bar)"), hir_lit("foobar"));
assert_eq!(t("quux(?:foo)(?:bar)baz"), hir_lit("quuxfoobarbaz"));
assert_eq!(
t("foo(?:bar^baz)quux"),
hir_cat(vec![
hir_lit("foobar"),
hir_look(hir::Look::Start),
hir_lit("bazquux"),
])
);
assert_eq!(
t("foo(?:ba(?:r^b)az)quux"),
hir_cat(vec![
hir_lit("foobar"),
hir_look(hir::Look::Start),
hir_lit("bazquux"),
])
);
}
// This tests that the smart Hir::alternation constructor simplifies the // given exprs in a way we expect. #[test] fn smart_alternation() {
assert_eq!(
t("(?:foo)|(?:bar)"),
hir_alt(vec![hir_lit("foo"), hir_lit("bar")])
);
assert_eq!(
t("quux|(?:abc|def|xyz)|baz"),
hir_alt(vec![
hir_lit("quux"),
hir_lit("abc"),
hir_lit("def"),
hir_lit("xyz"),
hir_lit("baz"),
])
);
assert_eq!(
t("quux|(?:abc|(?:def|mno)|xyz)|baz"),
hir_alt(vec![
hir_lit("quux"),
hir_lit("abc"),
hir_lit("def"),
hir_lit("mno"),
hir_lit("xyz"),
hir_lit("baz"),
])
);
assert_eq!(
t("a|b|c|d|e|f|x|y|z"),
hir_uclass(&[('a', 'f'), ('x', 'z')]),
); // Tests that we lift common prefixes out of an alternation.
assert_eq!(
t("[A-Z]foo|[A-Z]quux"),
hir_cat(vec![
hir_uclass(&[('A', 'Z')]),
hir_alt(vec![hir_lit("foo"), hir_lit("quux")]),
]),
);
assert_eq!(
t("[A-Z][A-Z]|[A-Z]quux"),
hir_cat(vec![
hir_uclass(&[('A', 'Z')]),
hir_alt(vec![hir_uclass(&[('A', 'Z')]), hir_lit("quux")]),
]),
);
assert_eq!(
t("[A-Z][A-Z]|[A-Z][A-Z]quux"),
hir_cat(vec![
hir_uclass(&[('A', 'Z')]),
hir_uclass(&[('A', 'Z')]),
hir_alt(vec![Hir::empty(), hir_lit("quux")]),
]),
);
assert_eq!(
t("[A-Z]foo|[A-Z]foobar"),
hir_cat(vec![
hir_uclass(&[('A', 'Z')]),
hir_alt(vec![hir_lit("foo"), hir_lit("foobar")]),
]),
);
}
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.120Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-21)
¤