[regexp] Named capture support for string replacements
This implements support for named captures in RegExp.prototype[@@replace] for when the replaceValue is not callable. Named captures can be referenced from replacement strings by using the "$<name>" syntax. A couple of examples: let re = /(?<fst>.)(?<snd>.)/u; "abcd".replace(re, "$<snd>$<fst>") // "bacd" "abcd".replace(re, "$2$1") // "bacd" (numbered refs work as always) "abcd".replace(re, "$<snd") // SyntaxError (unterminated named ref) "abcd".replace(re, "$<42$1>") // "cd" (invalid name) "abcd".replace(re, "$<thd>") // "cd" (non-existent name) "abcd".replace(/(?<fst>.)|(?<snd>.)/u, "$<snd>") // "cd" (non-matched capture) Support is currently behind the --harmony-regexp-named-captures flag. BUG=v8:5437 Review-Url: https://codereview.chromium.org/2775303002 Cr-Commit-Position: refs/heads/master@{#44171}
This commit is contained in:
parent
54a1942a84
commit
17f13863b6
@ -456,6 +456,7 @@ class ErrorUtils : public AllStatic {
|
||||
T(ReduceNoInitial, "Reduce of empty array with no initial value") \
|
||||
T(RegExpFlags, \
|
||||
"Cannot supply flags when constructing one RegExp from another") \
|
||||
T(RegExpInvalidReplaceString, "Invalid replacement string: '%'") \
|
||||
T(RegExpNonObject, "% getter called on non-object %") \
|
||||
T(RegExpNonRegExp, "% getter called on non-RegExp object") \
|
||||
T(ReinitializeIntl, "Trying to re-initialize % object.") \
|
||||
|
@ -11403,6 +11403,8 @@ int String::IndexOf(Isolate* isolate, Handle<String> receiver,
|
||||
|
||||
MaybeHandle<String> String::GetSubstitution(Isolate* isolate, Match* match,
|
||||
Handle<String> replacement) {
|
||||
DCHECK_IMPLIES(match->HasNamedCaptures(), FLAG_harmony_regexp_named_captures);
|
||||
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
const int replacement_length = replacement->length();
|
||||
@ -11489,6 +11491,37 @@ MaybeHandle<String> String::GetSubstitution(Isolate* isolate, Match* match,
|
||||
continue_from_ix = peek_ix + advance;
|
||||
break;
|
||||
}
|
||||
case '<': { // $<name> - named capture
|
||||
if (!match->HasNamedCaptures()) {
|
||||
builder.AppendCharacter('$');
|
||||
continue_from_ix = peek_ix;
|
||||
break;
|
||||
}
|
||||
|
||||
Handle<String> bracket_string =
|
||||
factory->LookupSingleCharacterStringFromCode('>');
|
||||
const int closing_bracket_ix =
|
||||
String::IndexOf(isolate, replacement, bracket_string, peek_ix + 1);
|
||||
|
||||
if (closing_bracket_ix == -1) {
|
||||
THROW_NEW_ERROR(
|
||||
isolate,
|
||||
NewSyntaxError(MessageTemplate::kRegExpInvalidReplaceString,
|
||||
replacement),
|
||||
String);
|
||||
}
|
||||
|
||||
Handle<String> capture_name =
|
||||
factory->NewSubString(replacement, peek_ix + 1, closing_bracket_ix);
|
||||
bool capture_exists;
|
||||
Handle<String> capture;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
isolate, capture,
|
||||
match->GetNamedCapture(capture_name, &capture_exists), String);
|
||||
if (capture_exists) builder.AppendString(capture);
|
||||
continue_from_ix = closing_bracket_ix + 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
builder.AppendCharacter('$');
|
||||
continue_from_ix = peek_ix;
|
||||
@ -11659,6 +11692,15 @@ bool String::IsUtf8EqualTo(Vector<const char> str, bool allow_prefix_match) {
|
||||
return (allow_prefix_match || i == slen) && remaining_in_str == 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool String::IsEqualTo(Vector<const uint8_t> str) {
|
||||
return IsOneByteEqualTo(str);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool String::IsEqualTo(Vector<const uc16> str) {
|
||||
return IsTwoByteEqualTo(str);
|
||||
}
|
||||
|
||||
bool String::IsOneByteEqualTo(Vector<const uint8_t> str) {
|
||||
int slen = length();
|
||||
|
@ -8269,6 +8269,7 @@ class JSRegExp: public JSObject {
|
||||
Handle<String> flags_string);
|
||||
|
||||
inline Type TypeTag();
|
||||
// Number of captures (without the match itself).
|
||||
inline int CaptureCount();
|
||||
inline Flags GetFlags();
|
||||
inline String* Pattern();
|
||||
@ -8341,7 +8342,7 @@ class JSRegExp: public JSObject {
|
||||
// Number of captures in the compiled regexp.
|
||||
static const int kIrregexpCaptureCountIndex = kDataIndex + 5;
|
||||
// Maps names of named capture groups (at indices 2i) to their corresponding
|
||||
// capture group indices (at indices 2i + 1).
|
||||
// (1-based) capture group indices (at indices 2i + 1).
|
||||
static const int kIrregexpCaptureNameMapIndex = kDataIndex + 6;
|
||||
|
||||
static const int kIrregexpDataSize = kIrregexpCaptureNameMapIndex + 1;
|
||||
@ -9196,10 +9197,15 @@ class String: public Name {
|
||||
class Match {
|
||||
public:
|
||||
virtual Handle<String> GetMatch() = 0;
|
||||
virtual MaybeHandle<String> GetCapture(int i, bool* capture_exists) = 0;
|
||||
virtual Handle<String> GetPrefix() = 0;
|
||||
virtual Handle<String> GetSuffix() = 0;
|
||||
|
||||
virtual int CaptureCount() = 0;
|
||||
virtual bool HasNamedCaptures() = 0;
|
||||
virtual MaybeHandle<String> GetCapture(int i, bool* capture_exists) = 0;
|
||||
virtual MaybeHandle<String> GetNamedCapture(Handle<String> name,
|
||||
bool* capture_exists) = 0;
|
||||
|
||||
virtual ~Match() {}
|
||||
};
|
||||
|
||||
@ -9214,6 +9220,11 @@ class String: public Name {
|
||||
inline bool Equals(String* other);
|
||||
inline static bool Equals(Handle<String> one, Handle<String> two);
|
||||
bool IsUtf8EqualTo(Vector<const char> str, bool allow_prefix_match = false);
|
||||
|
||||
// Dispatches to Is{One,Two}ByteEqualTo.
|
||||
template <typename Char>
|
||||
bool IsEqualTo(Vector<const Char> str);
|
||||
|
||||
bool IsOneByteEqualTo(Vector<const uint8_t> str);
|
||||
bool IsTwoByteEqualTo(Vector<const uc16> str);
|
||||
|
||||
|
@ -19,14 +19,45 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
namespace {
|
||||
|
||||
// Looks up the capture of the given name. Returns the (1-based) numbered
|
||||
// capture index or -1 on failure.
|
||||
int LookupNamedCapture(std::function<bool(String*)> name_matches,
|
||||
FixedArray* capture_name_map) {
|
||||
// TODO(jgruber): Sort capture_name_map and do binary search via
|
||||
// internalized strings.
|
||||
|
||||
int maybe_capture_index = -1;
|
||||
const int named_capture_count = capture_name_map->length() >> 1;
|
||||
for (int j = 0; j < named_capture_count; j++) {
|
||||
// The format of {capture_name_map} is documented at
|
||||
// JSRegExp::kIrregexpCaptureNameMapIndex.
|
||||
const int name_ix = j * 2;
|
||||
const int index_ix = j * 2 + 1;
|
||||
|
||||
String* capture_name = String::cast(capture_name_map->get(name_ix));
|
||||
if (!name_matches(capture_name)) continue;
|
||||
|
||||
maybe_capture_index = Smi::cast(capture_name_map->get(index_ix))->value();
|
||||
break;
|
||||
}
|
||||
|
||||
return maybe_capture_index;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CompiledReplacement {
|
||||
public:
|
||||
explicit CompiledReplacement(Zone* zone)
|
||||
: parts_(1, zone), replacement_substrings_(0, zone), zone_(zone) {}
|
||||
|
||||
// Return whether the replacement is simple.
|
||||
bool Compile(Handle<String> replacement, int capture_count,
|
||||
int subject_length);
|
||||
// Return whether the replacement is simple. Can also fail and return Nothing
|
||||
// if the given replacement string is invalid (and requires throwing a
|
||||
// SyntaxError).
|
||||
Maybe<bool> Compile(Handle<JSRegExp> regexp, Handle<String> replacement,
|
||||
int capture_count, int subject_length);
|
||||
|
||||
// Use Apply only if Compile returned false.
|
||||
void Apply(ReplacementStringBuilder* builder, int match_from, int match_to,
|
||||
@ -44,6 +75,7 @@ class CompiledReplacement {
|
||||
SUBJECT_CAPTURE,
|
||||
REPLACEMENT_SUBSTRING,
|
||||
REPLACEMENT_STRING,
|
||||
EMPTY,
|
||||
NUMBER_OF_PART_TYPES
|
||||
};
|
||||
|
||||
@ -68,6 +100,7 @@ class CompiledReplacement {
|
||||
DCHECK(to > from);
|
||||
return ReplacementPart(-from, to);
|
||||
}
|
||||
static inline ReplacementPart Empty() { return ReplacementPart(EMPTY, 0); }
|
||||
|
||||
// If tag <= 0 then it is the negation of a start index of a substring of
|
||||
// the replacement pattern, otherwise it's a value from PartType.
|
||||
@ -80,7 +113,8 @@ class CompiledReplacement {
|
||||
int tag;
|
||||
// The data value's interpretation depends on the value of tag:
|
||||
// tag == SUBJECT_PREFIX ||
|
||||
// tag == SUBJECT_SUFFIX: data is unused.
|
||||
// tag == SUBJECT_SUFFIX ||
|
||||
// tag == EMPTY: data is unused.
|
||||
// tag == SUBJECT_CAPTURE: data is the number of the capture.
|
||||
// tag == REPLACEMENT_SUBSTRING ||
|
||||
// tag == REPLACEMENT_STRING: data is index into array of substrings
|
||||
@ -93,9 +127,17 @@ class CompiledReplacement {
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
bool ParseReplacementPattern(ZoneList<ReplacementPart>* parts,
|
||||
Vector<Char> characters, int capture_count,
|
||||
int subject_length, Zone* zone) {
|
||||
Maybe<bool> ParseReplacementPattern(ZoneList<ReplacementPart>* parts,
|
||||
Vector<Char> characters,
|
||||
FixedArray* capture_name_map,
|
||||
int capture_count, int subject_length,
|
||||
Zone* zone) {
|
||||
// Equivalent to String::GetSubstitution, except that this method converts
|
||||
// the replacement string into an internal representation that avoids
|
||||
// repeated parsing when used repeatedly.
|
||||
DCHECK_IMPLIES(capture_name_map != nullptr,
|
||||
FLAG_harmony_regexp_named_captures);
|
||||
|
||||
int length = characters.length();
|
||||
int last = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
@ -183,6 +225,60 @@ class CompiledReplacement {
|
||||
i = next_index;
|
||||
break;
|
||||
}
|
||||
case '<': {
|
||||
if (capture_name_map == nullptr) {
|
||||
i = next_index;
|
||||
break;
|
||||
}
|
||||
|
||||
// Scan until the next '>', throwing a SyntaxError exception if one
|
||||
// is not found, and let the enclosed substring be groupName.
|
||||
|
||||
const int name_start_index = next_index + 1;
|
||||
int closing_bracket_index = -1;
|
||||
for (int j = name_start_index; j < length; j++) {
|
||||
if (characters[j] == '>') {
|
||||
closing_bracket_index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Throw a SyntaxError for invalid replacement strings.
|
||||
if (closing_bracket_index == -1) return Nothing<bool>();
|
||||
|
||||
Vector<Char> requested_name =
|
||||
characters.SubVector(name_start_index, closing_bracket_index);
|
||||
|
||||
// Let capture be ? Get(namedCaptures, groupName).
|
||||
|
||||
int capture_index = LookupNamedCapture(
|
||||
[=](String* capture_name) {
|
||||
return capture_name->IsEqualTo(requested_name);
|
||||
},
|
||||
capture_name_map);
|
||||
|
||||
// If capture is undefined, replace the text through the following
|
||||
// '>' with the empty string.
|
||||
// Otherwise, replace the text through the following '>' with
|
||||
// ? ToString(capture).
|
||||
|
||||
DCHECK_IMPLIES(
|
||||
capture_index != -1,
|
||||
1 <= capture_index && capture_index <= capture_count);
|
||||
|
||||
ReplacementPart replacement =
|
||||
(capture_index == -1)
|
||||
? ReplacementPart::Empty()
|
||||
: ReplacementPart::SubjectCapture(capture_index);
|
||||
|
||||
if (i > last) {
|
||||
parts->Add(ReplacementPart::ReplacementSubString(last, i), zone);
|
||||
}
|
||||
parts->Add(replacement, zone);
|
||||
last = closing_bracket_index + 1;
|
||||
i = closing_bracket_index;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
i = next_index;
|
||||
break;
|
||||
@ -192,12 +288,12 @@ class CompiledReplacement {
|
||||
if (length > last) {
|
||||
if (last == 0) {
|
||||
// Replacement is simple. Do not use Apply to do the replacement.
|
||||
return true;
|
||||
return Just(true);
|
||||
} else {
|
||||
parts->Add(ReplacementPart::ReplacementSubString(last, length), zone);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Just(false);
|
||||
}
|
||||
|
||||
ZoneList<ReplacementPart> parts_;
|
||||
@ -205,23 +301,37 @@ class CompiledReplacement {
|
||||
Zone* zone_;
|
||||
};
|
||||
|
||||
|
||||
bool CompiledReplacement::Compile(Handle<String> replacement, int capture_count,
|
||||
int subject_length) {
|
||||
Maybe<bool> CompiledReplacement::Compile(Handle<JSRegExp> regexp,
|
||||
Handle<String> replacement,
|
||||
int capture_count,
|
||||
int subject_length) {
|
||||
{
|
||||
DisallowHeapAllocation no_gc;
|
||||
String::FlatContent content = replacement->GetFlatContent();
|
||||
DCHECK(content.IsFlat());
|
||||
bool simple = false;
|
||||
|
||||
FixedArray* capture_name_map = nullptr;
|
||||
if (capture_count > 0) {
|
||||
DCHECK_EQ(regexp->TypeTag(), JSRegExp::IRREGEXP);
|
||||
Object* maybe_capture_name_map = regexp->CaptureNameMap();
|
||||
if (maybe_capture_name_map->IsFixedArray()) {
|
||||
DCHECK(FLAG_harmony_regexp_named_captures);
|
||||
capture_name_map = FixedArray::cast(maybe_capture_name_map);
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool> simple = Nothing<bool>();
|
||||
if (content.IsOneByte()) {
|
||||
simple = ParseReplacementPattern(&parts_, content.ToOneByteVector(),
|
||||
capture_count, subject_length, zone());
|
||||
capture_name_map, capture_count,
|
||||
subject_length, zone());
|
||||
} else {
|
||||
DCHECK(content.IsTwoByte());
|
||||
simple = ParseReplacementPattern(&parts_, content.ToUC16Vector(),
|
||||
capture_count, subject_length, zone());
|
||||
capture_name_map, capture_count,
|
||||
subject_length, zone());
|
||||
}
|
||||
if (simple) return true;
|
||||
if (simple.IsNothing() || simple.FromJust()) return simple;
|
||||
}
|
||||
|
||||
Isolate* isolate = replacement->GetIsolate();
|
||||
@ -243,7 +353,7 @@ bool CompiledReplacement::Compile(Handle<String> replacement, int capture_count,
|
||||
substring_index++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Just(false);
|
||||
}
|
||||
|
||||
|
||||
@ -276,6 +386,8 @@ void CompiledReplacement::Apply(ReplacementStringBuilder* builder,
|
||||
case REPLACEMENT_STRING:
|
||||
builder->AddString(replacement_substrings_[part.data]);
|
||||
break;
|
||||
case EMPTY:
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@ -491,14 +603,27 @@ MUST_USE_RESULT static Object* StringReplaceGlobalRegExpWithString(
|
||||
int capture_count = regexp->CaptureCount();
|
||||
int subject_length = subject->length();
|
||||
|
||||
JSRegExp::Type typeTag = regexp->TypeTag();
|
||||
if (typeTag == JSRegExp::IRREGEXP) {
|
||||
// Ensure the RegExp is compiled so we can access the capture-name map.
|
||||
RegExpImpl::IrregexpPrepare(regexp, subject);
|
||||
}
|
||||
|
||||
// CompiledReplacement uses zone allocation.
|
||||
Zone zone(isolate->allocator(), ZONE_NAME);
|
||||
CompiledReplacement compiled_replacement(&zone);
|
||||
bool simple_replace =
|
||||
compiled_replacement.Compile(replacement, capture_count, subject_length);
|
||||
Maybe<bool> maybe_simple_replace = compiled_replacement.Compile(
|
||||
regexp, replacement, capture_count, subject_length);
|
||||
if (maybe_simple_replace.IsNothing()) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewSyntaxError(MessageTemplate::kRegExpInvalidReplaceString,
|
||||
replacement));
|
||||
}
|
||||
|
||||
const bool simple_replace = maybe_simple_replace.FromJust();
|
||||
|
||||
// Shortcut for simple non-regexp global replacements
|
||||
if (regexp->TypeTag() == JSRegExp::ATOM && simple_replace) {
|
||||
if (typeTag == JSRegExp::ATOM && simple_replace) {
|
||||
if (subject->HasOnlyOneByteChars() && replacement->HasOnlyOneByteChars()) {
|
||||
return StringReplaceGlobalAtomRegExpWithString<SeqOneByteString>(
|
||||
isolate, subject, regexp, replacement, last_match_info);
|
||||
@ -649,7 +774,7 @@ MUST_USE_RESULT static Object* StringReplaceGlobalRegExpWithEmptyString(
|
||||
Heap* heap = isolate->heap();
|
||||
|
||||
// The trimming is performed on a newly allocated object, which is on a
|
||||
// fresly allocated page or on an already swept page. Hence, the sweeper
|
||||
// freshly allocated page or on an already swept page. Hence, the sweeper
|
||||
// thread can not get confused with the filler creation. No synchronization
|
||||
// needed.
|
||||
// TODO(hpayer): We should shrink the large object page if the size
|
||||
@ -843,23 +968,28 @@ namespace {
|
||||
|
||||
class MatchInfoBackedMatch : public String::Match {
|
||||
public:
|
||||
MatchInfoBackedMatch(Isolate* isolate, Handle<String> subject,
|
||||
MatchInfoBackedMatch(Isolate* isolate, Handle<JSRegExp> regexp,
|
||||
Handle<String> subject,
|
||||
Handle<RegExpMatchInfo> match_info)
|
||||
: isolate_(isolate), match_info_(match_info) {
|
||||
subject_ = String::Flatten(subject);
|
||||
|
||||
if (regexp->TypeTag() == JSRegExp::IRREGEXP) {
|
||||
Object* o = regexp->CaptureNameMap();
|
||||
has_named_captures_ = o->IsFixedArray();
|
||||
if (has_named_captures_) {
|
||||
DCHECK(FLAG_harmony_regexp_named_captures);
|
||||
capture_name_map_ = handle(FixedArray::cast(o));
|
||||
}
|
||||
} else {
|
||||
has_named_captures_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
Handle<String> GetMatch() override {
|
||||
return RegExpUtils::GenericCaptureGetter(isolate_, match_info_, 0, nullptr);
|
||||
}
|
||||
|
||||
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
|
||||
Handle<Object> capture_obj = RegExpUtils::GenericCaptureGetter(
|
||||
isolate_, match_info_, i, capture_exists);
|
||||
return (*capture_exists) ? Object::ToString(isolate_, capture_obj)
|
||||
: isolate_->factory()->empty_string();
|
||||
}
|
||||
|
||||
Handle<String> GetPrefix() override {
|
||||
const int match_start = match_info_->Capture(0);
|
||||
return isolate_->factory()->NewSubString(subject_, 0, match_start);
|
||||
@ -871,42 +1001,63 @@ class MatchInfoBackedMatch : public String::Match {
|
||||
subject_->length());
|
||||
}
|
||||
|
||||
bool HasNamedCaptures() override { return has_named_captures_; }
|
||||
|
||||
int CaptureCount() override {
|
||||
return match_info_->NumberOfCaptureRegisters() / 2;
|
||||
}
|
||||
|
||||
virtual ~MatchInfoBackedMatch() {}
|
||||
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
|
||||
Handle<Object> capture_obj = RegExpUtils::GenericCaptureGetter(
|
||||
isolate_, match_info_, i, capture_exists);
|
||||
return (*capture_exists) ? Object::ToString(isolate_, capture_obj)
|
||||
: isolate_->factory()->empty_string();
|
||||
}
|
||||
|
||||
MaybeHandle<String> GetNamedCapture(Handle<String> name,
|
||||
bool* capture_exists) override {
|
||||
DCHECK(has_named_captures_);
|
||||
const int capture_index = LookupNamedCapture(
|
||||
[=](String* capture_name) { return capture_name->Equals(*name); },
|
||||
*capture_name_map_);
|
||||
|
||||
if (capture_index == -1) {
|
||||
*capture_exists = false;
|
||||
return name; // Arbitrary string handle.
|
||||
}
|
||||
|
||||
DCHECK(1 <= capture_index && capture_index <= CaptureCount());
|
||||
return GetCapture(capture_index, capture_exists);
|
||||
}
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
Handle<String> subject_;
|
||||
Handle<RegExpMatchInfo> match_info_;
|
||||
|
||||
bool has_named_captures_;
|
||||
Handle<FixedArray> capture_name_map_;
|
||||
};
|
||||
|
||||
class VectorBackedMatch : public String::Match {
|
||||
public:
|
||||
VectorBackedMatch(Isolate* isolate, Handle<String> subject,
|
||||
Handle<String> match, int match_position,
|
||||
std::vector<Handle<Object>>* captures)
|
||||
std::vector<Handle<Object>>* captures,
|
||||
Handle<Object> groups_obj)
|
||||
: isolate_(isolate),
|
||||
match_(match),
|
||||
match_position_(match_position),
|
||||
captures_(captures) {
|
||||
subject_ = String::Flatten(subject);
|
||||
|
||||
DCHECK(groups_obj->IsUndefined(isolate) || groups_obj->IsJSReceiver());
|
||||
has_named_captures_ = !groups_obj->IsUndefined(isolate);
|
||||
if (has_named_captures_) groups_obj_ = Handle<JSReceiver>::cast(groups_obj);
|
||||
}
|
||||
|
||||
Handle<String> GetMatch() override { return match_; }
|
||||
|
||||
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
|
||||
Handle<Object> capture_obj = captures_->at(i);
|
||||
if (capture_obj->IsUndefined(isolate_)) {
|
||||
*capture_exists = false;
|
||||
return isolate_->factory()->empty_string();
|
||||
}
|
||||
*capture_exists = true;
|
||||
return Object::ToString(isolate_, capture_obj);
|
||||
}
|
||||
|
||||
Handle<String> GetPrefix() override {
|
||||
return isolate_->factory()->NewSubString(subject_, 0, match_position_);
|
||||
}
|
||||
@ -917,9 +1068,34 @@ class VectorBackedMatch : public String::Match {
|
||||
subject_->length());
|
||||
}
|
||||
|
||||
bool HasNamedCaptures() override { return has_named_captures_; }
|
||||
|
||||
int CaptureCount() override { return static_cast<int>(captures_->size()); }
|
||||
|
||||
virtual ~VectorBackedMatch() {}
|
||||
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
|
||||
Handle<Object> capture_obj = captures_->at(i);
|
||||
if (capture_obj->IsUndefined(isolate_)) {
|
||||
*capture_exists = false;
|
||||
return isolate_->factory()->empty_string();
|
||||
}
|
||||
*capture_exists = true;
|
||||
return Object::ToString(isolate_, capture_obj);
|
||||
}
|
||||
|
||||
MaybeHandle<String> GetNamedCapture(Handle<String> name,
|
||||
bool* capture_exists) override {
|
||||
DCHECK(has_named_captures_);
|
||||
Handle<Object> capture_obj;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(isolate_, capture_obj,
|
||||
Object::GetProperty(groups_obj_, name), String);
|
||||
if (capture_obj->IsUndefined(isolate_)) {
|
||||
*capture_exists = false;
|
||||
return name;
|
||||
} else {
|
||||
*capture_exists = true;
|
||||
return Object::ToString(isolate_, capture_obj);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
@ -927,6 +1103,9 @@ class VectorBackedMatch : public String::Match {
|
||||
Handle<String> match_;
|
||||
const int match_position_;
|
||||
std::vector<Handle<Object>>* captures_;
|
||||
|
||||
bool has_named_captures_;
|
||||
Handle<JSReceiver> groups_obj_;
|
||||
};
|
||||
|
||||
// Create the groups object (see also the RegExp result creation in
|
||||
@ -1072,6 +1251,7 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
|
||||
elements->set(cursor++, *subject);
|
||||
|
||||
if (has_named_captures) {
|
||||
DCHECK(FLAG_harmony_regexp_named_captures);
|
||||
Handle<FixedArray> capture_map =
|
||||
Handle<FixedArray>::cast(maybe_capture_map);
|
||||
Handle<JSObject> groups = ConstructNamedCaptureGroupsObject(
|
||||
@ -1183,7 +1363,7 @@ MUST_USE_RESULT MaybeHandle<String> RegExpReplace(Isolate* isolate,
|
||||
builder.AppendString(factory->NewSubString(string, 0, start_index));
|
||||
|
||||
if (replace->length() > 0) {
|
||||
MatchInfoBackedMatch m(isolate, string, match_indices);
|
||||
MatchInfoBackedMatch m(isolate, regexp, string, match_indices);
|
||||
Handle<String> replacement;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement,
|
||||
String::GetSubstitution(isolate, &m, replace),
|
||||
@ -1316,6 +1496,7 @@ RUNTIME_FUNCTION(Runtime_StringReplaceNonGlobalRegExpWithFunction) {
|
||||
|
||||
Object* maybe_capture_map = regexp->CaptureNameMap();
|
||||
if (maybe_capture_map->IsFixedArray()) {
|
||||
DCHECK(FLAG_harmony_regexp_named_captures);
|
||||
has_named_captures = true;
|
||||
capture_map = handle(FixedArray::cast(maybe_capture_map));
|
||||
}
|
||||
@ -1703,7 +1884,13 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) {
|
||||
isolate, replacement, Object::ToString(isolate, replacement_obj));
|
||||
} else {
|
||||
DCHECK(!functional_replace);
|
||||
VectorBackedMatch m(isolate, string, match, position, &captures);
|
||||
if (!groups_obj->IsUndefined(isolate)) {
|
||||
// TODO(jgruber): Behavior in this case is not yet specced.
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, groups_obj, JSReceiver::ToObject(isolate, groups_obj));
|
||||
}
|
||||
VectorBackedMatch m(isolate, string, match, position, &captures,
|
||||
groups_obj);
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, replacement, String::GetSubstitution(isolate, &m, replace));
|
||||
}
|
||||
|
@ -31,13 +31,20 @@ RUNTIME_FUNCTION(Runtime_GetSubstitution) {
|
||||
: match_(match), prefix_(prefix), suffix_(suffix) {}
|
||||
|
||||
Handle<String> GetMatch() override { return match_; }
|
||||
Handle<String> GetPrefix() override { return prefix_; }
|
||||
Handle<String> GetSuffix() override { return suffix_; }
|
||||
|
||||
int CaptureCount() override { return 0; }
|
||||
bool HasNamedCaptures() override { return false; }
|
||||
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
|
||||
*capture_exists = false;
|
||||
return match_; // Return arbitrary string handle.
|
||||
}
|
||||
Handle<String> GetPrefix() override { return prefix_; }
|
||||
Handle<String> GetSuffix() override { return suffix_; }
|
||||
int CaptureCount() override { return 0; }
|
||||
MaybeHandle<String> GetNamedCapture(Handle<String> name,
|
||||
bool* capture_exists) override {
|
||||
UNREACHABLE();
|
||||
return MaybeHandle<String>();
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<String> match_, prefix_, suffix_;
|
||||
|
@ -217,7 +217,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(12),
|
||||
B(LdaConstant), U8(11),
|
||||
B(Star), R(13),
|
||||
@ -701,7 +701,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(12),
|
||||
B(LdaConstant), U8(11),
|
||||
B(Star), R(13),
|
||||
@ -1219,7 +1219,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(12),
|
||||
B(LdaConstant), U8(11),
|
||||
B(Star), R(13),
|
||||
@ -1627,7 +1627,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(11),
|
||||
B(LdaConstant), U8(10),
|
||||
B(Star), R(12),
|
||||
|
@ -85,7 +85,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(12),
|
||||
B(LdaConstant), U8(8),
|
||||
B(Star), R(13),
|
||||
@ -226,7 +226,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(13),
|
||||
B(LdaConstant), U8(8),
|
||||
B(Star), R(14),
|
||||
@ -380,7 +380,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(12),
|
||||
B(LdaConstant), U8(8),
|
||||
B(Star), R(13),
|
||||
@ -524,7 +524,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(11),
|
||||
B(LdaConstant), U8(10),
|
||||
B(Star), R(12),
|
||||
|
@ -493,7 +493,7 @@ bytecodes: [
|
||||
B(TestTypeOf), U8(5),
|
||||
B(JumpIfFalse), U8(4),
|
||||
B(Jump), U8(18),
|
||||
B(Wide), B(LdaSmi), I16(129),
|
||||
B(Wide), B(LdaSmi), I16(130),
|
||||
B(Star), R(11),
|
||||
B(LdaConstant), U8(10),
|
||||
B(Star), R(12),
|
||||
|
@ -208,3 +208,59 @@ function toSlowMode(re) {
|
||||
});
|
||||
assertEquals("bacd", result);
|
||||
}
|
||||
|
||||
// @@replace with a string replacement argument (no named captures).
|
||||
{
|
||||
let re = /(.)(.)/u;
|
||||
assertEquals("$<snd>$<fst>cd", "abcd".replace(re, "$<snd>$<fst>"));
|
||||
assertEquals("bacd", "abcd".replace(re, "$2$1"));
|
||||
assertEquals("$<sndcd", "abcd".replace(re, "$<snd"));
|
||||
assertEquals("$<42a>cd", "abcd".replace(re, "$<42$1>"));
|
||||
assertEquals("$<thd>cd", "abcd".replace(re, "$<thd>"));
|
||||
assertEquals("$<a>cd", "abcd".replace(re, "$<$1>"));
|
||||
}
|
||||
|
||||
// @@replace with a string replacement argument (global, named captures).
|
||||
{
|
||||
let re = /(?<fst>.)(?<snd>.)/gu;
|
||||
assertEquals("badc", "abcd".replace(re, "$<snd>$<fst>"));
|
||||
assertEquals("badc", "abcd".replace(re, "$2$1"));
|
||||
assertThrows(() => "abcd".replace(re, "$<snd"), SyntaxError);
|
||||
assertEquals("", "abcd".replace(re, "$<42$1>"));
|
||||
assertEquals("", "abcd".replace(re, "$<thd>"));
|
||||
assertEquals("", "abcd".replace(re, "$<$1>"));
|
||||
}
|
||||
|
||||
// @@replace with a string replacement argument (non-global, named captures).
|
||||
{
|
||||
let re = /(?<fst>.)(?<snd>.)/u;
|
||||
assertEquals("bacd", "abcd".replace(re, "$<snd>$<fst>"));
|
||||
assertEquals("bacd", "abcd".replace(re, "$2$1"));
|
||||
assertThrows(() => "abcd".replace(re, "$<snd"), SyntaxError);
|
||||
assertEquals("cd", "abcd".replace(re, "$<42$1>"));
|
||||
assertEquals("cd", "abcd".replace(re, "$<thd>"));
|
||||
assertEquals("cd", "abcd".replace(re, "$<$1>"));
|
||||
}
|
||||
|
||||
// @@replace with a string replacement argument (slow, global, named captures).
|
||||
{
|
||||
let re = toSlowMode(/(?<fst>.)(?<snd>.)/gu);
|
||||
assertEquals("badc", "abcd".replace(re, "$<snd>$<fst>"));
|
||||
assertEquals("badc", "abcd".replace(re, "$2$1"));
|
||||
assertThrows(() => "abcd".replace(re, "$<snd"), SyntaxError);
|
||||
assertEquals("", "abcd".replace(re, "$<42$1>"));
|
||||
assertEquals("", "abcd".replace(re, "$<thd>"));
|
||||
assertEquals("", "abcd".replace(re, "$<$1>"));
|
||||
}
|
||||
|
||||
// @@replace with a string replacement argument (slow, non-global,
|
||||
// named captures).
|
||||
{
|
||||
let re = toSlowMode(/(?<fst>.)(?<snd>.)/u);
|
||||
assertEquals("bacd", "abcd".replace(re, "$<snd>$<fst>"));
|
||||
assertEquals("bacd", "abcd".replace(re, "$2$1"));
|
||||
assertThrows(() => "abcd".replace(re, "$<snd"), SyntaxError);
|
||||
assertEquals("cd", "abcd".replace(re, "$<42$1>"));
|
||||
assertEquals("cd", "abcd".replace(re, "$<thd>"));
|
||||
assertEquals("cd", "abcd".replace(re, "$<$1>"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user