mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(regular_expression): parse class set character prefixes once (#28512)
Share the ClassSetCharacter prefix between range and operand parsing while preserving malformed-range fallback order. AI-assisted (Codex).
This commit is contained in:
1 parent
3708934efb
commit
5395a40e36
2 files changed
+83
-32
No files matched your search
@@ -1135,12 +1135,11 @@ impl<'a> PatternParser<'a> {
|
||||
&mut self,
|
||||
) -> Result<(ast::CharacterClassContentsKind, ArenaVec<'a, ast::CharacterClassContents<'a>>)>
|
||||
{
|
||||
// ClassUnion :: ClassSetRange ClassUnion[opt]
|
||||
if let Some(class_set_range) = self.parse_class_set_range()? {
|
||||
return self.parse_class_set_union(class_set_range);
|
||||
}
|
||||
|
||||
if let Some(class_set_operand) = self.parse_class_set_operand()? {
|
||||
if let Some(class_set_operand) = self.parse_class_set_range_or_operand()? {
|
||||
// A range can only start a union, not an intersection or subtraction.
|
||||
if matches!(class_set_operand, ast::CharacterClassContents::CharacterClassRange(_)) {
|
||||
return self.parse_class_set_union(class_set_operand);
|
||||
}
|
||||
// ClassIntersection
|
||||
if self.reader.peek().as_ref().is_some_and(|&cp| cp == '&' as u32)
|
||||
&& self.reader.peek2().as_ref().is_some_and(|&cp| cp == '&' as u32)
|
||||
@@ -1178,11 +1177,7 @@ impl<'a> PatternParser<'a> {
|
||||
body.push(class_set_range_or_class_set_operand);
|
||||
|
||||
loop {
|
||||
if let Some(class_set_range) = self.parse_class_set_range()? {
|
||||
body.push(class_set_range);
|
||||
continue;
|
||||
}
|
||||
if let Some(class_set_operand) = self.parse_class_set_operand()? {
|
||||
if let Some(class_set_operand) = self.parse_class_set_range_or_operand()? {
|
||||
body.push(class_set_operand);
|
||||
continue;
|
||||
}
|
||||
@@ -1274,34 +1269,45 @@ impl<'a> PatternParser<'a> {
|
||||
// ClassSetRange ::
|
||||
// ClassSetCharacter - ClassSetCharacter
|
||||
// ```
|
||||
fn parse_class_set_range(&mut self) -> Result<Option<ast::CharacterClassContents<'a>>> {
|
||||
fn parse_class_set_range_or_operand(
|
||||
&mut self,
|
||||
) -> Result<Option<ast::CharacterClassContents<'a>>> {
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
if let Some(class_set_character) = self.parse_class_set_character()?
|
||||
&& self.reader.eat('-')
|
||||
&& let Some(class_set_character_to) = self.parse_class_set_character()?
|
||||
{
|
||||
// [SS:EE] ClassSetRange :: ClassSetCharacter - ClassSetCharacter
|
||||
// It is a Syntax Error if the CharacterValue of the first ClassSetCharacter is strictly greater than the CharacterValue of the second ClassSetCharacter.
|
||||
if class_set_character_to.value < class_set_character.value {
|
||||
return Err(diagnostics::character_class_range_out_of_order(
|
||||
class_set_character.span.merge(class_set_character_to.span),
|
||||
"class set",
|
||||
));
|
||||
if let Some(class_set_character) = self.parse_class_set_character()? {
|
||||
if !self.reader.eat('-') {
|
||||
return Ok(Some(ast::CharacterClassContents::Character(ArenaBox::new_in(
|
||||
class_set_character,
|
||||
self,
|
||||
))));
|
||||
}
|
||||
|
||||
return Ok(Some(ast::CharacterClassContents::CharacterClassRange(ArenaBox::new_in(
|
||||
ast::CharacterClassRange {
|
||||
span: class_set_character.span.merge(class_set_character_to.span),
|
||||
min: class_set_character,
|
||||
max: class_set_character_to,
|
||||
},
|
||||
self,
|
||||
))));
|
||||
if let Some(class_set_character_to) = self.parse_class_set_character()? {
|
||||
// [SS:EE] ClassSetRange :: ClassSetCharacter - ClassSetCharacter
|
||||
// It is a Syntax Error if the CharacterValue of the first ClassSetCharacter is strictly greater than the CharacterValue of the second ClassSetCharacter.
|
||||
if class_set_character_to.value < class_set_character.value {
|
||||
return Err(diagnostics::character_class_range_out_of_order(
|
||||
class_set_character.span.merge(class_set_character_to.span),
|
||||
"class set",
|
||||
));
|
||||
}
|
||||
|
||||
return Ok(Some(ast::CharacterClassContents::CharacterClassRange(
|
||||
ArenaBox::new_in(
|
||||
ast::CharacterClassRange {
|
||||
span: class_set_character.span.merge(class_set_character_to.span),
|
||||
min: class_set_character,
|
||||
max: class_set_character_to,
|
||||
},
|
||||
self,
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
// Failed ranges retain the original operand parse and diagnostic order.
|
||||
self.reader.rewind(checkpoint);
|
||||
|
||||
Ok(None)
|
||||
self.parse_class_set_operand()
|
||||
}
|
||||
|
||||
// ```
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_regular_expression::{
|
||||
LiteralParser, Options,
|
||||
ast::{CharacterClassContents, Term},
|
||||
};
|
||||
use oxc_span::Span;
|
||||
|
||||
#[test]
|
||||
fn class_set_character_values_and_ranges() {
|
||||
let allocator = Allocator::default();
|
||||
let pattern = r"[\u{61}\x62c-e]";
|
||||
let parsed =
|
||||
LiteralParser::new(&allocator, pattern, Some("v"), Options::default()).parse().unwrap();
|
||||
let Term::CharacterClass(class) = &parsed.body.body[0].body[0] else {
|
||||
panic!("expected character class");
|
||||
};
|
||||
assert_eq!(class.body.len(), 3);
|
||||
for (entry, value, span) in
|
||||
[(&class.body[0], 'a', Span::new(1, 7)), (&class.body[1], 'b', Span::new(7, 11))]
|
||||
{
|
||||
let CharacterClassContents::Character(character) = entry else {
|
||||
panic!("expected class character");
|
||||
};
|
||||
assert_eq!(character.value, u32::from(value));
|
||||
assert_eq!(character.span, span);
|
||||
}
|
||||
let CharacterClassContents::CharacterClassRange(range) = &class.body[2] else {
|
||||
panic!("expected class range");
|
||||
};
|
||||
assert_eq!((range.min.value, range.max.value), (u32::from('c'), u32::from('e')));
|
||||
assert_eq!(range.span, Span::new(11, 14));
|
||||
|
||||
for (pattern, flags, message, span) in [
|
||||
("[a-]", "v", "Unterminated character class", Span::new(0, 2)),
|
||||
("[a-b&&c]", "v", "Unterminated character class", Span::new(0, 4)),
|
||||
("[z-a]", "v", "Character class set range out of order", Span::new(1, 4)),
|
||||
] {
|
||||
let error = LiteralParser::new(&allocator, pattern, Some(flags), Options::default())
|
||||
.parse()
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains(message), "/{pattern}/{flags}: {error}");
|
||||
assert_eq!(error.labels[0].offset(), span.start);
|
||||
assert_eq!(error.labels[0].len(), span.size());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user