mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
601 lines
17 KiB
Rust
601 lines
17 KiB
Rust
//! Intermediate representation for a regex
|
|
|
|
use crate::api;
|
|
use crate::types::{BracketContents, CaptureGroupID, CaptureGroupName};
|
|
#[cfg(not(feature = "std"))]
|
|
use alloc::{boxed::Box, string::ToString, vec::Vec};
|
|
use core::fmt;
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum AnchorType {
|
|
StartOfLine, // ^
|
|
EndOfLine, // $
|
|
}
|
|
|
|
/// A Quantifier.
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct Quantifier {
|
|
/// Minimum number of iterations of the loop, inclusive.
|
|
pub min: usize,
|
|
|
|
/// Maximum number of iterations of the loop, inclusive;
|
|
/// or None if unbounded.
|
|
pub max: Option<usize>,
|
|
|
|
/// Whether the loop is greedy.
|
|
pub greedy: bool,
|
|
}
|
|
|
|
/// The node types of our IR.
|
|
#[derive(Debug)]
|
|
pub enum Node {
|
|
/// Matches the empty string.
|
|
Empty,
|
|
|
|
/// Reaching this node terminates the match successfully.
|
|
Goal,
|
|
|
|
/// Match a literal character.
|
|
/// If icase is true, then `c` MUST be already folded.
|
|
Char { c: u32, icase: bool },
|
|
|
|
/// Match a literal sequence of bytes.
|
|
ByteSequence(Vec<u8>),
|
|
|
|
/// Match any of a set of *bytes*.
|
|
/// This may not exceed length MAX_BYTE_SET_LENGTH.
|
|
ByteSet(Vec<u8>),
|
|
|
|
/// Match any of a set of *chars*, case-insensitive.
|
|
/// This may not exceed length MAX_CHAR_SET_LENGTH.
|
|
CharSet(Vec<u32>),
|
|
|
|
/// Match the catenation of multiple nodes.
|
|
Cat(Vec<Node>),
|
|
|
|
/// Match an alternation like a|b.
|
|
Alt(Box<Node>, Box<Node>),
|
|
|
|
/// Match anything including newlines.
|
|
MatchAny,
|
|
|
|
/// Match anything except a newline.
|
|
MatchAnyExceptLineTerminator,
|
|
|
|
/// Match an anchor like ^ or $. The `multiline` flag controls whether the
|
|
/// anchor should treat line terminators as boundaries.
|
|
Anchor {
|
|
anchor_type: AnchorType,
|
|
multiline: bool,
|
|
},
|
|
|
|
/// Word boundary (\b or \B).
|
|
WordBoundary { invert: bool },
|
|
|
|
/// A capturing group.
|
|
/// If the name is set, it is guaranteed nonempty.
|
|
/// Note that multiple capture groups may share the same name in different alternations.
|
|
CaptureGroup {
|
|
id: CaptureGroupID,
|
|
contents: Box<Node>,
|
|
name: Option<CaptureGroupName>,
|
|
},
|
|
|
|
/// A backreference.
|
|
/// Indices are 1-based. That is, it matches JS syntax: \1 is the first capture group,
|
|
/// not everything.
|
|
BackRef(u32),
|
|
|
|
/// A backreference to a name shared by multiple capture groups.
|
|
/// Indices are 1-based, one per occurrence in source order. At match time it
|
|
/// matches the text of the last occurrence that is defined; if none is
|
|
/// defined, it matches the empty string.
|
|
BackRefLastDefined(Vec<u32>),
|
|
|
|
/// A bracket.
|
|
Bracket(BracketContents),
|
|
|
|
/// A lookaround assertions like (?:) or (?!).
|
|
LookaroundAssertion {
|
|
negate: bool,
|
|
backwards: bool,
|
|
start_group: CaptureGroupID,
|
|
end_group: CaptureGroupID,
|
|
contents: Box<Node>,
|
|
},
|
|
|
|
/// A loop like /.*/ or /x{3, 5}?/
|
|
Loop {
|
|
loopee: Box<Node>,
|
|
quant: Quantifier,
|
|
enclosed_groups: core::ops::Range<u16>,
|
|
},
|
|
|
|
/// A loop whose body matches exactly one character.
|
|
/// Enclosed capture groups are forbidden here.
|
|
Loop1CharBody {
|
|
loopee: Box<Node>,
|
|
quant: Quantifier,
|
|
},
|
|
}
|
|
|
|
pub type NodeList = Vec<Node>;
|
|
|
|
impl Node {
|
|
/// Helper to return an "always fails" node.
|
|
pub fn make_always_fails() -> Node {
|
|
Node::CharSet(Vec::new())
|
|
}
|
|
|
|
/// Reverse the children of \p self if in a lookbehind.
|
|
/// Used as a parameter to walk_mut.
|
|
pub fn reverse_cats(&mut self, w: &mut Walk) {
|
|
match self {
|
|
Node::Cat(nodes) if w.in_lookbehind => nodes.reverse(),
|
|
Node::ByteSequence(..) => panic!("Should not be reversing literal bytes"),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// \return whether this is an Empty node.
|
|
pub fn is_empty(&self) -> bool {
|
|
matches!(self, Node::Empty)
|
|
}
|
|
|
|
/// \return whether this is a Cat node.
|
|
pub fn is_cat(&self) -> bool {
|
|
matches!(self, Node::Cat(..))
|
|
}
|
|
|
|
/// \return whether this node is known to match exactly one char.
|
|
/// This is best-effort: a false return is always safe.
|
|
pub fn matches_exactly_one_char(&self) -> bool {
|
|
match self {
|
|
Node::Char { .. } => true,
|
|
Node::CharSet(contents) => !contents.is_empty(),
|
|
Node::Bracket(contents) => !contents.is_empty(),
|
|
Node::MatchAny => true,
|
|
Node::MatchAnyExceptLineTerminator => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// \return true if this node will always fail to match.
|
|
/// Note this is different than matching the empty string.
|
|
/// For example, an empty bracket /[]/ tries to match one char
|
|
/// from an empty set.
|
|
pub fn match_always_fails(&self) -> bool {
|
|
match self {
|
|
Node::ByteSet(bytes) => bytes.is_empty(),
|
|
Node::CharSet(contents) => contents.is_empty(),
|
|
Node::Bracket(contents) => contents.is_empty(),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Duplicate a node, perhaps assigning new loop IDs. Note we must never
|
|
/// copy a capture group.
|
|
///
|
|
/// Returns None if the depth is too high.
|
|
pub fn try_duplicate(&self, mut depth: usize) -> Option<Node> {
|
|
if depth > 100 {
|
|
return None;
|
|
}
|
|
depth += 1;
|
|
Some(match self {
|
|
Node::Empty => Node::Empty,
|
|
Node::Goal => Node::Goal,
|
|
&Node::Char { c, icase } => Node::Char { c, icase },
|
|
Node::ByteSequence(bytes) => Node::ByteSequence(bytes.clone()),
|
|
Node::ByteSet(bytes) => Node::ByteSet(bytes.clone()),
|
|
Node::CharSet(chars) => Node::CharSet(chars.clone()),
|
|
Node::Cat(nodes) => {
|
|
let mut new_nodes = Vec::with_capacity(nodes.len());
|
|
for n in nodes {
|
|
new_nodes.push(n.try_duplicate(depth)?);
|
|
}
|
|
Node::Cat(new_nodes)
|
|
}
|
|
Node::Alt(left, right) => Node::Alt(
|
|
Box::new(left.try_duplicate(depth)?),
|
|
Box::new(right.try_duplicate(depth)?),
|
|
),
|
|
Node::MatchAny => Node::MatchAny,
|
|
Node::MatchAnyExceptLineTerminator => Node::MatchAnyExceptLineTerminator,
|
|
&Node::Anchor {
|
|
anchor_type,
|
|
multiline,
|
|
} => Node::Anchor {
|
|
anchor_type,
|
|
multiline,
|
|
},
|
|
|
|
Node::Loop {
|
|
loopee,
|
|
quant,
|
|
enclosed_groups,
|
|
} => {
|
|
assert!(
|
|
enclosed_groups.start >= enclosed_groups.end,
|
|
"Cannot duplicate a loop with enclosed groups"
|
|
);
|
|
Node::Loop {
|
|
loopee: Box::new(loopee.as_ref().try_duplicate(depth)?),
|
|
quant: *quant,
|
|
enclosed_groups: enclosed_groups.clone(),
|
|
}
|
|
}
|
|
|
|
Node::Loop1CharBody { loopee, quant } => Node::Loop1CharBody {
|
|
loopee: Box::new(loopee.as_ref().try_duplicate(depth)?),
|
|
quant: *quant,
|
|
},
|
|
|
|
Node::CaptureGroup { .. } => {
|
|
panic!("Refusing to duplicate a capture group");
|
|
}
|
|
&Node::WordBoundary { invert } => Node::WordBoundary { invert },
|
|
&Node::BackRef(idx) => Node::BackRef(idx),
|
|
Node::BackRefLastDefined(groups) => Node::BackRefLastDefined(groups.clone()),
|
|
Node::Bracket(bc) => Node::Bracket(bc.clone()),
|
|
// Do not reverse into lookarounds, they already have the right sense.
|
|
Node::LookaroundAssertion {
|
|
negate,
|
|
backwards,
|
|
start_group,
|
|
end_group,
|
|
contents,
|
|
} => {
|
|
assert!(
|
|
start_group >= end_group,
|
|
"Cannot duplicate an assertion with enclosed groups"
|
|
);
|
|
Node::LookaroundAssertion {
|
|
negate: *negate,
|
|
backwards: *backwards,
|
|
start_group: *start_group,
|
|
end_group: *end_group,
|
|
contents: Box::new((*contents).try_duplicate(depth)?),
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A helper type for walking.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Walk {
|
|
// It set to true, skip the children of this node.
|
|
pub skip_children: bool,
|
|
|
|
// The current depth of the walk.
|
|
pub depth: usize,
|
|
|
|
// If true, we are in a lookbehind (and so the cursor will move backwards).
|
|
pub in_lookbehind: bool,
|
|
|
|
// If the regex is in unicode mode.
|
|
pub unicode: bool,
|
|
}
|
|
|
|
impl Walk {
|
|
fn new(unicode: bool) -> Self {
|
|
Self {
|
|
skip_children: false,
|
|
depth: 0,
|
|
in_lookbehind: false,
|
|
unicode,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Walker<'a, F>
|
|
where
|
|
F: FnMut(&Node, &mut Walk),
|
|
{
|
|
func: &'a mut F,
|
|
postorder: bool,
|
|
walk: Walk,
|
|
}
|
|
|
|
impl<F> Walker<'_, F>
|
|
where
|
|
F: FnMut(&Node, &mut Walk),
|
|
{
|
|
fn process_children(&mut self, n: &Node) {
|
|
match n {
|
|
Node::Empty
|
|
| Node::Goal
|
|
| Node::Char { .. }
|
|
| Node::ByteSequence(..)
|
|
| Node::ByteSet(..)
|
|
| Node::CharSet(..)
|
|
| Node::WordBoundary { .. }
|
|
| Node::BackRef { .. }
|
|
| Node::BackRefLastDefined { .. }
|
|
| Node::Bracket { .. }
|
|
| Node::MatchAny
|
|
| Node::MatchAnyExceptLineTerminator
|
|
| Node::Anchor { .. } => {}
|
|
Node::Cat(nodes) => {
|
|
for node in nodes {
|
|
self.process(node);
|
|
}
|
|
}
|
|
Node::Alt(left, right) => {
|
|
self.process(left.as_ref());
|
|
self.process(right.as_ref());
|
|
}
|
|
|
|
Node::Loop { loopee, .. } | Node::Loop1CharBody { loopee, .. } => self.process(loopee),
|
|
Node::CaptureGroup { contents, .. } => self.process(contents.as_ref()),
|
|
|
|
Node::LookaroundAssertion {
|
|
backwards,
|
|
contents,
|
|
..
|
|
} => {
|
|
let saved = self.walk.in_lookbehind;
|
|
self.walk.in_lookbehind = *backwards;
|
|
self.process(contents.as_ref());
|
|
self.walk.in_lookbehind = saved;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process(&mut self, n: &Node) {
|
|
self.walk.skip_children = false;
|
|
if !self.postorder {
|
|
(self.func)(n, &mut self.walk);
|
|
}
|
|
if !self.walk.skip_children {
|
|
self.walk.depth += 1;
|
|
self.process_children(n);
|
|
self.walk.depth -= 1;
|
|
}
|
|
if self.postorder {
|
|
(self.func)(n, &mut self.walk)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct MutWalker<'a, F>
|
|
where
|
|
F: FnMut(&mut Node, &mut Walk),
|
|
{
|
|
func: &'a mut F,
|
|
postorder: bool,
|
|
walk: Walk,
|
|
}
|
|
|
|
impl<F> MutWalker<'_, F>
|
|
where
|
|
F: FnMut(&mut Node, &mut Walk),
|
|
{
|
|
fn process_children(&mut self, n: &mut Node) {
|
|
match n {
|
|
Node::Empty
|
|
| Node::Goal
|
|
| Node::Char { .. }
|
|
| Node::ByteSequence(..)
|
|
| Node::ByteSet(..)
|
|
| Node::CharSet(..)
|
|
| Node::MatchAny
|
|
| Node::MatchAnyExceptLineTerminator
|
|
| Node::Anchor { .. }
|
|
| Node::WordBoundary { .. }
|
|
| Node::BackRef { .. }
|
|
| Node::BackRefLastDefined { .. }
|
|
| Node::Bracket { .. } => {}
|
|
Node::Cat(nodes) => {
|
|
nodes.iter_mut().for_each(|node| self.process(node));
|
|
}
|
|
Node::Alt(left, right) => {
|
|
self.process(left.as_mut());
|
|
self.process(right.as_mut());
|
|
}
|
|
|
|
Node::Loop { loopee, .. } | Node::Loop1CharBody { loopee, .. } => {
|
|
self.process(loopee);
|
|
}
|
|
Node::CaptureGroup { contents, .. } => self.process(contents.as_mut()),
|
|
|
|
Node::LookaroundAssertion {
|
|
backwards,
|
|
contents,
|
|
..
|
|
} => {
|
|
let saved = self.walk.in_lookbehind;
|
|
self.walk.in_lookbehind = *backwards;
|
|
self.process(contents.as_mut());
|
|
self.walk.in_lookbehind = saved;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process(&mut self, n: &mut Node) {
|
|
self.walk.skip_children = false;
|
|
if !self.postorder {
|
|
(self.func)(n, &mut self.walk);
|
|
}
|
|
if !self.walk.skip_children {
|
|
self.walk.depth += 1;
|
|
self.process_children(n);
|
|
self.walk.depth -= 1;
|
|
}
|
|
if self.postorder {
|
|
(self.func)(n, &mut self.walk);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Call a function on every Node.
|
|
/// If \p postorder is true, then process children before the node;
|
|
/// otherwise process children after the node.
|
|
pub fn walk<F>(postorder: bool, unicode: bool, n: &Node, func: &mut F)
|
|
where
|
|
F: FnMut(&Node, &mut Walk),
|
|
{
|
|
let mut walker = Walker {
|
|
func,
|
|
postorder,
|
|
walk: Walk::new(unicode),
|
|
};
|
|
walker.process(n);
|
|
}
|
|
|
|
/// Call a function on every Node, which may mutate the node.
|
|
/// If \p postorder is true, then process children before the node;
|
|
/// otherwise process children after the node.
|
|
/// If postorder is false, the function should return true to process children,
|
|
/// false to avoid descending into children. If postorder is true, the return
|
|
/// value is ignored.
|
|
pub fn walk_mut<F>(postorder: bool, unicode: bool, n: &mut Node, func: &mut F)
|
|
where
|
|
F: FnMut(&mut Node, &mut Walk),
|
|
{
|
|
let mut walker = MutWalker {
|
|
func,
|
|
postorder,
|
|
walk: Walk::new(unicode),
|
|
};
|
|
walker.process(n);
|
|
}
|
|
|
|
/// A regex in IR form.
|
|
pub struct Regex {
|
|
pub node: Node,
|
|
pub flags: api::Flags,
|
|
}
|
|
|
|
impl Regex {}
|
|
|
|
fn display_node(node: &Node, depth: usize, f: &mut fmt::Formatter) -> fmt::Result {
|
|
for _ in 0..depth {
|
|
write!(f, "..")?;
|
|
}
|
|
match node {
|
|
Node::Empty => {
|
|
writeln!(f, "Empty")?;
|
|
}
|
|
Node::Goal => {
|
|
writeln!(f, "Goal")?;
|
|
}
|
|
Node::Char { c, icase: _ } => {
|
|
writeln!(f, "'{}'", &c.to_string())?;
|
|
}
|
|
Node::ByteSequence(bytes) => {
|
|
write!(f, "ByteSeq{} 0x", bytes.len())?;
|
|
for &b in bytes {
|
|
write!(f, "{:x}", b)?;
|
|
}
|
|
writeln!(f)?;
|
|
}
|
|
Node::ByteSet(bytes) => {
|
|
let len = bytes.len();
|
|
write!(f, "ByteSet{}", len)?;
|
|
for &b in bytes {
|
|
write!(f, " 0x{:x}", b)?;
|
|
}
|
|
writeln!(f)?;
|
|
}
|
|
Node::CharSet(chars) => {
|
|
write!(f, "CharSet ")?;
|
|
let mut first = true;
|
|
for &c in chars {
|
|
if !first {
|
|
write!(f, ", ")?;
|
|
}
|
|
first = false;
|
|
write!(f, "0x{:x}", { c })?;
|
|
}
|
|
writeln!(f)?;
|
|
}
|
|
Node::Cat(..) => {
|
|
writeln!(f, "Cat")?;
|
|
}
|
|
Node::Alt(..) => {
|
|
writeln!(f, "Alt")?;
|
|
}
|
|
Node::MatchAny => {
|
|
writeln!(f, "MatchAny")?;
|
|
}
|
|
Node::MatchAnyExceptLineTerminator => {
|
|
writeln!(f, "MatchAnyExceptLineTerminator")?;
|
|
}
|
|
Node::Anchor {
|
|
anchor_type,
|
|
multiline,
|
|
} => {
|
|
writeln!(f, "Anchor {:?} multiline={}", anchor_type, multiline)?;
|
|
}
|
|
Node::Loop {
|
|
quant,
|
|
enclosed_groups,
|
|
..
|
|
} => {
|
|
writeln!(f, "Loop (groups {:?}) {:?}", enclosed_groups, quant)?;
|
|
}
|
|
Node::Loop1CharBody { quant, .. } => {
|
|
writeln!(f, "Loop1Char {:?}", quant)?;
|
|
}
|
|
Node::CaptureGroup { id, name, .. } => {
|
|
if let Some(name) = name {
|
|
writeln!(f, "CaptureGroup {:?} {:?}", id, name)?;
|
|
} else {
|
|
writeln!(f, "CaptureGroup {:?}", id)?;
|
|
}
|
|
}
|
|
&Node::WordBoundary { invert } => {
|
|
let kind = if invert { "\\B" } else { "\\b" };
|
|
writeln!(f, "WordBoundary {:?} ", kind)?;
|
|
}
|
|
&Node::BackRef(group) => {
|
|
writeln!(f, "BackRef {:?} ", group)?;
|
|
}
|
|
Node::BackRefLastDefined(groups) => {
|
|
writeln!(f, "BackRefLastDefined {:?} ", groups)?;
|
|
}
|
|
Node::Bracket(contents) => {
|
|
writeln!(f, "Bracket {:?}", contents)?;
|
|
}
|
|
|
|
&Node::LookaroundAssertion {
|
|
negate,
|
|
backwards,
|
|
start_group,
|
|
end_group,
|
|
..
|
|
} => {
|
|
let sense = if negate { "negative" } else { "positive" };
|
|
let direction = if backwards { "backwards" } else { "forwards" };
|
|
writeln!(
|
|
f,
|
|
"LookaroundAssertion {} {} {:?} {:?}",
|
|
sense, direction, start_group, end_group
|
|
)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
impl fmt::Display for Regex {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
//display_node(&self.node, 0, f)
|
|
let mut result = Ok(());
|
|
walk(
|
|
false,
|
|
self.flags.unicode,
|
|
&self.node,
|
|
&mut |node: &Node, walk: &mut Walk| {
|
|
if result.is_ok() {
|
|
result = display_node(node, walk.depth, f)
|
|
}
|
|
},
|
|
);
|
|
result
|
|
}
|
|
}
|