Browse Source

Adding operations (#85)

* adding operations to exec

* using StringData instead of PrimitiveData (to match with spec)

* function signature changed to allow mutable ctx (this is needed for some functions)

* tidy  (clippy)

* value to rust string (clippy)

* removing static lifetime - Not needed from Rust 1.36
pull/86/head
Jason Williams 5 years ago committed by GitHub
parent
commit
903cbcdccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 162
      src/lib/exec.rs
  2. 18
      src/lib/js/array.rs
  3. 4
      src/lib/js/console.rs
  4. 4
      src/lib/js/error.rs
  5. 2
      src/lib/js/function.rs
  6. 4
      src/lib/js/json.rs
  7. 38
      src/lib/js/math.rs
  8. 16
      src/lib/js/object.rs
  9. 199
      src/lib/js/string.rs
  10. 7
      src/lib/lib.rs
  11. 3
      tests/js/test.js

162
src/lib/exec.rs

@ -6,7 +6,7 @@ use crate::{
json, math, object,
object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
string,
value::{from_value, to_value, ResultValue, ValueData},
value::{from_value, to_value, ResultValue, Value, ValueData},
},
syntax::ast::{
constant::Const,
@ -15,7 +15,7 @@ use crate::{
},
};
use gc::{Gc, GcCell};
use std::borrow::Borrow;
use std::{borrow::Borrow, ops::Deref};
/// An execution engine
pub trait Executor {
@ -106,34 +106,8 @@ impl Executor for Interpreter {
for arg in args.iter() {
v_args.push(self.run(arg)?);
}
match *func {
ValueData::Function(ref inner_func) => match *inner_func.as_ref().borrow() {
Function::NativeFunc(ref ntv) => {
let func = ntv.data;
func(&this, &v_args, &self)
}
Function::RegularFunc(ref data) => {
let env = &mut self.environment;
// New target (second argument) is only needed for constructors, just pass undefined
let undefined = Gc::new(ValueData::Undefined);
env.push(new_function_environment(
func.clone(),
undefined,
Some(env.get_current_environment_ref().clone()),
));
for i in 0..data.args.len() {
let name = data.args.get(i).unwrap();
let expr = v_args.get(i).unwrap();
self.environment.create_mutable_binding(name.clone(), false);
self.environment.initialize_binding(name, expr.to_owned());
}
let result = self.run(&data.expr);
self.environment.pop();
result
}
},
_ => Err(Gc::new(ValueData::Undefined)),
}
self.call(&func, &this, v_args)
}
ExprDef::WhileLoop(ref cond, ref expr) => {
let mut result = Gc::new(ValueData::Undefined);
@ -304,7 +278,7 @@ impl Executor for Interpreter {
ValueData::Function(ref inner_func) => match inner_func.clone().into_inner() {
Function::NativeFunc(ref ntv) => {
let func = ntv.data;
func(&this, &v_args, &self)
func(&this, &v_args, self)
}
Function::RegularFunc(ref data) => {
// Create new scope
@ -396,3 +370,129 @@ impl Executor for Interpreter {
}
}
}
impl Interpreter {
/// https://tc39.es/ecma262/#sec-call
fn call(&mut self, f: &Value, v: &Value, arguments_list: Vec<Value>) -> ResultValue {
match (*f).deref() {
ValueData::Function(ref inner_func) => match *inner_func.deref().borrow() {
Function::NativeFunc(ref ntv) => {
let func = ntv.data;
func(v, &arguments_list, self)
}
Function::RegularFunc(ref data) => {
let env = &mut self.environment;
// New target (second argument) is only needed for constructors, just pass undefined
let undefined = Gc::new(ValueData::Undefined);
env.push(new_function_environment(
f.clone(),
undefined,
Some(env.get_current_environment_ref().clone()),
));
for i in 0..data.args.len() {
let name = data.args.get(i).unwrap();
let expr = arguments_list.get(i).unwrap();
self.environment.create_mutable_binding(name.clone(), false);
self.environment.initialize_binding(name, expr.to_owned());
}
let result = self.run(&data.expr);
self.environment.pop();
result
}
},
_ => Err(Gc::new(ValueData::Undefined)),
}
}
/// https://tc39.es/ecma262/#sec-ordinarytoprimitive
fn ordinary_to_primitive(&mut self, o: &Value, hint: &str) -> Value {
debug_assert!(o.get_type() == "object");
debug_assert!(hint == "string" || hint == "number");
let method_names: Vec<&str> = if hint == "string" {
vec!["toString", "valueOf"]
} else {
vec!["valueOf", "toString"]
};
for name in method_names.iter() {
let method: Value = o.get_field_slice(name);
if method.is_function() {
let result = self.call(&method, &o, vec![]);
match result {
Ok(val) => {
if val.is_object() {
// TODO: throw exception
continue;
} else {
return val;
}
}
Err(_) => continue,
}
}
}
Gc::new(ValueData::Undefined)
}
/// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType.
/// https://tc39.es/ecma262/#sec-toprimitive
#[allow(clippy::wrong_self_convention)]
pub fn to_primitive(&mut self, input: &Value, preferred_type: Option<&str>) -> Value {
let mut hint: &str;
match (*input).deref() {
ValueData::Object(_) => {
hint = match preferred_type {
None => "default",
Some(pt) => match pt {
"string" => "string",
"number" => "number",
_ => "default",
},
};
// Skip d, e we don't support Symbols yet
// TODO: add when symbols are supported
if hint == "default" {
hint = "number";
};
self.ordinary_to_primitive(&input, hint)
}
_ => input.clone(),
}
}
/// to_string() converts a value into a String
/// https://tc39.es/ecma262/#sec-tostring
#[allow(clippy::wrong_self_convention)]
pub fn to_string(&mut self, value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Undefined => to_value("undefined"),
ValueData::Null => to_value("null"),
ValueData::Boolean(ref boolean) => to_value(boolean.to_string()),
ValueData::Number(ref num) => to_value(num.to_string()),
ValueData::Integer(ref num) => to_value(num.to_string()),
ValueData::String(ref string) => to_value(string.clone()),
ValueData::Object(_) => {
let prim_value = self.to_primitive(value, Some("string"));
self.to_string(&prim_value)
}
_ => to_value("function(){...}"),
}
}
/// value_to_rust_string() converts a value into a rust heap allocated string
pub fn value_to_rust_string(&mut self, value: &Value) -> String {
match *value.deref().borrow() {
ValueData::Null => String::from("null"),
ValueData::Boolean(ref boolean) => boolean.to_string(),
ValueData::Number(ref num) => num.to_string(),
ValueData::Integer(ref num) => num.to_string(),
ValueData::String(ref string) => string.clone(),
ValueData::Object(_) => {
let prim_value = self.to_primitive(value, Some("string"));
self.to_string(&prim_value).to_string()
}
_ => String::from("undefined"),
}
}
}

18
src/lib/js/array.rs

@ -44,7 +44,7 @@ fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue {
}
/// Create a new array
pub fn make_array(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn make_array(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
// Make a new Object which will internally represent the Array (mapping
// between indices and values): this creates an Object with no prototype
this.set_field_slice("length", to_value(0_i32));
@ -64,7 +64,7 @@ pub fn make_array(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue
}
/// Get an array's length
pub fn get_array_length(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn get_array_length(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
// Access the inner hash map which represents the actual Array contents
// (mapping between indices and values)
Ok(this.get_field_slice("length"))
@ -76,7 +76,7 @@ pub fn get_array_length(this: &Value, _: &[Value], _: &Interpreter) -> ResultVal
/// array containing the array elements of the object followed by the array
/// elements of each argument in order.
/// <https://tc39.es/ecma262/#sec-array.prototype.concat>
pub fn concat(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn concat(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if args.is_empty() {
// If concat is called with no arguments, it returns the original array
return Ok(this.clone());
@ -107,7 +107,7 @@ pub fn concat(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// they appear. The new length of the array is returned as the result of the
/// call.
/// <https://tc39.es/ecma262/#sec-array.prototype.push>
pub fn push(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn push(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let new_array = add_to_array_object(this, args)?;
Ok(new_array.get_field_slice("length"))
}
@ -116,7 +116,7 @@ pub fn push(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
///
/// The last element of the array is removed from the array and returned.
/// <https://tc39.es/ecma262/#sec-array.prototype.pop>
pub fn pop(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let curr_length: i32 = from_value(this.get_field_slice("length")).unwrap();
if curr_length < 1 {
return Err(to_value(
@ -136,7 +136,7 @@ pub fn pop(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
/// then concatenated, separated by occurrences of the separator. If no
/// separator is provided, a single comma is used as the separator.
/// <https://tc39.es/ecma262/#sec-array.prototype.join>
pub fn join(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn join(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let separator = if args.is_empty() {
String::from(",")
} else {
@ -158,7 +158,7 @@ pub fn join(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// The elements of the array are rearranged so as to reverse their order.
/// The object is returned as the result of the call.
/// <https://tc39.es/ecma262/#sec-array.prototype.reverse/>
pub fn reverse(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
let middle: i32 = len / 2;
@ -190,7 +190,7 @@ pub fn reverse(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
///
/// The first element of the array is removed from the array and returned.
/// <https://tc39.es/ecma262/#sec-array.prototype.shift/>
pub fn shift(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
if len == 0 {
@ -225,7 +225,7 @@ pub fn shift(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
/// within the array is the same as the order in which they appear in the
/// argument list.
/// <https://tc39.es/ecma262/#sec-array.prototype.unshift/>
pub fn unshift(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
let arg_c: i32 = args.len() as i32;

4
src/lib/js/console.rs

@ -80,7 +80,7 @@ fn log_string_from(x: Value) -> String {
/// Print a javascript value to the standard output stream
/// <https://console.spec.whatwg.org/#logger>
pub fn log(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn log(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
// Welcome to console.log! The output here is what the developer sees, so its best matching through value types and stringifying to the correct output
// The input is a vector of Values, we generate a vector of strings then
// pass them to println!
@ -95,7 +95,7 @@ pub fn log(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
Ok(Gc::new(ValueData::Undefined))
}
/// Print a javascript value to the standard error stream
pub fn error(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn error(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let args: Vec<String> = FromIterator::from_iter(
args.iter()
.map(|x| from_value::<String>(x.clone()).unwrap()),

4
src/lib/js/error.rs

@ -9,7 +9,7 @@ use crate::{
use gc::Gc;
/// Create a new error
pub fn make_error(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() {
this.set_field_slice(
"message",
@ -26,7 +26,7 @@ pub fn make_error(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue
Ok(Gc::new(ValueData::Undefined))
}
/// Get the string representation of the error
pub fn to_string(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field_slice("name");
let message = this.get_field_slice("message");
Ok(to_value(format!("{}: {}", name, message).to_string()))

2
src/lib/js/function.rs

@ -11,7 +11,7 @@ use gc_derive::{Finalize, Trace};
use std::fmt::{self, Debug};
/// fn(this, arguments, ctx)
pub type NativeFunctionData = fn(&Value, &[Value], &Interpreter) -> ResultValue;
pub type NativeFunctionData = fn(&Value, &[Value], &mut Interpreter) -> ResultValue;
/// A Javascript function
/// A member of the Object type that may be invoked as a subroutine

4
src/lib/js/json.rs

@ -7,7 +7,7 @@ use serde_json::{self, to_string_pretty, Value as JSONValue};
/// Parse a JSON string into a Javascript object
/// <https://tc39.github.io/ecma262/#sec-json.parse>
pub fn parse(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn parse(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
match serde_json::from_str::<JSONValue>(
&args
.get(0)
@ -20,7 +20,7 @@ pub fn parse(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}
}
/// Process a Javascript object into a JSON string
pub fn stringify(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn stringify(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("cannot get argument for JSON.stringify");
let json = obj.to_json();
Ok(to_value(to_string_pretty(&json).expect("")))

38
src/lib/js/math.rs

@ -9,7 +9,7 @@ use rand::random;
use std::f64;
/// Get the absolute value of a number
pub fn abs(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn abs(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -19,7 +19,7 @@ pub fn abs(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the arccos of a number
pub fn acos(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn acos(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -29,7 +29,7 @@ pub fn acos(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the arcsine of a number
pub fn asin(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn asin(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -39,7 +39,7 @@ pub fn asin(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the arctangent of a number
pub fn atan(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn atan(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -49,7 +49,7 @@ pub fn atan(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the arctangent of a numbers
pub fn atan2(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn atan2(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -59,7 +59,7 @@ pub fn atan2(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the cubic root of a number
pub fn cbrt(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn cbrt(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -69,7 +69,7 @@ pub fn cbrt(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get lowest integer above a number
pub fn ceil(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn ceil(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -79,7 +79,7 @@ pub fn ceil(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the cosine of a number
pub fn cos(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn cos(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -89,7 +89,7 @@ pub fn cos(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the power to raise the natural logarithm to get the number
pub fn exp(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn exp(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -99,7 +99,7 @@ pub fn exp(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the highest integer below a number
pub fn floor(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn floor(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -109,7 +109,7 @@ pub fn floor(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the natural logarithm of a number
pub fn log(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn log(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -119,7 +119,7 @@ pub fn log(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the maximum of several numbers
pub fn max(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn max(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let mut max = f64::NEG_INFINITY;
for arg in args {
let num = arg.to_num();
@ -128,7 +128,7 @@ pub fn max(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
Ok(to_value(max))
}
/// Get the minimum of several numbers
pub fn min(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn min(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let mut max = f64::INFINITY;
for arg in args {
let num = arg.to_num();
@ -137,7 +137,7 @@ pub fn min(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
Ok(to_value(max))
}
/// Raise a number to a power
pub fn pow(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn pow(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.len() >= 2 {
let num: f64 = from_value(args.get(0).unwrap().clone()).unwrap();
let power: f64 = from_value(args.get(1).unwrap().clone()).unwrap();
@ -147,11 +147,11 @@ pub fn pow(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Generate a random floating-point number between 0 and 1
pub fn _random(_: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn _random(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(random::<f64>()))
}
/// Round a number to the nearest integer
pub fn round(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn round(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -161,7 +161,7 @@ pub fn round(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the sine of a number
pub fn sin(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn sin(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -171,7 +171,7 @@ pub fn sin(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the square root of a number
pub fn sqrt(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn sqrt(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
@ -181,7 +181,7 @@ pub fn sqrt(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}))
}
/// Get the tangent of a number
pub fn tan(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn tan(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {

16
src/lib/js/object.rs

@ -11,11 +11,11 @@ use std::collections::HashMap;
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
/// As this string will be used a lot throughout the program, its best being a static global string which will be referenced
pub static PROTOTYPE: &'static str = "prototype";
pub static PROTOTYPE: &str = "prototype";
/// Static `__proto__`, usually set on Object instances as a key to point to their respective prototype object.
/// As this string will be used a lot throughout the program, its best being a static global string which will be referenced
pub static INSTANCE_PROTOTYPE: &'static str = "__proto__";
pub static INSTANCE_PROTOTYPE: &str = "__proto__";
/// `ObjectData` is the representation of an object in JavaScript
#[derive(Trace, Finalize, Debug, Clone)]
@ -116,18 +116,18 @@ impl FromValue for Property {
}
/// Create a new object
pub fn make_object(_: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn make_object(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(Gc::new(ValueData::Undefined))
}
/// Get the prototype of an object
pub fn get_proto_of(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn get_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).unwrap();
Ok(obj.get_field_slice(INSTANCE_PROTOTYPE))
}
/// Set the prototype of an object
pub fn set_proto_of(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).unwrap().clone();
let proto = args.get(1).unwrap().clone();
obj.set_field_slice(INSTANCE_PROTOTYPE, proto);
@ -135,7 +135,7 @@ pub fn set_proto_of(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}
/// Define a property in an object
pub fn define_prop(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).unwrap();
let prop = from_value::<String>(args.get(1).unwrap().clone()).unwrap();
let desc = from_value::<Property>(args.get(2).unwrap().clone()).unwrap();
@ -144,12 +144,12 @@ pub fn define_prop(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}
/// To string
pub fn to_string(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(this.to_string()))
}
/// Check if it has a property
pub fn has_own_prop(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let prop = if args.is_empty() {
None
} else {

199
src/lib/js/string.rs

@ -15,29 +15,34 @@ use std::{
/// Create new string
/// <https://searchfox.org/mozilla-central/source/js/src/vm/StringObject.h#19>
// This gets called when a new String() is created, it's called by exec:346
pub fn make_string(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
pub fn make_string(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
// If we're constructing a string, we should set the initial length
// To do this we need to convert the string back to a Rust String, then get the .len()
// let a: String = from_value(args[0].clone()).unwrap();
// let a: String = from_value(args.get(0).expect("failed to get argument for String method").clone()).unwrap();
// this.set_field_slice("length", to_value(a.len() as i32));
// This value is used by console.log and other routines to match Object type
// This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::String);
this.set_internal_slot("PrimitiveValue", args[0].clone());
this.set_internal_slot(
"StringData",
args.get(0)
.expect("failed to get StringData for make_string()")
.clone(),
);
Ok(this.clone())
}
/// Get a string's length
pub fn get_string_length(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
let this_str: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
pub fn get_string_length(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str = ctx.value_to_rust_string(this);
Ok(to_value::<i32>(this_str.chars().count() as i32))
}
/// Get the string value to a primitive string
pub fn to_string(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
// Get String from String Object and send it back as a new value
let primitive_val = this.get_internal_slot("PrimitiveValue");
let primitive_val = this.get_internal_slot("StringData");
Ok(to_value(format!("{}", primitive_val).to_string()))
}
@ -45,12 +50,16 @@ pub fn to_string(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
/// resulting from converting this object to a String. If there is no element at that index, the
/// result is the empty String. The result is a String value, not a String object.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.charat>
pub fn char_at(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments (we only care about the first one in this case)
pub fn char_at(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let pos: i32 = from_value(args[0].clone()).unwrap();
let primitive_val = ctx.value_to_rust_string(this);
let pos: i32 = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
// Calling .len() on a string would give the wrong result, as they are bytes not the number of
// unicode code points
@ -64,7 +73,10 @@ pub fn char_at(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
}
Ok(to_value::<char>(
primitive_val.chars().nth(pos as usize).unwrap(),
primitive_val
.chars()
.nth(pos as usize)
.expect("failed to get value"),
))
}
@ -72,22 +84,29 @@ pub fn char_at(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// unit at index pos within the String resulting from converting this object to a String. If there
/// is no element at that index, the result is NaN.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.charcodeat>
pub fn char_code_at(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments (we only care about the first one in this case)
pub fn char_code_at(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points
// Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
let length = primitive_val.chars().count();
let pos: i32 = from_value(args[0].clone()).unwrap();
let pos: i32 = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
if pos >= length as i32 || pos < 0 {
return Ok(to_value(NAN));
}
let utf16_val = primitive_val.encode_utf16().nth(pos as usize).unwrap();
let utf16_val = primitive_val
.encode_utf16()
.nth(pos as usize)
.expect("failed to get utf16 value");
// If there is no element at that index, the result is NaN
// TODO: We currently don't have NaN
Ok(to_value(f64::from(utf16_val)))
@ -96,16 +115,15 @@ pub fn char_code_at(this: &Value, args: &[Value], _: &Interpreter) -> ResultValu
/// Returns a String that is the result of concatenating this String and all strings provided as
/// arguments
/// <https://tc39.github.io/ecma262/#sec-string.prototype.concat>
pub fn concat(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments
pub fn concat(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
let mut new_str = primitive_val.clone();
for arg in args {
let concat_str: String = from_value(arg.clone()).unwrap();
let concat_str: String = from_value(arg.clone()).expect("failed to get argument value");
new_str.push_str(&concat_str);
}
@ -115,39 +133,52 @@ pub fn concat(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// Returns a String that is the result of repeating this String the number of times given by the
/// first argument
/// <https://tc39.github.io/ecma262/#sec-string.prototype.repeat>
pub fn repeat(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments (only care about the first one in this case)
pub fn repeat(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let repeat_times: usize = from_value(args[0].clone()).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
let repeat_times: usize = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
Ok(to_value(primitive_val.repeat(repeat_times)))
}
/// Returns a String which contains the slice of the JS String from character at "start" index up
/// to but not including character at "end" index
/// <https://tc39.github.io/ecma262/#sec-string.prototype.slice>
pub fn slice(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments)
pub fn slice(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let start: i32 = from_value(args[0].clone()).unwrap();
let end: i32 = from_value(args[1].clone()).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
let start: i32 = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let end: i32 = from_value(
args.get(1)
.expect("failed to get argument in slice")
.clone(),
)
.expect("failed to parse argument");
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points
// Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
let length: i32 = primitive_val.chars().count() as i32;
let from: i32 = if start < 0 {
max(length + start, 0)
max(length.wrapping_add(start), 0)
} else {
min(start, length)
};
let to: i32 = if end < 0 {
max(length + end, 0)
max(length.wrapping_add(end), 0)
} else {
min(end, length)
};
@ -165,14 +196,18 @@ pub fn slice(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// "search string" is the same as the corresponding code units of this string
/// starting at index "position"
/// <https://tc39.github.io/ecma262/#sec-string.prototype.startswith>
pub fn starts_with(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments)
pub fn starts_with(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
// TODO: Should throw TypeError if pattern is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
let search_string: String = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let length: i32 = primitive_val.chars().count() as i32;
let search_length: i32 = search_string.chars().count() as i32;
@ -181,11 +216,11 @@ pub fn starts_with(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue
let position: i32 = if args.len() < 2 {
0
} else {
from_value(args[1].clone()).unwrap()
from_value(args.get(1).expect("failed to get arg").clone()).expect("failed to get argument")
};
let start = min(max(position, 0), length);
let end = start + search_length;
let end = start.wrapping_add(search_length);
if end > length {
Ok(to_value(false))
@ -200,14 +235,18 @@ pub fn starts_with(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue
/// "search string" is the same as the corresponding code units of this string
/// starting at position "end position" - length
/// <https://tc39.github.io/ecma262/#sec-string.prototype.endswith>
pub fn ends_with(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments)
pub fn ends_with(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
let search_string: String = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let length: i32 = primitive_val.chars().count() as i32;
let search_length: i32 = search_string.chars().count() as i32;
@ -237,14 +276,18 @@ pub fn ends_with(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// that are greater than or equal to position. If position is undefined, 0 is
/// assumed, so as to search all of the String.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.includes>
pub fn includes(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments)
pub fn includes(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
let search_string: String = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let length: i32 = primitive_val.chars().count() as i32;
@ -269,14 +312,18 @@ pub fn includes(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// returned. If position is undefined, 0 is assumed, so as to search all of the
/// String.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.includes>
pub fn index_of(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments)
pub fn index_of(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
let search_string: String = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let length: i32 = primitive_val.chars().count() as i32;
@ -310,14 +357,18 @@ pub fn index_of(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// returned. If position is undefined, the length of the String value is
/// assumed, so as to search all of the String.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.lastindexof>
pub fn last_index_of(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
// ^^ represents instance ^^ represents arguments)
pub fn last_index_of(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 by wrapping it in from_value
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
let primitive_val: String = ctx.value_to_rust_string(this);
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
let search_string: String = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let length: i32 = primitive_val.chars().count() as i32;
@ -391,12 +442,17 @@ fn string_pad(
/// Pads the string with the given filler at the end of the string.
/// Filler defaults to single space.
/// <https://tc39.es/ecma262/#sec-string.prototype.padend/>
pub fn pad_end(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
pub fn pad_end(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive_val: String = ctx.value_to_rust_string(this);
if args.is_empty() {
return Err(to_value("padEnd requires maxLength argument"));
}
let max_length = from_value(args[0].clone()).unwrap();
let max_length = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let fill_string: Option<String> = match args.len() {
1 => None,
_ => Some(from_value(args[1].clone()).unwrap()),
@ -410,12 +466,17 @@ pub fn pad_end(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
/// Pads the string with the given filler at the start of the string.
/// Filler defaults to single space.
/// <https://tc39.es/ecma262/#sec-string.prototype.padstart/>
pub fn pad_start(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let primitive_val: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
pub fn pad_start(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive_val: String = ctx.value_to_rust_string(this);
if args.is_empty() {
return Err(to_value("padStart requires maxLength argument"));
}
let max_length = from_value(args[0].clone()).unwrap();
let max_length = from_value(
args.get(0)
.expect("failed to get argument for String method")
.clone(),
)
.expect("failed to parse argument for String method");
let fill_string: Option<String> = match args.len() {
1 => None,
_ => Some(from_value(args[1].clone()).unwrap()),
@ -442,20 +503,20 @@ fn is_trimmable_whitespace(c: char) -> bool {
}
}
pub fn trim(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
let this_str: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
pub fn trim(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str: String = ctx.value_to_rust_string(this);
Ok(to_value(this_str.trim_matches(is_trimmable_whitespace)))
}
pub fn trim_start(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
let this_str: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
pub fn trim_start(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str: String = ctx.value_to_rust_string(this);
Ok(to_value(
this_str.trim_start_matches(is_trimmable_whitespace),
))
}
pub fn trim_end(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
let this_str: String = from_value(this.get_internal_slot("PrimitiveValue")).unwrap();
pub fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str: String = ctx.value_to_rust_string(this);
Ok(to_value(this_str.trim_end_matches(is_trimmable_whitespace)))
}

7
src/lib/lib.rs

@ -14,7 +14,7 @@
#![deny(unused_qualifications)]
#![deny(clippy::all)]
#![warn(
clippy::pedantic,
// clippy::pedantic,
clippy::restriction,
clippy::cognitive_complexity,
//missing_docs
@ -26,7 +26,10 @@
clippy::wildcard_enum_match_arm,
clippy::cognitive_complexity,
clippy::module_name_repetitions,
clippy::print_stdout
clippy::print_stdout,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::non_ascii_literal
)]
pub mod environment;

3
tests/js/test.js

@ -1,2 +1 @@
const a = new String("12345");
a.length;
"12345".charAt(2);

Loading…
Cancel
Save