diff --git a/crates/dash_middle/src/interner.rs b/crates/dash_middle/src/interner.rs index 8b1fbd5d..e4b89796 100644 --- a/crates/dash_middle/src/interner.rs +++ b/crates/dash_middle/src/interner.rs @@ -266,7 +266,8 @@ pub mod sym { getPrototypeOf, isPrototypeOf, arguments, - propertyIsEnumerable + propertyIsEnumerable, + apply } ] } diff --git a/crates/dash_vm/src/js_std/function.rs b/crates/dash_vm/src/js_std/function.rs index abbc17d4..c66e19b2 100644 --- a/crates/dash_vm/src/js_std/function.rs +++ b/crates/dash_vm/src/js_std/function.rs @@ -3,12 +3,42 @@ use crate::value::function::bound::BoundFunction; use crate::value::function::native::CallContext; use crate::value::function::Function; use crate::value::object::Object; -use crate::value::{Root, Typeof, Value}; +use crate::value::ops::conversions::ValueConversion; +use crate::value::{Root, Typeof, Value, ValueContext}; pub fn constructor(cx: CallContext) -> Result { throw!(cx.scope, Error, "Dynamic code compilation is currently not supported") } +pub fn apply(cx: CallContext) -> Result { + let target_this = cx.args.first().cloned(); + let target_args = if let Some(array) = cx.args.get(1).cloned() { + if array.is_nullish() { + vec![] + } else { + let mut target_args = vec![]; + for i in 0..array.length_of_array_like(cx.scope)? { + let sym = cx.scope.intern_usize(i).into(); + + let arg_i = array.get_property(cx.scope, sym).root(cx.scope)?; + target_args.push(arg_i); + } + target_args + } + } else { + vec![] + }; + + let target_callee = match cx.this { + Value::Object(o) if matches!(o.type_of(), Typeof::Function) => o, + _ => throw!(cx.scope, TypeError, "Bound value must be a function"), + }; + + target_callee + .apply(cx.scope, target_this.unwrap_or_undefined(), target_args) + .root(cx.scope) +} + pub fn bind(cx: CallContext) -> Result { let target_this = cx.args.first().cloned(); let target_args = cx.args.get(1..).map(|s| s.to_vec()); @@ -32,7 +62,7 @@ pub fn call(cx: CallContext) -> Result { target_callee .apply( cx.scope, - target_this.unwrap_or_else(Value::undefined), + target_this.unwrap_or_undefined(), target_args.unwrap_or_default(), ) .root(cx.scope) diff --git a/crates/dash_vm/src/lib.rs b/crates/dash_vm/src/lib.rs index 660c3735..ce0acda5 100644 --- a/crates/dash_vm/src/lib.rs +++ b/crates/dash_vm/src/lib.rs @@ -218,6 +218,7 @@ impl Vm { scope.statics.object_prototype.clone(), function_ctor.clone(), [ + (sym::apply, scope.statics.function_apply.clone()), (sym::bind, scope.statics.function_bind.clone()), (sym::call, scope.statics.function_call.clone()), (sym::toString, scope.statics.function_to_string.clone()), diff --git a/crates/dash_vm/src/statics.rs b/crates/dash_vm/src/statics.rs index 8b191a23..5bde8fcc 100644 --- a/crates/dash_vm/src/statics.rs +++ b/crates/dash_vm/src/statics.rs @@ -26,6 +26,7 @@ use super::value::primitive::Symbol; pub struct Statics { pub function_proto: Handle, pub function_ctor: Handle, + pub function_apply: Handle, pub function_bind: Handle, pub function_call: Handle, pub function_to_string: Handle, @@ -266,6 +267,7 @@ impl Statics { Self { function_proto: empty_object(gc), function_ctor: function(gc, sym::Function, js_std::function::constructor), + function_apply: function(gc, sym::apply, js_std::function::apply), function_bind: function(gc, sym::bind, js_std::function::bind), function_call: function(gc, sym::call, js_std::function::call), function_to_string: function(gc, sym::toString, js_std::function::to_string), diff --git a/crates/dash_vm/src/test/mod.rs b/crates/dash_vm/src/test/mod.rs index 5445db77..d0ef200a 100644 --- a/crates/dash_vm/src/test/mod.rs +++ b/crates/dash_vm/src/test/mod.rs @@ -356,3 +356,15 @@ assert([,,].length === 2); "#, Value::undefined() ); + +simple_test!( + function_apply, + r#" +function sum(...args) { return args.reduce((p,c)=>p+c,0); } +assert(sum.apply(null, [1,2,3]) === 6); +assert(sum.apply(null) === 0); +assert(sum.apply(null, null) === 0); +assert(sum.apply(null, 0) === 0); + "#, + Value::undefined() +);