LibRegex: Compile ECMAScript patterns from UTF-16
Accept Utf16View patterns at the LibRegex compile boundary and pass UTF-16 or ASCII storage directly into the Rust regex parser. This keeps JavaScript regular expression construction from converting patterns through UTF-8 when LibRegex can consume the same UTF-16 representation used by LibJS. Update RegExp construction, HTML pattern validation, the regex fuzzer, and LibRegex tests to use the UTF-16 compile API.
This commit is contained in:
parent
bcf7f27a4f
commit
2d20322fce
15 changed files with 192 additions and 104 deletions
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Find.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/RegExpConstructor.h>
|
||||
|
|
@ -179,7 +180,7 @@ ThrowCompletionOr<GC::Ref<Object>> RegExpConstructor::construct_impl(FunctionObj
|
|||
}
|
||||
|
||||
// 22.2.5.1.1 EncodeForRegExpEscape ( cp ), https://tc39.es/ecma262/#sec-encodeforregexpescape
|
||||
static String encode_for_regexp_escape(u32 code_point)
|
||||
static Utf16String encode_for_regexp_escape(u32 code_point)
|
||||
{
|
||||
// https://tc39.es/ecma262/#table-controlescape-code-point-values
|
||||
// Table 63: ControlEscape Code Point Values
|
||||
|
|
@ -198,7 +199,10 @@ static String encode_for_regexp_escape(u32 code_point)
|
|||
// 1. If c is matched by SyntaxCharacter or c is U+002F (SOLIDUS), then
|
||||
if (is_syntax_character(code_point) || code_point == '/') {
|
||||
// a. Return the string-concatenation of 0x005C (REVERSE SOLIDUS) and UTF16EncodeCodePoint(c).
|
||||
return MUST(String::formatted("\\{}", String::from_code_point(code_point)));
|
||||
Utf16StringBuilder builder;
|
||||
builder.append_ascii('\\');
|
||||
builder.append_code_point(code_point);
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
// 2. Else if c is the code point listed in some cell of the “Code Point” column of Table 63, then
|
||||
|
|
@ -209,7 +213,10 @@ static String encode_for_regexp_escape(u32 code_point)
|
|||
if (it != control_escapes.end()) {
|
||||
// a. Return the string-concatenation of 0x005C (REVERSE SOLIDUS) and the string in the “ControlEscape” column
|
||||
// of the row whose “Code Point” column contains c.
|
||||
return MUST(String::formatted("\\{}", it->control_escape));
|
||||
Utf16StringBuilder builder;
|
||||
builder.append_ascii('\\');
|
||||
builder.append_ascii(it->control_escape);
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
// 3. Let otherPunctuators be the string-concatenation of ",-=<>#&!%:;@~'`" and the code unit 0x0022 (QUOTATION MARK).
|
||||
|
|
@ -225,7 +232,7 @@ static String encode_for_regexp_escape(u32 code_point)
|
|||
// i. Let hex be Number::toString(𝔽(cNum), 16).
|
||||
// ii. Return the string-concatenation of the code unit 0x005C (REVERSE SOLIDUS), "x", and
|
||||
// StringPad(hex, 2, "0", START).
|
||||
return MUST(String::formatted("\\x{:02x}", code_point));
|
||||
return Utf16String::formatted("\\x{:02x}", code_point);
|
||||
}
|
||||
|
||||
// c. Let escaped be the empty String.
|
||||
|
|
@ -233,11 +240,11 @@ static String encode_for_regexp_escape(u32 code_point)
|
|||
// e. For each code unit cu of codeUnits, do
|
||||
// i. Set escaped to the string-concatenation of escaped and UnicodeEscape(cu).
|
||||
// f. Return escaped.
|
||||
return MUST(String::formatted("\\u{:04x}", code_point));
|
||||
return Utf16String::formatted("\\u{:04x}", code_point);
|
||||
}
|
||||
|
||||
// 6. Return UTF16EncodeCodePoint(c).
|
||||
return String::from_code_point(code_point);
|
||||
return Utf16String::from_code_point(code_point);
|
||||
}
|
||||
|
||||
// 22.2.5.1 RegExp.escape ( S ), https://tc39.es/ecma262/#sec-regexp.escape
|
||||
|
|
@ -250,13 +257,13 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::escape)
|
|||
return vm.throw_completion<TypeError>(ErrorType::NotAString, string);
|
||||
|
||||
// 2. Let escaped be the empty String.
|
||||
StringBuilder escaped(string.as_string().utf8_string().byte_count());
|
||||
Utf16StringBuilder escaped(string.as_string().utf16_string_view().length_in_code_units());
|
||||
|
||||
// 3. Let cpList be StringToCodePoints(S).
|
||||
auto code_point_list = string.as_string().utf8_string();
|
||||
auto code_point_list = string.as_string().utf16_string_view();
|
||||
|
||||
// 4. For each code point c of cpList, do
|
||||
for (auto code_point : code_point_list.code_points()) {
|
||||
for (auto code_point : code_point_list) {
|
||||
// a. If escaped is the empty String and c is matched by either DecimalDigit or AsciiLetter, then
|
||||
if (escaped.is_empty() && is_ascii_alphanumeric(code_point)) {
|
||||
// i. NOTE: Escaping a leading digit ensures that output corresponds with pattern text which may be used
|
||||
|
|
@ -278,7 +285,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::escape)
|
|||
}
|
||||
|
||||
// 5. Return escaped.
|
||||
return JS::PrimitiveString::create(vm, MUST(escaped.to_string()));
|
||||
return JS::PrimitiveString::create(vm, escaped.to_string());
|
||||
}
|
||||
|
||||
// 22.2.5.3 get RegExp [ %Symbol.species% ], https://tc39.es/ecma262/#sec-get-regexp-@@species
|
||||
|
|
|
|||
|
|
@ -261,14 +261,14 @@ static Result<RegExpObject::Flags, String> validate_flags(Utf16View const& flags
|
|||
}
|
||||
|
||||
// 22.2.3.4 Static Semantics: ParsePattern ( patternText, u, v ), https://tc39.es/ecma262/#sec-parsepattern
|
||||
ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pattern, bool unicode, bool unicode_sets)
|
||||
ErrorOr<Utf16String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pattern, bool unicode, bool unicode_sets)
|
||||
{
|
||||
if (unicode && unicode_sets)
|
||||
return ParseRegexPatternError { MUST(String::formatted(ErrorType::RegExpObjectIncompatibleFlags.format(), 'u', 'v')) };
|
||||
|
||||
TRY(validate_named_group_name_surrogates(pattern, unicode || unicode_sets));
|
||||
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
|
||||
auto previous_code_unit_was_backslash = false;
|
||||
for (size_t i = 0; i < pattern.length_in_code_units(); ++i) {
|
||||
|
|
@ -280,7 +280,7 @@ ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pat
|
|||
// leading to a matcher for the literal string "\uhhhh" instead of the intended code unit <c>.
|
||||
// As such, we're going to remove the (invalid) backslash and pretend it never existed.
|
||||
if (!previous_code_unit_was_backslash)
|
||||
builder.append('\\');
|
||||
builder.append_ascii('\\');
|
||||
|
||||
if ((unicode || unicode_sets) && AK::UnicodeUtils::is_utf16_high_surrogate(code_unit) && i + 1 < pattern.length_in_code_units()) {
|
||||
u16 next_code_unit = pattern.code_unit_at(i + 1);
|
||||
|
|
@ -298,7 +298,7 @@ ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pat
|
|||
else
|
||||
builder.appendff("u{:04x}", code_unit);
|
||||
} else {
|
||||
builder.append_code_point(code_unit);
|
||||
builder.append_code_unit(code_unit);
|
||||
}
|
||||
|
||||
if (code_unit == '\\')
|
||||
|
|
@ -307,11 +307,11 @@ ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pat
|
|||
previous_code_unit_was_backslash = false;
|
||||
}
|
||||
|
||||
return builder.to_string_without_validation();
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
// 22.2.3.4 Static Semantics: ParsePattern ( patternText, u, v ), https://tc39.es/ecma262/#sec-parsepattern
|
||||
ThrowCompletionOr<String> parse_regex_pattern(VM& vm, Utf16View const& pattern, bool unicode, bool unicode_sets)
|
||||
ThrowCompletionOr<Utf16String> parse_regex_pattern(VM& vm, Utf16View const& pattern, bool unicode, bool unicode_sets)
|
||||
{
|
||||
auto result = parse_regex_pattern(pattern, unicode, unicode_sets);
|
||||
if (result.is_error())
|
||||
|
|
@ -402,9 +402,9 @@ ThrowCompletionOr<GC::Ref<RegExpObject>> RegExpObject::regexp_initialize(VM& vm,
|
|||
bool unicode = has_flag(flag_bits, Flags::Unicode);
|
||||
bool unicode_sets = has_flag(flag_bits, Flags::UnicodeSets);
|
||||
|
||||
auto parsed_pattern = String {};
|
||||
auto parsed_pattern = Utf16String {};
|
||||
|
||||
// Convert UTF-16 pattern to UTF-8 (with escape normalization for non-ASCII).
|
||||
// Normalize non-ASCII code units to ASCII escapes before compiling the pattern.
|
||||
if (!pattern.is_empty()) {
|
||||
auto result = parse_regex_pattern(pattern, unicode, unicode_sets);
|
||||
if (result.is_error())
|
||||
|
|
@ -425,7 +425,7 @@ ThrowCompletionOr<GC::Ref<RegExpObject>> RegExpObject::regexp_initialize(VM& vm,
|
|||
compile_flags.unicode_sets = unicode_sets;
|
||||
compile_flags.sticky = has_flag(flag_bits, Flags::Sticky);
|
||||
|
||||
auto compiled = regex::ECMAScriptRegex::compile(parsed_pattern.bytes_as_string_view(), compile_flags);
|
||||
auto compiled = regex::ECMAScriptRegex::compile(parsed_pattern.utf16_view(), compile_flags);
|
||||
if (compiled.is_error())
|
||||
return vm.throw_completion<SyntaxError>(ErrorType::RegExpCompileError, compiled.release_error());
|
||||
|
||||
|
|
@ -449,7 +449,7 @@ ThrowCompletionOr<GC::Ref<RegExpObject>> RegExpObject::regexp_initialize(VM& vm,
|
|||
}
|
||||
|
||||
// 22.2.6.13.1 EscapeRegExpPattern ( P, F ), https://tc39.es/ecma262/#sec-escaperegexppattern
|
||||
String RegExpObject::escape_regexp_pattern() const
|
||||
Utf16String RegExpObject::escape_regexp_pattern() const
|
||||
{
|
||||
// 1. Let S be a String in the form of a Pattern[~UnicodeMode] (Pattern[+UnicodeMode] if F contains "u") equivalent
|
||||
// to P interpreted as UTF-16 encoded Unicode code points (6.1.4), in which certain code points are escaped as
|
||||
|
|
@ -465,30 +465,30 @@ String RegExpObject::escape_regexp_pattern() const
|
|||
// specification can be met by letting S be "(?:)".
|
||||
// 3. Return S.
|
||||
if (m_pattern.is_empty())
|
||||
return "(?:)"_string;
|
||||
return "(?:)"_utf16;
|
||||
|
||||
// FIXME: Check the 'u' and 'v' flags and escape accordingly
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
auto escaped = false;
|
||||
auto in_character_class = false;
|
||||
|
||||
for (auto code_point : m_pattern) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
builder.append('\\');
|
||||
builder.append_ascii('\\');
|
||||
|
||||
switch (code_point) {
|
||||
case '\n':
|
||||
builder.append('n');
|
||||
builder.append_ascii('n');
|
||||
break;
|
||||
case '\r':
|
||||
builder.append('r');
|
||||
builder.append_ascii('r');
|
||||
break;
|
||||
case LINE_SEPARATOR:
|
||||
builder.append("u2028"sv);
|
||||
builder.append_ascii("u2028"sv);
|
||||
break;
|
||||
case PARAGRAPH_SEPARATOR:
|
||||
builder.append("u2029"sv);
|
||||
builder.append_ascii("u2029"sv);
|
||||
break;
|
||||
default:
|
||||
builder.append_code_point(code_point);
|
||||
|
|
@ -511,21 +511,21 @@ String RegExpObject::escape_regexp_pattern() const
|
|||
switch (code_point) {
|
||||
case '/':
|
||||
if (in_character_class)
|
||||
builder.append('/');
|
||||
builder.append_ascii('/');
|
||||
else
|
||||
builder.append("\\/"sv);
|
||||
builder.append_ascii("\\/"sv);
|
||||
break;
|
||||
case '\n':
|
||||
builder.append("\\n"sv);
|
||||
builder.append_ascii("\\n"sv);
|
||||
break;
|
||||
case '\r':
|
||||
builder.append("\\r"sv);
|
||||
builder.append_ascii("\\r"sv);
|
||||
break;
|
||||
case LINE_SEPARATOR:
|
||||
builder.append("\\u2028"sv);
|
||||
builder.append_ascii("\\u2028"sv);
|
||||
break;
|
||||
case PARAGRAPH_SEPARATOR:
|
||||
builder.append("\\u2029"sv);
|
||||
builder.append_ascii("\\u2029"sv);
|
||||
break;
|
||||
default:
|
||||
builder.append_code_point(code_point);
|
||||
|
|
@ -533,7 +533,7 @@ String RegExpObject::escape_regexp_pattern() const
|
|||
}
|
||||
}
|
||||
|
||||
return builder.to_string_without_validation();
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
void RegExpObject::visit_edges(JS::Cell::Visitor& visitor)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ ThrowCompletionOr<GC::Ref<RegExpObject>> regexp_alloc(VM&, FunctionObject& new_t
|
|||
struct ParseRegexPatternError {
|
||||
String error;
|
||||
};
|
||||
ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pattern, bool unicode, bool unicode_sets);
|
||||
ThrowCompletionOr<String> parse_regex_pattern(VM& vm, Utf16View const& pattern, bool unicode, bool unicode_sets);
|
||||
ErrorOr<Utf16String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pattern, bool unicode, bool unicode_sets);
|
||||
ThrowCompletionOr<Utf16String> parse_regex_pattern(VM& vm, Utf16View const& pattern, bool unicode, bool unicode_sets);
|
||||
|
||||
class JS_API RegExpObject : public Object {
|
||||
JS_OBJECT(RegExpObject, Object);
|
||||
|
|
@ -45,7 +45,7 @@ public:
|
|||
static GC::Ref<RegExpObject> create(Realm&, Utf16String pattern, Utf16String flags);
|
||||
|
||||
ThrowCompletionOr<GC::Ref<RegExpObject>> regexp_initialize(VM&, Value pattern, Value flags);
|
||||
String escape_regexp_pattern() const;
|
||||
Utf16String escape_regexp_pattern() const;
|
||||
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~RegExpObject() override = default;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ static regex::ECMAScriptRegex const* get_or_compile_regex(RegExpObject& regexp_o
|
|||
flags.sticky = has_flag(flag_bits, RegExpObject::Flags::Sticky);
|
||||
flags.has_indices = has_flag(flag_bits, RegExpObject::Flags::HasIndices);
|
||||
|
||||
auto compiled = regex::ECMAScriptRegex::compile(parsed_pattern.release_value(), flags);
|
||||
auto normalized_pattern = parsed_pattern.release_value();
|
||||
auto compiled = regex::ECMAScriptRegex::compile(normalized_pattern.utf16_view(), flags);
|
||||
if (compiled.is_error())
|
||||
return nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -1160,7 +1160,7 @@ void free_function_ast(void* ast)
|
|||
namespace JS::FFI {
|
||||
|
||||
struct RustCompiledRegex {
|
||||
String parsed_pattern;
|
||||
Utf16String parsed_pattern;
|
||||
};
|
||||
|
||||
static Utf16View view_from_ffi(FFIUtf16Slice slice)
|
||||
|
|
@ -1889,7 +1889,7 @@ extern "C" void* rust_compile_regex(
|
|||
}
|
||||
}
|
||||
|
||||
auto compiled = regex::ECMAScriptRegex::compile(pattern_str.bytes_as_string_view(), compile_flags);
|
||||
auto compiled = regex::ECMAScriptRegex::compile(pattern_str.utf16_view(), compile_flags);
|
||||
if (compiled.is_error()) {
|
||||
auto msg = MUST(String::formatted("RegExp compile error: {}", compiled.release_error()));
|
||||
auto* buf = static_cast<char*>(kmalloc(msg.byte_count() + 1));
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ struct ECMAScriptRegex::Impl {
|
|||
Vector<ECMAScriptNamedCaptureGroup> named_groups;
|
||||
};
|
||||
|
||||
ErrorOr<ECMAScriptRegex, String> ECMAScriptRegex::compile(StringView utf8_pattern, ECMAScriptCompileFlags flags)
|
||||
ErrorOr<ECMAScriptRegex, String> ECMAScriptRegex::compile(Utf16View pattern, ECMAScriptCompileFlags flags)
|
||||
{
|
||||
RustRegexFlags rust_flags {};
|
||||
rust_flags.global = flags.global;
|
||||
|
|
@ -27,7 +27,7 @@ ErrorOr<ECMAScriptRegex, String> ECMAScriptRegex::compile(StringView utf8_patter
|
|||
rust_flags.sticky = flags.sticky;
|
||||
rust_flags.has_indices = flags.has_indices;
|
||||
|
||||
auto compiled = CompiledRustRegex::compile(utf8_pattern, rust_flags);
|
||||
auto compiled = CompiledRustRegex::compile(pattern, rust_flags);
|
||||
if (compiled.is_error())
|
||||
return compiled.release_error();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class REGEX_API ECMAScriptRegex {
|
|||
AK_MAKE_NONCOPYABLE(ECMAScriptRegex);
|
||||
|
||||
public:
|
||||
static ErrorOr<ECMAScriptRegex, String> compile(StringView utf8_pattern, ECMAScriptCompileFlags);
|
||||
static ErrorOr<ECMAScriptRegex, String> compile(Utf16View pattern, ECMAScriptCompileFlags);
|
||||
|
||||
~ECMAScriptRegex();
|
||||
ECMAScriptRegex(ECMAScriptRegex&&);
|
||||
|
|
|
|||
|
|
@ -28,29 +28,12 @@ pub struct RustRegexFlags {
|
|||
pub has_indices: bool,
|
||||
}
|
||||
|
||||
/// Compile a regex pattern. Returns an opaque handle, or null on error.
|
||||
/// On error, writes the error message to `error_out` and `error_len_out`.
|
||||
/// The caller must free the error string with `rust_regex_free_error`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `pattern` must be a valid pointer to `pattern_len` bytes of UTF-8.
|
||||
/// `error_out` and `error_len_out` must be valid pointers.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_regex_compile(
|
||||
pattern: *const u8,
|
||||
pattern_len: usize,
|
||||
fn compile_pattern(
|
||||
pattern: Vec<char>,
|
||||
flags: RustRegexFlags,
|
||||
error_out: *mut *const u8,
|
||||
error_len_out: *mut usize,
|
||||
) -> *mut RustRegex {
|
||||
if pattern.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let pattern_bytes = unsafe { slice::from_raw_parts(pattern, pattern_len) };
|
||||
let Ok(pattern_str) = std::str::from_utf8(pattern_bytes) else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
|
||||
let flags = Flags {
|
||||
global: flags.global,
|
||||
ignore_case: flags.ignore_case,
|
||||
|
|
@ -62,7 +45,7 @@ pub unsafe extern "C" fn rust_regex_compile(
|
|||
has_indices: flags.has_indices,
|
||||
};
|
||||
|
||||
match Regex::compile(pattern_str, flags) {
|
||||
match Regex::compile_chars(pattern, flags) {
|
||||
Ok(regex) => Box::into_raw(Box::new(RustRegex(regex))),
|
||||
Err(e) => {
|
||||
if !error_out.is_null() && !error_len_out.is_null() {
|
||||
|
|
@ -78,6 +61,58 @@ pub unsafe extern "C" fn rust_regex_compile(
|
|||
}
|
||||
}
|
||||
|
||||
/// Compile a regex pattern. Returns an opaque handle, or null on error.
|
||||
/// On error, writes the error message to `error_out` and `error_len_out`.
|
||||
/// The caller must free the error string with `rust_regex_free_error`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `pattern` must be a valid pointer to `pattern_len` UTF-16 code units.
|
||||
/// `error_out` and `error_len_out` must be valid pointers.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_regex_compile(
|
||||
pattern: *const u16,
|
||||
pattern_len: usize,
|
||||
flags: RustRegexFlags,
|
||||
error_out: *mut *const u8,
|
||||
error_len_out: *mut usize,
|
||||
) -> *mut RustRegex {
|
||||
if pattern.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let code_units = unsafe { slice::from_raw_parts(pattern, pattern_len) };
|
||||
let pattern = char::decode_utf16(code_units.iter().copied())
|
||||
.map(|result| result.unwrap_or(char::REPLACEMENT_CHARACTER))
|
||||
.collect();
|
||||
|
||||
compile_pattern(pattern, flags, error_out, error_len_out)
|
||||
}
|
||||
|
||||
/// Compile an ASCII-only regex pattern. Returns an opaque handle, or null on error.
|
||||
/// On error, writes the error message to `error_out` and `error_len_out`.
|
||||
/// The caller must free the error string with `rust_regex_free_error`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `pattern` must be a valid pointer to `pattern_len` ASCII bytes.
|
||||
/// `error_out` and `error_len_out` must be valid pointers.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_regex_compile_ascii(
|
||||
pattern: *const u8,
|
||||
pattern_len: usize,
|
||||
flags: RustRegexFlags,
|
||||
error_out: *mut *const u8,
|
||||
error_len_out: *mut usize,
|
||||
) -> *mut RustRegex {
|
||||
if pattern.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let bytes = unsafe { slice::from_raw_parts(pattern, pattern_len) };
|
||||
let pattern = bytes.iter().map(|byte| *byte as char).collect();
|
||||
|
||||
compile_pattern(pattern, flags, error_out, error_len_out)
|
||||
}
|
||||
|
||||
/// Free an error string returned by `rust_regex_compile`.
|
||||
///
|
||||
/// # Safety
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ pub fn parse(source: &str, flags: Flags) -> Result<Pattern, Error> {
|
|||
Parser::new(source, flags).parse()
|
||||
}
|
||||
|
||||
pub fn parse_chars(source: Vec<char>, flags: Flags) -> Result<Pattern, Error> {
|
||||
Parser::new_from_chars(source, flags).parse()
|
||||
}
|
||||
|
||||
/// Parse a flags string like `"gimsu"` into [`Flags`].
|
||||
pub fn parse_flags(s: &str) -> Result<Flags, Error> {
|
||||
let mut flags = Flags::default();
|
||||
|
|
@ -127,7 +131,10 @@ struct Parser {
|
|||
|
||||
impl Parser {
|
||||
fn new(source: &str, flags: Flags) -> Self {
|
||||
let chars: Vec<char> = source.chars().collect();
|
||||
Self::new_from_chars(source.chars().collect(), flags)
|
||||
}
|
||||
|
||||
fn new_from_chars(chars: Vec<char>, flags: Flags) -> Self {
|
||||
let (total, has_named) = Self::prescan_captures(&chars);
|
||||
Self {
|
||||
source: chars,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ impl Regex {
|
|||
/// Compile a regex pattern with the given flags.
|
||||
pub fn compile(pattern: &str, flags: Flags) -> Result<Self, parser::Error> {
|
||||
let parsed = parser::parse(pattern, flags)?;
|
||||
Self::compile_parsed(parsed, flags)
|
||||
}
|
||||
|
||||
pub fn compile_chars(pattern: Vec<char>, flags: Flags) -> Result<Self, parser::Error> {
|
||||
let parsed = parser::parse_chars(pattern, flags)?;
|
||||
Self::compile_parsed(parsed, flags)
|
||||
}
|
||||
|
||||
fn compile_parsed(parsed: Pattern, flags: Flags) -> Result<Self, parser::Error> {
|
||||
let mut program = compiler::compile(&parsed);
|
||||
Self::resolve_properties(&mut program);
|
||||
let required_literal_hint = extract_required_literal_hint(&parsed, flags);
|
||||
|
|
|
|||
|
|
@ -8,17 +8,29 @@
|
|||
|
||||
namespace regex {
|
||||
|
||||
ErrorOr<CompiledRustRegex, String> CompiledRustRegex::compile(StringView pattern, RustRegexFlags flags)
|
||||
ErrorOr<CompiledRustRegex, String> CompiledRustRegex::compile(Utf16View pattern, RustRegexFlags flags)
|
||||
{
|
||||
unsigned char const* error_ptr = nullptr;
|
||||
size_t error_len = 0;
|
||||
|
||||
auto* regex = rust_regex_compile(
|
||||
reinterpret_cast<unsigned char const*>(pattern.characters_without_null_termination()),
|
||||
pattern.length(),
|
||||
RustRegex* regex = nullptr;
|
||||
if (pattern.has_ascii_storage()) {
|
||||
auto ascii = pattern.ascii_span();
|
||||
regex = rust_regex_compile_ascii(
|
||||
reinterpret_cast<unsigned char const*>(ascii.data()),
|
||||
ascii.size(),
|
||||
flags,
|
||||
&error_ptr,
|
||||
&error_len);
|
||||
} else {
|
||||
auto utf16 = pattern.utf16_span();
|
||||
regex = rust_regex_compile(
|
||||
reinterpret_cast<unsigned short const*>(utf16.data()),
|
||||
utf16.size(),
|
||||
flags,
|
||||
&error_ptr,
|
||||
&error_len);
|
||||
}
|
||||
if (!regex) {
|
||||
String error_message = "Invalid pattern"_string;
|
||||
if (error_ptr) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class REGEX_API CompiledRustRegex {
|
|||
AK_MAKE_NONCOPYABLE(CompiledRustRegex);
|
||||
|
||||
public:
|
||||
static ErrorOr<CompiledRustRegex, String> compile(StringView pattern, RustRegexFlags flags);
|
||||
static ErrorOr<CompiledRustRegex, String> compile(Utf16View pattern, RustRegexFlags flags);
|
||||
|
||||
~CompiledRustRegex();
|
||||
CompiledRustRegex(CompiledRustRegex&& other);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibGfx/DecodedImageFrame.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
|
@ -279,22 +280,26 @@ Optional<regex::ECMAScriptRegex> HTMLInputElement::compiled_pattern_regular_expr
|
|||
return {};
|
||||
|
||||
// 2. Let pattern be the value of the pattern attribute of the element.
|
||||
auto pattern = maybe_pattern.release_value();
|
||||
auto pattern = Utf16String::from_utf8(maybe_pattern.release_value());
|
||||
|
||||
// 3. Let regexpCompletion be RegExpCreate(pattern, "v").
|
||||
regex::ECMAScriptCompileFlags compile_flags {};
|
||||
compile_flags.unicode_sets = true;
|
||||
auto regexp_completion = regex::ECMAScriptRegex::compile(pattern.bytes_as_string_view(), compile_flags);
|
||||
auto regexp_completion = regex::ECMAScriptRegex::compile(pattern.utf16_view(), compile_flags);
|
||||
|
||||
// 4. If regexpCompletion is an abrupt completion, then return nothing. The element has no compiled pattern regular expression.
|
||||
if (regexp_completion.is_error())
|
||||
return {};
|
||||
|
||||
// 5. Let anchoredPattern be the string "^(?:", followed by pattern, followed by ")$".
|
||||
auto anchored_pattern = MUST(String::formatted("^(?:{})$", pattern));
|
||||
Utf16StringBuilder anchored_pattern_builder;
|
||||
anchored_pattern_builder.append_ascii("^(?:"sv);
|
||||
anchored_pattern_builder.append(pattern.utf16_view());
|
||||
anchored_pattern_builder.append_ascii(")$"sv);
|
||||
auto anchored_pattern = anchored_pattern_builder.to_string();
|
||||
|
||||
// 6. Return ! RegExpCreate(anchoredPattern, "v").
|
||||
auto anchored = regex::ECMAScriptRegex::compile(anchored_pattern.bytes_as_string_view(), compile_flags);
|
||||
auto anchored = regex::ECMAScriptRegex::compile(anchored_pattern.utf16_view(), compile_flags);
|
||||
if (anchored.is_error())
|
||||
return {};
|
||||
return anchored.release_value();
|
||||
|
|
@ -3605,7 +3610,7 @@ bool HTMLInputElement::suffering_from_being_missing() const
|
|||
static regex::ECMAScriptRegex& valid_email_address_regex()
|
||||
{
|
||||
static NeverDestroyed<regex::ECMAScriptRegex> regex { MUST(regex::ECMAScriptRegex::compile(
|
||||
"^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"sv,
|
||||
"^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"_utf16,
|
||||
regex::ECMAScriptCompileFlags {})) };
|
||||
return *regex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <LibRegex/ECMAScriptRegex.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -12,7 +13,7 @@
|
|||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||
{
|
||||
AK::set_debug_enabled(false);
|
||||
auto pattern = StringView(static_cast<unsigned char const*>(data), size);
|
||||
[[maybe_unused]] auto re = regex::ECMAScriptRegex::compile(pattern, {});
|
||||
auto pattern = Utf16String::from_utf8_with_replacement_character(StringView(static_cast<unsigned char const*>(data), size));
|
||||
[[maybe_unused]] auto re = regex::ECMAScriptRegex::compile(pattern.utf16_view(), {});
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,25 @@
|
|||
|
||||
#include <LibRegex/ECMAScriptRegex.h>
|
||||
|
||||
static ErrorOr<regex::ECMAScriptRegex, String> compile_regex_result(Utf16View pattern, regex::ECMAScriptCompileFlags flags = {})
|
||||
{
|
||||
return regex::ECMAScriptRegex::compile(pattern, flags);
|
||||
}
|
||||
|
||||
static ErrorOr<regex::ECMAScriptRegex, String> compile_regex_result(StringView pattern, regex::ECMAScriptCompileFlags flags = {})
|
||||
{
|
||||
auto utf16_pattern = Utf16String::from_utf8(pattern);
|
||||
return compile_regex_result(utf16_pattern.utf16_view(), flags);
|
||||
}
|
||||
|
||||
static regex::ECMAScriptRegex compile_regex(StringView pattern, regex::ECMAScriptCompileFlags flags = {})
|
||||
{
|
||||
return MUST(regex::ECMAScriptRegex::compile(pattern, flags));
|
||||
return MUST(compile_regex_result(pattern, flags));
|
||||
}
|
||||
|
||||
static bool compile_succeeds(StringView pattern, regex::ECMAScriptCompileFlags flags = {})
|
||||
{
|
||||
return !regex::ECMAScriptRegex::compile(pattern, flags).is_error();
|
||||
return !compile_regex_result(pattern, flags).is_error();
|
||||
}
|
||||
|
||||
static bool matches(StringView pattern, StringView subject, regex::ECMAScriptCompileFlags flags = {})
|
||||
|
|
@ -55,13 +66,13 @@ static void expect_capture_unmatched(regex::ECMAScriptRegex const& regex, unsign
|
|||
|
||||
TEST_CASE(compile_rejects_invalid_pattern)
|
||||
{
|
||||
auto regex = regex::ECMAScriptRegex::compile("("sv, {});
|
||||
auto regex = compile_regex_result("("sv, {});
|
||||
EXPECT(regex.is_error());
|
||||
}
|
||||
|
||||
TEST_CASE(exec_tracks_named_capture_slots)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(?<word>foo)(bar)"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(?<word>foo)(bar)"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.capture_count(), 2u);
|
||||
EXPECT_EQ(regex.total_groups(), 3u);
|
||||
|
|
@ -80,7 +91,7 @@ TEST_CASE(exec_tracks_named_capture_slots)
|
|||
|
||||
TEST_CASE(exec_reports_unmatched_optional_groups)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(foo)?bar"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(foo)?bar"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.exec(u"bar"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.capture_slot(0), 0);
|
||||
|
|
@ -91,7 +102,7 @@ TEST_CASE(exec_reports_unmatched_optional_groups)
|
|||
|
||||
TEST_CASE(ascii_backed_inputs_preserve_match_results)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(?<word>foo)(bar)"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(?<word>foo)(bar)"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.exec("foobar"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.capture_slot(0), 0);
|
||||
|
|
@ -111,7 +122,7 @@ TEST_CASE(ascii_backed_inputs_preserve_match_results)
|
|||
|
||||
TEST_CASE(test_honors_ignore_case)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("casesensitive"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("casesensitive"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.test(u"CaseSensitive"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.test(u"something else"sv, 0), regex::MatchResult::NoMatch);
|
||||
|
|
@ -119,7 +130,7 @@ TEST_CASE(test_honors_ignore_case)
|
|||
|
||||
TEST_CASE(ascii_ignore_case_literal_search_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("zfvr"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("zfvr"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.exec("...ZFVR..."sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.capture_slot(0), 3);
|
||||
|
|
@ -133,7 +144,7 @@ TEST_CASE(ascii_ignore_case_literal_search_preserves_behavior)
|
|||
|
||||
TEST_CASE(ascii_ignore_case_literal_search_handles_punctuation_prefixes)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("##yv22##"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("##yv22##"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.find_all("##YV22## and ##yv22##"sv, 0), 2);
|
||||
EXPECT_EQ(regex.find_all_match(0).start, 0);
|
||||
|
|
@ -144,7 +155,7 @@ TEST_CASE(ascii_ignore_case_literal_search_handles_punctuation_prefixes)
|
|||
|
||||
TEST_CASE(ascii_ignore_case_literal_alternation_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("##yv22##|zfvr|puebzr"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("##yv22##|zfvr|puebzr"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.find_all("##YV22## zFVr PUEBZR"sv, 0), 3);
|
||||
EXPECT_EQ(regex.find_all_match(0).start, 0);
|
||||
|
|
@ -157,7 +168,7 @@ TEST_CASE(ascii_ignore_case_literal_alternation_preserves_behavior)
|
|||
|
||||
TEST_CASE(ascii_ignore_case_literal_alternation_respects_source_order)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("foo|f"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("foo|f"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.exec("FoO"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.capture_slot(0), 0);
|
||||
|
|
@ -166,7 +177,7 @@ TEST_CASE(ascii_ignore_case_literal_alternation_respects_source_order)
|
|||
|
||||
TEST_CASE(unicode_ignore_case_literal_alternation_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("s|k"sv, { .ignore_case = true, .unicode = true }));
|
||||
auto regex = MUST(compile_regex_result("s|k"sv, { .ignore_case = true, .unicode = true }));
|
||||
|
||||
EXPECT_EQ(regex.test(u"\u017F"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.test(u"\u212A"sv, 0), regex::MatchResult::Match);
|
||||
|
|
@ -174,7 +185,7 @@ TEST_CASE(unicode_ignore_case_literal_alternation_preserves_behavior)
|
|||
|
||||
TEST_CASE(word_boundary_literal_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("\\bfoo\\b"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("\\bfoo\\b"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.find_all("foo foo-bar barfoo foo2 _foo foo_"sv, 0), 2);
|
||||
EXPECT_EQ(regex.find_all_match(0).start, 0);
|
||||
|
|
@ -185,7 +196,7 @@ TEST_CASE(word_boundary_literal_preserves_behavior)
|
|||
|
||||
TEST_CASE(ascii_ignore_case_word_boundary_literal_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("\\bzfvr\\b"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("\\bzfvr\\b"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.find_all("ZFVR zfvr1 _ZFVR zFVr"sv, 0), 2);
|
||||
EXPECT_EQ(regex.find_all_match(0).start, 0);
|
||||
|
|
@ -196,14 +207,14 @@ TEST_CASE(ascii_ignore_case_word_boundary_literal_preserves_behavior)
|
|||
|
||||
TEST_CASE(unicode_ignore_case_word_boundary_literal_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("\\bk\\b"sv, { .ignore_case = true, .unicode = true }));
|
||||
auto regex = MUST(compile_regex_result("\\bk\\b"sv, { .ignore_case = true, .unicode = true }));
|
||||
|
||||
EXPECT_EQ(regex.test(u"\u212A"sv, 0), regex::MatchResult::Match);
|
||||
}
|
||||
|
||||
TEST_CASE(mixed_positive_class_with_word_builtin_preserves_legacy_ignore_case_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("[\\w\\$]+"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("[\\w\\$]+"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.test("AZ_09$"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.test(u"\u017F"sv, 0), regex::MatchResult::NoMatch);
|
||||
|
|
@ -212,7 +223,7 @@ TEST_CASE(mixed_positive_class_with_word_builtin_preserves_legacy_ignore_case_be
|
|||
|
||||
TEST_CASE(mixed_positive_class_with_digit_builtin_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("[A-Z\\d-]+"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("[A-Z\\d-]+"sv, { .ignore_case = true }));
|
||||
|
||||
EXPECT_EQ(regex.test("ABC-123"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.test("abc"sv, 0), regex::MatchResult::Match);
|
||||
|
|
@ -221,7 +232,7 @@ TEST_CASE(mixed_positive_class_with_digit_builtin_preserves_behavior)
|
|||
|
||||
TEST_CASE(find_all_returns_non_overlapping_matches)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("aba"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("aba"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.find_all(u"aba aba"sv, 0), 2);
|
||||
EXPECT_EQ(regex.find_all_match(0).start, 0);
|
||||
|
|
@ -232,7 +243,7 @@ TEST_CASE(find_all_returns_non_overlapping_matches)
|
|||
|
||||
TEST_CASE(unicode_property_matching_works)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("\\p{ASCII}+"sv, { .unicode = true }));
|
||||
auto regex = MUST(compile_regex_result("\\p{ASCII}+"sv, { .unicode = true }));
|
||||
|
||||
EXPECT_EQ(regex.test(u"ASCII"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.test(u"😀"sv, 0), regex::MatchResult::NoMatch);
|
||||
|
|
@ -240,7 +251,7 @@ TEST_CASE(unicode_property_matching_works)
|
|||
|
||||
TEST_CASE(end_anchored_suffix_patterns_preserve_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(.*)\\/client-(.*)\\.js$"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(.*)\\/client-(.*)\\.js$"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.test(u"https://cdn.example.com/assets/client-main.js"sv, 0), regex::MatchResult::Match);
|
||||
EXPECT_EQ(regex.test(u"<script src=\"/assets/client-main.js\"></script>"sv, 0), regex::MatchResult::NoMatch);
|
||||
|
|
@ -248,7 +259,7 @@ TEST_CASE(end_anchored_suffix_patterns_preserve_behavior)
|
|||
|
||||
TEST_CASE(leading_start_or_separator_prefix_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(?:^|;)\\s*foo=([^;]*)"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(?:^|;)\\s*foo=([^;]*)"sv, {}));
|
||||
|
||||
{
|
||||
auto subject = Utf16String::from_utf8("foo=bar"sv);
|
||||
|
|
@ -267,7 +278,7 @@ TEST_CASE(leading_start_or_separator_prefix_preserves_behavior)
|
|||
|
||||
TEST_CASE(required_literal_prefilter_preserves_assignment_extractors)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(?:^|;)\\s*foo=([^;]*)"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(?:^|;)\\s*foo=([^;]*)"sv, {}));
|
||||
|
||||
{
|
||||
auto subject = Utf16String::from_utf8("a=1; bar=baz; foo=qux"sv);
|
||||
|
|
@ -280,7 +291,7 @@ TEST_CASE(required_literal_prefilter_preserves_assignment_extractors)
|
|||
|
||||
TEST_CASE(ascii_ignore_case_required_literal_prefilter_preserves_behavior)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("\\bfoo\\s*=\\s*([^;]*)"sv, { .ignore_case = true }));
|
||||
auto regex = MUST(compile_regex_result("\\bfoo\\s*=\\s*([^;]*)"sv, { .ignore_case = true }));
|
||||
|
||||
{
|
||||
auto subject = Utf16String::from_utf8("FOO = Bar"sv);
|
||||
|
|
@ -293,7 +304,7 @@ TEST_CASE(ascii_ignore_case_required_literal_prefilter_preserves_behavior)
|
|||
|
||||
TEST_CASE(required_literal_prefilter_handles_common_substrings_across_alternatives)
|
||||
{
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile("(\\$\\{name\\})|(\\$name\\b)"sv, {}));
|
||||
auto regex = MUST(compile_regex_result("(\\$\\{name\\})|(\\$name\\b)"sv, {}));
|
||||
|
||||
EXPECT_EQ(regex.find_all("${name} $name"sv, 0), 2);
|
||||
EXPECT_EQ(regex.find_all_match(0).start, 0);
|
||||
|
|
@ -327,7 +338,7 @@ TEST_CASE(required_literal_prefilter_compiles_long_literal_alternations)
|
|||
pattern_builder.append("c"sv);
|
||||
auto pattern = MUST(pattern_builder.to_string());
|
||||
|
||||
auto regex = MUST(regex::ECMAScriptRegex::compile(pattern, {}));
|
||||
auto regex = MUST(compile_regex_result(pattern, {}));
|
||||
|
||||
StringBuilder subject_builder;
|
||||
subject_builder.append(shared_prefix);
|
||||
|
|
|
|||
Loading…
Reference in a new issue