From 16137343252b7703bf3c640400cc3327aea58baa Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 26 May 2023 16:13:23 +1000 Subject: [PATCH] Simplify native_fn --- valuescript_vm/src/bigint_methods.rs | 40 +- valuescript_vm/src/builtins/array_builtin.rs | 132 +- valuescript_vm/src/builtins/debug_builtin.rs | 16 +- valuescript_vm/src/builtins/error_builtin.rs | 30 +- valuescript_vm/src/builtins/math_builtin.rs | 441 +++---- valuescript_vm/src/builtins/number_builtin.rs | 180 ++- .../src/builtins/range_error_builtin.rs | 30 +- valuescript_vm/src/builtins/string_builtin.rs | 28 +- .../src/builtins/type_error_builtin.rs | 30 +- valuescript_vm/src/native_function.rs | 6 + valuescript_vm/src/number_methods.rs | 140 +-- valuescript_vm/src/operations.rs | 29 +- valuescript_vm/src/string_methods.rs | 1084 ++++++++--------- valuescript_vm/src/todo_fn.rs | 7 +- valuescript_vm/src/vs_array.rs | 891 +++++++------- 15 files changed, 1436 insertions(+), 1648 deletions(-) diff --git a/valuescript_vm/src/bigint_methods.rs b/valuescript_vm/src/bigint_methods.rs index 47aad4a..0bd126c 100644 --- a/valuescript_vm/src/bigint_methods.rs +++ b/valuescript_vm/src/bigint_methods.rs @@ -2,7 +2,7 @@ use num_bigint::BigInt; use crate::{ builtins::error_builtin::ToError, - native_function::{NativeFunction, ThisWrapper}, + native_function::{native_fn, NativeFunction}, todo_fn::TODO, vs_value::{ToVal, Val}, }; @@ -17,26 +17,22 @@ pub fn op_sub_bigint(_bigint: &BigInt, subscript: &Val) -> Val { .to_val() } -static TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::BigInt(bigint) => match params.get(0) { - Some(_) => { - return Err("TODO: toString with radix".to_error()); - } +static TO_STRING: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::BigInt(bigint) => match params.get(0) { + Some(_) => { + return Err("TODO: toString with radix".to_error()); + } - None => bigint.clone().to_val().to_string().to_val(), - }, - _ => return Err("TODO: bigint indirection".to_error()), - }) - }, -}; + None => bigint.clone().to_val().to_string().to_val(), + }, + _ => return Err("TODO: bigint indirection".to_error()), + }) +}); -static VALUE_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::BigInt(bigint) => Val::BigInt(bigint.clone()), - _ => return Err("TODO: bigint indirection".to_error()), - }) - }, -}; +static VALUE_OF: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::BigInt(bigint) => Val::BigInt(bigint.clone()), + _ => return Err("TODO: bigint indirection".to_error()), + }) +}); diff --git a/valuescript_vm/src/builtins/array_builtin.rs b/valuescript_vm/src/builtins/array_builtin.rs index ce86559..2d2d1ab 100644 --- a/valuescript_vm/src/builtins/array_builtin.rs +++ b/valuescript_vm/src/builtins/array_builtin.rs @@ -2,7 +2,7 @@ use std::{fmt, rc::Rc}; use crate::{ builtins::range_error_builtin::to_range_error, - native_function::{NativeFunction, ThisWrapper}, + native_function::{native_fn, NativeFunction, ThisWrapper}, operations::op_sub, range_error, vs_array::VsArray, @@ -23,12 +23,12 @@ impl BuiltinObject for ArrayBuiltin { } fn bo_sub(key: &str) -> Val { - Val::Static(match key { - "isArray" => &IS_ARRAY, - "from" => &FROM, - "of" => &OF, + match key { + "isArray" => IS_ARRAY.to_val(), + "from" => FROM.to_val(), + "of" => OF.to_val(), _ => return Val::Undefined, - }) + } } fn bo_load_function() -> LoadFunctionResult { @@ -46,77 +46,65 @@ impl fmt::Display for ArrayBuiltin { } } -static IS_ARRAY: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - Ok(match params.get(0) { +static IS_ARRAY: NativeFunction = native_fn(|_this, params| { + Ok(match params.get(0) { + None => Val::Bool(false), + Some(p) => match p.as_array_data() { None => Val::Bool(false), - Some(p) => match p.as_array_data() { - None => Val::Bool(false), - Some(_) => Val::Bool(true), - }, - }) - }, -}; + Some(_) => Val::Bool(true), + }, + }) +}); -static FROM: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let first_param = match params.get(0) { - None => return Err("undefined is not iterable".to_type_error()), - Some(p) => p, - }; +static FROM: NativeFunction = native_fn(|_this, params| { + let first_param = match params.get(0) { + None => return Err("undefined is not iterable".to_type_error()), + Some(p) => p, + }; - if params.len() > 1 { - return Err(format!("TODO: Using Array.from with a map function").to_val()); + if params.len() > 1 { + return Err(format!("TODO: Using Array.from with a map function").to_val()); + } + + Ok(match first_param { + Val::Array(arr) => Val::Array(arr.clone()), + Val::String(s) => s.chars().map(|c| c.to_val()).collect::>().to_val(), + Val::Void | Val::Undefined | Val::Null => return Err("items is not iterable".to_type_error()), + Val::Bool(..) | Val::Number(..) | Val::BigInt(..) | Val::Symbol(..) => VsArray::new().to_val(), + Val::Object(..) | Val::Function(..) | Val::Class(..) | Val::Static(..) | Val::Custom(..) => { + let len = op_sub(first_param.clone(), "length".to_val()) + .map_err(|e| e.to_string()) + .unwrap() // TODO: Exception + .to_number(); + + if len.is_sign_negative() || len.is_nan() { + return Ok(VsArray::new().to_val()); + } + + if len.is_infinite() { + return range_error!("Invalid array length"); + } + + let len = len as usize; + + let mut arr = Vec::with_capacity(len); + + // TODO: We should probably use a frame and step through this + // Also using op_sub is slow. Should write specialized stuff instead. + for i in 0..len { + arr.push( + op_sub(first_param.clone(), Val::Number(i as f64)) + .map_err(|e| e.to_string()) + .unwrap(), // TODO: Exception + ); + } + + VsArray::from(arr).to_val() } + }) +}); - Ok(match first_param { - Val::Array(arr) => Val::Array(arr.clone()), - Val::String(s) => s.chars().map(|c| c.to_val()).collect::>().to_val(), - Val::Void | Val::Undefined | Val::Null => { - return Err("items is not iterable".to_type_error()) - } - Val::Bool(..) | Val::Number(..) | Val::BigInt(..) | Val::Symbol(..) => { - VsArray::new().to_val() - } - Val::Object(..) | Val::Function(..) | Val::Class(..) | Val::Static(..) | Val::Custom(..) => { - let len = op_sub(first_param.clone(), "length".to_val()) - .map_err(|e| e.to_string()) - .unwrap() // TODO: Exception - .to_number(); - - if len.is_sign_negative() || len.is_nan() { - return Ok(VsArray::new().to_val()); - } - - if len.is_infinite() { - return range_error!("Invalid array length"); - } - - let len = len as usize; - - let mut arr = Vec::with_capacity(len); - - // TODO: We should probably use a frame and step through this - // Also using op_sub is slow. Should write specialized stuff instead. - for i in 0..len { - arr.push( - op_sub(first_param.clone(), Val::Number(i as f64)) - .map_err(|e| e.to_string()) - .unwrap(), // TODO: Exception - ); - } - - VsArray::from(arr).to_val() - } - }) - }, -}; - -static OF: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - Ok(VsArray::from(params).to_val()) - }, -}; +static OF: NativeFunction = native_fn(|_this, params| Ok(VsArray::from(params).to_val())); fn to_array(_: ThisWrapper, params: Vec) -> Result { if params.len() != 1 { diff --git a/valuescript_vm/src/builtins/debug_builtin.rs b/valuescript_vm/src/builtins/debug_builtin.rs index 66220d4..30e15a7 100644 --- a/valuescript_vm/src/builtins/debug_builtin.rs +++ b/valuescript_vm/src/builtins/debug_builtin.rs @@ -1,7 +1,7 @@ use std::fmt; use std::rc::Rc; -use crate::native_function::{NativeFunction, ThisWrapper}; +use crate::native_function::{native_fn, NativeFunction}; use crate::vs_class::VsClass; use crate::vs_value::{LoadFunctionResult, Val}; @@ -38,12 +38,10 @@ impl fmt::Display for DebugBuiltin { } } -static LOG: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - for p in params { - println!("Debug.log: {}", p.pretty()); - } +static LOG: NativeFunction = native_fn(|_this, params| { + for p in params { + println!("Debug.log: {}", p.pretty()); + } - Ok(Val::Undefined) - }, -}; + Ok(Val::Undefined) +}); diff --git a/valuescript_vm/src/builtins/error_builtin.rs b/valuescript_vm/src/builtins/error_builtin.rs index 403dd3a..a54e770 100644 --- a/valuescript_vm/src/builtins/error_builtin.rs +++ b/valuescript_vm/src/builtins/error_builtin.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::fmt; use std::rc::Rc; -use crate::native_function::ThisWrapper; +use crate::native_function::{native_fn, ThisWrapper}; use crate::vs_class::VsClass; use crate::vs_value::ToVal; use crate::{ @@ -93,22 +93,18 @@ fn make_error_prototype() -> Val { .to_val() } -static SET_MESSAGE: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let message = match params.get(0) { - Some(param) => param.to_string(), - None => "".to_string(), - }; +static SET_MESSAGE: NativeFunction = native_fn(|mut this, params| { + let message = match params.get(0) { + Some(param) => param.to_string(), + None => "".to_string(), + }; - op_submov(this.get_mut()?, "message".to_val(), message.to_val())?; + op_submov(this.get_mut()?, "message".to_val(), message.to_val())?; - Ok(Val::Undefined) - }, -}; + Ok(Val::Undefined) +}); -static ERROR_TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - let message = op_sub(this.get().clone(), "message".to_val())?; - Ok(format!("Error({})", message).to_val()) // TODO: Fixes needed here (and other errors) - }, -}; +static ERROR_TO_STRING: NativeFunction = native_fn(|this, _params| { + let message = op_sub(this.get().clone(), "message".to_val())?; + Ok(format!("Error({})", message).to_val()) // TODO: Fixes needed here (and other errors) +}); diff --git a/valuescript_vm/src/builtins/math_builtin.rs b/valuescript_vm/src/builtins/math_builtin.rs index d519660..01376fd 100644 --- a/valuescript_vm/src/builtins/math_builtin.rs +++ b/valuescript_vm/src/builtins/math_builtin.rs @@ -1,10 +1,10 @@ use std::fmt; use std::rc::Rc; -use crate::native_function::{NativeFunction, ThisWrapper}; +use crate::native_function::{native_fn, NativeFunction}; use crate::operations::to_u32; use crate::vs_class::VsClass; -use crate::vs_value::{LoadFunctionResult, Val, ValTrait}; +use crate::vs_value::{LoadFunctionResult, ToVal, Val, ValTrait}; use super::builtin_object::BuiltinObject; @@ -19,51 +19,50 @@ impl BuiltinObject for MathBuiltin { fn bo_sub(key: &str) -> Val { match key { - "E" => Val::Number(std::f64::consts::E), - "LN10" => Val::Number(std::f64::consts::LN_10), - "LN2" => Val::Number(std::f64::consts::LN_2), - "LOG10E" => Val::Number(std::f64::consts::LOG10_E), - "LOG2E" => Val::Number(std::f64::consts::LOG2_E), - "PI" => Val::Number(std::f64::consts::PI), - "SQRT1_2" => Val::Number(std::f64::consts::FRAC_1_SQRT_2), - "SQRT2" => Val::Number(std::f64::consts::SQRT_2), - "abs" => Val::Static(&ABS), - - "acos" => Val::Static(&ACOS), - "acosh" => Val::Static(&ACOSH), - "asin" => Val::Static(&ASIN), - "asinh" => Val::Static(&ASINH), - "atan" => Val::Static(&ATAN), - "atan2" => Val::Static(&ATAN2), - "atanh" => Val::Static(&ATANH), - "cbrt" => Val::Static(&CBRT), - "ceil" => Val::Static(&CEIL), - "clz32" => Val::Static(&CLZ32), - "cos" => Val::Static(&COS), - "cosh" => Val::Static(&COSH), - "exp" => Val::Static(&EXP), - "expm1" => Val::Static(&EXPM1), - "floor" => Val::Static(&FLOOR), - "fround" => Val::Static(&FROUND), - "hypot" => Val::Static(&HYPOT), - "imul" => Val::Static(&IMUL), - "log" => Val::Static(&LOG), - "log10" => Val::Static(&LOG10), - "log1p" => Val::Static(&LOG1P), - "log2" => Val::Static(&LOG2), - "max" => Val::Static(&MAX), - "min" => Val::Static(&MIN), - "pow" => Val::Static(&POW), + "E" => std::f64::consts::E.to_val(), + "LN10" => std::f64::consts::LN_10.to_val(), + "LN2" => std::f64::consts::LN_2.to_val(), + "LOG10E" => std::f64::consts::LOG10_E.to_val(), + "LOG2E" => std::f64::consts::LOG2_E.to_val(), + "PI" => std::f64::consts::PI.to_val(), + "SQRT1_2" => std::f64::consts::FRAC_1_SQRT_2.to_val(), + "SQRT2" => std::f64::consts::SQRT_2.to_val(), + "abs" => ABS.to_val(), + "acos" => ACOS.to_val(), + "acosh" => ACOSH.to_val(), + "asin" => ASIN.to_val(), + "asinh" => ASINH.to_val(), + "atan" => ATAN.to_val(), + "atan2" => ATAN2.to_val(), + "atanh" => ATANH.to_val(), + "cbrt" => CBRT.to_val(), + "ceil" => CEIL.to_val(), + "clz32" => CLZ32.to_val(), + "cos" => COS.to_val(), + "cosh" => COSH.to_val(), + "exp" => EXP.to_val(), + "expm1" => EXPM1.to_val(), + "floor" => FLOOR.to_val(), + "fround" => FROUND.to_val(), + "hypot" => HYPOT.to_val(), + "imul" => IMUL.to_val(), + "log" => LOG.to_val(), + "log10" => LOG10.to_val(), + "log1p" => LOG1P.to_val(), + "log2" => LOG2.to_val(), + "max" => MAX.to_val(), + "min" => MIN.to_val(), + "pow" => POW.to_val(), // random: Not included because it cannot work as expected in ValueScript - "round" => Val::Static(&ROUND), - "sign" => Val::Static(&SIGN), - "sin" => Val::Static(&SIN), - "sinh" => Val::Static(&SINH), - "sqrt" => Val::Static(&SQRT), - "tan" => Val::Static(&TAN), - "tanh" => Val::Static(&TANH), - "trunc" => Val::Static(&TRUNC), + "round" => ROUND.to_val(), + "sign" => SIGN.to_val(), + "sin" => SIN.to_val(), + "sinh" => SINH.to_val(), + "sqrt" => SQRT.to_val(), + "tan" => TAN.to_val(), + "tanh" => TANH.to_val(), + "trunc" => TRUNC.to_val(), _ => Val::Undefined, } @@ -91,247 +90,179 @@ fn param_to_number(param: Option<&Val>) -> f64 { } } -static ABS: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.abs())) - }, -}; +static ABS: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.abs())) +}); -static ACOS: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.acos())) - }, -}; +static ACOS: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.acos())) +}); -static ACOSH: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.acosh())) - }, -}; +static ACOSH: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.acosh())) +}); -static ASIN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.asin())) - }, -}; +static ASIN: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.asin())) +}); -static ASINH: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.sinh())) - }, -}; +static ASINH: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.sinh())) +}); -static ATAN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.atan())) - }, -}; +static ATAN: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.atan())) +}); -static ATAN2: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - let y = param_to_number(params.get(1)); +static ATAN2: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + let y = param_to_number(params.get(1)); - Ok(Val::Number(x.atan2(y))) - }, -}; + Ok(Val::Number(x.atan2(y))) +}); -static ATANH: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.atanh())) - }, -}; +static ATANH: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.atanh())) +}); -static CBRT: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.cbrt())) - }, -}; +static CBRT: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.cbrt())) +}); -static CEIL: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.ceil())) - }, -}; +static CEIL: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.ceil())) +}); -static CLZ32: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(to_u32(x).leading_zeros() as f64)) - }, -}; +static CLZ32: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(to_u32(x).leading_zeros() as f64)) +}); -static COS: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.cos())) - }, -}; +static COS: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.cos())) +}); -static COSH: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.cosh())) - }, -}; +static COSH: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.cosh())) +}); -static EXP: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.exp())) - }, -}; +static EXP: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.exp())) +}); -static EXPM1: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.exp_m1())) - }, -}; +static EXPM1: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.exp_m1())) +}); -static FLOOR: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.floor())) - }, -}; +static FLOOR: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.floor())) +}); -static FROUND: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x as f32 as f64)) - }, -}; +static FROUND: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x as f32 as f64)) +}); -static HYPOT: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - let y = param_to_number(params.get(1)); - Ok(Val::Number(x.hypot(y))) - }, -}; +static HYPOT: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + let y = param_to_number(params.get(1)); + Ok(Val::Number(x.hypot(y))) +}); -static IMUL: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - let y = param_to_number(params.get(1)); - Ok(Val::Number((to_u32(x) * to_u32(y)) as i32 as f64)) - }, -}; +static IMUL: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + let y = param_to_number(params.get(1)); + Ok(Val::Number((to_u32(x) * to_u32(y)) as i32 as f64)) +}); -static LOG: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.ln())) - }, -}; +static LOG: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.ln())) +}); -static LOG10: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.log10())) - }, -}; +static LOG10: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.log10())) +}); -static LOG1P: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.ln_1p())) - }, -}; +static LOG1P: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.ln_1p())) +}); -static LOG2: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.log2())) - }, -}; +static LOG2: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.log2())) +}); -static MAX: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - let y = param_to_number(params.get(1)); - Ok(Val::Number(x.max(y))) - }, -}; +static MAX: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + let y = param_to_number(params.get(1)); + Ok(Val::Number(x.max(y))) +}); -static MIN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - let y = param_to_number(params.get(1)); - Ok(Val::Number(x.min(y))) - }, -}; +static MIN: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + let y = param_to_number(params.get(1)); + Ok(Val::Number(x.min(y))) +}); -static POW: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - let y = param_to_number(params.get(1)); - Ok(Val::Number(x.powf(y))) - }, -}; +static POW: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + let y = param_to_number(params.get(1)); + Ok(Val::Number(x.powf(y))) +}); -static ROUND: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.round())) - }, -}; +static ROUND: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.round())) +}); -static SIGN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.signum())) - }, -}; +static SIGN: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.signum())) +}); -static SIN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.sin())) - }, -}; +static SIN: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.sin())) +}); -static SINH: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.sinh())) - }, -}; +static SINH: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.sinh())) +}); -static SQRT: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.sqrt())) - }, -}; +static SQRT: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.sqrt())) +}); -static TAN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.tan())) - }, -}; +static TAN: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.tan())) +}); -static TANH: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.tanh())) - }, -}; +static TANH: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.tanh())) +}); -static TRUNC: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let x = param_to_number(params.get(0)); - Ok(Val::Number(x.trunc())) - }, -}; +static TRUNC: NativeFunction = native_fn(|_this, params| { + let x = param_to_number(params.get(0)); + Ok(Val::Number(x.trunc())) +}); diff --git a/valuescript_vm/src/builtins/number_builtin.rs b/valuescript_vm/src/builtins/number_builtin.rs index 7f93f6e..9797dc4 100644 --- a/valuescript_vm/src/builtins/number_builtin.rs +++ b/valuescript_vm/src/builtins/number_builtin.rs @@ -1,7 +1,7 @@ use std::fmt; use std::rc::Rc; -use crate::native_function::ThisWrapper; +use crate::native_function::{native_fn, ThisWrapper}; use crate::vs_value::ToVal; use crate::{ native_function::NativeFunction, @@ -62,104 +62,92 @@ impl fmt::Display for NumberBuiltin { } } -pub static IS_FINITE: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - Ok(if let Some(value) = params.get(0) { - let number = value.to_number(); - Val::Bool(number.is_finite()) - } else { - Val::Bool(false) - }) - }, -}; +pub static IS_FINITE: NativeFunction = native_fn(|_this, params| { + Ok(if let Some(value) = params.get(0) { + let number = value.to_number(); + Val::Bool(number.is_finite()) + } else { + Val::Bool(false) + }) +}); -static IS_INTEGER: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let num = match params.get(0) { - Some(n) => n.to_number(), - None => return Ok(Val::Bool(false)), +static IS_INTEGER: NativeFunction = native_fn(|_this, params| { + let num = match params.get(0) { + Some(n) => n.to_number(), + None => return Ok(Val::Bool(false)), + }; + + let is_finite = num.is_finite(); + let is_integer = num.floor() == num; + + Ok(Val::Bool(is_finite && is_integer)) +}); + +pub static IS_NAN: NativeFunction = native_fn(|_this, params| { + Ok(if let Some(value) = params.get(0) { + let number = value.to_number(); + Val::Bool(number.is_nan()) + } else { + Val::Bool(false) + }) +}); + +static IS_SAFE_INTEGER: NativeFunction = native_fn(|_this, params| { + let num = match params.get(0) { + Some(n) => n.to_number(), + None => return Ok(Val::Bool(false)), + }; + + let is_finite = num.is_finite(); + let is_integer = num.floor() == num; + let min_safe_integer = -(2f64.powi(53) - 1f64); + let max_safe_integer = 2f64.powi(53) - 1f64; + let in_safe_range = min_safe_integer <= num && num <= max_safe_integer; + + Ok(Val::Bool(is_finite && is_integer && in_safe_range)) +}); + +pub static PARSE_FLOAT: NativeFunction = native_fn(|_this, params| { + Ok(if let Some(value) = params.get(0) { + let string_value = value.to_string().trim().to_string(); + + match string_value.parse::() { + Ok(number) => Val::Number(number), + Err(_) => Val::Number(core::f64::NAN), + } + } else { + Val::Number(core::f64::NAN) + }) +}); + +pub static PARSE_INT: NativeFunction = native_fn(|_this, params| { + Ok(if let Some(value) = params.get(0) { + let string_value = value.to_string().trim_start().to_string(); + let radix = params.get(1).and_then(|v| v.to_index()).unwrap_or(10); + + if radix < 2 || radix > 36 { + return Ok(Val::Number(core::f64::NAN)); + } + + let (is_negative, string_value) = if string_value.starts_with('-') { + (true, &string_value[1..]) + } else { + (false, string_value.as_str()) }; - let is_finite = num.is_finite(); - let is_integer = num.floor() == num; - - Ok(Val::Bool(is_finite && is_integer)) - }, -}; - -pub static IS_NAN: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - Ok(if let Some(value) = params.get(0) { - let number = value.to_number(); - Val::Bool(number.is_nan()) - } else { - Val::Bool(false) - }) - }, -}; - -static IS_SAFE_INTEGER: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let num = match params.get(0) { - Some(n) => n.to_number(), - None => return Ok(Val::Bool(false)), + let string_value = match string_value.find(|c: char| !c.is_digit(radix as u32)) { + Some(pos) => &string_value[..pos], + None => &string_value, }; - let is_finite = num.is_finite(); - let is_integer = num.floor() == num; - let min_safe_integer = -(2f64.powi(53) - 1f64); - let max_safe_integer = 2f64.powi(53) - 1f64; - let in_safe_range = min_safe_integer <= num && num <= max_safe_integer; - - Ok(Val::Bool(is_finite && is_integer && in_safe_range)) - }, -}; - -pub static PARSE_FLOAT: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - Ok(if let Some(value) = params.get(0) { - let string_value = value.to_string().trim().to_string(); - - match string_value.parse::() { - Ok(number) => Val::Number(number), - Err(_) => Val::Number(core::f64::NAN), + match i64::from_str_radix(string_value, radix as u32) { + Ok(number) => { + let number = if is_negative { -number } else { number }; + Val::Number(number as f64) } - } else { - Val::Number(core::f64::NAN) - }) - }, -}; - -pub static PARSE_INT: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - Ok(if let Some(value) = params.get(0) { - let string_value = value.to_string().trim_start().to_string(); - let radix = params.get(1).and_then(|v| v.to_index()).unwrap_or(10); - - if radix < 2 || radix > 36 { - return Ok(Val::Number(core::f64::NAN)); - } - - let (is_negative, string_value) = if string_value.starts_with('-') { - (true, &string_value[1..]) - } else { - (false, string_value.as_str()) - }; - - let string_value = match string_value.find(|c: char| !c.is_digit(radix as u32)) { - Some(pos) => &string_value[..pos], - None => &string_value, - }; - - match i64::from_str_radix(string_value, radix as u32) { - Ok(number) => { - let number = if is_negative { -number } else { number }; - Val::Number(number as f64) - } - Err(_) => Val::Number(core::f64::NAN), - } - } else { - Val::Number(core::f64::NAN) - }) - }, -}; + Err(_) => Val::Number(core::f64::NAN), + } + } else { + Val::Number(core::f64::NAN) + }) +}); diff --git a/valuescript_vm/src/builtins/range_error_builtin.rs b/valuescript_vm/src/builtins/range_error_builtin.rs index a163674..2791e24 100644 --- a/valuescript_vm/src/builtins/range_error_builtin.rs +++ b/valuescript_vm/src/builtins/range_error_builtin.rs @@ -1,7 +1,7 @@ use std::fmt; use std::{collections::BTreeMap, rc::Rc}; -use crate::native_function::ThisWrapper; +use crate::native_function::{native_fn, ThisWrapper}; use crate::vs_value::ToVal; use crate::{ native_function::NativeFunction, @@ -57,18 +57,16 @@ fn make_range_error_prototype() -> Val { .to_val() } -static SET_MESSAGE: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let message = match params.get(0) { - Some(param) => param.to_string(), - None => "".to_string(), - }; +static SET_MESSAGE: NativeFunction = native_fn(|mut this, params| { + let message = match params.get(0) { + Some(param) => param.to_string(), + None => "".to_string(), + }; - op_submov(this.get_mut()?, "message".to_val(), message.to_val())?; + op_submov(this.get_mut()?, "message".to_val(), message.to_val())?; - Ok(Val::Undefined) - }, -}; + Ok(Val::Undefined) +}); pub fn to_range_error(_: ThisWrapper, params: Vec) -> Result { Ok( @@ -87,12 +85,10 @@ pub fn to_range_error(_: ThisWrapper, params: Vec) -> Result { ) } -static RANGE_ERROR_TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - let message = op_sub(this.get().clone(), "message".to_val())?; - Ok(format!("RangeError({})", message).to_val()) - }, -}; +static RANGE_ERROR_TO_STRING: NativeFunction = native_fn(|this, _params| { + let message = op_sub(this.get().clone(), "message".to_val())?; + Ok(format!("RangeError({})", message).to_val()) +}); #[macro_export] macro_rules! range_error { diff --git a/valuescript_vm/src/builtins/string_builtin.rs b/valuescript_vm/src/builtins/string_builtin.rs index 2cf31aa..a2b4287 100644 --- a/valuescript_vm/src/builtins/string_builtin.rs +++ b/valuescript_vm/src/builtins/string_builtin.rs @@ -1,7 +1,7 @@ use std::fmt; use std::rc::Rc; -use crate::native_function::ThisWrapper; +use crate::native_function::{native_fn, ThisWrapper}; use crate::vs_value::ToVal; use crate::{builtins::range_error_builtin::to_range_error, range_error}; use crate::{ @@ -49,24 +49,22 @@ impl fmt::Display for StringBuiltin { } } -static FROM_CODE_POINT: NativeFunction = NativeFunction { - fn_: |_this: ThisWrapper, params: Vec| -> Result { - let mut result = String::new(); +static FROM_CODE_POINT: NativeFunction = native_fn(|_this, params| { + let mut result = String::new(); - for param in params { - let code_point = param.to_number() as u32; // TODO: Check overflow behavior + for param in params { + let code_point = param.to_number() as u32; // TODO: Check overflow behavior - let char = match std::char::from_u32(code_point) { - Some(c) => c, - None => return range_error!("Invalid code point"), - }; + let char = match std::char::from_u32(code_point) { + Some(c) => c, + None => return range_error!("Invalid code point"), + }; - result.push(char); - } + result.push(char); + } - Ok(result.to_val()) - }, -}; + Ok(result.to_val()) +}); fn to_string(_: ThisWrapper, params: Vec) -> Result { Ok(if let Some(value) = params.get(0) { diff --git a/valuescript_vm/src/builtins/type_error_builtin.rs b/valuescript_vm/src/builtins/type_error_builtin.rs index 15bd0bd..ec3c4c1 100644 --- a/valuescript_vm/src/builtins/type_error_builtin.rs +++ b/valuescript_vm/src/builtins/type_error_builtin.rs @@ -1,7 +1,7 @@ use std::fmt; use std::{collections::BTreeMap, rc::Rc}; -use crate::native_function::ThisWrapper; +use crate::native_function::{native_fn, ThisWrapper}; use crate::vs_value::ToVal; use crate::{ native_function::NativeFunction, @@ -65,25 +65,21 @@ fn make_type_error_prototype() -> Val { .to_val() } -static SET_MESSAGE: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let message = match params.get(0) { - Some(param) => param.to_string(), - None => "".to_string(), - }; +static SET_MESSAGE: NativeFunction = native_fn(|mut this, params| { + let message = match params.get(0) { + Some(param) => param.to_string(), + None => "".to_string(), + }; - op_submov(this.get_mut()?, "message".to_val(), message.to_val())?; + op_submov(this.get_mut()?, "message".to_val(), message.to_val())?; - Ok(Val::Undefined) - }, -}; + Ok(Val::Undefined) +}); -static TYPE_ERROR_TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - let message = op_sub(this.get().clone(), "message".to_val())?; - Ok(format!("TypeError({})", message).to_val()) - }, -}; +static TYPE_ERROR_TO_STRING: NativeFunction = native_fn(|this, _params| { + let message = op_sub(this.get().clone(), "message".to_val())?; + Ok(format!("TypeError({})", message).to_val()) +}); pub trait ToTypeError { fn to_type_error(self) -> Val; diff --git a/valuescript_vm/src/native_function.rs b/valuescript_vm/src/native_function.rs index 52f7a23..1a6dff1 100644 --- a/valuescript_vm/src/native_function.rs +++ b/valuescript_vm/src/native_function.rs @@ -37,6 +37,12 @@ pub struct NativeFunction { pub fn_: fn(this: ThisWrapper, params: Vec) -> Result, } +pub const fn native_fn( + fn_: fn(this: ThisWrapper, params: Vec) -> Result, +) -> NativeFunction { + NativeFunction { fn_ } +} + impl ValTrait for NativeFunction { fn typeof_(&self) -> VsType { VsType::Function diff --git a/valuescript_vm/src/number_methods.rs b/valuescript_vm/src/number_methods.rs index 7d17149..c71deb3 100644 --- a/valuescript_vm/src/number_methods.rs +++ b/valuescript_vm/src/number_methods.rs @@ -1,5 +1,5 @@ use crate::builtins::error_builtin::ToError; -use crate::native_function::ThisWrapper; +use crate::native_function::{native_fn, ThisWrapper}; use crate::vs_value::ToVal; use crate::{builtins::range_error_builtin::to_range_error, range_error}; use crate::{ @@ -21,92 +21,80 @@ pub fn op_sub_number(_number: f64, subscript: &Val) -> Val { .to_val() } -static TO_FIXED: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Number(number) => { - if number.is_infinite() { - return Ok( - if number.is_sign_positive() { - "Infinity" - } else { - "-Infinity" - } - .to_val(), - ); - } +static TO_FIXED: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Number(number) => { + if number.is_infinite() { + return Ok( + if number.is_sign_positive() { + "Infinity" + } else { + "-Infinity" + } + .to_val(), + ); + } - let mut precision = match params.get(0) { - Some(p) => p.to_number(), - _ => return Ok(number.to_val().to_string().to_val()), - }; + let mut precision = match params.get(0) { + Some(p) => p.to_number(), + _ => return Ok(number.to_val().to_string().to_val()), + }; + precision = f64::floor(precision); + + if precision < 1.0 || precision > 100.0 { + return range_error!("precision must be between 1 and 100"); + } + + format!("{:.*}", precision as usize, number).to_val() + } + _ => return Err(format!("TODO: number indirection").to_val()), + }) +}); + +static TO_EXPONENTIAL: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Number(number) => match params.get(0) { + Some(p) => { + let mut precision = p.to_number(); precision = f64::floor(precision); - if precision < 1.0 || precision > 100.0 { - return range_error!("precision must be between 1 and 100"); + if precision < 0.0 || precision > 100.0 { + return range_error!("precision must be between 0 and 100"); } - format!("{:.*}", precision as usize, number).to_val() + format_exponential(*number, Some(precision as usize)) } - _ => return Err(format!("TODO: number indirection").to_val()), - }) - }, -}; + None => format_exponential(*number, None), + }, + _ => return Err("number indirection".to_error()), + }) +}); -static TO_EXPONENTIAL: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Number(number) => match params.get(0) { - Some(p) => { - let mut precision = p.to_number(); - precision = f64::floor(precision); +static TODO_LOCALE: NativeFunction = native_fn(|this, _params| match this.get() { + Val::Number(_number) => return Err("TODO: locale".to_error()), + _ => return Err("number indirection".to_error()), +}); - if precision < 0.0 || precision > 100.0 { - return range_error!("precision must be between 0 and 100"); - } +static TO_STRING: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Number(number) => match params.get(0) { + Some(_) => { + return Err("TODO: toString with radix".to_error()); + } - format_exponential(*number, Some(precision as usize)) - } - None => format_exponential(*number, None), - }, - _ => return Err("number indirection".to_error()), - }) - }, -}; + None => number.to_val().to_string().to_val(), + }, + _ => return Err("number indirection".to_error()), + }) +}); -static TODO_LOCALE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - match this.get() { - Val::Number(_number) => return Err("TODO: locale".to_error()), - _ => return Err("number indirection".to_error()), - } - }, -}; - -static TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Number(number) => match params.get(0) { - Some(_) => { - return Err("TODO: toString with radix".to_error()); - } - - None => number.to_val().to_string().to_val(), - }, - _ => return Err("number indirection".to_error()), - }) - }, -}; - -static VALUE_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::Number(number) => Val::Number(*number), - _ => return Err("number indirection".to_error()), - }) - }, -}; +static VALUE_OF: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::Number(number) => Val::Number(*number), + _ => return Err("number indirection".to_error()), + }) +}); fn format_exponential(number: f64, precision: Option) -> Val { if number.is_infinite() { diff --git a/valuescript_vm/src/operations.rs b/valuescript_vm/src/operations.rs index 43e8d3c..aa49494 100644 --- a/valuescript_vm/src/operations.rs +++ b/valuescript_vm/src/operations.rs @@ -6,6 +6,7 @@ use num_traits::ToPrimitive; use crate::bigint_methods::op_sub_bigint; use crate::builtins::error_builtin::ToError; use crate::builtins::type_error_builtin::ToTypeError; +use crate::native_function::native_fn; use crate::native_function::NativeFunction; use crate::native_function::ThisWrapper; use crate::number_methods::op_sub_number; @@ -469,20 +470,16 @@ pub fn op_submov(target: &mut Val, subscript: Val, value: Val) -> Result<(), Val } } -static BOOL_TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::Bool(b) => b.to_string().to_val(), - _ => return Err("bool indirection".to_type_error()), - }) - }, -}; +static BOOL_TO_STRING: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::Bool(b) => b.to_string().to_val(), + _ => return Err("bool indirection".to_type_error()), + }) +}); -static BOOL_VALUE_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::Bool(b) => Val::Bool(*b), - _ => return Err("bool indirection".to_type_error()), - }) - }, -}; +static BOOL_VALUE_OF: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::Bool(b) => Val::Bool(*b), + _ => return Err("bool indirection".to_type_error()), + }) +}); diff --git a/valuescript_vm/src/string_methods.rs b/valuescript_vm/src/string_methods.rs index 46e1a4d..29a36ef 100644 --- a/valuescript_vm/src/string_methods.rs +++ b/valuescript_vm/src/string_methods.rs @@ -3,7 +3,7 @@ use std::{rc::Rc, str::Chars}; use crate::{ builtins::error_builtin::ToError, helpers::{to_wrapping_index, to_wrapping_index_clamped}, - native_function::{NativeFunction, ThisWrapper}, + native_function::{native_fn, NativeFunction}, vs_value::{ToVal, Val}, ValTrait, }; @@ -84,625 +84,577 @@ pub fn get_string_method(method: &str) -> Val { .to_val() } -static AT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); +static AT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); - let index = match to_wrapping_index(params.get(0), string_bytes.len()) { + let index = match to_wrapping_index(params.get(0), string_bytes.len()) { + None => return Ok(Val::Undefined), + Some(i) => i, + }; + + match unicode_at(string_bytes, string_bytes.len(), index) { + Some(char) => char.to_string().to_val(), + None => "".to_val(), + } + } + _ => return Err("string indirection".to_error()), + }) +}); + +static CODE_POINT_AT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); + + let index = match params.get(0) { + Some(i) => match i.to_index() { None => return Ok(Val::Undefined), Some(i) => i, - }; + }, + _ => return Ok(Val::Undefined), + }; - match unicode_at(string_bytes, string_bytes.len(), index) { - Some(char) => char.to_string().to_val(), - None => "".to_val(), - } + match code_point_at(string_bytes, string_bytes.len(), index) { + Some(code_point) => Val::Number(code_point as f64), + None => Val::Undefined, } - _ => return Err("string indirection".to_error()), - }) - }, -}; + } + _ => return Err("string indirection".to_error()), + }) +}); -static CODE_POINT_AT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); +static CONCAT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let mut result = string_data.as_str().to_string(); - let index = match params.get(0) { - Some(i) => match i.to_index() { - None => return Ok(Val::Undefined), - Some(i) => i, - }, - _ => return Ok(Val::Undefined), - }; - - match code_point_at(string_bytes, string_bytes.len(), index) { - Some(code_point) => Val::Number(code_point as f64), - None => Val::Undefined, - } + for param in params { + result.push_str(param.to_string().as_str()); } - _ => return Err("string indirection".to_error()), - }) - }, -}; -static CONCAT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let mut result = string_data.as_str().to_string(); + result.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); - for param in params { - result.push_str(param.to_string().as_str()); - } +static ENDS_WITH: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); - result.to_val() + let search_string = match params.get(0) { + Some(s) => s.to_string(), + _ => return Ok(Val::Bool(false)), + }; + + let end_pos = match params.get(1) { + Some(p) => match p.to_index() { + // FIXME: Using to_index for end_pos is not quite right (eg -1 should be 0) + None => return Ok(Val::Bool(false)), + Some(i) => std::cmp::min(i, string_bytes.len()), + }, + _ => string_bytes.len(), + }; + + let search_bytes = search_string.as_bytes(); + + let search_length = search_bytes.len(); + + if search_length > end_pos { + return Ok(Val::Bool(false)); } - _ => return Err("string indirection".to_error()), - }) - }, -}; -static ENDS_WITH: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); + let start_index = end_pos - search_length; - let search_string = match params.get(0) { - Some(s) => s.to_string(), - _ => return Ok(Val::Bool(false)), - }; - - let end_pos = match params.get(1) { - Some(p) => match p.to_index() { - // FIXME: Using to_index for end_pos is not quite right (eg -1 should be 0) - None => return Ok(Val::Bool(false)), - Some(i) => std::cmp::min(i, string_bytes.len()), - }, - _ => string_bytes.len(), - }; - - let search_bytes = search_string.as_bytes(); - - let search_length = search_bytes.len(); - - if search_length > end_pos { + for i in 0..search_length { + if string_bytes[start_index + i] != search_bytes[i] { return Ok(Val::Bool(false)); } - - let start_index = end_pos - search_length; - - for i in 0..search_length { - if string_bytes[start_index + i] != search_bytes[i] { - return Ok(Val::Bool(false)); - } - } - - Val::Bool(true) } - _ => return Err("string indirection".to_error()), - }) - }, -}; -static INCLUDES: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); - - let search_string = match params.get(0) { - Some(s) => s.to_string(), - _ => return Ok(Val::Bool(false)), - }; - - let search_bytes = search_string.as_bytes(); - - let start_pos = match params.get(1) { - Some(p) => match p.to_index() { - // FIXME: to_index isn't quite right here - Some(i) => i, - None => return Ok(Val::Bool(false)), - }, - _ => 0, - }; - - match index_of(string_bytes, search_bytes, start_pos) { - Some(_) => Val::Bool(true), - None => Val::Bool(false), - } - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -static INDEX_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); - - let search_string = match params.get(0) { - Some(s) => s.to_string(), - _ => return Ok(Val::Number(-1.0)), - }; - - let search_bytes = search_string.as_bytes(); - - let start_pos = match params.get(1) { - Some(p) => match p.to_index() { - // FIXME: to_index isn't quite right here - Some(i) => i, - None => return Ok(Val::Number(-1.0)), - }, - _ => 0, - }; - - match index_of(string_bytes, search_bytes, start_pos) { - Some(i) => Val::Number(i as f64), - None => Val::Number(-1.0), - } - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -static LAST_INDEX_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); - - let search_string = match params.get(0) { - Some(s) => s.to_string(), - _ => return Ok(Val::Number(-1.0)), - }; - - let search_bytes = search_string.as_bytes(); - - let at_least_pos = match params.get(1) { - Some(p) => match p.to_index() { - // FIXME: to_index isn't quite right here - Some(i) => i, - None => return Ok(Val::Number(-1.0)), - }, - _ => 0, - }; - - match last_index_of(string_bytes, search_bytes, at_least_pos) { - Some(i) => Val::Number(i as f64), - None => Val::Number(-1.0), - } - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -static TODO_LOCALE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - // TODO: Ok(...) - match this.get() { - Val::String(_string_data) => { - return Err("TODO: locale".to_error()); - } - _ => return Err("string indirection".to_error()), + Val::Bool(true) } - }, -}; + _ => return Err("string indirection".to_error()), + }) +}); -static TODO_REGEXES: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - // TODO: Ok(...) - match this.get() { - Val::String(_string_data) => { - return Err("TODO: regexes".to_error()); - } - _ => return Err("string indirection".to_error()), - } - }, -}; +static INCLUDES: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); -static NORMALIZE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - // TODO: Ok(...) - match this.get() { - Val::String(_string_data) => { - // Consider https://docs.rs/unicode-normalization/latest/unicode_normalization/ - return Err("TODO: normalize".to_error()); + let search_string = match params.get(0) { + Some(s) => s.to_string(), + _ => return Ok(Val::Bool(false)), + }; + + let search_bytes = search_string.as_bytes(); + + let start_pos = match params.get(1) { + Some(p) => match p.to_index() { + // FIXME: to_index isn't quite right here + Some(i) => i, + None => return Ok(Val::Bool(false)), + }, + _ => 0, + }; + + match index_of(string_bytes, search_bytes, start_pos) { + Some(_) => Val::Bool(true), + None => Val::Bool(false), } - _ => return Err("string indirection".to_error()), } - }, -}; + _ => return Err("string indirection".to_error()), + }) +}); + +static INDEX_OF: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); + + let search_string = match params.get(0) { + Some(s) => s.to_string(), + _ => return Ok(Val::Number(-1.0)), + }; + + let search_bytes = search_string.as_bytes(); + + let start_pos = match params.get(1) { + Some(p) => match p.to_index() { + // FIXME: to_index isn't quite right here + Some(i) => i, + None => return Ok(Val::Number(-1.0)), + }, + _ => 0, + }; + + match index_of(string_bytes, search_bytes, start_pos) { + Some(i) => Val::Number(i as f64), + None => Val::Number(-1.0), + } + } + _ => return Err("string indirection".to_error()), + }) +}); + +static LAST_INDEX_OF: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); + + let search_string = match params.get(0) { + Some(s) => s.to_string(), + _ => return Ok(Val::Number(-1.0)), + }; + + let search_bytes = search_string.as_bytes(); + + let at_least_pos = match params.get(1) { + Some(p) => match p.to_index() { + // FIXME: to_index isn't quite right here + Some(i) => i, + None => return Ok(Val::Number(-1.0)), + }, + _ => 0, + }; + + match last_index_of(string_bytes, search_bytes, at_least_pos) { + Some(i) => Val::Number(i as f64), + None => Val::Number(-1.0), + } + } + _ => return Err("string indirection".to_error()), + }) +}); + +static TODO_LOCALE: NativeFunction = native_fn(|this, _params| { + // TODO: Ok(...) + match this.get() { + Val::String(_string_data) => { + return Err("TODO: locale".to_error()); + } + _ => return Err("string indirection".to_error()), + } +}); + +static TODO_REGEXES: NativeFunction = native_fn(|this, _params| { + // TODO: Ok(...) + match this.get() { + Val::String(_string_data) => { + return Err("TODO: regexes".to_error()); + } + _ => return Err("string indirection".to_error()), + } +}); + +static NORMALIZE: NativeFunction = native_fn(|this, _params| { + // TODO: Ok(...) + match this.get() { + Val::String(_string_data) => { + // Consider https://docs.rs/unicode-normalization/latest/unicode_normalization/ + return Err("TODO: normalize".to_error()); + } + _ => return Err("string indirection".to_error()), + } +}); // TODO: JS has some locale-specific behavior, not sure yet how we should deal with that -static PAD_END: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let target_length = match params.get(0) { - Some(p) => match p.to_index() { - Some(i) => i, - None => return Ok(Val::String(string_data.clone())), - }, - _ => return Ok(Val::String(string_data.clone())), - }; - - if target_length <= string_data.as_bytes().len() { - return Ok(Val::String(string_data.clone())); - } - - let mut string = string_data.to_string(); - - let pad_string = match params.get(1) { - Some(s) => s.to_string(), - _ => " ".to_string(), - }; - - let mut length_deficit = target_length - string.as_bytes().len(); - - let whole_copies = length_deficit / pad_string.as_bytes().len(); - - for _ in 0..whole_copies { - string.push_str(&pad_string); - } - - length_deficit -= whole_copies * pad_string.as_bytes().len(); - - if length_deficit > 0 { - for c in pad_string.chars() { - let c_len = c.len_utf8(); - - if c_len > length_deficit { - break; - } - - string.push(c); - length_deficit -= c_len; - } - } - - string.to_val() - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -// TODO: JS has some locale-specific behavior, not sure yet how we should deal with that -static PAD_START: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let target_length = match params.get(0) { - Some(p) => match p.to_index() { - Some(i) => i, - None => return Ok(Val::String(string_data.clone())), - }, - _ => return Ok(Val::String(string_data.clone())), - }; - - if target_length <= string_data.as_bytes().len() { - return Ok(Val::String(string_data.clone())); - } - - let pad_string = match params.get(1) { - Some(s) => s.to_string(), - _ => " ".to_string(), - }; - - let mut length_deficit = target_length - string_data.as_bytes().len(); - - let whole_copies = length_deficit / pad_string.as_bytes().len(); - - let mut prefix = String::new(); - - for _ in 0..whole_copies { - prefix.push_str(&pad_string); - } - - length_deficit -= whole_copies * pad_string.as_bytes().len(); - - if length_deficit > 0 { - for c in pad_string.chars() { - let c_len = c.len_utf8(); - - if c_len > length_deficit { - break; - } - - prefix.push(c); - length_deficit -= c_len; - } - } - - prefix.push_str(string_data); - - prefix.to_val() - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -static REPEAT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let count = match params.get(0) { - Some(p) => match p.to_index() { - Some(i) => i, - None => return Ok(Val::String(string_data.clone())), - }, - _ => return Ok(Val::String(string_data.clone())), - }; - - let mut result = String::new(); - - for _ in 0..count { - result.push_str(string_data); - } - - result.to_val() - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -static SLICE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); - - let start = match params.get(0) { - None => 0, - Some(v) => to_wrapping_index_clamped(v, string_bytes.len()), - }; - - let end = match params.get(1) { - None => string_bytes.len() as isize, - Some(v) => to_wrapping_index_clamped(v, string_bytes.len()), - }; - - let mut new_string = String::new(); - - // FIXME: This is a slow way of doing it. Part of the reason is that we're using rust's - // string type, so we can't just find the relevant byte range and copy it in one go. - for i in start..end { - match unicode_at(string_bytes, end as usize, i as usize) { - Some(c) => new_string.push(c), - None => {} - } - } - - new_string.to_val() - } - _ => return Err("string indirection".to_error()), - }) - }, -}; - -static SPLIT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let separator = match params.get(0) { - Some(s) => s.to_string(), // TODO: Regexes +static PAD_END: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let target_length = match params.get(0) { + Some(p) => match p.to_index() { + Some(i) => i, None => return Ok(Val::String(string_data.clone())), - }; + }, + _ => return Ok(Val::String(string_data.clone())), + }; - let limit = match params.get(1) { - // FIXME: to_index isn't quite right - Some(l) => match l.to_index() { - Some(i) => i, - None => string_data.as_bytes().len() + 1, - }, - None => string_data.as_bytes().len() + 1, - }; - - let mut result = Vec::::new(); - - if limit == 0 { - return Ok(result.to_val()); - } - - if separator.is_empty() { - for c in string_data.chars() { - result.push(c.to_val()); - - if result.len() == limit { - break; - } - } - - return Ok(result.to_val()); - } - - let mut part = String::new(); - let mut str_chars = string_data.chars(); - - loop { - if match_chars(&mut str_chars, &separator) { - let mut new_part = String::new(); - std::mem::swap(&mut new_part, &mut part); - result.push(new_part.to_val()); - - if result.len() == limit { - break; - } - } else { - match str_chars.next() { - Some(c) => part.push(c), - None => { - result.push(part.to_val()); - break; - } - } - } - } - - result.to_val() + if target_length <= string_data.as_bytes().len() { + return Ok(Val::String(string_data.clone())); } - _ => return Err("string indirection".to_error()), - }) - }, -}; -static STARTS_WITH: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); + let mut string = string_data.to_string(); - let search_string = match params.get(0) { - Some(s) => s.to_string(), - _ => return Ok(Val::Bool(false)), - }; + let pad_string = match params.get(1) { + Some(s) => s.to_string(), + _ => " ".to_string(), + }; - let pos = match params.get(1) { - Some(p) => match p.to_index() { - // FIXME: Using to_index is not quite right - None => return Ok(Val::Bool(false)), - Some(i) => std::cmp::min(i, string_bytes.len()), - }, - _ => 0, - }; + let mut length_deficit = target_length - string.as_bytes().len(); - let search_bytes = search_string.as_bytes(); + let whole_copies = length_deficit / pad_string.as_bytes().len(); - let search_length = search_bytes.len(); + for _ in 0..whole_copies { + string.push_str(&pad_string); + } - if search_length > string_bytes.len() - pos { + length_deficit -= whole_copies * pad_string.as_bytes().len(); + + if length_deficit > 0 { + for c in pad_string.chars() { + let c_len = c.len_utf8(); + + if c_len > length_deficit { + break; + } + + string.push(c); + length_deficit -= c_len; + } + } + + string.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); + +// TODO: JS has some locale-specific behavior, not sure yet how we should deal with that +static PAD_START: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let target_length = match params.get(0) { + Some(p) => match p.to_index() { + Some(i) => i, + None => return Ok(Val::String(string_data.clone())), + }, + _ => return Ok(Val::String(string_data.clone())), + }; + + if target_length <= string_data.as_bytes().len() { + return Ok(Val::String(string_data.clone())); + } + + let pad_string = match params.get(1) { + Some(s) => s.to_string(), + _ => " ".to_string(), + }; + + let mut length_deficit = target_length - string_data.as_bytes().len(); + + let whole_copies = length_deficit / pad_string.as_bytes().len(); + + let mut prefix = String::new(); + + for _ in 0..whole_copies { + prefix.push_str(&pad_string); + } + + length_deficit -= whole_copies * pad_string.as_bytes().len(); + + if length_deficit > 0 { + for c in pad_string.chars() { + let c_len = c.len_utf8(); + + if c_len > length_deficit { + break; + } + + prefix.push(c); + length_deficit -= c_len; + } + } + + prefix.push_str(string_data); + + prefix.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); + +static REPEAT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let count = match params.get(0) { + Some(p) => match p.to_index() { + Some(i) => i, + None => return Ok(Val::String(string_data.clone())), + }, + _ => return Ok(Val::String(string_data.clone())), + }; + + let mut result = String::new(); + + for _ in 0..count { + result.push_str(string_data); + } + + result.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); + +static SLICE: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); + + let start = match params.get(0) { + None => 0, + Some(v) => to_wrapping_index_clamped(v, string_bytes.len()), + }; + + let end = match params.get(1) { + None => string_bytes.len() as isize, + Some(v) => to_wrapping_index_clamped(v, string_bytes.len()), + }; + + let mut new_string = String::new(); + + // FIXME: This is a slow way of doing it. Part of the reason is that we're using rust's + // string type, so we can't just find the relevant byte range and copy it in one go. + for i in start..end { + match unicode_at(string_bytes, end as usize, i as usize) { + Some(c) => new_string.push(c), + None => {} + } + } + + new_string.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); + +static SPLIT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let separator = match params.get(0) { + Some(s) => s.to_string(), // TODO: Regexes + None => return Ok(Val::String(string_data.clone())), + }; + + let limit = match params.get(1) { + // FIXME: to_index isn't quite right + Some(l) => match l.to_index() { + Some(i) => i, + None => string_data.as_bytes().len() + 1, + }, + None => string_data.as_bytes().len() + 1, + }; + + let mut result = Vec::::new(); + + if limit == 0 { + return Ok(result.to_val()); + } + + if separator.is_empty() { + for c in string_data.chars() { + result.push(c.to_val()); + + if result.len() == limit { + break; + } + } + + return Ok(result.to_val()); + } + + let mut part = String::new(); + let mut str_chars = string_data.chars(); + + loop { + if match_chars(&mut str_chars, &separator) { + let mut new_part = String::new(); + std::mem::swap(&mut new_part, &mut part); + result.push(new_part.to_val()); + + if result.len() == limit { + break; + } + } else { + match str_chars.next() { + Some(c) => part.push(c), + None => { + result.push(part.to_val()); + break; + } + } + } + } + + result.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); + +static STARTS_WITH: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); + + let search_string = match params.get(0) { + Some(s) => s.to_string(), + _ => return Ok(Val::Bool(false)), + }; + + let pos = match params.get(1) { + Some(p) => match p.to_index() { + // FIXME: Using to_index is not quite right + None => return Ok(Val::Bool(false)), + Some(i) => std::cmp::min(i, string_bytes.len()), + }, + _ => 0, + }; + + let search_bytes = search_string.as_bytes(); + + let search_length = search_bytes.len(); + + if search_length > string_bytes.len() - pos { + return Ok(Val::Bool(false)); + } + + for i in 0..search_length { + if string_bytes[pos + i] != search_bytes[i] { return Ok(Val::Bool(false)); } - - for i in 0..search_length { - if string_bytes[pos + i] != search_bytes[i] { - return Ok(Val::Bool(false)); - } - } - - Val::Bool(true) } - _ => return Err("string indirection".to_error()), - }) - }, -}; -static SUBSTRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => { - let string_bytes = string_data.as_bytes(); + Val::Bool(true) + } + _ => return Err("string indirection".to_error()), + }) +}); - let start = match params.get(0) { - Some(v) => match v.to_index() { - Some(i) => std::cmp::min(i, string_bytes.len()), - None => 0, - }, +static SUBSTRING: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::String(string_data) => { + let string_bytes = string_data.as_bytes(); + + let start = match params.get(0) { + Some(v) => match v.to_index() { + Some(i) => std::cmp::min(i, string_bytes.len()), None => 0, - }; + }, + None => 0, + }; - let end = match params.get(1) { - Some(v) => match v.to_index() { - Some(i) => std::cmp::min(i, string_bytes.len()), - None => string_bytes.len(), - }, + let end = match params.get(1) { + Some(v) => match v.to_index() { + Some(i) => std::cmp::min(i, string_bytes.len()), None => string_bytes.len(), - }; + }, + None => string_bytes.len(), + }; - let substring_start = std::cmp::min(start, end); - let substring_end = std::cmp::max(start, end); + let substring_start = std::cmp::min(start, end); + let substring_end = std::cmp::max(start, end); - let mut new_string = String::new(); + let mut new_string = String::new(); - // FIXME: This is a slow way of doing it. Part of the reason is that we're using rust's - // string type, so we can't just find the relevant byte range and copy it in one go. - for i in substring_start..substring_end { - match unicode_at(string_bytes, substring_end, i) { - Some(c) => new_string.push(c), - None => {} - } + // FIXME: This is a slow way of doing it. Part of the reason is that we're using rust's + // string type, so we can't just find the relevant byte range and copy it in one go. + for i in substring_start..substring_end { + match unicode_at(string_bytes, substring_end, i) { + Some(c) => new_string.push(c), + None => {} } - - new_string.to_val() } - _ => return Err("string indirection".to_error()), - }) - }, -}; -static TO_LOWER_CASE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => string_data.to_lowercase().to_val(), - _ => return Err("string indirection".to_error()), - }) - }, -}; + new_string.to_val() + } + _ => return Err("string indirection".to_error()), + }) +}); -static TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => Val::String(string_data.clone()), - _ => return Err("string indirection".to_error()), - }) - }, -}; +static TO_LOWER_CASE: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => string_data.to_lowercase().to_val(), + _ => return Err("string indirection".to_error()), + }) +}); -static TO_UPPER_CASE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => string_data.to_uppercase().to_val(), - _ => return Err("string indirection".to_error()), - }) - }, -}; +static TO_STRING: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => Val::String(string_data.clone()), + _ => return Err("string indirection".to_error()), + }) +}); -static TRIM: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => string_data.trim().to_val(), - _ => return Err("string indirection".to_error()), - }) - }, -}; +static TO_UPPER_CASE: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => string_data.to_uppercase().to_val(), + _ => return Err("string indirection".to_error()), + }) +}); -static TRIM_END: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => string_data.trim_end().to_val(), - _ => return Err("string indirection".to_error()), - }) - }, -}; +static TRIM: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => string_data.trim().to_val(), + _ => return Err("string indirection".to_error()), + }) +}); -static TRIM_START: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => string_data.trim_start().to_val(), - _ => return Err("string indirection".to_error()), - }) - }, -}; +static TRIM_END: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => string_data.trim_end().to_val(), + _ => return Err("string indirection".to_error()), + }) +}); -static VALUE_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(match this.get() { - Val::String(string_data) => Val::String(string_data.clone()), - _ => return Err("string indirection".to_error()), - }) - }, -}; +static TRIM_START: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => string_data.trim_start().to_val(), + _ => return Err("string indirection".to_error()), + }) +}); + +static VALUE_OF: NativeFunction = native_fn(|this, _params| { + Ok(match this.get() { + Val::String(string_data) => Val::String(string_data.clone()), + _ => return Err("string indirection".to_error()), + }) +}); /** * Tries to match str_chars_param against matcher. diff --git a/valuescript_vm/src/todo_fn.rs b/valuescript_vm/src/todo_fn.rs index 8e56297..d863590 100644 --- a/valuescript_vm/src/todo_fn.rs +++ b/valuescript_vm/src/todo_fn.rs @@ -1,9 +1,6 @@ use crate::{ builtins::error_builtin::ToError, - native_function::{NativeFunction, ThisWrapper}, - vs_value::Val, + native_function::{native_fn, NativeFunction}, }; -pub static TODO: NativeFunction = NativeFunction { - fn_: |_: ThisWrapper, _params: Vec| -> Result { Err("TODO".to_error()) }, -}; +pub static TODO: NativeFunction = native_fn(|_, _| Err("TODO".to_error())); diff --git a/valuescript_vm/src/vs_array.rs b/valuescript_vm/src/vs_array.rs index 27fc57e..51e6dea 100644 --- a/valuescript_vm/src/vs_array.rs +++ b/valuescript_vm/src/vs_array.rs @@ -12,7 +12,7 @@ use crate::array_higher_functions::{ use crate::builtins::error_builtin::ToError; use crate::builtins::type_error_builtin::ToTypeError; use crate::helpers::{to_wrapping_index, to_wrapping_index_clamped}; -use crate::native_function::{NativeFunction, ThisWrapper}; +use crate::native_function::{native_fn, NativeFunction}; use crate::operations::op_triple_eq_impl; use crate::todo_fn::TODO; use crate::vs_class::VsClass; @@ -162,520 +162,481 @@ impl fmt::Display for ArrayPrototype { } } -static AT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => match to_wrapping_index(params.get(0), array_data.elements.len()) { - None => Val::Undefined, - Some(i) => array_data.elements[i].clone(), - }, - _ => return Err("array indirection".to_error()), - }) - }, -}; +static AT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => match to_wrapping_index(params.get(0), array_data.elements.len()) { + None => Val::Undefined, + Some(i) => array_data.elements[i].clone(), + }, + _ => return Err("array indirection".to_error()), + }) +}); -static CONCAT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => { - let mut new_array = array_data.as_ref().clone(); +static CONCAT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => { + let mut new_array = array_data.as_ref().clone(); - for p in params { - match &p.as_array_data() { - None => { - new_array.elements.push(p); - } - Some(p_array_data) => { - for elem in &p_array_data.elements { - new_array.elements.push(elem.clone()); - } + for p in params { + match &p.as_array_data() { + None => { + new_array.elements.push(p); + } + Some(p_array_data) => { + for elem in &p_array_data.elements { + new_array.elements.push(elem.clone()); } } } - - new_array.to_val() } - _ => return Err("array indirection".to_error()), - }) - }, -}; -static COPY_WITHIN: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let this = this.get_mut()?; + new_array.to_val() + } + _ => return Err("array indirection".to_error()), + }) +}); - Ok(match this { - Val::Array(array_data) => { - let array_data_mut = Rc::make_mut(array_data); - let ulen = array_data_mut.elements.len(); +static COPY_WITHIN: NativeFunction = native_fn(|mut this, params| { + let this = this.get_mut()?; - if ulen > isize::MAX as usize { - return Err("TODO: array len exceeds isize".to_error()); + Ok(match this { + Val::Array(array_data) => { + let array_data_mut = Rc::make_mut(array_data); + let ulen = array_data_mut.elements.len(); + + if ulen > isize::MAX as usize { + return Err("TODO: array len exceeds isize".to_error()); + } + + let mut target = match params.get(0) { + None => 0, + Some(p) => to_wrapping_index_clamped(p, ulen), + }; + + let mut start = match params.get(1) { + None => 0, + Some(p) => to_wrapping_index_clamped(p, ulen), + }; + + let ilen = ulen as isize; + + let mut end = match params.get(2) { + None => ilen, + // FIXME: undefined -> len (and others in this file) + Some(p) => to_wrapping_index_clamped(p, ulen), + }; + + let copy_len = end - start; + + if copy_len <= 0 { + return Ok(this.clone()); + } + + if target <= start || target >= end { + while target < ilen && start < end { + array_data_mut.elements[target as usize] = + array_data_mut.elements[start as usize].clone(); + + target += 1; + start += 1; + } + } else { + // The target is after the start. If we copied from start to target + // and worked forwards we'd overwrite the values we needed later. + // Instead we simply do the copies in the reverse order. + + target += copy_len - 1; + end -= 1; + + if target >= ilen { + end -= target - ilen + 1; + target = ilen - 1; } - let mut target = match params.get(0) { - None => 0, - Some(p) => to_wrapping_index_clamped(p, ulen), - }; + while target >= 0 && end >= start { + array_data_mut.elements[target as usize] = array_data_mut.elements[end as usize].clone(); - let mut start = match params.get(1) { - None => 0, - Some(p) => to_wrapping_index_clamped(p, ulen), - }; - - let ilen = ulen as isize; - - let mut end = match params.get(2) { - None => ilen, - // FIXME: undefined -> len (and others in this file) - Some(p) => to_wrapping_index_clamped(p, ulen), - }; - - let copy_len = end - start; - - if copy_len <= 0 { - return Ok(this.clone()); - } - - if target <= start || target >= end { - while target < ilen && start < end { - array_data_mut.elements[target as usize] = - array_data_mut.elements[start as usize].clone(); - - target += 1; - start += 1; - } - } else { - // The target is after the start. If we copied from start to target - // and worked forwards we'd overwrite the values we needed later. - // Instead we simply do the copies in the reverse order. - - target += copy_len - 1; + target -= 1; end -= 1; - - if target >= ilen { - end -= target - ilen + 1; - target = ilen - 1; - } - - while target >= 0 && end >= start { - array_data_mut.elements[target as usize] = - array_data_mut.elements[end as usize].clone(); - - target -= 1; - end -= 1; - } } - - this.clone() } - _ => return Err("array indirection".to_error()), - }) - }, -}; -static ENTRIES: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - match this.get() { - Val::Array(_array_data) => return Err("TODO: iterators".to_error()), - _ => return Err("array indirection".to_error()), - }; - }, -}; + this.clone() + } + _ => return Err("array indirection".to_error()), + }) +}); -static FILL: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let this = this.get_mut()?; +static ENTRIES: NativeFunction = native_fn(|this, _params| { + match this.get() { + Val::Array(_array_data) => return Err("TODO: iterators".to_error()), + _ => return Err("array indirection".to_error()), + }; +}); - Ok(match this { - Val::Array(array_data) => { - let array_data_mut = Rc::make_mut(array_data); - let len = array_data_mut.elements.len(); +static FILL: NativeFunction = native_fn(|mut this, params| { + let this = this.get_mut()?; - let fill_val = params.get(0).unwrap_or(&Val::Undefined); + Ok(match this { + Val::Array(array_data) => { + let array_data_mut = Rc::make_mut(array_data); + let len = array_data_mut.elements.len(); - let start = match params.get(1) { - None => 0, - Some(v) => to_wrapping_index_clamped(v, len), - }; + let fill_val = params.get(0).unwrap_or(&Val::Undefined); - let end = match params.get(2) { - None => len as isize, - Some(v) => to_wrapping_index_clamped(v, len), - }; + let start = match params.get(1) { + None => 0, + Some(v) => to_wrapping_index_clamped(v, len), + }; - for i in start..end { - array_data_mut.elements[i as usize] = fill_val.clone(); - } + let end = match params.get(2) { + None => len as isize, + Some(v) => to_wrapping_index_clamped(v, len), + }; - this.clone() + for i in start..end { + array_data_mut.elements[i as usize] = fill_val.clone(); } - _ => return Err("array indirection".to_error()), - }) - }, -}; -static FLAT: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => { - if params.len() > 0 { - return Err("TODO: .flat depth parameter".to_error()); - } + this.clone() + } + _ => return Err("array indirection".to_error()), + }) +}); - let mut new_elems = Vec::::new(); +static FLAT: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => { + if params.len() > 0 { + return Err("TODO: .flat depth parameter".to_error()); + } - for el in &array_data.elements { - match &el.as_array_data() { - None => { - new_elems.push(el.clone()); - } - Some(p_array_data) => { - for elem in &p_array_data.elements { - new_elems.push(elem.clone()); - } + let mut new_elems = Vec::::new(); + + for el in &array_data.elements { + match &el.as_array_data() { + None => { + new_elems.push(el.clone()); + } + Some(p_array_data) => { + for elem in &p_array_data.elements { + new_elems.push(elem.clone()); } } } - - new_elems.to_val() } - _ => return Err("array indirection".to_error()), - }) - }, -}; -static INCLUDES: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => { - let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); + new_elems.to_val() + } + _ => return Err("array indirection".to_error()), + }) +}); - for elem in &array_data.elements { - let is_eq = op_triple_eq_impl(elem.clone(), search_param.clone()) - .map_err(|e| e.to_string()) - .unwrap(); // TODO: Exception +static INCLUDES: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => { + let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); - if is_eq { - return Ok(Val::Bool(true)); + for elem in &array_data.elements { + let is_eq = op_triple_eq_impl(elem.clone(), search_param.clone()) + .map_err(|e| e.to_string()) + .unwrap(); // TODO: Exception + + if is_eq { + return Ok(Val::Bool(true)); + } + } + + Val::Bool(false) + } + _ => return Err("array indirection".to_error()), + }) +}); + +static INDEX_OF: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => { + let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); + + for i in 0..array_data.elements.len() { + let is_eq = op_triple_eq_impl(array_data.elements[i].clone(), search_param.clone()) + .map_err(|e| e.to_string()) + .unwrap(); // TODO: Exception + + if is_eq { + return Ok(Val::Number(i as f64)); + } + } + + Val::Number(-1.0) + } + _ => return Err("array indirection".to_error()), + }) +}); + +static JOIN: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(vals) => { + if vals.elements.len() == 0 { + return Ok("".to_val()); + } + + if vals.elements.len() == 1 { + return Ok(vals.elements[0].clone().to_val_string()); + } + + let separator = match params.get(0) { + None => ",".to_string(), + Some(v) => v.to_string(), + }; + + let mut iter = vals.elements.iter(); + let mut res = iter.next().unwrap().to_string(); + + for val in iter { + res += &separator; + + match val.typeof_() { + VsType::Undefined => {} + _ => { + res += &val.to_string(); } + }; + } + + res.to_val() + } + _ => return Err("array indirection".to_error()), + }) +}); + +static LAST_INDEX_OF: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => { + let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); + + for i in (0..array_data.elements.len()).rev() { + let is_eq = op_triple_eq_impl(array_data.elements[i].clone(), search_param.clone()) + .map_err(|e| e.to_string()) + .unwrap(); // TODO: Exception + + if is_eq { + return Ok(Val::Number(i as f64)); + } + } + + Val::Number(-1_f64) + } + _ => return Err("array indirection".to_error()), + }) +}); + +static POP: NativeFunction = native_fn(|mut this, _params| { + let this = this.get_mut()?; + + Ok(match this { + Val::Array(array_data) => { + if array_data.elements.len() == 0 { + return Ok(Val::Undefined); + } + + let array_data_mut = Rc::make_mut(array_data); + + let removed_el = array_data_mut + .elements + .remove(array_data_mut.elements.len() - 1); + + match removed_el { + Val::Void => Val::Undefined, + _ => removed_el, + } + } + _ => return Err("array indirection".to_error()), + }) +}); + +static PUSH: NativeFunction = native_fn(|mut this, params| { + let this = this.get_mut()?; + + Ok(match this { + Val::Array(array_data) => { + let array_data_mut = Rc::make_mut(array_data); + + for p in params { + array_data_mut.elements.push(p); + } + + Val::Number(array_data_mut.elements.len() as f64) + } + _ => return Err("array indirection".to_error()), + }) +}); + +static REVERSE: NativeFunction = native_fn(|mut this, _params| { + let this = this.get_mut()?; + + Ok(match this { + Val::Array(array_data) => { + if array_data.elements.len() == 0 { + // Treating this as an edge case because rust protects us from + // underflow when computing last below. + return Ok(this.clone()); + } + + let array_data_mut = Rc::make_mut(array_data); + + let last = array_data_mut.elements.len() - 1; + + for i in 0..(array_data_mut.elements.len() / 2) { + let tmp = array_data_mut.elements[i].clone(); + array_data_mut.elements[i] = array_data_mut.elements[last - i].clone(); + array_data_mut.elements[last - i] = tmp; + } + + this.clone() + } + _ => return Err("array indirection".to_error()), + }) +}); + +static SHIFT: NativeFunction = native_fn(|mut this, _params| { + let this = this.get_mut()?; + + Ok(match this { + Val::Array(array_data) => { + if array_data.elements.len() == 0 { + return Ok(Val::Undefined); + } + + let array_data_mut = Rc::make_mut(array_data); + + array_data_mut.elements.remove(0) + } + _ => return Err("array indirection".to_error()), + }) +}); + +static SLICE: NativeFunction = native_fn(|this, params| { + Ok(match this.get() { + Val::Array(array_data) => { + let mut new_elems = Vec::::new(); + + let start = match params.get(0) { + None => 0, + Some(v) => to_wrapping_index_clamped(v, array_data.elements.len()), + }; + + let end = match params.get(1) { + None => array_data.elements.len() as isize, + Some(v) => to_wrapping_index_clamped(v, array_data.elements.len()), + }; + + for i in start..end { + new_elems.push(array_data.elements[i as usize].clone()); + } + + new_elems.to_val() + } + _ => return Err("array indirection".to_error()), + }) +}); + +static SPLICE: NativeFunction = native_fn(|mut this, params| { + let this = this.get_mut()?; + + Ok(match this { + Val::Array(array_data) => { + let array_data_mut = Rc::make_mut(array_data); + let len = array_data_mut.elements.len(); + + let start = match params.get(0) { + None => 0, + Some(v) => to_wrapping_index_clamped(v, len), + } as usize; + + let delete_count_f64 = match params.get(1) { + None => len as f64, + Some(v) => match v.typeof_() { + VsType::Undefined => len as f64, + _ => v.to_number(), + }, + }; + + let delete_count = match delete_count_f64 < 0_f64 { + true => 0, + false => min(delete_count_f64.floor() as usize, len - start), + }; + + let mut deleted_elements = Vec::::new(); + + for i in 0..delete_count { + deleted_elements.push(array_data_mut.elements[start + i].clone()); + } + + let insert_len = max(2, params.len()) - 2; + let replace_len = min(insert_len, delete_count); + + if insert_len > replace_len { + for i in 0..replace_len { + array_data_mut.elements[start + i] = params[i + 2].clone(); } - Val::Bool(false) - } - _ => return Err("array indirection".to_error()), - }) - }, -}; + let gap = insert_len - replace_len; -static INDEX_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => { - let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); + for _ in 0..gap { + array_data_mut.elements.push(Val::Void); + } - for i in 0..array_data.elements.len() { - let is_eq = op_triple_eq_impl(array_data.elements[i].clone(), search_param.clone()) - .map_err(|e| e.to_string()) - .unwrap(); // TODO: Exception + for i in ((start + replace_len)..len).rev() { + array_data_mut.elements[i + gap] = array_data_mut.elements[i].clone(); + } - if is_eq { - return Ok(Val::Number(i as f64)); + for i in replace_len..insert_len { + array_data_mut.elements[start + i] = params[i + 2].clone(); + } + } else { + for i in 0..insert_len { + array_data_mut.elements[start + i] = params[i + 2].clone(); + } + + let gap = delete_count - insert_len; + + if gap != 0 { + for i in (start + insert_len)..(len - gap) { + array_data_mut.elements[i] = array_data_mut.elements[i + gap].clone(); } - } - - Val::Number(-1.0) - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static JOIN: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(vals) => { - if vals.elements.len() == 0 { - return Ok("".to_val()); - } - - if vals.elements.len() == 1 { - return Ok(vals.elements[0].clone().to_val_string()); - } - - let separator = match params.get(0) { - None => ",".to_string(), - Some(v) => v.to_string(), - }; - - let mut iter = vals.elements.iter(); - let mut res = iter.next().unwrap().to_string(); - - for val in iter { - res += &separator; - - match val.typeof_() { - VsType::Undefined => {} - _ => { - res += &val.to_string(); - } - }; - } - - res.to_val() - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static LAST_INDEX_OF: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => { - let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); - - for i in (0..array_data.elements.len()).rev() { - let is_eq = op_triple_eq_impl(array_data.elements[i].clone(), search_param.clone()) - .map_err(|e| e.to_string()) - .unwrap(); // TODO: Exception - - if is_eq { - return Ok(Val::Number(i as f64)); - } - } - - Val::Number(-1_f64) - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static POP: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, _params: Vec| -> Result { - let this = this.get_mut()?; - - Ok(match this { - Val::Array(array_data) => { - if array_data.elements.len() == 0 { - return Ok(Val::Undefined); - } - - let array_data_mut = Rc::make_mut(array_data); - - let removed_el = array_data_mut - .elements - .remove(array_data_mut.elements.len() - 1); - - match removed_el { - Val::Void => Val::Undefined, - _ => removed_el, - } - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static PUSH: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let this = this.get_mut()?; - - Ok(match this { - Val::Array(array_data) => { - let array_data_mut = Rc::make_mut(array_data); - - for p in params { - array_data_mut.elements.push(p); - } - - Val::Number(array_data_mut.elements.len() as f64) - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static REVERSE: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, _params: Vec| -> Result { - let this = this.get_mut()?; - - Ok(match this { - Val::Array(array_data) => { - if array_data.elements.len() == 0 { - // Treating this as an edge case because rust protects us from - // underflow when computing last below. - return Ok(this.clone()); - } - - let array_data_mut = Rc::make_mut(array_data); - - let last = array_data_mut.elements.len() - 1; - - for i in 0..(array_data_mut.elements.len() / 2) { - let tmp = array_data_mut.elements[i].clone(); - array_data_mut.elements[i] = array_data_mut.elements[last - i].clone(); - array_data_mut.elements[last - i] = tmp; - } - - this.clone() - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static SHIFT: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, _params: Vec| -> Result { - let this = this.get_mut()?; - - Ok(match this { - Val::Array(array_data) => { - if array_data.elements.len() == 0 { - return Ok(Val::Undefined); - } - - let array_data_mut = Rc::make_mut(array_data); - - array_data_mut.elements.remove(0) - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static SLICE: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, params: Vec| -> Result { - Ok(match this.get() { - Val::Array(array_data) => { - let mut new_elems = Vec::::new(); - - let start = match params.get(0) { - None => 0, - Some(v) => to_wrapping_index_clamped(v, array_data.elements.len()), - }; - - let end = match params.get(1) { - None => array_data.elements.len() as isize, - Some(v) => to_wrapping_index_clamped(v, array_data.elements.len()), - }; - - for i in start..end { - new_elems.push(array_data.elements[i as usize].clone()); - } - - new_elems.to_val() - } - _ => return Err("array indirection".to_error()), - }) - }, -}; - -static SPLICE: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let this = this.get_mut()?; - - Ok(match this { - Val::Array(array_data) => { - let array_data_mut = Rc::make_mut(array_data); - let len = array_data_mut.elements.len(); - - let start = match params.get(0) { - None => 0, - Some(v) => to_wrapping_index_clamped(v, len), - } as usize; - - let delete_count_f64 = match params.get(1) { - None => len as f64, - Some(v) => match v.typeof_() { - VsType::Undefined => len as f64, - _ => v.to_number(), - }, - }; - - let delete_count = match delete_count_f64 < 0_f64 { - true => 0, - false => min(delete_count_f64.floor() as usize, len - start), - }; - - let mut deleted_elements = Vec::::new(); - - for i in 0..delete_count { - deleted_elements.push(array_data_mut.elements[start + i].clone()); - } - - let insert_len = max(2, params.len()) - 2; - let replace_len = min(insert_len, delete_count); - - if insert_len > replace_len { - for i in 0..replace_len { - array_data_mut.elements[start + i] = params[i + 2].clone(); - } - - let gap = insert_len - replace_len; for _ in 0..gap { - array_data_mut.elements.push(Val::Void); - } - - for i in ((start + replace_len)..len).rev() { - array_data_mut.elements[i + gap] = array_data_mut.elements[i].clone(); - } - - for i in replace_len..insert_len { - array_data_mut.elements[start + i] = params[i + 2].clone(); - } - } else { - for i in 0..insert_len { - array_data_mut.elements[start + i] = params[i + 2].clone(); - } - - let gap = delete_count - insert_len; - - if gap != 0 { - for i in (start + insert_len)..(len - gap) { - array_data_mut.elements[i] = array_data_mut.elements[i + gap].clone(); - } - - for _ in 0..gap { - array_data_mut.elements.pop(); - } + array_data_mut.elements.pop(); } } - - deleted_elements.to_val() } - _ => return Err("array indirection".to_error()), - }) - }, -}; + + deleted_elements.to_val() + } + _ => return Err("array indirection".to_error()), + }) +}); // TODO: Share this? (JS doesn't?) -static TO_STRING: NativeFunction = NativeFunction { - fn_: |this: ThisWrapper, _params: Vec| -> Result { - Ok(this.get().to_string().to_val()) - }, -}; +static TO_STRING: NativeFunction = native_fn(|this, _params| Ok(this.get().to_string().to_val())); -static UNSHIFT: NativeFunction = NativeFunction { - fn_: |mut this: ThisWrapper, params: Vec| -> Result { - let this = this.get_mut()?; +static UNSHIFT: NativeFunction = native_fn(|mut this, params| { + let this = this.get_mut()?; - Ok(match this { - Val::Array(array_data) => { - let array_data_mut = Rc::make_mut(array_data); + Ok(match this { + Val::Array(array_data) => { + let array_data_mut = Rc::make_mut(array_data); - let mut i = 0; + let mut i = 0; - for p in params { - array_data_mut.elements.insert(i, p); - i += 1; - } - - Val::Number(array_data_mut.elements.len() as f64) + for p in params { + array_data_mut.elements.insert(i, p); + i += 1; } - _ => return Err("array indirection".to_error()), - }) - }, -}; + + Val::Number(array_data_mut.elements.len() as f64) + } + _ => return Err("array indirection".to_error()), + }) +});