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

Conversation

thiagoarrais
Copy link

@thiagoarrais thiagoarrais commented Oct 24, 2019

Work in progress pull request for fixing #116

The implementation isn't complete yet, but I intend to incorporate changes based on feedback. For starters, just let me know if this is the structure you expect. I'll try to evolve the replace method to be spec-compliant.

Related question: does this project use tests? Where should I add them if so?

TODO List:

@jasonwilliams
Copy link
Member

jasonwilliams commented Oct 25, 2019

Related question: does this project use tests? Where should I add them if so?

Yes, there are quite a few tests in this project, each file (or module) has the tests embedded with the code. So for String, you will see the tests at the bottom of the file. Im sure there are enough tests here for you to familiar yourself with.
https://github.com/jasonwilliams/boa/blob/master/src/lib/builtins/string.rs#L781-L1044

@jasonwilliams
Copy link
Member

jasonwilliams commented Oct 25, 2019

Im not sure if using regex directly will work, as the rules may differ from rust to JS.
We have a RegExp object which has match here:
https://github.com/jasonwilliams/boa/blob/master/src/lib/builtins/regexp.rs#L257-L274

Maybe you could use that?

Tests could be added first to see which solution is more compatible

Secondly incase you're unaware your rustfmt is failing:
https://github.com/jasonwilliams/boa/pull/190/checks?check_run_id=273620005

You can run cargo fmt --verbose in the project

@thiagoarrais
Copy link
Author

Nice, @jasonwilliams! I'll add tests (sorry, hadn't located them just out of Rust-noobness) and rustfmt sometime today.

About RegExp, I started with that, but Rust complained about it not being exported or something. But my final version will most likely use that.

Thanks for the feedback!

@thiagoarrais
Copy link
Author

This is taking some shape now.

I have included a get_argument function inspired by but different than regexp::get_argument. It is designed to mimic JS' behaviour of interpreting missing parameters as undefined. I suspect such a function already exists somewhere in the codebase. Care to help me find it?

@@ -766,6 +798,7 @@ pub fn create_constructor(global: &Value) -> Value {
proto.set_field_slice("substr", to_value(substr as NativeFunctionData));
proto.set_field_slice("valueOf", to_value(value_of as NativeFunctionData));
proto.set_field_slice("matchAll", to_value(match_all as NativeFunctionData));
proto.set_field_slice("replace", to_value(replace as NativeFunctionData));
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to set the length property of the function as well.

Copy link
Author

Choose a reason for hiding this comment

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

I don't know if I understood that. Do you mean str.replace.length should return 2? If so, would you say that str.charAt.length should return 1 then?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, to both your questions. A function's length represents the number of "formal" parameters, e.g. those with no default value.

Copy link
Author

@thiagoarrais thiagoarrais Oct 31, 2019

Choose a reason for hiding this comment

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

Do the other functions in String already support that in boa or are you suggesting that they should? I'm locally getting str.charAt.length == undefined, but I may have an outdated branch...

Copy link
Contributor

@IovoslavIovchev IovoslavIovchev Oct 31, 2019

Choose a reason for hiding this comment

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

Unfortunately, some functions in boa (not just in String) don't have their length set because it is easily missable.

I have suggested an approach in #193 and would very much like we have something like that, so we don't miss these things.

Copy link
Author

Choose a reason for hiding this comment

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

Nice. I'm working on other stuff here, but I'll make sure to check where that PR of yours goes.

/// Regex matcher.
matcher: Regex,
pub matcher: Regex,
Copy link
Author

@thiagoarrais thiagoarrais Oct 31, 2019

Choose a reason for hiding this comment

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

Need some help here. I'm feeling a bit uneasy about making this public. What is the idiomatic way to access regex funcionality from outside the regexp module?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you intend to modify the value in any way?

The most "popular" and obvious way would be to have an immutable getter (and a mutable one only if really needed).

Copy link
Author

Choose a reason for hiding this comment

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

I don't intend to modify the value. I was considering getter vs. something like adding a replace_all delegating method. The second option could be used to deal with differences between JS and Rust regex engines.

Copy link
Contributor

Choose a reason for hiding this comment

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

What exactly is your idea for replace_all?

Copy link
Author

Choose a reason for hiding this comment

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

Something along the lines of fbf8817. But I'm not really sure this is the intended use for the lib structs.

// We then do the same thing for the other args
// TODO: what if there are missing arguments?
let pattern = get_argument(args, 0);
if undefined() == pattern {
Copy link
Contributor

Choose a reason for hiding this comment

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

If I am not mistaken, there was an is_undefined method.
So you can try pattern.is_undefined() instead.

Copy link
Author

Choose a reason for hiding this comment

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

Good catch!

@thiagoarrais thiagoarrais force-pushed the string-replace branch 2 times, most recently from 2e3b0a5 to 1899da7 Compare November 20, 2019 16:09
@thiagoarrais thiagoarrais changed the title WIP: Add String.replace Add String.replace Nov 20, 2019
@thiagoarrais
Copy link
Author

I think this is ready for another round of review.

My main concern here is about @IovoslavIovchev's suggestion on length. It doesn't seem to work, even after adapting for #206. I'm assuming this is unrelated to this change because some other functions in String don't return a valid length. But I'm open to adapting the code if needed.

@thiagoarrais
Copy link
Author

The problem with length seems related to #210. This should be good to go. Please let me know if you need anything else.

// Make a Rust Regex from the first argument
let pattern = get_argument(args, 0);
if pattern.is_undefined() {
return Ok(to_value(this_str));
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
return Ok(to_value(this_str));
return Ok(this.clone());

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 just a small optimisation to avoid calling ctx.value_to_rust_string if the regex is undefined

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.

.call(&replacement, &undefined(), args)
.unwrap_or_else(|_| undefined());
ctx.value_to_rust_string(&rs_value)
}) as Box<dyn FnMut((&Captures)) -> (String)>
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the parentheses, so it becomes

Suggested change
}) as Box<dyn FnMut((&Captures)) -> (String)>
}) as Box<dyn FnMut(&Captures) -> String>

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.

"#;

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?");

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?");
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, "regexResult"), "The quick brown fox jumps over the lazy monkey. If the monkey.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?"

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?");

"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?");
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.

Remove this line, it's a duplicate

@IovoslavIovchev
Copy link
Contributor

The current implementation replaces all occurrences of the first argument when it's a String, when, in fact, it should only replace the first occurrence. See the spec for more info.

@IovoslavIovchev
Copy link
Contributor

@thiagoarrais do you need any help with this?

@thiagoarrais
Copy link
Author

I think I've addressed most of your comments, @IovoslavIovchev . The one thing I couldn't tackle yet is the spec breakage regarding replacing only the first occurrence. I plan on getting back to this some time, but I'm a little short on time. If you could help on that, it would be greatly appreciated.

Maybe you can give some general direction on a good approach? I'd also include a commit of yours here if you're willing to branch from this PR.

@IovoslavIovchev
Copy link
Contributor

@thiagoarrais no worries. I will take a look.

@jasonwilliams
Copy link
Member

This has now been added

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants