mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-11 02:13:56 +09:00
LibJS: Add spec comments to RegExp.prototype [ @@replace ]
In doing so, this caught an erroneous ToObject invocation. In the one spot that requires the value to be an object (the invocation to LengthOfArrayLike), we know by then the value is an object because all other possibilities have been handled.
This commit is contained in:
parent
9f7c3e6cad
commit
25b6e79238
Notes:
sideshowbarker
2024-07-17 22:30:48 +09:00
Author: https://github.com/trflynn89
Commit: 25b6e79238
Pull-request: https://github.com/SerenityOS/serenity/pull/11334
Reviewed-by: https://github.com/davidot ✅
Reviewed-by: https://github.com/linusg ✅
1 changed files with 107 additions and 28 deletions
|
@ -478,105 +478,184 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace)
|
|||
auto string_value = vm.argument(0);
|
||||
auto replace_value = vm.argument(1);
|
||||
|
||||
// 1. Let rx be the this value.
|
||||
// 2. If Type(rx) is not Object, throw a TypeError exception.
|
||||
auto* regexp_object = TRY(this_object(global_object));
|
||||
auto string = TRY(string_value.to_utf16_string(global_object));
|
||||
auto string_view = string.view();
|
||||
|
||||
// 3. Let S be ? ToString(string).
|
||||
auto string = TRY(string_value.to_utf16_string(global_object));
|
||||
|
||||
// 4. Let lengthS be the number of code unit elements in S.
|
||||
// 5. Let functionalReplace be IsCallable(replaceValue).
|
||||
|
||||
// 6. If functionalReplace is false, then
|
||||
if (!replace_value.is_function()) {
|
||||
// a. Set replaceValue to ? ToString(replaceValue).
|
||||
auto replace_string = TRY(replace_value.to_string(global_object));
|
||||
replace_value = js_string(vm, move(replace_string));
|
||||
}
|
||||
|
||||
// 7. Let global be ! ToBoolean(? Get(rx, "global")).
|
||||
bool global = TRY(regexp_object->get(vm.names.global)).to_boolean();
|
||||
bool unicode = false;
|
||||
bool full_unicode = false;
|
||||
|
||||
// 8. If global is true, then
|
||||
if (global) {
|
||||
unicode = TRY(regexp_object->get(vm.names.unicode)).to_boolean();
|
||||
// a. Let fullUnicode be ! ToBoolean(? Get(rx, "unicode")).
|
||||
full_unicode = TRY(regexp_object->get(vm.names.unicode)).to_boolean();
|
||||
|
||||
// b. Perform ? Set(rx, "lastIndex", +0𝔽, true).
|
||||
TRY(regexp_object->set(vm.names.lastIndex, Value(0), Object::ShouldThrowExceptions::Yes));
|
||||
}
|
||||
|
||||
// 9. Let results be a new empty List.
|
||||
MarkedValueList results(vm.heap());
|
||||
|
||||
// 10. Let done be false.
|
||||
// 11. Repeat, while done is false,
|
||||
while (true) {
|
||||
// a. Let result be ? RegExpExec(rx, S).
|
||||
auto result = TRY(regexp_exec(global_object, *regexp_object, string));
|
||||
|
||||
// b. If result is null, set done to true.
|
||||
if (result.is_null())
|
||||
break;
|
||||
|
||||
auto* result_object = TRY(result.to_object(global_object));
|
||||
results.append(result_object);
|
||||
// c. Else,
|
||||
|
||||
// i. Append result to the end of results.
|
||||
results.append(result);
|
||||
|
||||
// ii. If global is false, set done to true.
|
||||
if (!global)
|
||||
break;
|
||||
|
||||
auto match_object = TRY(result_object->get(0));
|
||||
auto match_str = TRY(match_object.to_string(global_object));
|
||||
// iii. Else,
|
||||
|
||||
if (match_str.is_empty())
|
||||
TRY(increment_last_index(global_object, *regexp_object, string_view, unicode));
|
||||
// 1. Let matchStr be ? ToString(? Get(result, "0")).
|
||||
auto match_value = TRY(result.get(global_object, 0));
|
||||
auto match_str = TRY(match_value.to_string(global_object));
|
||||
|
||||
// 2. If matchStr is the empty String, then
|
||||
if (match_str.is_empty()) {
|
||||
// Stepsp 2a-2c are implemented by increment_last_index.
|
||||
TRY(increment_last_index(global_object, *regexp_object, string.view(), full_unicode));
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Let accumulatedResult be the empty String.
|
||||
StringBuilder accumulated_result;
|
||||
|
||||
// 13. Let nextSourcePosition be 0.
|
||||
size_t next_source_position = 0;
|
||||
|
||||
for (auto& result_value : results) {
|
||||
auto& result = result_value.as_object();
|
||||
size_t result_length = TRY(length_of_array_like(global_object, result));
|
||||
// 14. For each element result of results, do
|
||||
for (auto& result : results) {
|
||||
// a. Let resultLength be ? LengthOfArrayLike(result).
|
||||
size_t result_length = TRY(length_of_array_like(global_object, result.as_object()));
|
||||
|
||||
// b. Let nCaptures be max(resultLength - 1, 0).
|
||||
size_t n_captures = result_length == 0 ? 0 : result_length - 1;
|
||||
|
||||
auto matched_value = TRY(result.get(0));
|
||||
// c. Let matched be ? ToString(? Get(result, "0")).
|
||||
auto matched_value = TRY(result.get(global_object, 0));
|
||||
auto matched = TRY(matched_value.to_utf16_string(global_object));
|
||||
|
||||
// d. Let matchLength be the number of code units in matched.
|
||||
auto matched_length = matched.length_in_code_units();
|
||||
|
||||
auto position_value = TRY(result.get(vm.names.index));
|
||||
// e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")).
|
||||
auto position_value = TRY(result.get(global_object, vm.names.index));
|
||||
double position = TRY(position_value.to_integer_or_infinity(global_object));
|
||||
|
||||
// f. Set position to the result of clamping position between 0 and lengthS.
|
||||
position = clamp(position, static_cast<double>(0), static_cast<double>(string.length_in_code_units()));
|
||||
|
||||
MarkedValueList captures(vm.heap());
|
||||
for (size_t n = 1; n <= n_captures; ++n) {
|
||||
auto capture = TRY(result.get(n));
|
||||
if (!capture.is_undefined())
|
||||
capture = js_string(vm, TRY(capture.to_string(global_object)));
|
||||
// g. Let n be 1.
|
||||
|
||||
// h. Let captures be a new empty List.
|
||||
MarkedValueList captures(vm.heap());
|
||||
|
||||
// i. Repeat, while n ≤ nCaptures,
|
||||
for (size_t n = 1; n <= n_captures; ++n) {
|
||||
// i. Let capN be ? Get(result, ! ToString(𝔽(n))).
|
||||
auto capture = TRY(result.get(global_object, n));
|
||||
|
||||
// ii. If capN is not undefined, then
|
||||
if (!capture.is_undefined()) {
|
||||
// 1. Set capN to ? ToString(capN).
|
||||
capture = js_string(vm, TRY(capture.to_string(global_object)));
|
||||
}
|
||||
|
||||
// iii. Append capN as the last element of captures.
|
||||
captures.append(move(capture));
|
||||
|
||||
// iv. NOTE: When n = 1, the preceding step puts the first element into captures (at index 0). More generally, the nth capture (the characters captured by the nth set of capturing parentheses) is at captures[n - 1].
|
||||
// v. Set n to n + 1.
|
||||
}
|
||||
|
||||
auto named_captures = TRY(result.get(vm.names.groups));
|
||||
// j. Let namedCaptures be ? Get(result, "groups").
|
||||
auto named_captures = TRY(result.get(global_object, vm.names.groups));
|
||||
|
||||
String replacement;
|
||||
|
||||
// k. If functionalReplace is true, then
|
||||
if (replace_value.is_function()) {
|
||||
// i. Let replacerArgs be « matched ».
|
||||
MarkedValueList replacer_args(vm.heap());
|
||||
replacer_args.append(js_string(vm, move(matched)));
|
||||
|
||||
// ii. Append in List order the elements of captures to the end of the List replacerArgs.
|
||||
replacer_args.extend(move(captures));
|
||||
|
||||
// iii. Append 𝔽(position) and S to replacerArgs.
|
||||
replacer_args.append(Value(position));
|
||||
replacer_args.append(js_string(vm, string));
|
||||
|
||||
// iv. If namedCaptures is not undefined, then
|
||||
if (!named_captures.is_undefined()) {
|
||||
// 1. Append namedCaptures as the last element of replacerArgs.
|
||||
replacer_args.append(move(named_captures));
|
||||
}
|
||||
|
||||
// v. Let replValue be ? Call(replaceValue, undefined, replacerArgs).
|
||||
auto replace_result = TRY(vm.call(replace_value.as_function(), js_undefined(), move(replacer_args)));
|
||||
replacement = TRY(replace_result.to_string(global_object));
|
||||
} else {
|
||||
if (!named_captures.is_undefined())
|
||||
named_captures = TRY(named_captures.to_object(global_object));
|
||||
|
||||
replacement = TRY(get_substitution(global_object, matched.view(), string_view, position, captures, named_captures, replace_value));
|
||||
// vi. Let replacement be ? ToString(replValue).
|
||||
replacement = TRY(replace_result.to_string(global_object));
|
||||
}
|
||||
// l. Else,
|
||||
else {
|
||||
/// i. If namedCaptures is not undefined, then
|
||||
if (!named_captures.is_undefined()) {
|
||||
// 1. Set namedCaptures to ? ToObject(namedCaptures).
|
||||
named_captures = TRY(named_captures.to_object(global_object));
|
||||
}
|
||||
|
||||
// ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue).
|
||||
replacement = TRY(get_substitution(global_object, matched.view(), string.view(), position, captures, named_captures, replace_value));
|
||||
}
|
||||
|
||||
// m. If position ≥ nextSourcePosition, then
|
||||
if (position >= next_source_position) {
|
||||
auto substring = string_view.substring_view(next_source_position, position - next_source_position);
|
||||
// i. NOTE: position should not normally move backwards. If it does, it is an indication of an ill-behaving RegExp subclass or use of an access triggered side-effect to change the global flag or other characteristics of rx. In such cases, the corresponding substitution is ignored.
|
||||
|
||||
// ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement.
|
||||
auto substring = string.substring_view(next_source_position, position - next_source_position);
|
||||
accumulated_result.append(substring);
|
||||
accumulated_result.append(replacement);
|
||||
|
||||
// iii. Set nextSourcePosition to position + matchLength.
|
||||
next_source_position = position + matched_length;
|
||||
}
|
||||
}
|
||||
|
||||
// 15. If nextSourcePosition ≥ lengthS, return accumulatedResult.
|
||||
if (next_source_position >= string.length_in_code_units())
|
||||
return js_string(vm, accumulated_result.build());
|
||||
|
||||
auto substring = string_view.substring_view(next_source_position);
|
||||
// 16. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition.
|
||||
auto substring = string.substring_view(next_source_position);
|
||||
accumulated_result.append(substring);
|
||||
|
||||
return js_string(vm, accumulated_result.build());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue