1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 08:57:34 +00:00

LibJS: Implement RegExp legacy static properties

RegExp legacy static properties Spec url is https://github.com/tc39/proposal-regexp-legacy-features
This commit is contained in:
leeight 2022-10-17 08:59:27 +08:00 committed by Linus Groh
parent 5e2fe7e2bf
commit 0d96468e9b
12 changed files with 726 additions and 9 deletions

View file

@ -3510,7 +3510,14 @@ Completion RegExpLiteral::execute(Interpreter& interpreter) const
// 3. Return ! RegExpCreate(pattern, flags).
Regex<ECMA262> regex(parsed_regex(), parsed_pattern(), parsed_flags());
return Value { RegExpObject::create(realm, move(regex), move(pattern), move(flags)) };
// NOTE: We bypass RegExpCreate and subsequently RegExpAlloc as an optimization to use the already parsed values.
auto* regexp_object = RegExpObject::create(realm, move(regex), move(pattern), move(flags));
// RegExpAlloc has these two steps from the 'Legacy RegExp features' proposal.
regexp_object->set_realm(*vm.current_realm());
// We don't need to check 'If SameValue(newTarget, thisRealm.[[Intrinsics]].[[%RegExp%]]) is true'
// here as we know RegExpCreate calls RegExpAlloc with %RegExp% for newTarget.
regexp_object->set_legacy_features_enabled(true);
return Value { regexp_object };
}
void ArrayExpression::dump(int indent) const

View file

@ -169,6 +169,7 @@ set(SOURCES
Runtime/Reference.cpp
Runtime/ReflectObject.cpp
Runtime/RegExpConstructor.cpp
Runtime/RegExpLegacyStaticProperties.cpp
Runtime/RegExpObject.cpp
Runtime/RegExpPrototype.cpp
Runtime/RegExpStringIterator.cpp

View file

@ -18,6 +18,15 @@ namespace JS {
P(__defineSetter__) \
P(__lookupGetter__) \
P(__lookupSetter__) \
P($1) \
P($2) \
P($3) \
P($4) \
P($5) \
P($6) \
P($7) \
P($8) \
P($9) \
P(Atomics) \
P(BYTES_PER_ELEMENT) \
P(BigInt) \
@ -319,6 +328,9 @@ namespace JS {
P(log1p) \
P(log2) \
P(log10) \
P(lastMatch) \
P(lastParen) \
P(leftContext) \
P(map) \
P(max) \
P(maximize) \
@ -403,6 +415,7 @@ namespace JS {
P(roundingIncrement) \
P(roundingMode) \
P(roundingPriority) \
P(rightContext) \
P(script) \
P(seal) \
P(second) \
@ -567,6 +580,11 @@ struct CommonPropertyNames {
PropertyKey return_ { "return", PropertyKey::StringMayBeNumber::No };
PropertyKey throw_ { "throw", PropertyKey::StringMayBeNumber::No };
PropertyKey xor_ { "xor", PropertyKey::StringMayBeNumber::No };
PropertyKey inputAlias { "$_", PropertyKey::StringMayBeNumber::No };
PropertyKey lastMatchAlias { "$&", PropertyKey::StringMayBeNumber::No };
PropertyKey lastParenAlias { "$+", PropertyKey::StringMayBeNumber::No };
PropertyKey leftContextAlias { "$`", PropertyKey::StringMayBeNumber::No };
PropertyKey rightContextAlias { "$'", PropertyKey::StringMayBeNumber::No };
#define __ENUMERATE(x) PropertyKey x { #x, PropertyKey::StringMayBeNumber::No };
ENUMERATE_STANDARD_PROPERTY_NAMES(__ENUMERATE)
#undef __ENUMERATE

View file

@ -304,7 +304,12 @@
M(BadArgCountMany, "{}() needs {} arguments") \
M(NotEnoughMemoryToAllocate, "Not enough memory to allocate {} bytes") \
M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'") \
M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'")
M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'") \
M(SetLegacyRegExpStaticPropertyThisValueMismatch, "Legacy RegExp static property setter " \
"must be called with the RegExp constructor for the this value") \
M(GetLegacyRegExpStaticPropertyThisValueMismatch, "Legacy RegExp static property getter " \
"must be called with the RegExp constructor for the this value") \
M(GetLegacyRegExpStaticPropertyValueEmpty, "Legacy RegExp static property getter value is empty")
namespace JS {

View file

@ -8,6 +8,7 @@
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/RegExpConstructor.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
@ -27,6 +28,27 @@ void RegExpConstructor::initialize(Realm& realm)
define_native_accessor(realm, *vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
define_direct_property(vm.names.length, Value(2), Attribute::Configurable);
// Additional properties of the RegExp constructor, https://github.com/tc39/proposal-regexp-legacy-features#additional-properties-of-the-regexp-constructor
define_native_accessor(realm, vm.names.input, input_getter, input_setter, Attribute::Configurable);
define_native_accessor(realm, vm.names.inputAlias, input_alias_getter, input_alias_setter, Attribute::Configurable);
define_native_accessor(realm, vm.names.lastMatch, last_match_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.lastMatchAlias, last_match_alias_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.lastParen, last_paren_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.lastParenAlias, last_paren_alias_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.leftContext, left_context_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.leftContextAlias, left_context_alias_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.rightContext, right_context_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.rightContextAlias, right_context_alias_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$1, group_1_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$2, group_2_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$3, group_3_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$4, group_4_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$5, group_5_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$6, group_6_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$7, group_7_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$8, group_8_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.$9, group_9_getter, {}, Attribute::Configurable);
}
// 22.2.3.1 RegExp ( pattern, flags ), https://tc39.es/ecma262/#sec-regexp-pattern-flags
@ -125,4 +147,138 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::symbol_species_getter)
return vm.this_value();
}
// get RegExp.input, https://github.com/tc39/proposal-regexp-legacy-features#get-regexpinput
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_getter)
{
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpInput]]).
auto property_getter = &RegExpLegacyStaticProperties::input;
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
}
// get RegExp.$_, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp_
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_alias_getter)
{
// Keep the same implementation with `get RegExp.input`
return input_getter(vm);
}
// set RegExp.input, https://github.com/tc39/proposal-regexp-legacy-features#set-regexpinput--val
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_setter)
{
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
// 1. Perform ? SetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpInput]], val).
auto property_setter = &RegExpLegacyStaticProperties::set_input;
TRY(set_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_setter, vm.argument(0)));
return js_undefined();
}
// set RegExp.$_, https://github.com/tc39/proposal-regexp-legacy-features#set-regexp_---val
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_alias_setter)
{
// Keep the same implementation with `set RegExp.input`
return input_setter(vm);
}
// get RegExp.lastMatch, https://github.com/tc39/proposal-regexp-legacy-features#get-regexplastmatch
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_match_getter)
{
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpLastMatch]]).
auto property_getter = &RegExpLegacyStaticProperties::last_match;
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
}
// get RegExp.$&, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_match_alias_getter)
{
// Keep the same implementation with `get RegExp.lastMatch`
return last_match_getter(vm);
}
// get RegExp.lastParen, https://github.com/tc39/proposal-regexp-legacy-features#get-regexplastparen
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_paren_getter)
{
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpLastParen]]).
auto property_getter = &RegExpLegacyStaticProperties::last_paren;
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
}
// get RegExp.$+, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp-1
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_paren_alias_getter)
{
// Keep the same implementation with `get RegExp.lastParen`
return last_paren_getter(vm);
}
// get RegExp.leftContext, https://github.com/tc39/proposal-regexp-legacy-features#get-regexpleftcontext
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::left_context_getter)
{
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpLeftContext]]).
auto property_getter = &RegExpLegacyStaticProperties::left_context;
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
}
// get RegExp.$`, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp-2
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::left_context_alias_getter)
{
// Keep the same implementation with `get RegExp.leftContext`
return left_context_getter(vm);
}
// get RegExp.rightContext, https://github.com/tc39/proposal-regexp-legacy-features#get-regexprightcontext
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::right_context_getter)
{
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpRightContext]]).
auto property_getter = &RegExpLegacyStaticProperties::right_context;
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
}
// get RegExp.$', https://github.com/tc39/proposal-regexp-legacy-features#get-regexp-3
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::right_context_alias_getter)
{
// Keep the same implementation with `get RegExp.rightContext`
return right_context_getter(vm);
}
#define DEFINE_REGEXP_GROUP_GETTER(n) \
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::group_##n##_getter) \
{ \
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor(); \
\
/* 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpParen##n##]]).*/ \
auto property_getter = &RegExpLegacyStaticProperties::$##n; \
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter)); \
}
// get RegExp.$1, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp1
DEFINE_REGEXP_GROUP_GETTER(1);
// get RegExp.$2, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp2
DEFINE_REGEXP_GROUP_GETTER(2);
// get RegExp.$3, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp3
DEFINE_REGEXP_GROUP_GETTER(3);
// get RegExp.$4, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp4
DEFINE_REGEXP_GROUP_GETTER(4);
// get RegExp.$5, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp5
DEFINE_REGEXP_GROUP_GETTER(5);
// get RegExp.$6, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp6
DEFINE_REGEXP_GROUP_GETTER(6);
// get RegExp.$7, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp7
DEFINE_REGEXP_GROUP_GETTER(7);
// get RegExp.$8, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp8
DEFINE_REGEXP_GROUP_GETTER(8);
// get RegExp.$9, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp9
DEFINE_REGEXP_GROUP_GETTER(9);
#undef DEFINE_REGEXP_GROUP_GETTER
}

View file

@ -7,6 +7,7 @@
#pragma once
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/RegExpLegacyStaticProperties.h>
namespace JS {
@ -20,12 +21,37 @@ public:
virtual ThrowCompletionOr<Value> call() override;
virtual ThrowCompletionOr<Object*> construct(FunctionObject& new_target) override;
RegExpLegacyStaticProperties& legacy_static_properties() { return m_legacy_static_properties; }
private:
explicit RegExpConstructor(Realm&);
virtual bool has_constructor() const override { return true; }
JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter);
JS_DECLARE_NATIVE_FUNCTION(input_getter);
JS_DECLARE_NATIVE_FUNCTION(input_alias_getter);
JS_DECLARE_NATIVE_FUNCTION(input_setter);
JS_DECLARE_NATIVE_FUNCTION(input_alias_setter);
JS_DECLARE_NATIVE_FUNCTION(last_match_getter);
JS_DECLARE_NATIVE_FUNCTION(last_match_alias_getter);
JS_DECLARE_NATIVE_FUNCTION(last_paren_getter);
JS_DECLARE_NATIVE_FUNCTION(last_paren_alias_getter);
JS_DECLARE_NATIVE_FUNCTION(left_context_getter);
JS_DECLARE_NATIVE_FUNCTION(left_context_alias_getter);
JS_DECLARE_NATIVE_FUNCTION(right_context_getter);
JS_DECLARE_NATIVE_FUNCTION(right_context_alias_getter);
JS_DECLARE_NATIVE_FUNCTION(group_1_getter);
JS_DECLARE_NATIVE_FUNCTION(group_2_getter);
JS_DECLARE_NATIVE_FUNCTION(group_3_getter);
JS_DECLARE_NATIVE_FUNCTION(group_4_getter);
JS_DECLARE_NATIVE_FUNCTION(group_5_getter);
JS_DECLARE_NATIVE_FUNCTION(group_6_getter);
JS_DECLARE_NATIVE_FUNCTION(group_7_getter);
JS_DECLARE_NATIVE_FUNCTION(group_8_getter);
JS_DECLARE_NATIVE_FUNCTION(group_9_getter);
RegExpLegacyStaticProperties m_legacy_static_properties;
};
}

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2022, LI YUBEI <leeight@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/RegExpConstructor.h>
#include <LibJS/Runtime/RegExpLegacyStaticProperties.h>
#include <LibJS/Runtime/VM.h>
namespace JS {
void RegExpLegacyStaticProperties::invalidate()
{
m_input = {};
m_last_match = {};
m_last_paren = {};
m_left_context = {};
m_right_context = {};
m_$1 = {};
m_$2 = {};
m_$3 = {};
m_$4 = {};
m_$5 = {};
m_$6 = {};
m_$7 = {};
m_$8 = {};
m_$9 = {};
}
// GetLegacyRegExpStaticProperty( C, thisValue, internalSlotName ), https://github.com/tc39/proposal-regexp-legacy-features#getlegacyregexpstaticproperty-c-thisvalue-internalslotname-
ThrowCompletionOr<Value> get_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, Optional<String> const& (RegExpLegacyStaticProperties::*property_getter)() const)
{
// 1. Assert C is an object that has an internal slot named internalSlotName.
// 2. If SameValue(C, thisValue) is false, throw a TypeError exception.
if (!same_value(&constructor, this_value))
return vm.throw_completion<TypeError>(ErrorType::GetLegacyRegExpStaticPropertyThisValueMismatch);
// 3. Let val be the value of the internal slot of C named internalSlotName.
auto val = (constructor.legacy_static_properties().*property_getter)();
// 4. If val is empty, throw a TypeError exception.
if (!val.has_value())
return vm.throw_completion<TypeError>(ErrorType::GetLegacyRegExpStaticPropertyValueEmpty);
// 5. Return val.
return js_string(vm, val.release_value());
}
// SetLegacyRegExpStaticProperty( C, thisValue, internalSlotName, val ), https://github.com/tc39/proposal-regexp-legacy-features#setlegacyregexpstaticproperty-c-thisvalue-internalslotname-val-
ThrowCompletionOr<void> set_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, void (RegExpLegacyStaticProperties::*property_setter)(String), Value value)
{
// 1. Assert C is an object that has an internal slot named internalSlotName.
// 2. If SameValue(C, thisValue) is false, throw a TypeError exception.
if (!same_value(&constructor, this_value))
return vm.throw_completion<TypeError>(ErrorType::SetLegacyRegExpStaticPropertyThisValueMismatch);
// 3. Let strVal be ? ToString(val).
auto str_value = TRY(value.to_string(vm));
// 4. Set the value of the internal slot of C named internalSlotName to strVal.
(constructor.legacy_static_properties().*property_setter)(str_value);
return {};
}
// UpdateLegacyRegExpStaticProperties ( C, S, startIndex, endIndex, capturedValues ), https://github.com/tc39/proposal-regexp-legacy-features#updatelegacyregexpstaticproperties--c-s-startindex-endindex-capturedvalues-
void update_legacy_regexp_static_properties(RegExpConstructor& constructor, Utf16String const& string, size_t start_index, size_t end_index, Vector<String> const& captured_values)
{
auto& legacy_static_properties = constructor.legacy_static_properties();
// 1. Assert: C is an Object that has a [[RegExpInput]] internal slot.
// 2. Assert: Type(S) is String.
// 3. Let len be the number of code units in S.
auto len = string.length_in_code_units();
// 4. Assert: startIndex and endIndex are integers such that 0 ≤ startIndex ≤ endIndex ≤ len.
VERIFY(start_index <= end_index);
VERIFY(end_index <= len);
// 5. Assert: capturedValues is a List of Strings.
// 6. Let n be the number of elements in capturedValues.
auto group_count = captured_values.size();
// 7. Set the value of Cs [[RegExpInput]] internal slot to S.
legacy_static_properties.set_input(string.to_utf8());
// 8. Set the value of Cs [[RegExpLastMatch]] internal slot to a String whose length is endIndex - startIndex and containing the code units from S with indices startIndex through endIndex - 1, in ascending order.
auto last_match = string.view().substring_view(start_index, end_index - start_index);
legacy_static_properties.set_last_match(last_match.to_utf8());
// 9. If n > 0, set the value of Cs [[RegExpLastParen]] internal slot to the last element of capturedValues.
if (group_count > 0) {
auto item = captured_values[group_count - 1];
legacy_static_properties.set_last_paren(item);
}
// 10. Else, set the value of Cs [[RegExpLastParen]] internal slot to the empty String.
else {
legacy_static_properties.set_last_paren(String::empty());
}
// 11. Set the value of Cs [[RegExpLeftContext]] internal slot to a String whose length is startIndex and containing the code units from S with indices 0 through startIndex - 1, in ascending order.
auto left_context = string.view().substring_view(0, start_index);
legacy_static_properties.set_left_context(left_context.to_utf8());
// 12. Set the value of Cs [[RegExpRightContext]] internal slot to a String whose length is len - endIndex and containing the code units from S with indices endIndex through len - 1, in ascending order.
auto right_context = string.view().substring_view(end_index, len - end_index);
legacy_static_properties.set_right_context(right_context.to_utf8());
// 13. For each integer i such that 1 ≤ i ≤ 9
for (size_t i = 1; i <= 9; i++) {
auto value = String::empty();
// If i ≤ n, set the value of Cs [[RegExpPareni]] internal slot to the ith element of capturedValues.
if (i <= group_count) {
value = captured_values[i - 1];
}
// Else, set the value of Cs [[RegExpPareni]] internal slot to the empty String.
else {
// It's already an empty string
}
if (i == 1) {
legacy_static_properties.set_$1(value);
} else if (i == 2) {
legacy_static_properties.set_$2(value);
} else if (i == 3) {
legacy_static_properties.set_$3(value);
} else if (i == 4) {
legacy_static_properties.set_$4(value);
} else if (i == 5) {
legacy_static_properties.set_$5(value);
} else if (i == 6) {
legacy_static_properties.set_$6(value);
} else if (i == 7) {
legacy_static_properties.set_$7(value);
} else if (i == 8) {
legacy_static_properties.set_$8(value);
} else if (i == 9) {
legacy_static_properties.set_$9(value);
}
}
}
// InvalidateLegacyRegExpStaticProperties ( C ), https://github.com/tc39/proposal-regexp-legacy-features#invalidatelegacyregexpstaticproperties--c
void invalidate_legacy_regexp_static_properties(RegExpConstructor& constructor)
{
// 1. Assert: C is an Object that has a [[RegExpInput]] internal slot.
// 2. Set the value of the following internal slots of C to empty:
constructor.legacy_static_properties().invalidate();
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2022, LI YUBEI <leeight@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <LibJS/Forward.h>
namespace JS {
// https://github.com/tc39/proposal-regexp-legacy-features#regexp
// The %RegExp% instrinsic object, which is the builtin RegExp constructor, has the following additional internal slots:
// [[RegExpInput]]
// [[RegExpLastMatch]]
// [[RegExpLastParen]]
// [[RegExpLeftContext]]
// [[RegExpRightContext]]
// [[RegExpParen1]] ... [[RegExpParen9]]
class RegExpLegacyStaticProperties {
public:
Optional<String> const& input() const { return m_input; }
Optional<String> const& last_match() const { return m_last_match; }
Optional<String> const& last_paren() const { return m_last_paren; }
Optional<String> const& left_context() const { return m_left_context; }
Optional<String> const& right_context() const { return m_right_context; }
Optional<String> const& $1() const { return m_$1; }
Optional<String> const& $2() const { return m_$2; }
Optional<String> const& $3() const { return m_$3; }
Optional<String> const& $4() const { return m_$4; }
Optional<String> const& $5() const { return m_$5; }
Optional<String> const& $6() const { return m_$6; }
Optional<String> const& $7() const { return m_$7; }
Optional<String> const& $8() const { return m_$8; }
Optional<String> const& $9() const { return m_$9; }
void set_input(String input) { m_input = move(input); }
void set_last_match(String last_match) { m_last_match = move(last_match); }
void set_last_paren(String last_paren) { m_last_paren = move(last_paren); }
void set_left_context(String left_context) { m_left_context = move(left_context); }
void set_right_context(String right_context) { m_right_context = move(right_context); }
void set_$1(String value) { m_$1 = move(value); }
void set_$2(String value) { m_$2 = move(value); }
void set_$3(String value) { m_$3 = move(value); }
void set_$4(String value) { m_$4 = move(value); }
void set_$5(String value) { m_$5 = move(value); }
void set_$6(String value) { m_$6 = move(value); }
void set_$7(String value) { m_$7 = move(value); }
void set_$8(String value) { m_$8 = move(value); }
void set_$9(String value) { m_$9 = move(value); }
void invalidate();
private:
Optional<String> m_input;
Optional<String> m_last_match;
Optional<String> m_last_paren;
Optional<String> m_left_context;
Optional<String> m_right_context;
Optional<String> m_$1;
Optional<String> m_$2;
Optional<String> m_$3;
Optional<String> m_$4;
Optional<String> m_$5;
Optional<String> m_$6;
Optional<String> m_$7;
Optional<String> m_$8;
Optional<String> m_$9;
};
ThrowCompletionOr<void> set_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, void (RegExpLegacyStaticProperties::*property_setter)(String), Value value);
ThrowCompletionOr<Value> get_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, Optional<String> const& (RegExpLegacyStaticProperties::*property_getter)() const);
void update_legacy_regexp_static_properties(RegExpConstructor& constructor, Utf16String const& string, size_t start_index, size_t end_index, Vector<String> const& captured_values);
void invalidate_legacy_regexp_static_properties(RegExpConstructor& constructor);
}

View file

@ -152,6 +152,7 @@ void RegExpObject::initialize(Realm& realm)
{
auto& vm = this->vm();
Object::initialize(realm);
define_direct_property(vm.names.lastIndex, Value(0), Attribute::Writable);
}
@ -258,15 +259,34 @@ ThrowCompletionOr<NonnullGCPtr<RegExpObject>> regexp_create(VM& vm, Value patter
}
// 22.2.3.2 RegExpAlloc ( newTarget ), https://tc39.es/ecma262/#sec-regexpalloc
// 22.2.3.2 RegExpAlloc ( newTarget ), https://github.com/tc39/proposal-regexp-legacy-features#regexpalloc--newtarget-
ThrowCompletionOr<NonnullGCPtr<RegExpObject>> regexp_alloc(VM& vm, FunctionObject& new_target)
{
// 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[OriginalSource]], [[OriginalFlags]], [[RegExpRecord]], [[RegExpMatcher]] »).
auto* regexp_object = TRY(ordinary_create_from_constructor<RegExpObject>(vm, new_target, &Intrinsics::regexp_prototype));
// 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
// 2. Let thisRealm be the current Realm Record.
auto& this_realm = *vm.current_realm();
// 3. Set the value of objs [[Realm]] internal slot to thisRealm.
regexp_object->set_realm(this_realm);
// 4. If SameValue(newTarget, thisRealm.[[Intrinsics]].[[%RegExp%]]) is true, then
auto* regexp_constructor = this_realm.intrinsics().regexp_constructor();
if (same_value(&new_target, regexp_constructor)) {
// i. Set the value of objs [[LegacyFeaturesEnabled]] internal slot to true.
regexp_object->set_legacy_features_enabled(true);
}
// 5. Else,
else {
// i. Set the value of objs [[LegacyFeaturesEnabled]] internal slot to false.
regexp_object->set_legacy_features_enabled(false);
}
// 6. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
MUST(regexp_object->define_property_or_throw(vm.names.lastIndex, PropertyDescriptor { .writable = true, .enumerable = false, .configurable = false }));
// 3. Return obj.
// 7. Return obj.
return NonnullGCPtr { *regexp_object };
}

View file

@ -50,6 +50,11 @@ public:
String const& flags() const { return m_flags; }
Regex<ECMA262> const& regex() { return *m_regex; }
Regex<ECMA262> const& regex() const { return *m_regex; }
Realm& realm() { return *m_realm; }
Realm const& realm() const { return *m_realm; }
bool legacy_features_enabled() const { return m_legacy_features_enabled; }
void set_legacy_features_enabled(bool legacy_features_enabled) { m_legacy_features_enabled = legacy_features_enabled; }
void set_realm(Realm& realm) { m_realm = &realm; }
private:
RegExpObject(Object& prototype);
@ -57,6 +62,9 @@ private:
String m_pattern;
String m_flags;
bool m_legacy_features_enabled { false }; // [[LegacyFeaturesEnabled]]
// Note: This is initialized in RegExpAlloc, but will be non-null afterwards
GCPtr<Realm> m_realm; // [[Realm]]
Optional<Regex<ECMA262>> m_regex;
};

View file

@ -12,6 +12,7 @@
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/ErrorTypes.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/RegExpConstructor.h>
#include <LibJS/Runtime/RegExpObject.h>
@ -163,6 +164,7 @@ static Value make_match_indices_index_pair_array(VM& vm, Utf16View const& string
}
// 22.2.5.2.2 RegExpBuiltinExec ( R, S ), https://tc39.es/ecma262/#sec-regexpbuiltinexec
// 22.2.5.2.2 RegExpBuiltInExec ( R, S ), https://github.com/tc39/proposal-regexp-legacy-features#regexpbuiltinexec--r-s-
static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp_object, Utf16String string)
{
auto& realm = *vm.current_realm();
@ -172,7 +174,7 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
auto last_index_value = TRY(regexp_object.get(vm.names.lastIndex));
auto last_index = TRY(last_index_value.to_length(vm));
auto& regex = regexp_object.regex();
auto const& regex = regexp_object.regex();
// 3. Let flags be R.[[OriginalFlags]].
// 4. If flags contains "g", let global be true; else let global be false.
@ -263,6 +265,7 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
// 24. Let indices be a new empty List.
Vector<Optional<Match>> indices;
Vector<String> captured_values;
// 25. Let groupNames be a new empty List.
HashMap<FlyString, Match> group_names;
@ -296,6 +299,8 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
captured_value = js_undefined();
// ii. Append undefined to indices.
indices.append({});
// iii. Append capture to indices.
captured_values.append(String::empty());
}
// c. Else,
else {
@ -307,8 +312,10 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
// iv. Let capture be the Match { [[StartIndex]]: captureStart, [[EndIndex]: captureEnd }.
// v. Let capturedValue be ! GetMatchString(S, capture).
captured_value = js_string(vm, capture.view.u16_view());
// vi Append capture to indices.
// vi. Append capture to indices.
indices.append(Match::create(capture));
// vii. Append capturedValue to the end of capturedValues.
captured_values.append(capture.view.to_string());
}
// d. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(i)), capturedValue).
@ -332,6 +339,27 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
}
}
// https://github.com/tc39/proposal-regexp-legacy-features#regexpbuiltinexec--r-s-
// 5. Let thisRealm be the current Realm Record.
auto* this_realm = &realm;
// 6. Let rRealm be the value of R's [[Realm]] internal slot.
auto* regexp_object_realm = &regexp_object.realm();
// 7. If SameValue(thisRealm, rRealm) is true, then
if (this_realm == regexp_object_realm) {
// i. If the value of Rs [[LegacyFeaturesEnabled]] internal slot is true, then
if (regexp_object.legacy_features_enabled()) {
// a. Perform UpdateLegacyRegExpStaticProperties(%RegExp%, S, lastIndex, e, capturedValues).
auto* regexp_constructor = realm.intrinsics().regexp_constructor();
update_legacy_regexp_static_properties(*regexp_constructor, string, match_indices.start_index, match_indices.end_index, captured_values);
}
// ii. Else,
else {
// a. Perform InvalidateLegacyRegExpStaticProperties(%RegExp%).
auto* regexp_constructor = realm.intrinsics().regexp_constructor();
invalidate_legacy_regexp_static_properties(*regexp_constructor);
}
}
// 31. Perform ! CreateDataPropertyOrThrow(A, "groups", groups).
// NOTE: This step must be performed after the above loop in order for groups to be populated.
Value groups = has_groups ? groups_object : js_undefined();
@ -1060,6 +1088,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::to_string)
}
// B.2.4.1 RegExp.prototype.compile ( pattern, flags ), https://tc39.es/ecma262/#sec-regexp.prototype.compile
// B.2.4.1 RegExp.prototype.compile ( pattern, flags ), https://github.com/tc39/proposal-regexp-legacy-features#regexpprototypecompile--pattern-flags-
JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::compile)
{
auto pattern = vm.argument(0);
@ -1069,7 +1098,21 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::compile)
// 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]).
auto* regexp_object = TRY(typed_this_object(vm));
// 3. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then
// 3. Let thisRealm be the current Realm Record.
auto* this_realm = vm.current_realm();
// 4. Let oRealm be the value of Os [[Realm]] internal slot.
auto* regexp_object_realm = &regexp_object->realm();
// 5. If SameValue(thisRealm, oRealm) is false, throw a TypeError exception.
if (this_realm != regexp_object_realm)
return vm.throw_completion<TypeError>(ErrorType::RegExpCompileError, "thisRealm and oRealm is not same value");
// 6. If the value of Rs [[LegacyFeaturesEnabled]] internal slot is false, throw a TypeError exception.
if (!regexp_object->legacy_features_enabled())
return vm.throw_completion<TypeError>(ErrorType::RegExpCompileError, "legacy features is not enabled");
// 7. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then
if (pattern.is_object() && is<RegExpObject>(pattern.as_object())) {
// a. If flags is not undefined, throw a TypeError exception.
if (!flags.is_undefined())
@ -1083,11 +1126,11 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::compile)
// c. Let F be pattern.[[OriginalFlags]].
flags = js_string(vm, regexp_pattern.flags());
}
// 4. Else,
// 8. Else,
// a. Let P be pattern.
// b. Let F be flags.
// 5. Return ? RegExpInitialize(O, P, F).
// 9. Return ? RegExpInitialize(O, P, F).
return TRY(regexp_object->regexp_initialize(vm, pattern, flags));
}

View file

@ -0,0 +1,198 @@
// NOTE: This file came from https://github.com/v8/v8/blob/main/test/mjsunit/regexp-static.js
// and was modified to match LibJS test framework.
test("basic functionality", () => {
var re = /((\d+)\.(\d+))/;
var s = "abc123.456def";
re.exec(s);
expect(RegExp.input).toBe(s);
expect(RegExp["$_"]).toBe(s);
expect(RegExp.lastMatch).toBe("123.456");
expect(RegExp["$&"]).toBe("123.456");
expect(RegExp.lastParen).toBe("456");
expect(RegExp["$+"]).toBe("456");
expect(RegExp.leftContext).toBe("abc");
expect(RegExp["$`"]).toBe("abc");
expect(RegExp.rightContext).toBe("def");
expect(RegExp["$'"]).toBe("def");
expect(RegExp.$1).toBe("123.456");
expect(RegExp.$2).toBe("123");
expect(RegExp.$3).toBe("456");
expect(RegExp.$4).toBe("");
expect(RegExp.$5).toBe("");
expect(RegExp.$6).toBe("");
expect(RegExp.$7).toBe("");
expect(RegExp.$8).toBe("");
expect(RegExp.$9).toBe("");
});
test("They should be read only", () => {
RegExp["$1"] = "fisk";
expect(RegExp.$1).toBe("123.456");
RegExp.input = Number();
expect(typeof RegExp.input).toBe(typeof String());
});
test("They should be read only (strict mode)", () => {
"use strict";
try {
RegExp["$1"] = "fisk";
expect(true).toBeFalse(); // SHOULD NOT REACH HERE
} catch (e) {
expect(String(e)).toBe("TypeError: Cannot set property '$1' of [object RegExpConstructor]");
expect(RegExp.$1).toBe("123.456");
}
RegExp.input = Number();
expect(typeof RegExp.input).toBe(typeof String());
});
test("the original accessor vs the alias accessor", () => {
expect(Object.getOwnPropertyDescriptor(RegExp, "input").get).not.toBe(
Object.getOwnPropertyDescriptor(RegExp, "$_").get
);
expect(Object.getOwnPropertyDescriptor(RegExp, "input").set).not.toBe(
Object.getOwnPropertyDescriptor(RegExp, "$_").set
);
expect(Object.getOwnPropertyDescriptor(RegExp, "lastMatch").get).not.toBe(
Object.getOwnPropertyDescriptor(RegExp, "$&").get
);
expect(Object.getOwnPropertyDescriptor(RegExp, "lastParen").get).not.toBe(
Object.getOwnPropertyDescriptor(RegExp, "$+").get
);
expect(Object.getOwnPropertyDescriptor(RegExp, "leftContext").get).not.toBe(
Object.getOwnPropertyDescriptor(RegExp, "$`").get
);
expect(Object.getOwnPropertyDescriptor(RegExp, "rightContext").get).not.toBe(
Object.getOwnPropertyDescriptor(RegExp, "$'").get
);
});
test("match should all behave as if exec were called.", () => {
var re = /((\d+)\.(\d+))/;
var s = "ghi789.012jkl";
s.match(re);
expect(RegExp.input).toBe(s);
expect(RegExp["$_"]).toBe(s);
expect(RegExp.lastMatch).toBe("789.012");
expect(RegExp["$&"]).toBe("789.012");
expect(RegExp.lastParen).toBe("012");
expect(RegExp["$+"]).toBe("012");
expect(RegExp.leftContext).toBe("ghi");
expect(RegExp["$`"]).toBe("ghi");
expect(RegExp.rightContext).toBe("jkl");
expect(RegExp["$'"]).toBe("jkl");
expect(RegExp.$1).toBe("789.012");
expect(RegExp.$2).toBe("789");
expect(RegExp.$3).toBe("012");
expect(RegExp.$4).toBe("");
expect(RegExp.$5).toBe("");
expect(RegExp.$6).toBe("");
expect(RegExp.$7).toBe("");
expect(RegExp.$8).toBe("");
expect(RegExp.$9).toBe("");
});
test("replace should all behave as if exec were called.", () => {
var re = /((\d+)\.(\d+))/;
var s = "abc123.456def";
s.replace(re, "whocares");
expect(RegExp.input).toBe(s);
expect(RegExp["$_"]).toBe(s);
expect(RegExp.lastMatch).toBe("123.456");
expect(RegExp["$&"]).toBe("123.456");
expect(RegExp.lastParen).toBe("456");
expect(RegExp["$+"]).toBe("456");
expect(RegExp.leftContext).toBe("abc");
expect(RegExp["$`"]).toBe("abc");
expect(RegExp.rightContext).toBe("def");
expect(RegExp["$'"]).toBe("def");
expect(RegExp.$1).toBe("123.456");
expect(RegExp.$2).toBe("123");
expect(RegExp.$3).toBe("456");
expect(RegExp.$4).toBe("");
expect(RegExp.$5).toBe("");
expect(RegExp.$6).toBe("");
expect(RegExp.$7).toBe("");
expect(RegExp.$8).toBe("");
expect(RegExp.$9).toBe("");
});
test("test should all behave as if exec were called.", () => {
var re = /((\d+)\.(\d+))/;
var s = "ghi789.012jkl";
re.test(s);
expect(RegExp.input).toBe(s);
expect(RegExp["$_"]).toBe(s);
expect(RegExp.lastMatch).toBe("789.012");
expect(RegExp["$&"]).toBe("789.012");
expect(RegExp.lastParen).toBe("012");
expect(RegExp["$+"]).toBe("012");
expect(RegExp.leftContext).toBe("ghi");
expect(RegExp["$`"]).toBe("ghi");
expect(RegExp.rightContext).toBe("jkl");
expect(RegExp["$'"]).toBe("jkl");
expect(RegExp.$1).toBe("789.012");
expect(RegExp.$2).toBe("789");
expect(RegExp.$3).toBe("012");
expect(RegExp.$4).toBe("");
expect(RegExp.$5).toBe("");
expect(RegExp.$6).toBe("");
expect(RegExp.$7).toBe("");
expect(RegExp.$8).toBe("");
expect(RegExp.$9).toBe("");
});
test("replace must interleave matching and replacing when a global regexp is matched and replaced with the result of a function", () => {
var re = /(.)/g;
function f() {
return RegExp.$1;
}
expect("abcd".replace(re, f)).toBe("dddd");
});
test("the last parenthesis didn't match.", () => {
expect(/foo(?:a(x))?/.exec("foobx")).toEqual(["foo", undefined]);
expect(RegExp.lastParen).toBe("");
});
test("$1 to $9", () => {
for (var i = 1; i <= 9; i++) {
var haystack = "foo";
var re_text = "^foo";
for (var j = 0; j < i - 1; j++) {
haystack += "x";
re_text += "(x)";
}
re_text += "(?:a(x))?";
haystack += "bx";
var re = new RegExp(re_text);
expect(re.test(haystack)).toBeTrue();
for (var j = 1; j < i - 1; j++) {
expect(RegExp["$" + j]).toBe("x");
}
expect(RegExp["$" + i]).toBe("");
}
});
test("save the correct string as the last subject", () => {
var foo =
"lsdfj sldkfj sdklfj læsdfjl sdkfjlsdk fjsdl fjsdljskdj flsj flsdkj flskd regexp: /foobar/\nldkfj sdlkfj sdkl";
expect(/^([a-z]+): (.*)/.test(foo.substring(foo.indexOf("regexp:")))).toBeTrue();
expect(RegExp.$1).toBe("regexp");
});
test("calling with no argument is the same as calling with undefined.", () => {
expect(/^undefined$/.test()).toBeTrue();
expect(/^undefined$/.exec()).toEqual(["undefined"]);
});