use crate::js::function::NativeFunctionData;
use crate::js::object::{Property, PROTOTYPE};
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use gc::Gc;
use std::f64::NAN;
use std::cmp::{min, max};
pub fn make_string(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
this.set_private_field_slice("PrimitiveValue", args[0].clone());
Ok(this)
}
pub fn get_string_length(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
Ok(to_value::<i32>(this_str.len() as i32))
}
pub fn to_string(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let primitive_val = this.get_private_field(String::from("PrimitiveValue"));
Ok(to_value(format!("{}", primitive_val).to_string()))
}
pub fn char_at(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let pos = from_value(args[0].clone()).unwrap();
let length = primitive_val.chars().count();
if pos >= length || pos < 0 as usize {
return Ok(to_value::<String>(String::new()));
}
Ok(to_value::<char>(primitive_val.chars().nth(pos).unwrap()))
}
pub fn char_code_at(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let length = primitive_val.chars().count();
let pos = from_value(args[0].clone()).unwrap();
if pos >= length || pos < 0 as usize {
return Ok(to_value(NAN));
}
let utf16_val = primitive_val.encode_utf16().nth(pos).unwrap();
Ok(to_value(utf16_val as f64))
}
pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let mut new_str = primitive_val.clone();
for arg in args {
let concat_str: String = from_value(arg).unwrap();
new_str.push_str(&concat_str);
}
Ok(to_value(new_str))
}
pub fn repeat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let repeat_times: usize = from_value(args[0].clone()).unwrap();
Ok(to_value(primitive_val.repeat(repeat_times)))
}
pub fn slice(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let start: i32 = from_value(args[0].clone()).unwrap();
let end: i32 = from_value(args[1].clone()).unwrap();
let length: i32 = primitive_val.chars().count() as i32;
let from: i32 = if start < 0 {max(length + start, 0)} else {min(start, length)};
let to: i32 = if end < 0 {max(length + end, 0)} else {min(end, length)};
let span = max(to - from, 0);
let mut new_str = String::new();
for i in from..from + span {
new_str.push(primitive_val.chars().nth(i as usize).unwrap());
}
Ok(to_value(new_str))
}
pub fn starts_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let search_string: String = from_value(args[0].clone()).unwrap();
let length: i32 = primitive_val.chars().count() as i32;
let search_length: i32 = search_string.chars().count() as i32;
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let start = min(max(position, 0), length);
let end = start + search_length;
if end > length {
Ok(to_value(false))
} else {
let this_string: String = primitive_val.chars()
.skip(start as usize).collect();
Ok(to_value(this_string.starts_with(&search_string)))
}
}
pub fn ends_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let search_string: String = from_value(args[0].clone()).unwrap();
let length: i32 = primitive_val.chars().count() as i32;
let search_length: i32 = search_string.chars().count() as i32;
let end_position: i32 = if args.len() < 2 {length}
else {from_value(args[1].clone()).unwrap()};
let end = min(max(end_position, 0), length);
let start = end - search_length;
if start < 0 {
Ok(to_value(false))
} else {
let this_string: String = primitive_val.chars()
.take(end as usize).collect();
Ok(to_value(this_string.ends_with(&search_string)))
}
}
pub fn includes(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let search_string: String = from_value(args[0].clone()).unwrap();
let length: i32 = primitive_val.chars().count() as i32;
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let start = min(max(position, 0), length);
let this_string: String = primitive_val.chars()
.skip(start as usize).collect();
Ok(to_value(this_string.contains(&search_string)))
}
pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let search_string: String = from_value(args[0].clone()).unwrap();
let length: i32 = primitive_val.chars().count() as i32;
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let start = min(max(position, 0), length);
for index in start..length {
let this_string: String = primitive_val.chars()
.skip(index as usize).collect();
if this_string.starts_with(&search_string) {
return Ok(to_value(index))
}
}
Ok(to_value(-1 as i32))
}
pub fn last_index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let primitive_val: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let search_string: String = from_value(args[0].clone()).unwrap();
let length: i32 = primitive_val.chars().count() as i32;
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let start = min(max(position, 0), length);
let mut highest_index: i32 = -1;
for index in start..length {
let this_string: String = primitive_val.chars()
.skip(index as usize).collect();
if this_string.starts_with(&search_string) {
highest_index = index;
}
}
Ok(to_value(highest_index))
}
fn is_trimmable_whitespace(c: char) -> bool {
match c {
'\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' => true,
'\u{1680}' | '\u{2000}'..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' => true,
'\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => true,
_ => false,
}
}
pub fn trim(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
Ok(to_value(this_str.trim_matches(is_trimmable_whitespace)))
}
pub fn trim_start(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
Ok(to_value(this_str.trim_start_matches(is_trimmable_whitespace)))
}
pub fn trim_end(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
Ok(to_value(this_str.trim_end_matches(is_trimmable_whitespace)))
}
pub fn _create(global: &Value) -> Value {
let string = to_value(make_string as NativeFunctionData);
let proto = ValueData::new_obj(Some(global));
let prop = Property {
configurable: false,
enumerable: false,
writable: false,
value: Gc::new(ValueData::Undefined),
get: to_value(get_string_length as NativeFunctionData),
set: Gc::new(ValueData::Undefined),
};
proto.set_prop_slice("length", prop);
proto.set_field_slice("charAt", to_value(char_at as NativeFunctionData));
proto.set_field_slice("charCodeAt", to_value(char_code_at as NativeFunctionData));
proto.set_field_slice("toString", to_value(to_string as NativeFunctionData));
proto.set_field_slice("concat", to_value(concat as NativeFunctionData));
proto.set_field_slice("repeat", to_value(repeat as NativeFunctionData));
proto.set_field_slice("slice", to_value(slice as NativeFunctionData));
proto.set_field_slice("startsWith", to_value(starts_with as NativeFunctionData));
proto.set_field_slice("endsWith", to_value(ends_with as NativeFunctionData));
proto.set_field_slice("includes", to_value(includes as NativeFunctionData));
proto.set_field_slice("indexOf", to_value(index_of as NativeFunctionData));
proto.set_field_slice("lastIndexOf", to_value(last_index_of as NativeFunctionData));
proto.set_field_slice("trim", to_value(trim as NativeFunctionData));
proto.set_field_slice("trimStart", to_value(trim_start as NativeFunctionData));
string.set_field_slice(PROTOTYPE, proto);
string
}
pub fn init(global: &Value) {
global.set_field_slice("String", _create(global));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_string_constructor_is_function() {
let global = ValueData::new_obj(None);
let string_constructor = _create(&global);
assert_eq!(string_constructor.is_function(), true);
}
}