Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add String.replace #190

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/lib/builtins/regexp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
};

#[derive(Debug)]
struct RegExp {
pub struct RegExp {
/// Regex matcher.
matcher: Regex,
/// Update last_index, set if global or sticky flags are set.
Expand All @@ -37,6 +37,12 @@ struct RegExp {

impl InternalState for RegExp {}

impl RegExp {
pub fn get_matcher(&self) -> &Regex {
&self.matcher
}
}

fn get_argument<T: FromValue>(args: &[Value], idx: usize) -> Result<T, Value> {
match args.get(idx) {
Some(arg) => from_value(arg.clone()).map_err(to_value),
Expand Down
97 changes: 95 additions & 2 deletions src/lib/builtins/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use crate::{
function::NativeFunctionData,
object::{Object, ObjectKind, PROTOTYPE},
property::Property,
regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match},
value::{from_value, to_value, ResultValue, Value, ValueData},
regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match, RegExp},
value::{from_value, to_value, undefined, ResultValue, Value, ValueData},
},
exec::Interpreter,
};
use gc::Gc;
use regex::{Captures, Regex};
use std::{
cmp::{max, min},
f64::NAN,
Expand Down Expand Up @@ -722,6 +723,61 @@ pub fn match_all(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultV
regexp_match_all(&re, ctx.value_to_rust_string(this))
}

/// Return a String where the matches of a pattern in the original string is replaced with a given
/// replacement.
/// <https://tc39.es/ecma262/#sec-string.prototype.replace>
pub fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String
let this_str: &str = &ctx.value_to_rust_string(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this after the if statement below.


// Simple guard for undefined pattern
let pattern = get_argument(args, 0);
if pattern.is_undefined() {
return Ok(this.clone());
}

// Make a Rust Regex from the first argument
let pattern_re = if let Some(re_internal_state) = pattern.get_internal_state() {
if let Some(pattern_jsre) = re_internal_state.downcast_ref::<RegExp>() {
pattern_jsre.get_matcher().clone()
} else {
let pattern_str = &ctx.value_to_rust_string(&pattern);
Regex::new(&regex::escape(pattern_str)).unwrap()
}
} else {
let pattern_str = &ctx.value_to_rust_string(&pattern);
Regex::new(&regex::escape(pattern_str)).unwrap()
};

// Make a replacement closure from the second argument
let replacement = get_argument(args, 1);

let replacement_cls = if replacement.is_function() {
Box::new(|caps: &Captures| {
let m = caps.get(0).unwrap().as_str();
let args = vec![to_value(m)];
let rs_value = ctx
.call(&replacement, &undefined(), args)
.unwrap_or_else(|_| undefined());
ctx.value_to_rust_string(&rs_value)
}) as Box<dyn FnMut(&Captures) -> String>
} else {
Box::new(|_: &Captures| ctx.value_to_rust_string(&replacement))
};

// Replace the pattern by the replacement closure
let result_str = pattern_re.replace_all(this_str, replacement_cls);
Ok(to_value(result_str.into_owned()))
}

fn get_argument(args: &[Value], idx: usize) -> Value {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit more general, so it would be better you move it somewhere, where it can be widely accessed.

I am thinking exec.rs. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually wonder if this already exists somewhere. But I can't find it...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know something similar exists in regexp.rs but nothing else.

match args.get(idx) {
Some(arg) => arg.clone(),
None => undefined(),
}
}

/// Create a new `String` object
pub fn create_constructor(global: &Value) -> Value {
// Create constructor function object
Expand Down Expand Up @@ -760,6 +816,7 @@ pub fn create_constructor(global: &Value) -> Value {
make_builtin_fn!(substr, named "substr", with length 2, of proto);
make_builtin_fn!(value_of, named "valueOf", of proto);
make_builtin_fn!(match_all, named "matchAll", with length 1, of proto);
make_builtin_fn!(replace, named "replace", with length 2, of proto);

let string = to_value(string_constructor);
proto.set_field_slice("constructor", string.clone());
Expand Down Expand Up @@ -1036,4 +1093,40 @@ mod tests {
);
assert_eq!(forward(&mut engine, "result4[0]"), "B");
}

#[test]
fn replace() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var str = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';
var result1 = str.replace('dog', 'monkey');
var result2 = str.replace('dog.', 'monkey.');
var regexResult = str.replace(/dog./, 'monkey.');
var funResult1 = str.replace(/dog/, function(word) { return word.toUpperCase(); })
var funResult2 = str.replace('dog', function(word) { return word.toUpperCase(); })
var intResult = 'abc4e'.replace(4, 'd')
var exceptional1 = str.replace();
var exceptional2 = str.replace('dog');
"#;

forward(&mut engine, init);
assert_eq!(forward(&mut engine, "result1"), "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(forward(&mut engine, "result1"), "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?");
assert_eq!(forward(&mut engine, "result1"), "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?");

assert_eq!(forward(&mut engine, "result2"), "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?");
assert_eq!(forward(&mut engine, "regexResult"), "The quick brown fox jumps over the lazy monkey. If the monkey.reacted, was it really lazy?");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(forward(&mut engine, "regexResult"), "The quick brown fox jumps over the lazy monkey. If the monkey.reacted, was it really lazy?");
assert_eq!(forward(&mut engine, "regexResult"), "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?");

assert_eq!(
forward(&mut engine, "funResult1"),
"The quick brown fox jumps over the lazy DOG. If the DOG reacted, was it really lazy?"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"The quick brown fox jumps over the lazy DOG. If the DOG reacted, was it really lazy?"
"The quick brown fox jumps over the lazy DOG. If the dog reacted, was it really lazy?"

);
assert_eq!(
forward(&mut engine, "funResult2"),
"The quick brown fox jumps over the lazy DOG. If the DOG reacted, was it really lazy?"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"The quick brown fox jumps over the lazy DOG. If the DOG reacted, was it really lazy?"
"The quick brown fox jumps over the lazy DOG. If the dog reacted, was it really lazy?"

);
assert_eq!(forward(&mut engine, "intResult"), "abcde");
assert_eq!(
forward(&mut engine, "exceptional1"),
"The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?"
);
assert_eq!(forward(&mut engine, "exceptional2"), "The quick brown fox jumps over the lazy undefined. If the undefined reacted, was it really lazy?");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(forward(&mut engine, "exceptional2"), "The quick brown fox jumps over the lazy undefined. If the undefined reacted, was it really lazy?");
assert_eq!(forward(&mut engine, "exceptional2"), "The quick brown fox jumps over the lazy undefined. If the dog reacted, was it really lazy?");

}
}