@ -9,9 +9,11 @@ use crate::{
exec ::Interpreter ,
} ;
use gc ::Gc ;
use regex ::Regex ;
use std ::{
cmp ::{ max , min } ,
f64 ::NAN ,
ops ::Deref ,
} ;
/// Create new string [[Construct]]
@ -322,6 +324,116 @@ pub fn includes(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVa
Ok ( to_value ( this_string . contains ( & search_string ) ) )
}
/// Return either the string itself or the string of the regex equivalent
fn get_regex_string ( value : & Value ) -> String {
match value . deref ( ) {
ValueData ::String ( ref body ) = > body . into ( ) ,
ValueData ::Object ( ref obj ) = > {
let slots = & * obj . borrow ( ) . internal_slots ;
if slots . get ( "RegExpMatcher" ) . is_some ( ) {
// first argument is another `RegExp` object, so copy its pattern and flags
if let Some ( body ) = slots . get ( "OriginalSource" ) {
return from_value ( r#body . clone ( ) )
. expect ( "unable to get body from regex value" ) ;
}
}
"undefined" . to_string ( )
}
_ = > "undefined" . to_string ( ) ,
}
}
/// <https://tc39.es/ecma262/#sec-string.prototype.replace>
pub fn replace ( this : & Value , args : & [ Value ] , ctx : & mut Interpreter ) -> ResultValue {
// TODO: Support Symbol replacer
let primitive_val : String = ctx . value_to_rust_string ( this ) ;
if args . is_empty ( ) {
return Ok ( to_value ( primitive_val ) ) ;
}
let regex_body = get_regex_string ( args . get ( 0 ) . expect ( "Value needed" ) ) ;
let re = Regex ::new ( & regex_body ) . expect ( "unable to convert regex to regex object" ) ;
let mat = re . find ( & primitive_val ) . expect ( "unable to find value" ) ;
let caps = re
. captures ( & primitive_val )
. expect ( "unable to get capture groups from text" ) ;
let replace_value = if args . len ( ) > 1 {
// replace_object could be a string or function or not exist at all
let replace_object : & Value = args . get ( 1 ) . expect ( "second argument expected" ) ;
match replace_object . deref ( ) {
ValueData ::String ( val ) = > {
// https://tc39.es/ecma262/#table-45
let mut result : String = val . to_string ( ) ;
let re = Regex ::new ( r"\$(\d)" ) . unwrap ( ) ;
if val . find ( "$$" ) . is_some ( ) {
result = val . replace ( "$$" , "$" )
}
if val . find ( "$`" ) . is_some ( ) {
let start_of_match = mat . start ( ) ;
let slice = & primitive_val [ .. start_of_match ] ;
result = val . replace ( "$`" , slice ) ;
}
if val . find ( "$'" ) . is_some ( ) {
let end_of_match = mat . end ( ) ;
let slice = & primitive_val [ end_of_match .. ] ;
result = val . replace ( "$'" , slice ) ;
}
if val . find ( "$&" ) . is_some ( ) {
// get matched value
let matched = caps . get ( 0 ) . expect ( "cannot get matched value" ) ;
result = val . replace ( "$&" , matched . as_str ( ) ) ;
}
// Capture $1, $2, $3 etc
if re . is_match ( & result ) {
let mat_caps = re . captures ( & result ) . unwrap ( ) ;
let group_str = mat_caps . get ( 1 ) . unwrap ( ) . as_str ( ) ;
let group_int = group_str . parse ::< usize > ( ) . unwrap ( ) ;
result = re
. replace ( result . as_str ( ) , caps . get ( group_int ) . unwrap ( ) . as_str ( ) )
. to_string ( )
}
result
}
ValueData ::Function ( _ ) = > {
// This will return the matched substring first, then captured parenthesized groups later
let mut results : Vec < Value > = caps
. iter ( )
. map ( | capture | to_value ( capture . unwrap ( ) . as_str ( ) ) )
. collect ( ) ;
// Returns the starting byte offset of the match
let start = caps
. get ( 0 )
. expect ( "Unable to get Byte offset from string for match" )
. start ( ) ;
results . push ( to_value ( start ) ) ;
// Push the whole string being examined
results . push ( to_value ( primitive_val . to_string ( ) ) ) ;
let result = ctx . call ( & replace_object , & this , results ) . unwrap ( ) ;
ctx . value_to_rust_string ( & result )
}
_ = > "undefined" . to_string ( ) ,
}
} else {
"undefined" . to_string ( )
} ;
Ok ( to_value ( primitive_val . replacen (
& mat . as_str ( ) ,
& replace_value ,
1 ,
) ) )
}
/// If searchString appears as a substring of the result of converting this
/// object to a String, at one or more indices that are greater than or equal to
/// position, then the smallest such index is returned; otherwise, -1 is
@ -757,6 +869,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 ( ) ) ;
@ -872,6 +985,49 @@ mod tests {
) ;
}
#[ test ]
fn replace ( ) {
let realm = Realm ::create ( ) ;
let mut engine = Executor ::new ( realm ) ;
let init = r #"
var a = "abc" ;
a = a . replace ( "a" , "2" ) ;
a
" #;
forward ( & mut engine , init ) ;
let empty = String ::from ( "2bc" ) ;
assert_eq! ( forward ( & mut engine , "a" ) , empty ) ;
}
#[ test ]
fn replace_with_function ( ) {
let realm = Realm ::create ( ) ;
let mut engine = Executor ::new ( realm ) ;
let init = r #"
var a = "ecmascript is cool" ;
var p1 , p2 , p3 ;
var replacer = ( match , cap1 , cap2 , cap3 ) = > {
p1 = cap1 ;
p2 = cap2 ;
p3 = cap3 ;
return "awesome!" ;
} ;
a = a . replace ( / c ( o ) ( o ) ( l ) / , replacer ) ;
a ;
" #;
forward ( & mut engine , init ) ;
assert_eq! (
forward ( & mut engine , "a" ) ,
String ::from ( "ecmascript is awesome!" )
) ;
assert_eq! ( forward ( & mut engine , "p1" ) , String ::from ( "o" ) ) ;
assert_eq! ( forward ( & mut engine , "p2" ) , String ::from ( "o" ) ) ;
assert_eq! ( forward ( & mut engine , "p3" ) , String ::from ( "l" ) ) ;
}
#[ test ]
fn starts_with ( ) {
let realm = Realm ::create ( ) ;