| Index: src/runtime.cc
|
| diff --git a/src/runtime.cc b/src/runtime.cc
|
| index 3017f3f1e50710a3650032b9f78297435a64d18f..4e0a86b318c2a8225cefe177bb4bce23269e0677 100644
|
| --- a/src/runtime.cc
|
| +++ b/src/runtime.cc
|
| @@ -2574,24 +2574,28 @@ class ReplacementStringBuilder {
|
| class CompiledReplacement {
|
| public:
|
| explicit CompiledReplacement(Zone* zone)
|
| - : parts_(1, zone), replacement_substrings_(0, zone), zone_(zone) {}
|
| + : parts_(1, zone), replacement_substrings_(0, zone),
|
| + simple_hint_(false),
|
| + zone_(zone) {}
|
|
|
| - // Return whether the replacement is simple.
|
| - bool Compile(Handle<String> replacement,
|
| + void Compile(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,
|
| - int32_t* match);
|
| + Handle<JSArray> last_match_info);
|
|
|
| // Number of distinct parts of the replacement pattern.
|
| int parts() {
|
| return parts_.length();
|
| }
|
|
|
| + bool simple_hint() {
|
| + return simple_hint_;
|
| + }
|
| +
|
| Zone* zone() const { return zone_; }
|
|
|
| private:
|
| @@ -2652,11 +2656,11 @@ class CompiledReplacement {
|
| };
|
|
|
| template<typename Char>
|
| - bool ParseReplacementPattern(ZoneList<ReplacementPart>* parts,
|
| - Vector<Char> characters,
|
| - int capture_count,
|
| - int subject_length,
|
| - Zone* zone) {
|
| + static bool ParseReplacementPattern(ZoneList<ReplacementPart>* parts,
|
| + Vector<Char> characters,
|
| + int capture_count,
|
| + int subject_length,
|
| + Zone* zone) {
|
| int length = characters.length();
|
| int last = 0;
|
| for (int i = 0; i < length; i++) {
|
| @@ -2750,7 +2754,7 @@ class CompiledReplacement {
|
| }
|
| if (length > last) {
|
| if (last == 0) {
|
| - // Replacement is simple. Do not use Apply to do the replacement.
|
| + parts->Add(ReplacementPart::ReplacementString(), zone);
|
| return true;
|
| } else {
|
| parts->Add(ReplacementPart::ReplacementSubString(last, length), zone);
|
| @@ -2761,35 +2765,33 @@ class CompiledReplacement {
|
|
|
| ZoneList<ReplacementPart> parts_;
|
| ZoneList<Handle<String> > replacement_substrings_;
|
| + bool simple_hint_;
|
| Zone* zone_;
|
| };
|
|
|
|
|
| -bool CompiledReplacement::Compile(Handle<String> replacement,
|
| +void CompiledReplacement::Compile(Handle<String> replacement,
|
| int capture_count,
|
| int subject_length) {
|
| {
|
| AssertNoAllocation no_alloc;
|
| String::FlatContent content = replacement->GetFlatContent();
|
| ASSERT(content.IsFlat());
|
| - bool simple = false;
|
| if (content.IsAscii()) {
|
| - simple = ParseReplacementPattern(&parts_,
|
| - content.ToAsciiVector(),
|
| - capture_count,
|
| - subject_length,
|
| - zone());
|
| + simple_hint_ = ParseReplacementPattern(&parts_,
|
| + content.ToAsciiVector(),
|
| + capture_count,
|
| + subject_length,
|
| + zone());
|
| } else {
|
| ASSERT(content.IsTwoByte());
|
| - simple = ParseReplacementPattern(&parts_,
|
| - content.ToUC16Vector(),
|
| - capture_count,
|
| - subject_length,
|
| - zone());
|
| + simple_hint_ = ParseReplacementPattern(&parts_,
|
| + content.ToUC16Vector(),
|
| + capture_count,
|
| + subject_length,
|
| + zone());
|
| }
|
| - if (simple) return true;
|
| }
|
| -
|
| Isolate* isolate = replacement->GetIsolate();
|
| // Find substrings of replacement string and create them as String objects.
|
| int substring_index = 0;
|
| @@ -2809,15 +2811,13 @@ bool CompiledReplacement::Compile(Handle<String> replacement,
|
| substring_index++;
|
| }
|
| }
|
| - return false;
|
| }
|
|
|
|
|
| void CompiledReplacement::Apply(ReplacementStringBuilder* builder,
|
| int match_from,
|
| int match_to,
|
| - int32_t* match) {
|
| - ASSERT_LT(0, parts_.length());
|
| + Handle<JSArray> last_match_info) {
|
| for (int i = 0, n = parts_.length(); i < n; i++) {
|
| ReplacementPart part = parts_[i];
|
| switch (part.tag) {
|
| @@ -2833,8 +2833,9 @@ void CompiledReplacement::Apply(ReplacementStringBuilder* builder,
|
| }
|
| case SUBJECT_CAPTURE: {
|
| int capture = part.data;
|
| - int from = match[capture * 2];
|
| - int to = match[capture * 2 + 1];
|
| + FixedArray* match_info = FixedArray::cast(last_match_info->elements());
|
| + int from = RegExpImpl::GetCapture(match_info, capture * 2);
|
| + int to = RegExpImpl::GetCapture(match_info, capture * 2 + 1);
|
| if (from >= 0 && to > from) {
|
| builder->AddSubjectSlice(from, to);
|
| }
|
| @@ -2956,19 +2957,85 @@ void FindStringIndicesDispatch(Isolate* isolate,
|
| }
|
|
|
|
|
| +// Two smis before and after the match, for very long strings.
|
| +const int kMaxBuilderEntriesPerRegExpMatch = 5;
|
| +
|
| +
|
| +static void SetLastMatchInfoNoCaptures(Handle<String> subject,
|
| + Handle<JSArray> last_match_info,
|
| + int match_start,
|
| + int match_end) {
|
| + // Fill last_match_info with a single capture.
|
| + last_match_info->EnsureSize(2 + RegExpImpl::kLastMatchOverhead);
|
| + AssertNoAllocation no_gc;
|
| + FixedArray* elements = FixedArray::cast(last_match_info->elements());
|
| + RegExpImpl::SetLastCaptureCount(elements, 2);
|
| + RegExpImpl::SetLastInput(elements, *subject);
|
| + RegExpImpl::SetLastSubject(elements, *subject);
|
| + RegExpImpl::SetCapture(elements, 0, match_start);
|
| + RegExpImpl::SetCapture(elements, 1, match_end);
|
| +}
|
| +
|
| +
|
| +template <typename SubjectChar, typename PatternChar>
|
| +static bool SearchStringMultiple(Isolate* isolate,
|
| + Vector<const SubjectChar> subject,
|
| + Vector<const PatternChar> pattern,
|
| + String* pattern_string,
|
| + FixedArrayBuilder* builder,
|
| + int* match_pos) {
|
| + int pos = *match_pos;
|
| + int subject_length = subject.length();
|
| + int pattern_length = pattern.length();
|
| + int max_search_start = subject_length - pattern_length;
|
| + StringSearch<PatternChar, SubjectChar> search(isolate, pattern);
|
| + while (pos <= max_search_start) {
|
| + if (!builder->HasCapacity(kMaxBuilderEntriesPerRegExpMatch)) {
|
| + *match_pos = pos;
|
| + return false;
|
| + }
|
| + // Position of end of previous match.
|
| + int match_end = pos + pattern_length;
|
| + int new_pos = search.Search(subject, match_end);
|
| + if (new_pos >= 0) {
|
| + // A match.
|
| + if (new_pos > match_end) {
|
| + ReplacementStringBuilder::AddSubjectSlice(builder,
|
| + match_end,
|
| + new_pos);
|
| + }
|
| + pos = new_pos;
|
| + builder->Add(pattern_string);
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (pos < max_search_start) {
|
| + ReplacementStringBuilder::AddSubjectSlice(builder,
|
| + pos + pattern_length,
|
| + subject_length);
|
| + }
|
| + *match_pos = pos;
|
| + return true;
|
| +}
|
| +
|
| +
|
| +
|
| +
|
| template<typename ResultSeqString>
|
| MUST_USE_RESULT static MaybeObject* StringReplaceAtomRegExpWithString(
|
| Isolate* isolate,
|
| Handle<String> subject,
|
| Handle<JSRegExp> pattern_regexp,
|
| Handle<String> replacement,
|
| - Handle<JSArray> last_match_info) {
|
| + Handle<JSArray> last_match_info,
|
| + Zone* zone) {
|
| ASSERT(subject->IsFlat());
|
| ASSERT(replacement->IsFlat());
|
|
|
| - Zone* zone = isolate->runtime_zone();
|
| - ZoneScope zone_space(zone, DELETE_ON_EXIT);
|
| - ZoneList<int> indices(8, zone);
|
| + ZoneScope zone_space(isolate->runtime_zone(), DELETE_ON_EXIT);
|
| + ZoneList<int> indices(8, isolate->runtime_zone());
|
| ASSERT_EQ(JSRegExp::ATOM, pattern_regexp->TypeTag());
|
| String* pattern =
|
| String::cast(pattern_regexp->DataAt(JSRegExp::kAtomPatternIndex));
|
| @@ -2976,8 +3043,8 @@ MUST_USE_RESULT static MaybeObject* StringReplaceAtomRegExpWithString(
|
| int pattern_len = pattern->length();
|
| int replacement_len = replacement->length();
|
|
|
| - FindStringIndicesDispatch(
|
| - isolate, *subject, pattern, &indices, 0xffffffff, zone);
|
| + FindStringIndicesDispatch(isolate, *subject, pattern, &indices, 0xffffffff,
|
| + zone);
|
|
|
| int matches = indices.length();
|
| if (matches == 0) return *subject;
|
| @@ -3032,9 +3099,10 @@ MUST_USE_RESULT static MaybeObject* StringReplaceAtomRegExpWithString(
|
| subject_len);
|
| }
|
|
|
| - int32_t match_indices[] = { indices.at(matches - 1),
|
| - indices.at(matches - 1) + pattern_len };
|
| - RegExpImpl::SetLastMatchInfo(last_match_info, subject, 0, match_indices);
|
| + SetLastMatchInfoNoCaptures(subject,
|
| + last_match_info,
|
| + indices.at(matches - 1),
|
| + indices.at(matches - 1) + pattern_len);
|
|
|
| return *result;
|
| }
|
| @@ -3042,101 +3110,139 @@ MUST_USE_RESULT static MaybeObject* StringReplaceAtomRegExpWithString(
|
|
|
| MUST_USE_RESULT static MaybeObject* StringReplaceRegExpWithString(
|
| Isolate* isolate,
|
| - Handle<String> subject,
|
| - Handle<JSRegExp> regexp,
|
| - Handle<String> replacement,
|
| - Handle<JSArray> last_match_info) {
|
| + String* subject,
|
| + JSRegExp* regexp,
|
| + String* replacement,
|
| + JSArray* last_match_info,
|
| + Zone* zone) {
|
| ASSERT(subject->IsFlat());
|
| ASSERT(replacement->IsFlat());
|
|
|
| - bool is_global = regexp->GetFlags().is_global();
|
| - int capture_count = regexp->CaptureCount();
|
| - int subject_length = subject->length();
|
| + HandleScope handles(isolate);
|
| +
|
| + int length = subject->length();
|
| + Handle<String> subject_handle(subject);
|
| + Handle<JSRegExp> regexp_handle(regexp);
|
| + Handle<String> replacement_handle(replacement);
|
| + Handle<JSArray> last_match_info_handle(last_match_info);
|
| + Handle<Object> match = RegExpImpl::Exec(regexp_handle,
|
| + subject_handle,
|
| + 0,
|
| + last_match_info_handle);
|
| + if (match.is_null()) {
|
| + return Failure::Exception();
|
| + }
|
| + if (match->IsNull()) {
|
| + return *subject_handle;
|
| + }
|
| +
|
| + int capture_count = regexp_handle->CaptureCount();
|
|
|
| // CompiledReplacement uses zone allocation.
|
| - Zone* zone = isolate->runtime_zone();
|
| ZoneScope zonescope(zone, DELETE_ON_EXIT);
|
| CompiledReplacement compiled_replacement(zone);
|
| - bool simple_replace = compiled_replacement.Compile(replacement,
|
| - capture_count,
|
| - subject_length);
|
| +
|
| + compiled_replacement.Compile(replacement_handle,
|
| + capture_count,
|
| + length);
|
| +
|
| + bool is_global = regexp_handle->GetFlags().is_global();
|
|
|
| // Shortcut for simple non-regexp global replacements
|
| if (is_global &&
|
| - regexp->TypeTag() == JSRegExp::ATOM &&
|
| - simple_replace) {
|
| - if (subject->HasOnlyAsciiChars()) {
|
| + regexp_handle->TypeTag() == JSRegExp::ATOM &&
|
| + compiled_replacement.simple_hint()) {
|
| + if (subject_handle->HasOnlyAsciiChars() &&
|
| + replacement_handle->HasOnlyAsciiChars()) {
|
| return StringReplaceAtomRegExpWithString<SeqAsciiString>(
|
| - isolate, subject, regexp, replacement, last_match_info);
|
| - } else {
|
| + isolate,
|
| + subject_handle,
|
| + regexp_handle,
|
| + replacement_handle,
|
| + last_match_info_handle,
|
| + zone);
|
| + } else {
|
| return StringReplaceAtomRegExpWithString<SeqTwoByteString>(
|
| - isolate, subject, regexp, replacement, last_match_info);
|
| + isolate,
|
| + subject_handle,
|
| + regexp_handle,
|
| + replacement_handle,
|
| + last_match_info_handle,
|
| + zone);
|
| }
|
| }
|
|
|
| - RegExpImpl::GlobalCache global_cache(regexp, subject, is_global, isolate);
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| -
|
| - int32_t* current_match = global_cache.FetchNext();
|
| - if (current_match == NULL) {
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| - return *subject;
|
| - }
|
| -
|
| // Guessing the number of parts that the final result string is built
|
| // from. Global regexps can match any number of times, so we guess
|
| // conservatively.
|
| int expected_parts =
|
| (compiled_replacement.parts() + 1) * (is_global ? 4 : 1) + 1;
|
| ReplacementStringBuilder builder(isolate->heap(),
|
| - subject,
|
| + subject_handle,
|
| expected_parts);
|
|
|
| + // Index of end of last match.
|
| + int prev = 0;
|
| +
|
| +
|
| // Number of parts added by compiled replacement plus preceeding
|
| // string and possibly suffix after last match. It is possible for
|
| // all components to use two elements when encoded as two smis.
|
| const int parts_added_per_loop = 2 * (compiled_replacement.parts() + 2);
|
| -
|
| - int prev = 0;
|
| -
|
| + bool matched = true;
|
| do {
|
| + ASSERT(last_match_info_handle->HasFastObjectElements());
|
| + // Increase the capacity of the builder before entering local handle-scope,
|
| + // so its internal buffer can safely allocate a new handle if it grows.
|
| builder.EnsureCapacity(parts_added_per_loop);
|
|
|
| - int start = current_match[0];
|
| - int end = current_match[1];
|
| + HandleScope loop_scope(isolate);
|
| + int start, end;
|
| + {
|
| + AssertNoAllocation match_info_array_is_not_in_a_handle;
|
| + FixedArray* match_info_array =
|
| + FixedArray::cast(last_match_info_handle->elements());
|
| +
|
| + ASSERT_EQ(capture_count * 2 + 2,
|
| + RegExpImpl::GetLastCaptureCount(match_info_array));
|
| + start = RegExpImpl::GetCapture(match_info_array, 0);
|
| + end = RegExpImpl::GetCapture(match_info_array, 1);
|
| + }
|
|
|
| if (prev < start) {
|
| builder.AddSubjectSlice(prev, start);
|
| }
|
| + compiled_replacement.Apply(&builder,
|
| + start,
|
| + end,
|
| + last_match_info_handle);
|
|
|
| - if (simple_replace) {
|
| - builder.AddString(replacement);
|
| - } else {
|
| - compiled_replacement.Apply(&builder,
|
| - start,
|
| - end,
|
| - current_match);
|
| - }
|
| prev = end;
|
|
|
| // Only continue checking for global regexps.
|
| if (!is_global) break;
|
|
|
| - current_match = global_cache.FetchNext();
|
| - } while (current_match != NULL);
|
| + // Continue from where the match ended, unless it was an empty match.
|
| + int next = end;
|
| + if (start == end) {
|
| + next = end + 1;
|
| + if (next > length) break;
|
| + }
|
|
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| + match = RegExpImpl::Exec(regexp_handle,
|
| + subject_handle,
|
| + next,
|
| + last_match_info_handle);
|
| + if (match.is_null()) {
|
| + return Failure::Exception();
|
| + }
|
| + matched = !match->IsNull();
|
| + } while (matched);
|
|
|
| - if (prev < subject_length) {
|
| - builder.EnsureCapacity(2);
|
| - builder.AddSubjectSlice(prev, subject_length);
|
| + if (prev < length) {
|
| + builder.AddSubjectSlice(prev, length);
|
| }
|
|
|
| - RegExpImpl::SetLastMatchInfo(last_match_info,
|
| - subject,
|
| - capture_count,
|
| - global_cache.LastSuccessfulMatch());
|
| -
|
| return *(builder.ToString());
|
| }
|
|
|
| @@ -3144,51 +3250,69 @@ MUST_USE_RESULT static MaybeObject* StringReplaceRegExpWithString(
|
| template <typename ResultSeqString>
|
| MUST_USE_RESULT static MaybeObject* StringReplaceRegExpWithEmptyString(
|
| Isolate* isolate,
|
| - Handle<String> subject,
|
| - Handle<JSRegExp> regexp,
|
| - Handle<JSArray> last_match_info) {
|
| + String* subject,
|
| + JSRegExp* regexp,
|
| + JSArray* last_match_info,
|
| + Zone* zone) {
|
| ASSERT(subject->IsFlat());
|
|
|
| - bool is_global = regexp->GetFlags().is_global();
|
| + HandleScope handles(isolate);
|
| +
|
| + Handle<String> subject_handle(subject);
|
| + Handle<JSRegExp> regexp_handle(regexp);
|
| + Handle<JSArray> last_match_info_handle(last_match_info);
|
|
|
| // Shortcut for simple non-regexp global replacements
|
| - if (is_global &&
|
| - regexp->TypeTag() == JSRegExp::ATOM) {
|
| - Handle<String> empty_string(HEAP->empty_string());
|
| - if (subject->HasOnlyAsciiChars()) {
|
| + if (regexp_handle->GetFlags().is_global() &&
|
| + regexp_handle->TypeTag() == JSRegExp::ATOM) {
|
| + Handle<String> empty_string_handle(HEAP->empty_string());
|
| + if (subject_handle->HasOnlyAsciiChars()) {
|
| return StringReplaceAtomRegExpWithString<SeqAsciiString>(
|
| isolate,
|
| - subject,
|
| - regexp,
|
| - empty_string,
|
| - last_match_info);
|
| + subject_handle,
|
| + regexp_handle,
|
| + empty_string_handle,
|
| + last_match_info_handle,
|
| + zone);
|
| } else {
|
| return StringReplaceAtomRegExpWithString<SeqTwoByteString>(
|
| isolate,
|
| - subject,
|
| - regexp,
|
| - empty_string,
|
| - last_match_info);
|
| + subject_handle,
|
| + regexp_handle,
|
| + empty_string_handle,
|
| + last_match_info_handle,
|
| + zone);
|
| }
|
| }
|
|
|
| - RegExpImpl::GlobalCache global_cache(regexp, subject, is_global, isolate);
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| + Handle<Object> match = RegExpImpl::Exec(regexp_handle,
|
| + subject_handle,
|
| + 0,
|
| + last_match_info_handle);
|
| + if (match.is_null()) return Failure::Exception();
|
| + if (match->IsNull()) return *subject_handle;
|
| +
|
| + ASSERT(last_match_info_handle->HasFastObjectElements());
|
|
|
| - int32_t* current_match = global_cache.FetchNext();
|
| - if (current_match == NULL) {
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| - return *subject;
|
| + int start, end;
|
| + {
|
| + AssertNoAllocation match_info_array_is_not_in_a_handle;
|
| + FixedArray* match_info_array =
|
| + FixedArray::cast(last_match_info_handle->elements());
|
| +
|
| + start = RegExpImpl::GetCapture(match_info_array, 0);
|
| + end = RegExpImpl::GetCapture(match_info_array, 1);
|
| }
|
|
|
| - int start = current_match[0];
|
| - int end = current_match[1];
|
| - int capture_count = regexp->CaptureCount();
|
| - int subject_length = subject->length();
|
| + bool global = regexp_handle->GetFlags().is_global();
|
|
|
| - int new_length = subject_length - (end - start);
|
| - if (new_length == 0) return isolate->heap()->empty_string();
|
| + if (start == end && !global) return *subject_handle;
|
|
|
| + int length = subject_handle->length();
|
| + int new_length = length - (end - start);
|
| + if (new_length == 0) {
|
| + return isolate->heap()->empty_string();
|
| + }
|
| Handle<ResultSeqString> answer;
|
| if (ResultSeqString::kHasAsciiEncoding) {
|
| answer = Handle<ResultSeqString>::cast(
|
| @@ -3198,55 +3322,73 @@ MUST_USE_RESULT static MaybeObject* StringReplaceRegExpWithEmptyString(
|
| isolate->factory()->NewRawTwoByteString(new_length));
|
| }
|
|
|
| - if (!is_global) {
|
| - RegExpImpl::SetLastMatchInfo(
|
| - last_match_info, subject, capture_count, current_match);
|
| - if (start == end) {
|
| - return *subject;
|
| - } else {
|
| - if (start > 0) {
|
| - String::WriteToFlat(*subject, answer->GetChars(), 0, start);
|
| - }
|
| - if (end < subject_length) {
|
| - String::WriteToFlat(
|
| - *subject, answer->GetChars() + start, end, subject_length);
|
| - }
|
| - return *answer;
|
| + // If the regexp isn't global, only match once.
|
| + if (!global) {
|
| + if (start > 0) {
|
| + String::WriteToFlat(*subject_handle,
|
| + answer->GetChars(),
|
| + 0,
|
| + start);
|
| }
|
| + if (end < length) {
|
| + String::WriteToFlat(*subject_handle,
|
| + answer->GetChars() + start,
|
| + end,
|
| + length);
|
| + }
|
| + return *answer;
|
| }
|
|
|
| - int prev = 0;
|
| + int prev = 0; // Index of end of last match.
|
| + int next = 0; // Start of next search (prev unless last match was empty).
|
| int position = 0;
|
|
|
| do {
|
| - start = current_match[0];
|
| - end = current_match[1];
|
| if (prev < start) {
|
| // Add substring subject[prev;start] to answer string.
|
| - String::WriteToFlat(
|
| - *subject, answer->GetChars() + position, prev, start);
|
| + String::WriteToFlat(*subject_handle,
|
| + answer->GetChars() + position,
|
| + prev,
|
| + start);
|
| position += start - prev;
|
| }
|
| prev = end;
|
| + next = end;
|
| + // Continue from where the match ended, unless it was an empty match.
|
| + if (start == end) {
|
| + next++;
|
| + if (next > length) break;
|
| + }
|
| + match = RegExpImpl::Exec(regexp_handle,
|
| + subject_handle,
|
| + next,
|
| + last_match_info_handle);
|
| + if (match.is_null()) return Failure::Exception();
|
| + if (match->IsNull()) break;
|
| +
|
| + ASSERT(last_match_info_handle->HasFastObjectElements());
|
| + HandleScope loop_scope(isolate);
|
| + {
|
| + AssertNoAllocation match_info_array_is_not_in_a_handle;
|
| + FixedArray* match_info_array =
|
| + FixedArray::cast(last_match_info_handle->elements());
|
| + start = RegExpImpl::GetCapture(match_info_array, 0);
|
| + end = RegExpImpl::GetCapture(match_info_array, 1);
|
| + }
|
| + } while (true);
|
|
|
| - current_match = global_cache.FetchNext();
|
| - } while (current_match != NULL);
|
| -
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| -
|
| - RegExpImpl::SetLastMatchInfo(last_match_info,
|
| - subject,
|
| - capture_count,
|
| - global_cache.LastSuccessfulMatch());
|
| -
|
| - if (prev < subject_length) {
|
| + if (prev < length) {
|
| // Add substring subject[prev;length] to answer string.
|
| - String::WriteToFlat(
|
| - *subject, answer->GetChars() + position, prev, subject_length);
|
| - position += subject_length - prev;
|
| + String::WriteToFlat(*subject_handle,
|
| + answer->GetChars() + position,
|
| + prev,
|
| + length);
|
| + position += length - prev;
|
| }
|
|
|
| - if (position == 0) return isolate->heap()->empty_string();
|
| + if (position == 0) {
|
| + return isolate->heap()->empty_string();
|
| + }
|
|
|
| // Shorten string and fill
|
| int string_size = ResultSeqString::SizeFor(position);
|
| @@ -3269,31 +3411,50 @@ MUST_USE_RESULT static MaybeObject* StringReplaceRegExpWithEmptyString(
|
| RUNTIME_FUNCTION(MaybeObject*, Runtime_StringReplaceRegExpWithString) {
|
| ASSERT(args.length() == 4);
|
|
|
| - HandleScope scope(isolate);
|
| -
|
| - CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
|
| - CONVERT_ARG_HANDLE_CHECKED(String, replacement, 2);
|
| - CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
|
| - CONVERT_ARG_HANDLE_CHECKED(JSArray, last_match_info, 3);
|
| + CONVERT_ARG_CHECKED(String, subject, 0);
|
| + if (!subject->IsFlat()) {
|
| + Object* flat_subject;
|
| + { MaybeObject* maybe_flat_subject = subject->TryFlatten();
|
| + if (!maybe_flat_subject->ToObject(&flat_subject)) {
|
| + return maybe_flat_subject;
|
| + }
|
| + }
|
| + subject = String::cast(flat_subject);
|
| + }
|
|
|
| - if (!subject->IsFlat()) subject = FlattenGetString(subject);
|
| + CONVERT_ARG_CHECKED(String, replacement, 2);
|
| + if (!replacement->IsFlat()) {
|
| + Object* flat_replacement;
|
| + { MaybeObject* maybe_flat_replacement = replacement->TryFlatten();
|
| + if (!maybe_flat_replacement->ToObject(&flat_replacement)) {
|
| + return maybe_flat_replacement;
|
| + }
|
| + }
|
| + replacement = String::cast(flat_replacement);
|
| + }
|
|
|
| - if (!replacement->IsFlat()) replacement = FlattenGetString(replacement);
|
| + CONVERT_ARG_CHECKED(JSRegExp, regexp, 1);
|
| + CONVERT_ARG_CHECKED(JSArray, last_match_info, 3);
|
|
|
| ASSERT(last_match_info->HasFastObjectElements());
|
|
|
| + Zone* zone = isolate->runtime_zone();
|
| if (replacement->length() == 0) {
|
| if (subject->HasOnlyAsciiChars()) {
|
| return StringReplaceRegExpWithEmptyString<SeqAsciiString>(
|
| - isolate, subject, regexp, last_match_info);
|
| + isolate, subject, regexp, last_match_info, zone);
|
| } else {
|
| return StringReplaceRegExpWithEmptyString<SeqTwoByteString>(
|
| - isolate, subject, regexp, last_match_info);
|
| + isolate, subject, regexp, last_match_info, zone);
|
| }
|
| }
|
|
|
| - return StringReplaceRegExpWithString(
|
| - isolate, subject, regexp, replacement, last_match_info);
|
| + return StringReplaceRegExpWithString(isolate,
|
| + subject,
|
| + regexp,
|
| + replacement,
|
| + last_match_info,
|
| + zone);
|
| }
|
|
|
|
|
| @@ -3616,45 +3777,46 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringMatch) {
|
| CONVERT_ARG_HANDLE_CHECKED(JSArray, regexp_info, 2);
|
| HandleScope handles;
|
|
|
| - RegExpImpl::GlobalCache global_cache(regexp, subject, true, isolate);
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| -
|
| - int capture_count = regexp->CaptureCount();
|
| + Handle<Object> match = RegExpImpl::Exec(regexp, subject, 0, regexp_info);
|
|
|
| - Zone* zone = isolate->runtime_zone();
|
| - ZoneScope zone_space(zone, DELETE_ON_EXIT);
|
| - ZoneList<int> offsets(8, zone);
|
| -
|
| - while (true) {
|
| - int32_t* match = global_cache.FetchNext();
|
| - if (match == NULL) break;
|
| - offsets.Add(match[0], zone); // start
|
| - offsets.Add(match[1], zone); // end
|
| + if (match.is_null()) {
|
| + return Failure::Exception();
|
| }
|
| -
|
| - if (global_cache.HasException()) return Failure::Exception();
|
| -
|
| - if (offsets.length() == 0) {
|
| - // Not a single match.
|
| + if (match->IsNull()) {
|
| return isolate->heap()->null_value();
|
| }
|
| + int length = subject->length();
|
|
|
| - RegExpImpl::SetLastMatchInfo(regexp_info,
|
| - subject,
|
| - capture_count,
|
| - global_cache.LastSuccessfulMatch());
|
| -
|
| + Zone* zone = isolate->runtime_zone();
|
| + ZoneScope zone_space(zone, DELETE_ON_EXIT);
|
| + ZoneList<int> offsets(8, zone);
|
| + int start;
|
| + int end;
|
| + do {
|
| + {
|
| + AssertNoAllocation no_alloc;
|
| + FixedArray* elements = FixedArray::cast(regexp_info->elements());
|
| + start = Smi::cast(elements->get(RegExpImpl::kFirstCapture))->value();
|
| + end = Smi::cast(elements->get(RegExpImpl::kFirstCapture + 1))->value();
|
| + }
|
| + offsets.Add(start, zone);
|
| + offsets.Add(end, zone);
|
| + if (start == end) if (++end > length) break;
|
| + match = RegExpImpl::Exec(regexp, subject, end, regexp_info);
|
| + if (match.is_null()) {
|
| + return Failure::Exception();
|
| + }
|
| + } while (!match->IsNull());
|
| int matches = offsets.length() / 2;
|
| Handle<FixedArray> elements = isolate->factory()->NewFixedArray(matches);
|
| - Handle<String> substring =
|
| - isolate->factory()->NewSubString(subject, offsets.at(0), offsets.at(1));
|
| + Handle<String> substring = isolate->factory()->
|
| + NewSubString(subject, offsets.at(0), offsets.at(1));
|
| elements->set(0, *substring);
|
| - for (int i = 1; i < matches; i++) {
|
| - HandleScope temp_scope(isolate);
|
| + for (int i = 1; i < matches ; i++) {
|
| int from = offsets.at(i * 2);
|
| int to = offsets.at(i * 2 + 1);
|
| - Handle<String> substring =
|
| - isolate->factory()->NewProperSubString(subject, from, to);
|
| + Handle<String> substring = isolate->factory()->
|
| + NewProperSubString(subject, from, to);
|
| elements->set(i, *substring);
|
| }
|
| Handle<JSArray> result = isolate->factory()->NewJSArrayWithElements(elements);
|
| @@ -3663,100 +3825,149 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringMatch) {
|
| }
|
|
|
|
|
| -// Only called from Runtime_RegExpExecMultiple so it doesn't need to maintain
|
| -// separate last match info. See comment on that function.
|
| -template<bool has_capture>
|
| -static int SearchRegExpMultiple(
|
| +static bool SearchStringMultiple(Isolate* isolate,
|
| + Handle<String> subject,
|
| + Handle<String> pattern,
|
| + Handle<JSArray> last_match_info,
|
| + FixedArrayBuilder* builder) {
|
| + ASSERT(subject->IsFlat());
|
| + ASSERT(pattern->IsFlat());
|
| +
|
| + // Treating as if a previous match was before first character.
|
| + int match_pos = -pattern->length();
|
| +
|
| + for (;;) { // Break when search complete.
|
| + builder->EnsureCapacity(kMaxBuilderEntriesPerRegExpMatch);
|
| + AssertNoAllocation no_gc;
|
| + String::FlatContent subject_content = subject->GetFlatContent();
|
| + String::FlatContent pattern_content = pattern->GetFlatContent();
|
| + if (subject_content.IsAscii()) {
|
| + Vector<const char> subject_vector = subject_content.ToAsciiVector();
|
| + if (pattern_content.IsAscii()) {
|
| + if (SearchStringMultiple(isolate,
|
| + subject_vector,
|
| + pattern_content.ToAsciiVector(),
|
| + *pattern,
|
| + builder,
|
| + &match_pos)) break;
|
| + } else {
|
| + if (SearchStringMultiple(isolate,
|
| + subject_vector,
|
| + pattern_content.ToUC16Vector(),
|
| + *pattern,
|
| + builder,
|
| + &match_pos)) break;
|
| + }
|
| + } else {
|
| + Vector<const uc16> subject_vector = subject_content.ToUC16Vector();
|
| + if (pattern_content.IsAscii()) {
|
| + if (SearchStringMultiple(isolate,
|
| + subject_vector,
|
| + pattern_content.ToAsciiVector(),
|
| + *pattern,
|
| + builder,
|
| + &match_pos)) break;
|
| + } else {
|
| + if (SearchStringMultiple(isolate,
|
| + subject_vector,
|
| + pattern_content.ToUC16Vector(),
|
| + *pattern,
|
| + builder,
|
| + &match_pos)) break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (match_pos >= 0) {
|
| + SetLastMatchInfoNoCaptures(subject,
|
| + last_match_info,
|
| + match_pos,
|
| + match_pos + pattern->length());
|
| + return true;
|
| + }
|
| + return false; // No matches at all.
|
| +}
|
| +
|
| +
|
| +static int SearchRegExpNoCaptureMultiple(
|
| Isolate* isolate,
|
| Handle<String> subject,
|
| Handle<JSRegExp> regexp,
|
| Handle<JSArray> last_match_array,
|
| FixedArrayBuilder* builder) {
|
| ASSERT(subject->IsFlat());
|
| - ASSERT_NE(has_capture, regexp->CaptureCount() == 0);
|
| -
|
| - RegExpImpl::GlobalCache global_cache(regexp, subject, true, isolate);
|
| - if (global_cache.HasException()) return RegExpImpl::RE_EXCEPTION;
|
| -
|
| - int capture_count = regexp->CaptureCount();
|
| - int subject_length = subject->length();
|
| -
|
| - // Position to search from.
|
| + ASSERT(regexp->CaptureCount() == 0);
|
| int match_start = -1;
|
| int match_end = 0;
|
| + int pos = 0;
|
| + int registers_per_match = RegExpImpl::IrregexpPrepare(regexp, subject);
|
| + if (registers_per_match < 0) return RegExpImpl::RE_EXCEPTION;
|
| +
|
| + int max_matches;
|
| + int num_registers = RegExpImpl::GlobalOffsetsVectorSize(regexp,
|
| + registers_per_match,
|
| + &max_matches);
|
| + OffsetsVector registers(num_registers, isolate);
|
| + Vector<int32_t> register_vector(registers.vector(), registers.length());
|
| + int subject_length = subject->length();
|
| bool first = true;
|
| -
|
| - // Two smis before and after the match, for very long strings.
|
| - static const int kMaxBuilderEntriesPerRegExpMatch = 5;
|
| -
|
| - while (true) {
|
| - int32_t* current_match = global_cache.FetchNext();
|
| - if (current_match == NULL) break;
|
| - match_start = current_match[0];
|
| - builder->EnsureCapacity(kMaxBuilderEntriesPerRegExpMatch);
|
| - if (match_end < match_start) {
|
| - ReplacementStringBuilder::AddSubjectSlice(builder,
|
| - match_end,
|
| - match_start);
|
| - }
|
| - match_end = current_match[1];
|
| - {
|
| - // Avoid accumulating new handles inside loop.
|
| - HandleScope temp_scope(isolate);
|
| - Handle<String> match;
|
| - if (!first) {
|
| - match = isolate->factory()->NewProperSubString(subject,
|
| - match_start,
|
| - match_end);
|
| - } else {
|
| - match = isolate->factory()->NewSubString(subject,
|
| - match_start,
|
| - match_end);
|
| - first = false;
|
| + for (;;) { // Break on failure, return on exception.
|
| + int num_matches = RegExpImpl::IrregexpExecRaw(regexp,
|
| + subject,
|
| + pos,
|
| + register_vector);
|
| + if (num_matches > 0) {
|
| + for (int match_index = 0; match_index < num_matches; match_index++) {
|
| + int32_t* current_match = ®ister_vector[match_index * 2];
|
| + match_start = current_match[0];
|
| + builder->EnsureCapacity(kMaxBuilderEntriesPerRegExpMatch);
|
| + if (match_end < match_start) {
|
| + ReplacementStringBuilder::AddSubjectSlice(builder,
|
| + match_end,
|
| + match_start);
|
| + }
|
| + match_end = current_match[1];
|
| + HandleScope loop_scope(isolate);
|
| + if (!first) {
|
| + builder->Add(*isolate->factory()->NewProperSubString(subject,
|
| + match_start,
|
| + match_end));
|
| + } else {
|
| + builder->Add(*isolate->factory()->NewSubString(subject,
|
| + match_start,
|
| + match_end));
|
| + first = false;
|
| + }
|
| }
|
|
|
| - if (has_capture) {
|
| - // Arguments array to replace function is match, captures, index and
|
| - // subject, i.e., 3 + capture count in total.
|
| - Handle<FixedArray> elements =
|
| - isolate->factory()->NewFixedArray(3 + capture_count);
|
| -
|
| - elements->set(0, *match);
|
| - for (int i = 1; i <= capture_count; i++) {
|
| - int start = current_match[i * 2];
|
| - if (start >= 0) {
|
| - int end = current_match[i * 2 + 1];
|
| - ASSERT(start <= end);
|
| - Handle<String> substring =
|
| - isolate->factory()->NewSubString(subject, start, end);
|
| - elements->set(i, *substring);
|
| - } else {
|
| - ASSERT(current_match[i * 2 + 1] < 0);
|
| - elements->set(i, isolate->heap()->undefined_value());
|
| - }
|
| - }
|
| - elements->set(capture_count + 1, Smi::FromInt(match_start));
|
| - elements->set(capture_count + 2, *subject);
|
| - builder->Add(*isolate->factory()->NewJSArrayWithElements(elements));
|
| + // If we did not get the maximum number of matches, we can stop here
|
| + // since there are no matches left.
|
| + if (num_matches < max_matches) break;
|
| +
|
| + if (match_start != match_end) {
|
| + pos = match_end;
|
| } else {
|
| - builder->Add(*match);
|
| + pos = match_end + 1;
|
| + if (pos > subject_length) break;
|
| }
|
| + } else if (num_matches == 0) {
|
| + break;
|
| + } else {
|
| + ASSERT_EQ(num_matches, RegExpImpl::RE_EXCEPTION);
|
| + return RegExpImpl::RE_EXCEPTION;
|
| }
|
| }
|
|
|
| - if (global_cache.HasException()) return RegExpImpl::RE_EXCEPTION;
|
| -
|
| if (match_start >= 0) {
|
| - // Finished matching, with at least one match.
|
| if (match_end < subject_length) {
|
| ReplacementStringBuilder::AddSubjectSlice(builder,
|
| match_end,
|
| subject_length);
|
| }
|
| -
|
| - RegExpImpl::SetLastMatchInfo(
|
| - last_match_array, subject, capture_count, NULL);
|
| -
|
| + SetLastMatchInfoNoCaptures(subject,
|
| + last_match_array,
|
| + match_start,
|
| + match_end);
|
| return RegExpImpl::RE_SUCCESS;
|
| } else {
|
| return RegExpImpl::RE_FAILURE; // No matches at all.
|
| @@ -3764,6 +3975,147 @@ static int SearchRegExpMultiple(
|
| }
|
|
|
|
|
| +// Only called from Runtime_RegExpExecMultiple so it doesn't need to maintain
|
| +// separate last match info. See comment on that function.
|
| +static int SearchRegExpMultiple(
|
| + Isolate* isolate,
|
| + Handle<String> subject,
|
| + Handle<JSRegExp> regexp,
|
| + Handle<JSArray> last_match_array,
|
| + FixedArrayBuilder* builder,
|
| + Zone* zone) {
|
| +
|
| + ASSERT(subject->IsFlat());
|
| + int registers_per_match = RegExpImpl::IrregexpPrepare(regexp, subject);
|
| + if (registers_per_match < 0) return RegExpImpl::RE_EXCEPTION;
|
| +
|
| + int max_matches;
|
| + int num_registers = RegExpImpl::GlobalOffsetsVectorSize(regexp,
|
| + registers_per_match,
|
| + &max_matches);
|
| + OffsetsVector registers(num_registers, isolate);
|
| + Vector<int32_t> register_vector(registers.vector(), registers.length());
|
| +
|
| + int num_matches = RegExpImpl::IrregexpExecRaw(regexp,
|
| + subject,
|
| + 0,
|
| + register_vector);
|
| +
|
| + int capture_count = regexp->CaptureCount();
|
| + int subject_length = subject->length();
|
| +
|
| + // Position to search from.
|
| + int pos = 0;
|
| + // End of previous match. Differs from pos if match was empty.
|
| + int match_end = 0;
|
| + bool first = true;
|
| +
|
| + if (num_matches > 0) {
|
| + do {
|
| + int match_start = 0;
|
| + for (int match_index = 0; match_index < num_matches; match_index++) {
|
| + int32_t* current_match =
|
| + ®ister_vector[match_index * registers_per_match];
|
| + match_start = current_match[0];
|
| + builder->EnsureCapacity(kMaxBuilderEntriesPerRegExpMatch);
|
| + if (match_end < match_start) {
|
| + ReplacementStringBuilder::AddSubjectSlice(builder,
|
| + match_end,
|
| + match_start);
|
| + }
|
| + match_end = current_match[1];
|
| +
|
| + {
|
| + // Avoid accumulating new handles inside loop.
|
| + HandleScope temp_scope(isolate);
|
| + // Arguments array to replace function is match, captures, index and
|
| + // subject, i.e., 3 + capture count in total.
|
| + Handle<FixedArray> elements =
|
| + isolate->factory()->NewFixedArray(3 + capture_count);
|
| + Handle<String> match;
|
| + if (!first) {
|
| + match = isolate->factory()->NewProperSubString(subject,
|
| + match_start,
|
| + match_end);
|
| + } else {
|
| + match = isolate->factory()->NewSubString(subject,
|
| + match_start,
|
| + match_end);
|
| + }
|
| + elements->set(0, *match);
|
| + for (int i = 1; i <= capture_count; i++) {
|
| + int start = current_match[i * 2];
|
| + if (start >= 0) {
|
| + int end = current_match[i * 2 + 1];
|
| + ASSERT(start <= end);
|
| + Handle<String> substring;
|
| + if (!first) {
|
| + substring =
|
| + isolate->factory()->NewProperSubString(subject, start, end);
|
| + } else {
|
| + substring =
|
| + isolate->factory()->NewSubString(subject, start, end);
|
| + }
|
| + elements->set(i, *substring);
|
| + } else {
|
| + ASSERT(current_match[i * 2 + 1] < 0);
|
| + elements->set(i, isolate->heap()->undefined_value());
|
| + }
|
| + }
|
| + elements->set(capture_count + 1, Smi::FromInt(match_start));
|
| + elements->set(capture_count + 2, *subject);
|
| + builder->Add(*isolate->factory()->NewJSArrayWithElements(elements));
|
| + }
|
| + first = false;
|
| + }
|
| +
|
| + // If we did not get the maximum number of matches, we can stop here
|
| + // since there are no matches left.
|
| + if (num_matches < max_matches) break;
|
| +
|
| + if (match_end > match_start) {
|
| + pos = match_end;
|
| + } else {
|
| + pos = match_end + 1;
|
| + if (pos > subject_length) {
|
| + break;
|
| + }
|
| + }
|
| +
|
| + num_matches = RegExpImpl::IrregexpExecRaw(regexp,
|
| + subject,
|
| + pos,
|
| + register_vector);
|
| + } while (num_matches > 0);
|
| +
|
| + if (num_matches != RegExpImpl::RE_EXCEPTION) {
|
| + // Finished matching, with at least one match.
|
| + if (match_end < subject_length) {
|
| + ReplacementStringBuilder::AddSubjectSlice(builder,
|
| + match_end,
|
| + subject_length);
|
| + }
|
| +
|
| + int last_match_capture_count = (capture_count + 1) * 2;
|
| + int last_match_array_size =
|
| + last_match_capture_count + RegExpImpl::kLastMatchOverhead;
|
| + last_match_array->EnsureSize(last_match_array_size);
|
| + AssertNoAllocation no_gc;
|
| + FixedArray* elements = FixedArray::cast(last_match_array->elements());
|
| + // We have to set this even though the rest of the last match array is
|
| + // ignored.
|
| + RegExpImpl::SetLastCaptureCount(elements, last_match_capture_count);
|
| + // These are also read without consulting the override.
|
| + RegExpImpl::SetLastSubject(elements, *subject);
|
| + RegExpImpl::SetLastInput(elements, *subject);
|
| + return RegExpImpl::RE_SUCCESS;
|
| + }
|
| + }
|
| + // No matches at all, return failure or exception result directly.
|
| + return num_matches;
|
| +}
|
| +
|
| +
|
| // This is only called for StringReplaceGlobalRegExpWithFunction. This sets
|
| // lastMatchInfoOverride to maintain the last match info, so we don't need to
|
| // set any other last match array info.
|
| @@ -3789,15 +4141,34 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_RegExpExecMultiple) {
|
| }
|
| FixedArrayBuilder builder(result_elements);
|
|
|
| + if (regexp->TypeTag() == JSRegExp::ATOM) {
|
| + Handle<String> pattern(
|
| + String::cast(regexp->DataAt(JSRegExp::kAtomPatternIndex)));
|
| + ASSERT(pattern->IsFlat());
|
| + if (SearchStringMultiple(isolate, subject, pattern,
|
| + last_match_info, &builder)) {
|
| + return *builder.ToJSArray(result_array);
|
| + }
|
| + return isolate->heap()->null_value();
|
| + }
|
| +
|
| + ASSERT_EQ(regexp->TypeTag(), JSRegExp::IRREGEXP);
|
| +
|
| int result;
|
| if (regexp->CaptureCount() == 0) {
|
| - result = SearchRegExpMultiple<false>(
|
| - isolate, subject, regexp, last_match_info, &builder);
|
| + result = SearchRegExpNoCaptureMultiple(isolate,
|
| + subject,
|
| + regexp,
|
| + last_match_info,
|
| + &builder);
|
| } else {
|
| - result = SearchRegExpMultiple<true>(
|
| - isolate, subject, regexp, last_match_info, &builder);
|
| + result = SearchRegExpMultiple(isolate,
|
| + subject,
|
| + regexp,
|
| + last_match_info,
|
| + &builder,
|
| + isolate->runtime_zone());
|
| }
|
| -
|
| if (result == RegExpImpl::RE_SUCCESS) return *builder.ToJSArray(result_array);
|
| if (result == RegExpImpl::RE_FAILURE) return isolate->heap()->null_value();
|
| ASSERT_EQ(result, RegExpImpl::RE_EXCEPTION);
|
|
|