From 0d96468e9bef6d5a3a9f0fc2628d7fe98d8940fe Mon Sep 17 00:00:00 2001 From: leeight Date: Mon, 17 Oct 2022 08:59:27 +0800 Subject: [PATCH] LibJS: Implement RegExp legacy static properties RegExp legacy static properties Spec url is https://github.com/tc39/proposal-regexp-legacy-features --- Userland/Libraries/LibJS/AST.cpp | 9 +- Userland/Libraries/LibJS/CMakeLists.txt | 1 + .../LibJS/Runtime/CommonPropertyNames.h | 18 ++ Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 7 +- .../LibJS/Runtime/RegExpConstructor.cpp | 156 ++++++++++++++ .../LibJS/Runtime/RegExpConstructor.h | 26 +++ .../Runtime/RegExpLegacyStaticProperties.cpp | 157 ++++++++++++++ .../Runtime/RegExpLegacyStaticProperties.h | 78 +++++++ .../Libraries/LibJS/Runtime/RegExpObject.cpp | 24 ++- .../Libraries/LibJS/Runtime/RegExpObject.h | 8 + .../LibJS/Runtime/RegExpPrototype.cpp | 53 ++++- .../Tests/builtins/RegExp/RegExp.legacy.js | 198 ++++++++++++++++++ 12 files changed, 726 insertions(+), 9 deletions(-) create mode 100644 Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.legacy.js diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 80b9c82b2b..cfdfec846c 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -3510,7 +3510,14 @@ Completion RegExpLiteral::execute(Interpreter& interpreter) const // 3. Return ! RegExpCreate(pattern, flags). Regex 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 diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 4c05bb3ba8..3da1848749 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -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 diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 6bbf4ffd07..33fc78f5c4 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -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 diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index cb7cb43b33..53e2f28d22 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -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 { diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp index 10beb5a69b..a6f27d3de6 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include 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 + } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h index 2d182ac041..11e14a5a2f 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h @@ -7,6 +7,7 @@ #pragma once #include +#include namespace JS { @@ -20,12 +21,37 @@ public: virtual ThrowCompletionOr call() override; virtual ThrowCompletionOr 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; }; } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.cpp b/Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.cpp new file mode 100644 index 0000000000..0682234f6c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022, LI YUBEI + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +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 get_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, Optional 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(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(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 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(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 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 C’s [[RegExpInput]] internal slot to S. + legacy_static_properties.set_input(string.to_utf8()); + + // 8. Set the value of C’s [[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 C’s [[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 C’s [[RegExpLastParen]] internal slot to the empty String. + else { + legacy_static_properties.set_last_paren(String::empty()); + } + + // 11. Set the value of C’s [[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 C’s [[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 C’s [[RegExpPareni]] internal slot to the ith element of capturedValues. + if (i <= group_count) { + value = captured_values[i - 1]; + } + // Else, set the value of C’s [[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(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.h b/Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.h new file mode 100644 index 0000000000..e036fb7e67 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpLegacyStaticProperties.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022, LI YUBEI + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +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 const& input() const { return m_input; } + Optional const& last_match() const { return m_last_match; } + Optional const& last_paren() const { return m_last_paren; } + Optional const& left_context() const { return m_left_context; } + Optional const& right_context() const { return m_right_context; } + Optional const& $1() const { return m_$1; } + Optional const& $2() const { return m_$2; } + Optional const& $3() const { return m_$3; } + Optional const& $4() const { return m_$4; } + Optional const& $5() const { return m_$5; } + Optional const& $6() const { return m_$6; } + Optional const& $7() const { return m_$7; } + Optional const& $8() const { return m_$8; } + Optional 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 m_input; + Optional m_last_match; + Optional m_last_paren; + Optional m_left_context; + Optional m_right_context; + Optional m_$1; + Optional m_$2; + Optional m_$3; + Optional m_$4; + Optional m_$5; + Optional m_$6; + Optional m_$7; + Optional m_$8; + Optional m_$9; +}; + +ThrowCompletionOr set_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, void (RegExpLegacyStaticProperties::*property_setter)(String), Value value); +ThrowCompletionOr get_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, Optional 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 const& captured_values); +void invalidate_legacy_regexp_static_properties(RegExpConstructor& constructor); + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp index fd2ed13a8c..89cdd2a455 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp @@ -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> 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> 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(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 obj’s [[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 obj’s [[LegacyFeaturesEnabled]] internal slot to true. + regexp_object->set_legacy_features_enabled(true); + } + // 5. Else, + else { + // i. Set the value of obj’s [[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 }; } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.h b/Userland/Libraries/LibJS/Runtime/RegExpObject.h index 719c766696..55201649fb 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpObject.h +++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.h @@ -50,6 +50,11 @@ public: String const& flags() const { return m_flags; } Regex const& regex() { return *m_regex; } Regex 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 m_realm; // [[Realm]] Optional> m_regex; }; diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp index 781c64ec42..c8260a3011 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -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 regexp_builtin_exec(VM& vm, RegExpObject& regexp_object, Utf16String string) { auto& realm = *vm.current_realm(); @@ -172,7 +174,7 @@ static ThrowCompletionOr 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 regexp_builtin_exec(VM& vm, RegExpObject& regexp // 24. Let indices be a new empty List. Vector> indices; + Vector captured_values; // 25. Let groupNames be a new empty List. HashMap group_names; @@ -296,6 +299,8 @@ static ThrowCompletionOr 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 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 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 = ®exp_object.realm(); + // 7. If SameValue(thisRealm, rRealm) is true, then + if (this_realm == regexp_object_realm) { + // i. If the value of R’s [[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 O’s [[Realm]] internal slot. + auto* regexp_object_realm = ®exp_object->realm(); + + // 5. If SameValue(thisRealm, oRealm) is false, throw a TypeError exception. + if (this_realm != regexp_object_realm) + return vm.throw_completion(ErrorType::RegExpCompileError, "thisRealm and oRealm is not same value"); + + // 6. If the value of R’s [[LegacyFeaturesEnabled]] internal slot is false, throw a TypeError exception. + if (!regexp_object->legacy_features_enabled()) + return vm.throw_completion(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(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)); } diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.legacy.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.legacy.js new file mode 100644 index 0000000000..9191e45b6d --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.legacy.js @@ -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"]); +});