mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 05:27:40 +00:00
LibJS: Implement and use the GetSubstitution abstract operation
Used by String.prototype.replace, String.prototype.replaceAll, and RegExp.prototype [ @@replace ].
This commit is contained in:
parent
4985d3ef42
commit
e0c9f58b0c
5 changed files with 129 additions and 6 deletions
|
@ -5,6 +5,7 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/CharacterTypes.h>
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
|
@ -574,4 +575,91 @@ Value canonical_numeric_index_string(GlobalObject& global_object, PropertyName c
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 22.1.3.17.1 GetSubstitution ( matched, str, position, captures, namedCaptures, replacement ), https://tc39.es/ecma262/#sec-getsubstitution
|
||||||
|
String get_substitution(GlobalObject& global_object, String const& matched, String const& str, size_t position, Vector<Value> const& captures, Value named_captures, Value replacement)
|
||||||
|
{
|
||||||
|
auto& vm = global_object.vm();
|
||||||
|
|
||||||
|
auto replace_string = replacement.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
StringBuilder result;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < replace_string.length(); ++i) {
|
||||||
|
char curr = replace_string[i];
|
||||||
|
|
||||||
|
if ((curr != '$') || (i + 1 >= replace_string.length())) {
|
||||||
|
result.append(curr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char next = replace_string[i + 1];
|
||||||
|
|
||||||
|
if (next == '$') {
|
||||||
|
result.append(next);
|
||||||
|
++i;
|
||||||
|
} else if (next == '&') {
|
||||||
|
result.append(matched);
|
||||||
|
++i;
|
||||||
|
} else if (next == '`') {
|
||||||
|
result.append(str.substring_view(0, position));
|
||||||
|
++i;
|
||||||
|
} else if (next == '\'') {
|
||||||
|
auto tail_pos = position + matched.length();
|
||||||
|
if (tail_pos < str.length())
|
||||||
|
result.append(str.substring_view(tail_pos));
|
||||||
|
++i;
|
||||||
|
} else if (is_ascii_digit(next)) {
|
||||||
|
bool is_two_digits = (i + 2 < replace_string.length()) && is_ascii_digit(replace_string[i + 2]);
|
||||||
|
|
||||||
|
auto capture_postition_string = replace_string.substring_view(i + 1, is_two_digits ? 2 : 1);
|
||||||
|
auto capture_position = capture_postition_string.to_uint();
|
||||||
|
|
||||||
|
if (capture_position.has_value() && (*capture_position > 0) && (*capture_position <= captures.size())) {
|
||||||
|
auto& value = captures[*capture_position - 1];
|
||||||
|
|
||||||
|
if (!value.is_undefined()) {
|
||||||
|
auto value_string = value.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
result.append(value_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
i += is_two_digits ? 2 : 1;
|
||||||
|
} else {
|
||||||
|
result.append(curr);
|
||||||
|
}
|
||||||
|
} else if (next == '<') {
|
||||||
|
auto start_position = i + 2;
|
||||||
|
auto end_position = replace_string.find('>', start_position);
|
||||||
|
|
||||||
|
if (named_captures.is_undefined() || !end_position.has_value()) {
|
||||||
|
result.append(curr);
|
||||||
|
} else {
|
||||||
|
auto group_name = replace_string.substring(start_position, *end_position - start_position);
|
||||||
|
|
||||||
|
auto capture = named_captures.as_object().get(group_name);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (!capture.is_undefined()) {
|
||||||
|
auto capture_string = capture.to_string(global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
result.append(capture_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = *end_position;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.append(curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ Object* get_prototype_from_constructor(GlobalObject&, FunctionObject const& cons
|
||||||
Object* create_unmapped_arguments_object(GlobalObject&, Vector<Value> const& arguments);
|
Object* create_unmapped_arguments_object(GlobalObject&, Vector<Value> const& arguments);
|
||||||
Object* create_mapped_arguments_object(GlobalObject&, FunctionObject&, Vector<FunctionNode::Parameter> const&, Vector<Value> const& arguments, Environment&);
|
Object* create_mapped_arguments_object(GlobalObject&, FunctionObject&, Vector<FunctionNode::Parameter> const&, Vector<Value> const& arguments, Environment&);
|
||||||
Value canonical_numeric_index_string(GlobalObject&, PropertyName const&);
|
Value canonical_numeric_index_string(GlobalObject&, PropertyName const&);
|
||||||
|
String get_substitution(GlobalObject&, String const& matched, String const& str, size_t position, Vector<Value> const& captures, Value named_captures, Value replacement);
|
||||||
|
|
||||||
enum class CallerMode {
|
enum class CallerMode {
|
||||||
Strict,
|
Strict,
|
||||||
|
|
|
@ -399,8 +399,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace)
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Implement the GetSubstituion algorithm for substituting placeholder '$' characters - https://tc39.es/ecma262/#sec-getsubstitution
|
replacement = get_substitution(global_object, matched, string, position, captures, named_captures, replace_value);
|
||||||
replacement = replace_value.to_string(global_object);
|
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -822,8 +822,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Implement the GetSubstituion algorithm for substituting placeholder '$' characters - https://tc39.es/ecma262/#sec-getsubstitution
|
replacement = get_substitution(global_object, search_string, string, *position, {}, js_undefined(), replace_value);
|
||||||
replacement = replace_value.to_string(global_object);
|
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -902,8 +901,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Implement the GetSubstituion algorithm for substituting placeholder '$' characters - https://tc39.es/ecma262/#sec-getsubstitution
|
replacement = get_substitution(global_object, search_string, string, position, {}, js_undefined(), replace_value);
|
||||||
replacement = replace_value.to_string(global_object);
|
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,3 +102,40 @@ test("functional regex replacement", () => {
|
||||||
})
|
})
|
||||||
).toBe("xd");
|
).toBe("xd");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("replacement with substitution", () => {
|
||||||
|
expect("abc".replace("b", "$")).toBe("a$c");
|
||||||
|
expect("abc".replace("b", "$.")).toBe("a$.c");
|
||||||
|
|
||||||
|
expect("abc".replace("b", "$$")).toBe("a$c");
|
||||||
|
expect("abc".replace("b", ">$$<")).toBe("a>$<c");
|
||||||
|
expect("abc".replace("b", "$$$$")).toBe("a$$c");
|
||||||
|
|
||||||
|
expect("abc".replace("b", "$&")).toBe("abc");
|
||||||
|
expect("a123c".replace(/\d+/, "$&")).toBe("a123c");
|
||||||
|
|
||||||
|
expect("abc".replace("b", "$`")).toBe("aac");
|
||||||
|
expect("aabc".replace("b", "$`")).toBe("aaaac");
|
||||||
|
expect("a123c".replace(/\d+/, "$`")).toBe("aac");
|
||||||
|
|
||||||
|
expect("abc".replace("b", "$'")).toBe("acc");
|
||||||
|
expect("abcc".replace("b", "$'")).toBe("acccc");
|
||||||
|
expect("a123c".replace(/\d+/, "$'")).toBe("acc");
|
||||||
|
|
||||||
|
expect("abc".replace("b", "$0")).toBe("a$0c");
|
||||||
|
expect("abc".replace("b", "$99")).toBe("a$99c");
|
||||||
|
expect("abc".replace("b", "$100")).toBe("a$100c");
|
||||||
|
expect("abc".replace(/(a)b(c)/, "$0")).toBe("$0");
|
||||||
|
expect("abc".replace(/(a)b(c)/, "$1")).toBe("a");
|
||||||
|
expect("abc".replace(/(a)b(c)/, "$2")).toBe("c");
|
||||||
|
expect("abc".replace(/(a)b(c)/, "$3")).toBe("$3");
|
||||||
|
expect("abc".replace(/(a)b(c)/, "$2b$1")).toBe("cba");
|
||||||
|
|
||||||
|
expect("abc".replace("b", "$<val>")).toBe("a$<val>c");
|
||||||
|
expect("abc".replace(/(?<val1>a)b(?<val2>c)/, "$<")).toBe("$<");
|
||||||
|
expect("abc".replace(/(?<val1>a)b(?<val2>c)/, "$<not_terminated")).toBe("$<not_terminated");
|
||||||
|
expect("abc".replace(/(?<val1>a)b(?<val2>c)/, "$<not_found>")).toBe("");
|
||||||
|
expect("abc".replace(/(?<val1>a)b(?<val2>c)/, "$<val1>")).toBe("a");
|
||||||
|
expect("abc".replace(/(?<val1>a)b(?<val2>c)/, "$<val2>")).toBe("c");
|
||||||
|
expect("abc".replace(/(?<val1>a)b(?<val2>c)/, "$<val2>b$<val1>")).toBe("cba");
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue