From 903cbcdccc97dd18458f636581178c3c14ecd824 Mon Sep 17 00:00:00 2001 From: Jason Williams <936006+jasonwilliams@users.noreply.github.com> Date: Thu, 1 Aug 2019 23:38:38 +0100 Subject: [PATCH] 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 --- src/lib/exec.rs | 162 ++++++++++++++++++++++++++------- src/lib/js/array.rs | 18 ++-- src/lib/js/console.rs | 4 +- src/lib/js/error.rs | 4 +- src/lib/js/function.rs | 2 +- src/lib/js/json.rs | 4 +- src/lib/js/math.rs | 38 ++++---- src/lib/js/object.rs | 16 ++-- src/lib/js/string.rs | 199 +++++++++++++++++++++++++++-------------- src/lib/lib.rs | 7 +- tests/js/test.js | 3 +- 11 files changed, 310 insertions(+), 147 deletions(-) diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 60578e8fb7..86498fef7e 100644 --- a/src/lib/exec.rs +++ b/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) -> 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"), + } + } +} diff --git a/src/lib/js/array.rs b/src/lib/js/array.rs index 1916b610a1..b63a5f806e 100644 --- a/src/lib/js/array.rs +++ b/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. /// -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. /// -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. /// -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. /// -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. /// -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. /// -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. /// -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; diff --git a/src/lib/js/console.rs b/src/lib/js/console.rs index a4bbbe546f..662b133fd7 100644 --- a/src/lib/js/console.rs +++ b/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 /// -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 = FromIterator::from_iter( args.iter() .map(|x| from_value::(x.clone()).unwrap()), diff --git a/src/lib/js/error.rs b/src/lib/js/error.rs index 46b20c1ea9..39590c6bfc 100644 --- a/src/lib/js/error.rs +++ b/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())) diff --git a/src/lib/js/function.rs b/src/lib/js/function.rs index d638b85ddc..7d575ceb26 100644 --- a/src/lib/js/function.rs +++ b/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 diff --git a/src/lib/js/json.rs b/src/lib/js/json.rs index 3e002d73cc..7f1bcf7f2d 100644 --- a/src/lib/js/json.rs +++ b/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 /// -pub fn parse(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue { +pub fn parse(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { match serde_json::from_str::( &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(""))) diff --git a/src/lib/js/math.rs b/src/lib/js/math.rs index 0c95a54e6b..6d8eaef3f3 100644 --- a/src/lib/js/math.rs +++ b/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::())) } /// 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 { diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index bdf8c148cb..73ad610c2e 100644 --- a/src/lib/js/object.rs +++ b/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::(args.get(1).unwrap().clone()).unwrap(); let desc = from_value::(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 { diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index 684553f324..05538c84eb 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -15,29 +15,34 @@ use std::{ /// Create new string /// // 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::(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. /// -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::( - 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. /// -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 /// -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 /// -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 /// -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" /// -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 /// -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. /// -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. /// -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. /// -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. /// -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 = 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. /// -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 = 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))) } diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 664fdb6b3f..7db021799b 100644 --- a/src/lib/lib.rs +++ b/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; diff --git a/tests/js/test.js b/tests/js/test.js index 987a210968..022bf1a2fb 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1,2 +1 @@ -const a = new String("12345"); -a.length; +"12345".charAt(2);