mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 19:51:11 +08:00
deps/vm: vendor regress 0.11.1 补 Script/Script_Extensions 的 Unknown(Zzzz) 取值——上游表缺未分配码点集取值,规范合法模式 \p{Script=Unknown} 编译期 SyntaxError;fork 纯增量(0 行改动上游行,仅补区间表与解析臂),回归钉四则(未分配匹配/已分配不匹配/sc·scx·Zzzz 别名/既有取值不扰动)
This commit is contained in:
Vendored
+410
@@ -0,0 +1,410 @@
|
||||
//! Regex compiler back-end: transforms IR into a CompiledRegex
|
||||
|
||||
use crate::bytesearch::{AsciiBitmap, ByteArraySet};
|
||||
use crate::insn::{CompiledRegex, Insn, LoopFields, MAX_BYTE_SEQ_LENGTH, MAX_CHAR_SET_LENGTH};
|
||||
use crate::ir;
|
||||
use crate::ir::Node;
|
||||
use crate::startpredicate;
|
||||
use crate::types::{BracketContents, CaptureGroupID, LoopID};
|
||||
use crate::unicode;
|
||||
use core::convert::TryInto;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use {alloc::vec::Vec, hashbrown::HashMap};
|
||||
|
||||
/// \return an anchor instruction for a given IR anchor.
|
||||
fn make_anchor(anchor_type: ir::AnchorType, multiline: bool) -> Insn {
|
||||
match anchor_type {
|
||||
ir::AnchorType::StartOfLine => Insn::StartOfLine { multiline },
|
||||
ir::AnchorType::EndOfLine => Insn::EndOfLine { multiline },
|
||||
}
|
||||
}
|
||||
|
||||
/// Weirdly placed optimization.
|
||||
/// If the given bracket can be represented as ASCII contents, return the
|
||||
/// bitmap. Otherwise nothing.
|
||||
fn bracket_as_ascii(bc: &BracketContents) -> Option<AsciiBitmap> {
|
||||
let mut result = AsciiBitmap::default();
|
||||
// We just assume that inverted brackets contain non-ASCII characters.
|
||||
if bc.invert {
|
||||
return None;
|
||||
}
|
||||
for r in bc.cps.intervals() {
|
||||
debug_assert!(r.first <= r.last);
|
||||
if r.last >= 128 {
|
||||
return None;
|
||||
}
|
||||
for bit in r.first..=r.last {
|
||||
result.set(bit as u8)
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Type which wraps up the context needed to emit a CompiledRegex.
|
||||
struct Emitter {
|
||||
result: CompiledRegex,
|
||||
|
||||
// Number of loops seen so far.
|
||||
next_loop_id: LoopID,
|
||||
|
||||
// List of group names, in order, with empties for unnamed groups.
|
||||
group_names: Vec<Box<str>>,
|
||||
|
||||
// Whether the current node is within a lookbehind.
|
||||
in_lookbehind: bool,
|
||||
}
|
||||
|
||||
impl Emitter {
|
||||
/// Emit a ByteSet instruction.
|
||||
/// We awkwardly optimize it like so.
|
||||
fn emit_byte_set_insn(&mut self, bytes: &[u8]) {
|
||||
let insn = match bytes.len() {
|
||||
0 => Insn::JustFail,
|
||||
1 => Insn::ByteSeq1(bytes.try_into().unwrap()),
|
||||
2 => Insn::ByteSet2(ByteArraySet(bytes.try_into().unwrap())),
|
||||
3 => Insn::ByteSet3(ByteArraySet(bytes.try_into().unwrap())),
|
||||
4 => Insn::ByteSet4(ByteArraySet(bytes.try_into().unwrap())),
|
||||
_ => panic!("Byte set is too long"),
|
||||
};
|
||||
self.emit_insn(insn);
|
||||
}
|
||||
|
||||
// Emit a nonempty byte sequence instruction. The sequence must be at most MAX_BYTE_SEQ_LENGTH bytes.
|
||||
fn emit_byte_sequence_insn(&mut self, seq: &[u8]) {
|
||||
const {
|
||||
assert!(
|
||||
MAX_BYTE_SEQ_LENGTH == 16,
|
||||
"Need to update our emitting logic"
|
||||
);
|
||||
};
|
||||
let insn = match seq.len() {
|
||||
1 => Insn::ByteSeq1(seq.try_into().unwrap()),
|
||||
2 => Insn::ByteSeq2(seq.try_into().unwrap()),
|
||||
3 => Insn::ByteSeq3(seq.try_into().unwrap()),
|
||||
4 => Insn::ByteSeq4(seq.try_into().unwrap()),
|
||||
5 => Insn::ByteSeq5(seq.try_into().unwrap()),
|
||||
6 => Insn::ByteSeq6(seq.try_into().unwrap()),
|
||||
7 => Insn::ByteSeq7(seq.try_into().unwrap()),
|
||||
8 => Insn::ByteSeq8(seq.try_into().unwrap()),
|
||||
9 => Insn::ByteSeq9(seq.try_into().unwrap()),
|
||||
10 => Insn::ByteSeq10(seq.try_into().unwrap()),
|
||||
11 => Insn::ByteSeq11(seq.try_into().unwrap()),
|
||||
12 => Insn::ByteSeq12(seq.try_into().unwrap()),
|
||||
13 => Insn::ByteSeq13(seq.try_into().unwrap()),
|
||||
14 => Insn::ByteSeq14(seq.try_into().unwrap()),
|
||||
15 => Insn::ByteSeq15(seq.try_into().unwrap()),
|
||||
16 => Insn::ByteSeq16(seq.try_into().unwrap()),
|
||||
_ => panic!("Unexpected chunk size"),
|
||||
};
|
||||
self.emit_insn(insn);
|
||||
}
|
||||
|
||||
/// Emit an instruction.
|
||||
/// Return the "instruction" as an index.
|
||||
fn emit_insn(&mut self, insn: Insn) {
|
||||
self.result.insns.push(insn);
|
||||
}
|
||||
|
||||
/// Get an instruction at a given index.
|
||||
fn get_insn(&mut self, idx: u32) -> &mut Insn {
|
||||
&mut self.result.insns[idx as usize]
|
||||
}
|
||||
|
||||
/// \return the offset of the next instruction emitted.
|
||||
fn next_offset(&self) -> u32 {
|
||||
self.result.insns.len() as u32
|
||||
}
|
||||
|
||||
fn emit_insn_offset(&mut self, insn: Insn) -> u32 {
|
||||
let ret = self.next_offset();
|
||||
self.emit_insn(insn);
|
||||
ret
|
||||
}
|
||||
|
||||
/// Emit instructions corresponding to a given node.
|
||||
fn emit_node(&mut self, node: &Node) {
|
||||
enum Emitter<'a> {
|
||||
Node(&'a Node),
|
||||
NodeLoopFinish {
|
||||
loop_instruction_index: u32,
|
||||
},
|
||||
NodeLookaroundAssertionFinish {
|
||||
lookaround_instruction_index: u32,
|
||||
prev_in_lookbehind: bool,
|
||||
},
|
||||
NodeAltMiddle {
|
||||
alt_instruction_index: u32,
|
||||
right_node: &'a Node,
|
||||
},
|
||||
NodeAltFinish {
|
||||
alt_instruction_index: u32,
|
||||
jump_instruction_index: u32,
|
||||
right_branch_index: u32,
|
||||
},
|
||||
EndCaptureGroup {
|
||||
group: CaptureGroupID,
|
||||
},
|
||||
}
|
||||
|
||||
let mut stack = vec![Emitter::Node(node)];
|
||||
|
||||
while let Some(inst) = stack.pop() {
|
||||
match inst {
|
||||
Emitter::NodeLoopFinish {
|
||||
loop_instruction_index,
|
||||
} => {
|
||||
self.emit_insn(Insn::LoopAgain {
|
||||
begin: loop_instruction_index,
|
||||
});
|
||||
// Fix up our loop exit.
|
||||
let exit = self.next_offset();
|
||||
match self.get_insn(loop_instruction_index) {
|
||||
Insn::EnterLoop(fields) => fields.exit = exit,
|
||||
_ => panic!("Should be an EnterLoop instruction"),
|
||||
}
|
||||
}
|
||||
Emitter::NodeLookaroundAssertionFinish {
|
||||
lookaround_instruction_index,
|
||||
prev_in_lookbehind,
|
||||
} => {
|
||||
self.emit_insn(Insn::Goal);
|
||||
// Fix up the continuation.
|
||||
let next_insn = self.next_offset();
|
||||
match self.get_insn(lookaround_instruction_index) {
|
||||
Insn::Lookbehind { continuation, .. } => *continuation = next_insn,
|
||||
Insn::Lookahead { continuation, .. } => *continuation = next_insn,
|
||||
_ => panic!("Should be a Lookaround instruction"),
|
||||
}
|
||||
self.in_lookbehind = prev_in_lookbehind;
|
||||
}
|
||||
Emitter::NodeAltMiddle {
|
||||
alt_instruction_index,
|
||||
right_node,
|
||||
} => {
|
||||
let jump_insn = self.emit_insn_offset(Insn::Jump { target: 0 });
|
||||
let right_branch = self.next_offset();
|
||||
stack.push(Emitter::NodeAltFinish {
|
||||
alt_instruction_index,
|
||||
jump_instruction_index: jump_insn,
|
||||
right_branch_index: right_branch,
|
||||
});
|
||||
stack.push(Emitter::Node(right_node));
|
||||
}
|
||||
Emitter::NodeAltFinish {
|
||||
alt_instruction_index,
|
||||
jump_instruction_index,
|
||||
right_branch_index,
|
||||
} => {
|
||||
let exit = self.next_offset();
|
||||
// Fix up our jump targets.
|
||||
match self.get_insn(alt_instruction_index) {
|
||||
Insn::Alt { secondary } => *secondary = right_branch_index,
|
||||
_ => panic!("Should be an Alt instruction"),
|
||||
}
|
||||
match self.get_insn(jump_instruction_index) {
|
||||
Insn::Jump { target } => *target = exit,
|
||||
_ => panic!("Should be a Jump instruction"),
|
||||
}
|
||||
}
|
||||
Emitter::EndCaptureGroup { group } => self.emit_insn(Insn::EndCaptureGroup(group)),
|
||||
Emitter::Node(node) => match node {
|
||||
Node::Empty => {}
|
||||
Node::Goal => self.emit_insn(Insn::Goal),
|
||||
Node::Char { c, icase } => {
|
||||
let c = *c;
|
||||
if !*icase {
|
||||
self.emit_insn(Insn::Char(c))
|
||||
} else {
|
||||
core::debug_assert!(
|
||||
unicode::fold_code_point(c, self.result.flags.unicode) == c,
|
||||
"Char {:x} should be folded",
|
||||
c
|
||||
);
|
||||
self.emit_insn(Insn::CharICase(c))
|
||||
}
|
||||
}
|
||||
Node::Cat(children) => {
|
||||
for nn in children.iter().rev() {
|
||||
stack.push(Emitter::Node(nn));
|
||||
}
|
||||
}
|
||||
Node::Alt(left, right) => {
|
||||
// Alternation is followed by the primary branch and has a jump to secondary
|
||||
// branch. After primary branch, jump to the continuation.
|
||||
let alt_insn = self.emit_insn_offset(Insn::Alt { secondary: 0 });
|
||||
stack.push(Emitter::NodeAltMiddle {
|
||||
alt_instruction_index: alt_insn,
|
||||
right_node: right,
|
||||
});
|
||||
stack.push(Emitter::Node(left));
|
||||
}
|
||||
Node::Bracket(contents) => {
|
||||
if let Some(ascii_contents) = bracket_as_ascii(contents) {
|
||||
self.emit_insn(Insn::AsciiBracket(ascii_contents))
|
||||
} else {
|
||||
let idx = self.result.brackets.len();
|
||||
self.result.brackets.push(contents.clone());
|
||||
self.emit_insn(Insn::Bracket(idx))
|
||||
}
|
||||
}
|
||||
Node::MatchAny => self.emit_insn(Insn::MatchAny),
|
||||
Node::MatchAnyExceptLineTerminator => {
|
||||
self.emit_insn(Insn::MatchAnyExceptLineTerminator)
|
||||
}
|
||||
Node::Anchor {
|
||||
anchor_type,
|
||||
multiline,
|
||||
} => self.emit_insn(make_anchor(*anchor_type, *multiline)),
|
||||
Node::Loop {
|
||||
loopee,
|
||||
quant,
|
||||
enclosed_groups,
|
||||
} => {
|
||||
let loop_id = self.next_loop_id;
|
||||
self.next_loop_id += 1;
|
||||
let loop_insn = self.emit_insn_offset(Insn::EnterLoop(LoopFields {
|
||||
loop_id,
|
||||
min_iters: quant.min,
|
||||
// If the loop is unbounded, just emit usize::MAX,
|
||||
// as we cannot match more characters than that.
|
||||
max_iters: quant.max.unwrap_or(usize::MAX),
|
||||
greedy: quant.greedy,
|
||||
exit: 0,
|
||||
}));
|
||||
self.result.loops += 1;
|
||||
// Emit a sequence of ResetCaptureGroup for any contained groups.
|
||||
for gid in enclosed_groups.start..enclosed_groups.end {
|
||||
self.emit_insn(Insn::ResetCaptureGroup(gid))
|
||||
}
|
||||
stack.push(Emitter::NodeLoopFinish {
|
||||
loop_instruction_index: loop_insn,
|
||||
});
|
||||
stack.push(Emitter::Node(loopee));
|
||||
}
|
||||
Node::Loop1CharBody { loopee, quant } => {
|
||||
self.emit_insn(Insn::Loop1CharBody {
|
||||
min_iters: quant.min,
|
||||
max_iters: quant.max.unwrap_or(usize::MAX),
|
||||
greedy: quant.greedy,
|
||||
});
|
||||
stack.push(Emitter::Node(loopee));
|
||||
}
|
||||
Node::CaptureGroup { id, contents, name } => {
|
||||
let group = *id;
|
||||
self.result.groups += 1;
|
||||
self.group_names.push(name.as_deref().unwrap_or("").into());
|
||||
self.emit_insn(Insn::BeginCaptureGroup(group));
|
||||
stack.push(Emitter::EndCaptureGroup { group });
|
||||
stack.push(Emitter::Node(contents));
|
||||
}
|
||||
Node::LookaroundAssertion {
|
||||
negate,
|
||||
backwards,
|
||||
start_group,
|
||||
end_group,
|
||||
contents,
|
||||
} => {
|
||||
let lookaround = if *backwards {
|
||||
self.emit_insn_offset(Insn::Lookbehind {
|
||||
negate: *negate,
|
||||
start_group: *start_group,
|
||||
end_group: *end_group,
|
||||
continuation: 0,
|
||||
})
|
||||
} else {
|
||||
self.emit_insn_offset(Insn::Lookahead {
|
||||
negate: *negate,
|
||||
start_group: *start_group,
|
||||
end_group: *end_group,
|
||||
continuation: 0,
|
||||
})
|
||||
};
|
||||
let prev_in_lookbehind = self.in_lookbehind;
|
||||
self.in_lookbehind = *backwards;
|
||||
stack.push(Emitter::NodeLookaroundAssertionFinish {
|
||||
lookaround_instruction_index: lookaround,
|
||||
prev_in_lookbehind,
|
||||
});
|
||||
stack.push(Emitter::Node(contents));
|
||||
}
|
||||
&Node::WordBoundary { invert } => {
|
||||
if self.result.flags.unicode && self.result.flags.icase {
|
||||
self.emit_insn(Insn::WordBoundaryUnicodeICase { invert })
|
||||
} else {
|
||||
self.emit_insn(Insn::WordBoundary { invert })
|
||||
}
|
||||
}
|
||||
&Node::BackRef(group) => {
|
||||
debug_assert!(group >= 1, "Group should not be zero");
|
||||
// -1 because \1 matches the first capture group, which has index 0.
|
||||
self.emit_insn(Insn::BackRef(group - 1))
|
||||
}
|
||||
|
||||
Node::ByteSet(bytes) => self.emit_byte_set_insn(bytes),
|
||||
|
||||
Node::CharSet(chars) => {
|
||||
debug_assert!(chars.len() <= MAX_CHAR_SET_LENGTH);
|
||||
if chars.is_empty() {
|
||||
self.emit_insn(Insn::JustFail);
|
||||
} else {
|
||||
let mut arr = [chars[0]; MAX_CHAR_SET_LENGTH];
|
||||
arr[..chars.len()].copy_from_slice(chars.as_slice());
|
||||
self.emit_insn(Insn::CharSet(arr))
|
||||
}
|
||||
}
|
||||
|
||||
Node::ByteSequence(bytes) => {
|
||||
// In a lookbehind, instruction order is reversed, but ByteSequence contents are not,
|
||||
// to take advantage of optimized memcmp. For example, `(?<=abcd)` may be emitted
|
||||
// as "d", "c", "b", "a" char matches (traversing input from right to left), or
|
||||
// optimized to a single forwards ByteSequence("abcd"), which matches left-to-right.
|
||||
// Therefore if we split a byte sequence into multiple instructions,
|
||||
// we need to emit them in reverse order iff in a lookbehind.
|
||||
let chunks = bytes.as_slice().chunks(MAX_BYTE_SEQ_LENGTH);
|
||||
if self.in_lookbehind {
|
||||
for chunk in chunks.rev() {
|
||||
self.emit_byte_sequence_insn(chunk);
|
||||
}
|
||||
} else {
|
||||
for chunk in chunks {
|
||||
self.emit_byte_sequence_insn(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile the given IR to a CompiledRegex.
|
||||
pub fn emit(n: &ir::Regex) -> CompiledRegex {
|
||||
let mut emitter = Emitter {
|
||||
next_loop_id: 0,
|
||||
group_names: Vec::new(),
|
||||
in_lookbehind: false,
|
||||
result: CompiledRegex {
|
||||
insns: Vec::new(),
|
||||
brackets: Vec::new(),
|
||||
loops: 0,
|
||||
groups: 0,
|
||||
group_names: Box::new([]),
|
||||
flags: n.flags,
|
||||
start_pred: startpredicate::predicate_for_re(n),
|
||||
},
|
||||
};
|
||||
emitter.emit_node(&n.node);
|
||||
let mut result = emitter.result;
|
||||
// Populate group names, unless all are empty.
|
||||
debug_assert!(
|
||||
result.group_names.is_empty(),
|
||||
"Group names should not be set"
|
||||
);
|
||||
if emitter.group_names.iter().any(|s| !s.is_empty()) {
|
||||
result.group_names = emitter.group_names.into_boxed_slice();
|
||||
}
|
||||
debug_assert!(
|
||||
result.group_names.is_empty() || result.group_names.len() == result.groups as usize
|
||||
);
|
||||
result
|
||||
}
|
||||
Reference in New Issue
Block a user