Skip to content

Commit 21d412a

Browse files
oscaroteroload1n9
andauthored
String/replaceall (#428)
Co-authored-by: Dean Srebnik <[email protected]>
1 parent cfe4147 commit 21d412a

File tree

3 files changed

+125
-42
lines changed

3 files changed

+125
-42
lines changed

nova_vm/src/ecmascript/builtins/text_processing/string_objects/string_prototype.rs

Lines changed: 122 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use std::{collections::VecDeque, iter::repeat};
5+
use std::{cmp::max, collections::VecDeque, iter::repeat};
66

77
use small_string::SmallString;
88

@@ -682,7 +682,7 @@ impl StringPrototype {
682682
let search_length = search_string.len(agent);
683683

684684
// 8. Let position be StringIndexOf(s, searchString, 0).
685-
let position = if let Some(position) = search_string.as_str(agent).find(s.as_str(agent))
685+
let position = if let Some(position) = s.as_str(agent).find(search_string.as_str(agent))
686686
{
687687
position
688688
} else {
@@ -708,7 +708,7 @@ impl StringPrototype {
708708
// 11. Let following be the substring of s from position + searchLength.
709709
// 12. If functionalReplace is true,
710710
let preceding = &s.as_str(agent)[0..position].to_owned();
711-
let following = &s.as_str(agent)[position..search_length].to_owned();
711+
let following = &s.as_str(agent)[position + search_length..].to_owned();
712712

713713
// 14. Return the string-concatenation of preceding, replacement, and following.
714714
let concatenated_result = format!("{}{}{}", preceding, result.as_str(agent), following);
@@ -718,16 +718,131 @@ impl StringPrototype {
718718
// 6. If functionalReplace is false, Set replaceValue to ? ToString(replaceValue).
719719
let replace_string = to_string(agent, replace_value)?;
720720
// Everything are strings: `"foo".replace("o", "a")` => use rust's replace
721+
let result =
722+
s.as_str(agent)
723+
.replacen(search_string.as_str(agent), replace_string.as_str(agent), 1);
724+
Ok(String::from_string(agent, result).into_value())
725+
}
726+
727+
/// ### [22.1.3.20 String.prototype.replaceAll ( searchValue, replaceValue )](https://tc39.es/ecma262/multipage/text-processing.html#sec-string.prototype.replaceall)
728+
fn replace_all(agent: &mut Agent, this_value: Value, args: ArgumentsList) -> JsResult<Value> {
729+
// 1. Let O be ? RequireObjectCoercible(this value).
730+
let o = require_object_coercible(agent, this_value)?;
731+
732+
let search_value = args.get(0);
733+
let replace_value = args.get(1);
734+
735+
// 2. If searchValue is neither undefined nor null, then
736+
if !search_value.is_null() && !search_value.is_undefined() {
737+
// a. Let isRegExp be ? IsRegExp(searchValue).
738+
let is_reg_exp = false;
739+
740+
// b. If isRegExp is true, then
741+
if is_reg_exp {
742+
// i. Let flags be ? Get(searchValue, "flags").
743+
// ii. Perform ? RequireObjectCoercible(flags).
744+
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
745+
todo!();
746+
}
747+
748+
// c. Let replacer be ? GetMethod(searchValue, %Symbol.replace%).
749+
let symbol = WellKnownSymbolIndexes::Replace.into();
750+
let replacer = get_method(agent, search_value, symbol)?;
751+
752+
// d. If replacer is not undefined, Return ? Call(replacer, searchValue, « O, replaceValue »).
753+
if let Some(replacer) = replacer {
754+
return call_function(
755+
agent,
756+
replacer,
757+
search_value,
758+
Some(ArgumentsList(&[o, replace_value])),
759+
);
760+
}
761+
}
762+
763+
// 3. Let s be ? ToString(O).
764+
let s = to_string(agent, o)?;
765+
766+
// 4. Let searchString be ? ToString(searchValue).
767+
let search_string = to_string(agent, search_value)?;
768+
769+
// 5. Let functionalReplace be IsCallable(replaceValue).
770+
if let Some(functional_replace) = is_callable(replace_value) {
771+
// 7. Let searchLength be the length of searchString.
772+
let search_length = search_string.len(agent);
773+
774+
// 8. Let advanceBy be max(1, searchLength).
775+
let advance_by = max(1, search_length);
776+
777+
// 9. Let matchPositions be a new empty List.
778+
let mut match_positions: Vec<usize> = vec![];
779+
780+
// 10. Let position be StringIndexOf(s, searchString, 0).
781+
let search_str = search_string.as_str(agent);
782+
let subject = s.as_str(agent).to_owned();
783+
let mut position = 0;
784+
785+
// 11. Repeat, while position is not not-found,
786+
while let Some(pos) = subject[position..].find(search_str) {
787+
match_positions.push(position + pos);
788+
position += advance_by + pos;
789+
}
790+
791+
// If none has found, return s.
792+
if match_positions.is_empty() {
793+
return Ok(s.into_value());
794+
}
795+
796+
// 12. Let endOfLastMatch be 0.
797+
let mut end_of_last_match = 0;
798+
799+
// 13. Let result be the empty String.
800+
let mut result = std::string::String::with_capacity(subject.len());
801+
802+
// 14. For each element p of matchPositions, do
803+
for p in match_positions {
804+
// b. let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
805+
let replacement = call_function(
806+
agent,
807+
functional_replace,
808+
Value::Undefined,
809+
Some(ArgumentsList(&[
810+
search_string.into_value(),
811+
Number::from(position as u32).into_value(),
812+
s.into_value(),
813+
])),
814+
)?;
815+
816+
// a. Let preserved be the substring of string from endOfLastMatch to p.
817+
let preserved = &subject[end_of_last_match..p];
818+
// d. Set result to the string-concatenation of result, preserved, and replacement.
819+
let replacement_str = replacement.to_string(agent)?;
820+
let replacement_str = replacement_str.as_str(agent);
821+
result.reserve(preserved.len() + replacement_str.len());
822+
result.push_str(preserved);
823+
result.push_str(replacement_str);
824+
end_of_last_match = p + search_length;
825+
}
826+
827+
// 15. If endOfLastMatch < the length of string, set result to the string-concatenation of result and the substring of string from endOfLastMatch.
828+
if end_of_last_match < subject.len() {
829+
let preserved = &subject[end_of_last_match..];
830+
result.push_str(preserved);
831+
}
832+
833+
// 16. Return result.
834+
return Ok(String::from_string(agent, result).into_value());
835+
}
836+
837+
// 6. If functionalReplace is false, Set replaceValue to ? ToString(replaceValue).
838+
let replace_string = to_string(agent, replace_value)?;
839+
// Everything are strings: `"foo".replaceAll("o", "a")` => use rust's replace
721840
let result = s
722841
.as_str(agent)
723842
.replace(search_string.as_str(agent), replace_string.as_str(agent));
724843
Ok(String::from_string(agent, result).into_value())
725844
}
726845

727-
fn replace_all(_agent: &mut Agent, _this_value: Value, _: ArgumentsList) -> JsResult<Value> {
728-
todo!()
729-
}
730-
731846
fn search(_agent: &mut Agent, _this_value: Value, _: ArgumentsList) -> JsResult<Value> {
732847
todo!()
733848
}

tests/expectations.json

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6191,16 +6191,10 @@
61916191
"built-ins/String/prototype/repeat/repeat-string-n-times.js": "TIMEOUT",
61926192
"built-ins/String/prototype/replace/15.5.4.11-1.js": "CRASH",
61936193
"built-ins/String/prototype/replace/S15.5.4.11_A12.js": "CRASH",
6194-
"built-ins/String/prototype/replace/S15.5.4.11_A1_T10.js": "CRASH",
61956194
"built-ins/String/prototype/replace/S15.5.4.11_A1_T14.js": "CRASH",
61966195
"built-ins/String/prototype/replace/S15.5.4.11_A1_T16.js": "CRASH",
61976196
"built-ins/String/prototype/replace/S15.5.4.11_A1_T17.js": "CRASH",
6198-
"built-ins/String/prototype/replace/S15.5.4.11_A1_T4.js": "CRASH",
6199-
"built-ins/String/prototype/replace/S15.5.4.11_A1_T5.js": "CRASH",
6200-
"built-ins/String/prototype/replace/S15.5.4.11_A1_T6.js": "CRASH",
6201-
"built-ins/String/prototype/replace/S15.5.4.11_A1_T7.js": "CRASH",
62026197
"built-ins/String/prototype/replace/S15.5.4.11_A1_T8.js": "CRASH",
6203-
"built-ins/String/prototype/replace/S15.5.4.11_A1_T9.js": "CRASH",
62046198
"built-ins/String/prototype/replace/S15.5.4.11_A2_T1.js": "CRASH",
62056199
"built-ins/String/prototype/replace/S15.5.4.11_A2_T10.js": "CRASH",
62066200
"built-ins/String/prototype/replace/S15.5.4.11_A2_T2.js": "CRASH",
@@ -6219,7 +6213,6 @@
62196213
"built-ins/String/prototype/replace/S15.5.4.11_A4_T3.js": "CRASH",
62206214
"built-ins/String/prototype/replace/S15.5.4.11_A4_T4.js": "CRASH",
62216215
"built-ins/String/prototype/replace/S15.5.4.11_A5_T1.js": "CRASH",
6222-
"built-ins/String/prototype/replace/cstm-replace-is-null.js": "CRASH",
62236216
"built-ins/String/prototype/replace/regexp-prototype-replace-v-flag.js": "CRASH",
62246217
"built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0024.js": "CRASH",
62256218
"built-ins/String/prototype/replaceAll/getSubstitution-0x0024-0x0026.js": "CRASH",
@@ -6229,35 +6222,16 @@
62296222
"built-ins/String/prototype/replaceAll/getSubstitution-0x0024.js": "CRASH",
62306223
"built-ins/String/prototype/replaceAll/getSubstitution-0x0024N.js": "CRASH",
62316224
"built-ins/String/prototype/replaceAll/getSubstitution-0x0024NN.js": "CRASH",
6232-
"built-ins/String/prototype/replaceAll/replaceValue-call-abrupt.js": "CRASH",
62336225
"built-ins/String/prototype/replaceAll/replaceValue-call-each-match-position.js": "CRASH",
62346226
"built-ins/String/prototype/replaceAll/replaceValue-call-matching-empty.js": "CRASH",
6235-
"built-ins/String/prototype/replaceAll/replaceValue-call-skip-no-match.js": "CRASH",
6236-
"built-ins/String/prototype/replaceAll/replaceValue-call-tostring-abrupt.js": "CRASH",
6237-
"built-ins/String/prototype/replaceAll/replaceValue-fn-skip-toString.js": "CRASH",
6238-
"built-ins/String/prototype/replaceAll/replaceValue-tostring-abrupt.js": "CRASH",
6239-
"built-ins/String/prototype/replaceAll/replaceValue-value-replaces-string.js": "CRASH",
6240-
"built-ins/String/prototype/replaceAll/replaceValue-value-tostring.js": "CRASH",
6241-
"built-ins/String/prototype/replaceAll/searchValue-empty-string-this-empty-string.js": "CRASH",
6242-
"built-ins/String/prototype/replaceAll/searchValue-empty-string.js": "CRASH",
62436227
"built-ins/String/prototype/replaceAll/searchValue-flags-no-g-throws.js": "CRASH",
62446228
"built-ins/String/prototype/replaceAll/searchValue-flags-null-undefined-throws.js": "CRASH",
62456229
"built-ins/String/prototype/replaceAll/searchValue-flags-toString-abrupt.js": "CRASH",
62466230
"built-ins/String/prototype/replaceAll/searchValue-get-flags-abrupt.js": "CRASH",
62476231
"built-ins/String/prototype/replaceAll/searchValue-isRegExp-abrupt.js": "CRASH",
62486232
"built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call-fn.js": "CRASH",
62496233
"built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": "CRASH",
6250-
"built-ins/String/prototype/replaceAll/searchValue-replacer-before-tostring.js": "CRASH",
6251-
"built-ins/String/prototype/replaceAll/searchValue-replacer-call-abrupt.js": "CRASH",
6252-
"built-ins/String/prototype/replaceAll/searchValue-replacer-call.js": "CRASH",
6253-
"built-ins/String/prototype/replaceAll/searchValue-replacer-is-null.js": "CRASH",
6254-
"built-ins/String/prototype/replaceAll/searchValue-replacer-method-abrupt.js": "CRASH",
6255-
"built-ins/String/prototype/replaceAll/searchValue-tostring-abrupt.js": "CRASH",
62566234
"built-ins/String/prototype/replaceAll/searchValue-tostring-regexp.js": "CRASH",
6257-
"built-ins/String/prototype/replaceAll/this-is-null-throws.js": "CRASH",
6258-
"built-ins/String/prototype/replaceAll/this-is-undefined-throws.js": "CRASH",
6259-
"built-ins/String/prototype/replaceAll/this-tostring-abrupt.js": "CRASH",
6260-
"built-ins/String/prototype/replaceAll/this-tostring.js": "CRASH",
62616235
"built-ins/String/prototype/search/S15.5.4.12_A1.1_T1.js": "CRASH",
62626236
"built-ins/String/prototype/search/S15.5.4.12_A1_T1.js": "CRASH",
62636237
"built-ins/String/prototype/search/S15.5.4.12_A1_T10.js": "CRASH",
@@ -18497,12 +18471,6 @@
1849718471
"language/expressions/yield/star-rhs-unresolvable.js": "CRASH",
1849818472
"language/expressions/yield/star-string.js": "CRASH",
1849918473
"language/expressions/yield/star-throw-is-null.js": "CRASH",
18500-
"language/function-code/10.4.3-1-100-s.js": "CRASH",
18501-
"language/function-code/10.4.3-1-100gs.js": "CRASH",
18502-
"language/function-code/10.4.3-1-101-s.js": "CRASH",
18503-
"language/function-code/10.4.3-1-101gs.js": "CRASH",
18504-
"language/function-code/10.4.3-1-102-s.js": "CRASH",
18505-
"language/function-code/10.4.3-1-102gs.js": "CRASH",
1850618474
"language/function-code/eval-param-env-with-computed-key.js": "CRASH",
1850718475
"language/function-code/eval-param-env-with-prop-initializer.js": "CRASH",
1850818476
"language/global-code/decl-func-dup.js": "FAIL",

tests/metrics.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"results": {
3-
"crash": 16310,
4-
"fail": 8211,
5-
"pass": 20648,
3+
"crash": 16279,
4+
"fail": 8210,
5+
"pass": 20679,
66
"skip": 40,
77
"timeout": 3,
88
"unresolved": 0

0 commit comments

Comments
 (0)