Bug 1435829 - Part 1: Implement String.prototype.matchAll proposal. r=jorendorff

This commit is contained in:
André Bargull 2018-12-13 08:04:00 -08:00
parent 49626a83f6
commit 564dc6c495
15 changed files with 309 additions and 51 deletions

View File

@ -854,7 +854,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE =
// application.
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37;
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 38;
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | \

View File

@ -62,7 +62,8 @@ extern JS_PUBLIC_API JSString* GetSymbolDescription(Handle<Symbol*> symbol);
MACRO(toPrimitive) \
MACRO(toStringTag) \
MACRO(unscopables) \
MACRO(asyncIterator)
MACRO(asyncIterator) \
MACRO(matchAll)
enum class SymbolCode : uint32_t {
// There is one SymbolCode for each well-known symbol.

View File

@ -785,6 +785,9 @@ const JSFunctionSpec js::regexp_methods[] = {
JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1, 0),
JS_SELF_HOSTED_FN("test", "RegExpTest", 1, 0),
JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1, 0),
#ifdef NIGHTLY_BUILD
JS_SELF_HOSTED_SYM_FN(matchAll, "RegExpMatchAll", 1, 0),
#endif
JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2, 0),
JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1, 0),
JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2, 0),

View File

@ -1075,3 +1075,128 @@ function RegExpSpecies() {
return this;
}
_SetCanonicalName(RegExpSpecies, "get [Symbol.species]");
// String.prototype.matchAll proposal.
//
// RegExp.prototype [ @@matchAll ] ( string )
function RegExpMatchAll(string) {
// Step 1.
var rx = this;
// Step 2.
if (!IsObject(rx))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
// Step 3.
var str = ToString(string);
// Step 4.
var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp"));
// Step 5.
var flags = ToString(rx.flags);
// Step 6.
var matcher = new C(rx, flags);
// Steps 7-8.
matcher.lastIndex = ToLength(rx.lastIndex);
// Steps 9-12.
var flags = (callFunction(std_String_includes, flags, "g") ? REGEXP_GLOBAL_FLAG : 0) |
(callFunction(std_String_includes, flags, "u") ? REGEXP_UNICODE_FLAG : 0);
// Step 13.
return CreateRegExpStringIterator(matcher, str, flags);
}
// String.prototype.matchAll proposal.
//
// CreateRegExpStringIterator ( R, S, global, fullUnicode )
function CreateRegExpStringIterator(regexp, string, flags) {
// Step 1.
assert(typeof string === "string", "|string| is a string value");
// Steps 2-3.
assert(typeof flags === "number", "|flags| is a number value");
// Steps 4-9.
var iterator = NewRegExpStringIterator();
UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp);
UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_STRING_SLOT, string);
UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_FLAGS_SLOT, flags | 0);
UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_DONE_SLOT, false);
// Step 10.
return iterator;
}
// String.prototype.matchAll proposal.
//
// %RegExpStringIteratorPrototype%.next ( )
function RegExpStringIteratorNext() {
// Steps 1-3.
var obj;
if (!IsObject(this) || (obj = GuardToRegExpStringIterator(this)) === null) {
return callFunction(CallRegExpStringIteratorMethodIfWrapped, this,
"RegExpStringIteratorNext");
}
var result = { value: undefined, done: false };
// Step 4.
var done = UnsafeGetReservedSlot(obj, REGEXP_STRING_ITERATOR_DONE_SLOT);
if (done) {
result.done = true;
return result;
}
// Step 5.
var regexp = UnsafeGetObjectFromReservedSlot(obj, REGEXP_STRING_ITERATOR_REGEXP_SLOT);
// Step 6.
var string = UnsafeGetStringFromReservedSlot(obj, REGEXP_STRING_ITERATOR_STRING_SLOT);
// Steps 7-8.
var flags = UnsafeGetInt32FromReservedSlot(obj, REGEXP_STRING_ITERATOR_FLAGS_SLOT);
var global = !!(flags & REGEXP_GLOBAL_FLAG);
var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
// Step 9.
var match = RegExpExec(regexp, string, false);
// Step 10.
if (match === null) {
// Step 10.a.
UnsafeSetReservedSlot(obj, REGEXP_STRING_ITERATOR_DONE_SLOT, true);
// Step 10.b.
result.done = true;
return result;
}
// Step 11.a.
if (global) {
// Step 11.a.i.
var matchStr = ToString(match[0]);
// Step 11.a.ii.
if (matchStr.length === 0) {
// Step 11.a.ii.1.
var thisIndex = ToLength(regexp.lastIndex);
// Step 11.a.ii.2.
var nextIndex = fullUnicode ? AdvanceStringIndex(string, thisIndex) : thisIndex + 1;
// Step 11.a.ii.3.
regexp.lastIndex = nextIndex;
}
} else {
// Step 11.b.i.
UnsafeSetReservedSlot(obj, REGEXP_STRING_ITERATOR_DONE_SLOT, true);
}
// Steps 11.a.iii and 11.b.ii.
result.value = match;
return result;
}

View File

@ -96,6 +96,11 @@
#define REGEXP_STICKY_FLAG 0x08
#define REGEXP_UNICODE_FLAG 0x10
#define REGEXP_STRING_ITERATOR_REGEXP_SLOT 0
#define REGEXP_STRING_ITERATOR_STRING_SLOT 1
#define REGEXP_STRING_ITERATOR_FLAGS_SLOT 2
#define REGEXP_STRING_ITERATOR_DONE_SLOT 3
#define MODULE_OBJECT_ENVIRONMENT_SLOT 1
#define MODULE_OBJECT_STATUS_SLOT 3
#define MODULE_OBJECT_EVALUATION_ERROR_SLOT 4

View File

@ -3376,6 +3376,9 @@ static const JSFunctionSpec string_methods[] = {
/* Perl-ish methods (search is actually Python-esque). */
JS_SELF_HOSTED_FN("match", "String_match", 1, 0),
#ifdef NIGHTLY_BUILD
JS_SELF_HOSTED_FN("matchAll", "String_matchAll", 1, 0),
#endif
JS_SELF_HOSTED_FN("search", "String_search", 1, 0),
JS_SELF_HOSTED_FN("replace", "String_replace", 2, 0),
JS_SELF_HOSTED_FN("split", "String_split", 2, 0),

View File

@ -64,6 +64,33 @@ function String_generic_match(thisValue, regexp) {
return callFunction(String_match, thisValue, regexp);
}
// String.prototype.matchAll proposal.
//
// String.prototype.matchAll ( regexp )
function String_matchAll(regexp) {
// Step 1.
RequireObjectCoercible(this);
// Step 2.
if (regexp !== undefined && regexp !== null) {
// Step 2.a.
var matcher = GetMethod(regexp, std_matchAll);
// Step 2.b.
if (matcher !== undefined)
return callContentFunction(matcher, regexp, this);
}
// Step 3.
var string = ToString(this);
// Step 4.
var rx = RegExpCreate(regexp, "g");
// Step 5.
return callContentFunction(GetMethod(rx, std_matchAll), rx, string);
}
/**
* A helper function implementing the logic for both String.prototype.padStart
* and String.prototype.padEnd as described in ES7 Draft March 29, 2016

View File

@ -64,6 +64,11 @@ JSObject* SymbolObject::initClass(JSContext* cx, Handle<GlobalObject*> global,
unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
#ifndef NIGHTLY_BUILD
if (i == SymbolCode::matchAll) {
continue;
}
#endif
value.setSymbol(wks->get(i));
if (!NativeDefineDataProperty(cx, ctor, names[i], value, attrs)) {
return nullptr;

View File

@ -323,6 +323,7 @@
MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
MACRO(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \
MACRO(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \
MACRO(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \
MACRO(RegExpTester, RegExpTester, "RegExpTester") \
MACRO(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
MACRO(Reify, Reify, "Reify") \

View File

@ -674,57 +674,29 @@ static bool InitBareBuiltinCtor(JSContext* cx, Handle<GlobalObject*> global,
return false;
}
RootedValue std_isConcatSpreadable(cx);
std_isConcatSpreadable.setSymbol(
cx->wellKnownSymbols().get(JS::SymbolCode::isConcatSpreadable));
if (!JS_DefineProperty(cx, global, "std_isConcatSpreadable",
std_isConcatSpreadable,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
struct SymbolAndName {
JS::SymbolCode code;
const char* name;
};
// Define a top-level property 'std_iterator' with the name of the method
// used by for-of loops to create an iterator.
RootedValue std_iterator(cx);
std_iterator.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::iterator));
if (!JS_DefineProperty(cx, global, "std_iterator", std_iterator,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
SymbolAndName wellKnownSymbols[] = {
{JS::SymbolCode::isConcatSpreadable, "std_isConcatSpreadable"},
{JS::SymbolCode::iterator, "std_iterator"},
{JS::SymbolCode::match, "std_match"},
{JS::SymbolCode::matchAll, "std_matchAll"},
{JS::SymbolCode::replace, "std_replace"},
{JS::SymbolCode::search, "std_search"},
{JS::SymbolCode::species, "std_species"},
{JS::SymbolCode::split, "std_split"},
};
RootedValue std_match(cx);
std_match.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::match));
if (!JS_DefineProperty(cx, global, "std_match", std_match,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
RootedValue std_replace(cx);
std_replace.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::replace));
if (!JS_DefineProperty(cx, global, "std_replace", std_replace,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
RootedValue std_search(cx);
std_search.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::search));
if (!JS_DefineProperty(cx, global, "std_search", std_search,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
RootedValue std_species(cx);
std_species.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::species));
if (!JS_DefineProperty(cx, global, "std_species", std_species,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
RootedValue std_split(cx);
std_split.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::split));
if (!JS_DefineProperty(cx, global, "std_split", std_split,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
RootedValue symVal(cx);
for (const auto& sym : wellKnownSymbols) {
symVal.setSymbol(cx->wellKnownSymbols().get(sym.code));
if (!JS_DefineProperty(cx, global, sym.name, symVal,
JSPROP_PERMANENT | JSPROP_READONLY)) {
return false;
}
}
return InitBareBuiltinCtor(cx, global, JSProto_Array) &&

View File

@ -76,6 +76,7 @@ class GlobalObject : public NativeObject {
ITERATOR_PROTO,
ARRAY_ITERATOR_PROTO,
STRING_ITERATOR_PROTO,
REGEXP_STRING_ITERATOR_PROTO,
GENERATOR_OBJECT_PROTO,
GENERATOR_FUNCTION_PROTO,
GENERATOR_FUNCTION,
@ -627,6 +628,13 @@ class GlobalObject : public NativeObject {
cx, global, STRING_ITERATOR_PROTO, initStringIteratorProto));
}
static NativeObject* getOrCreateRegExpStringIteratorPrototype(
JSContext* cx, Handle<GlobalObject*> global) {
return MaybeNativeObject(getOrCreateObject(cx, global,
REGEXP_STRING_ITERATOR_PROTO,
initRegExpStringIteratorProto));
}
static NativeObject* getOrCreateGeneratorObjectPrototype(
JSContext* cx, Handle<GlobalObject*> global) {
return MaybeNativeObject(
@ -817,6 +825,8 @@ class GlobalObject : public NativeObject {
Handle<GlobalObject*> global);
static bool initStringIteratorProto(JSContext* cx,
Handle<GlobalObject*> global);
static bool initRegExpStringIteratorProto(JSContext* cx,
Handle<GlobalObject*> global);
// Implemented in vm/GeneratorObject.cpp.
static bool initGenerators(JSContext* cx, Handle<GlobalObject*> global);

View File

@ -23,6 +23,7 @@
#include "jsutil.h"
#include "builtin/Array.h"
#include "builtin/SelfHostingDefines.h"
#include "ds/Sort.h"
#include "gc/FreeOp.h"
#include "gc/Marking.h"
@ -1182,6 +1183,54 @@ StringIteratorObject* js::NewStringIteratorObject(JSContext* cx,
return NewObjectWithGivenProto<StringIteratorObject>(cx, proto, newKind);
}
static const Class RegExpStringIteratorPrototypeClass = {
"RegExp String Iterator", 0};
enum {
RegExpStringIteratorSlotRegExp,
RegExpStringIteratorSlotString,
RegExpStringIteratorSlotFlags,
RegExpStringIteratorSlotDone,
RegExpStringIteratorSlotCount
};
static_assert(RegExpStringIteratorSlotRegExp ==
REGEXP_STRING_ITERATOR_REGEXP_SLOT,
"RegExpStringIteratorSlotRegExp must match self-hosting define "
"for regexp slot.");
static_assert(RegExpStringIteratorSlotString ==
REGEXP_STRING_ITERATOR_STRING_SLOT,
"RegExpStringIteratorSlotString must match self-hosting define "
"for string slot.");
static_assert(RegExpStringIteratorSlotFlags ==
REGEXP_STRING_ITERATOR_FLAGS_SLOT,
"RegExpStringIteratorSlotFlags must match self-hosting define "
"for flags slot.");
static_assert(RegExpStringIteratorSlotDone == REGEXP_STRING_ITERATOR_DONE_SLOT,
"RegExpStringIteratorSlotDone must match self-hosting define for "
"done slot.");
const Class RegExpStringIteratorObject::class_ = {
"RegExp String Iterator",
JSCLASS_HAS_RESERVED_SLOTS(RegExpStringIteratorSlotCount)};
static const JSFunctionSpec regexp_string_iterator_methods[] = {
JS_SELF_HOSTED_FN("next", "RegExpStringIteratorNext", 0, 0),
JS_FS_END};
RegExpStringIteratorObject* js::NewRegExpStringIteratorObject(
JSContext* cx, NewObjectKind newKind) {
RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype(
cx, cx->global()));
if (!proto) {
return nullptr;
}
return NewObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto,
newKind);
}
JSObject* js::ValueToIterator(JSContext* cx, HandleValue vp) {
RootedObject obj(cx);
if (vp.isObject()) {
@ -1533,3 +1582,29 @@ static const JSFunctionSpec iterator_proto_methods[] = {
global->setReservedSlot(STRING_ITERATOR_PROTO, ObjectValue(*proto));
return true;
}
/* static */ bool GlobalObject::initRegExpStringIteratorProto(
JSContext* cx, Handle<GlobalObject*> global) {
if (global->getReservedSlot(REGEXP_STRING_ITERATOR_PROTO).isObject()) {
return true;
}
RootedObject iteratorProto(
cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
if (!iteratorProto) {
return false;
}
const Class* cls = &RegExpStringIteratorPrototypeClass;
RootedObject proto(
cx, GlobalObject::createBlankPrototypeInheriting(cx, cls, iteratorProto));
if (!proto ||
!DefinePropertiesAndFunctions(cx, proto, nullptr,
regexp_string_iterator_methods) ||
!DefineToStringTag(cx, proto, cx->names().RegExpStringIterator)) {
return false;
}
global->setReservedSlot(REGEXP_STRING_ITERATOR_PROTO, ObjectValue(*proto));
return true;
}

View File

@ -357,6 +357,14 @@ class StringIteratorObject : public NativeObject {
StringIteratorObject* NewStringIteratorObject(
JSContext* cx, NewObjectKind newKind = GenericObject);
class RegExpStringIteratorObject : public NativeObject {
public:
static const Class class_;
};
RegExpStringIteratorObject* NewRegExpStringIteratorObject(
JSContext* cx, NewObjectKind newKind = GenericObject);
JSObject* GetIterator(JSContext* cx, HandleObject obj);
PropertyIteratorObject* LookupInIteratorCache(JSContext* cx, HandleObject obj);

View File

@ -864,6 +864,20 @@ bool js::intrinsic_NewStringIterator(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
bool js::intrinsic_NewRegExpStringIterator(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
JSObject* obj = NewRegExpStringIteratorObject(cx);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
static bool intrinsic_SetCanonicalName(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
@ -2482,6 +2496,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("GuardToStringIterator",
intrinsic_GuardToBuiltin<StringIteratorObject>, 1, 0,
IntrinsicGuardToStringIterator),
JS_FN("GuardToRegExpStringIterator",
intrinsic_GuardToBuiltin<RegExpStringIteratorObject>, 1, 0),
JS_FN("_CreateMapIterationResultPair",
intrinsic_CreateMapIterationResultPair, 0, 0),
@ -2504,6 +2520,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("CallStringIteratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<StringIteratorObject>>, 2, 0),
JS_FN("NewRegExpStringIterator", intrinsic_NewRegExpStringIterator, 0, 0),
JS_FN("CallRegExpStringIteratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<RegExpStringIteratorObject>>, 2, 0),
JS_FN("IsGeneratorObject", intrinsic_IsInstanceOfBuiltin<GeneratorObject>,
1, 0),
JS_FN("GeneratorObjectIsClosed", intrinsic_GeneratorObjectIsClosed, 1, 0),

View File

@ -53,6 +53,9 @@ bool intrinsic_NewArrayIterator(JSContext* cx, unsigned argc, JS::Value* vp);
bool intrinsic_NewStringIterator(JSContext* cx, unsigned argc, JS::Value* vp);
bool intrinsic_NewRegExpStringIterator(JSContext* cx, unsigned argc,
JS::Value* vp);
bool intrinsic_IsSuspendedGenerator(JSContext* cx, unsigned argc,
JS::Value* vp);