Skip to main content

pipa/builtins/
function.rs

1use crate::host::HostFunction;
2use crate::object::function::JSFunction;
3use crate::object::object::JSObject;
4use crate::runtime::context::JSContext;
5use crate::value::JSValue;
6
7fn create_builtin_method(ctx: &mut JSContext, name: &str) -> JSValue {
8    let mut func = JSFunction::new_builtin(ctx.intern(name), 1);
9    func.set_builtin_marker(ctx, name);
10    if let Some(fn_proto_ptr) = ctx.get_function_prototype() {
11        func.base.set_prototype_raw(fn_proto_ptr);
12    }
13    let ptr = Box::into_raw(Box::new(func)) as usize;
14    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
15    JSValue::new_function(ptr)
16}
17
18fn patch_function_values_with_function_prototype(obj: &mut JSObject, fn_proto_ptr: *mut JSObject) {
19    let mut values = Vec::new();
20    obj.for_each_property(|_atom, value, _attrs| {
21        values.push(value);
22    });
23
24    for value in values {
25        if value.is_function() {
26            let func = unsafe { JSValue::function_from_ptr_mut(value.get_ptr()) };
27            func.base.set_prototype_raw(fn_proto_ptr);
28        }
29    }
30}
31
32pub fn init_function(ctx: &mut JSContext) {
33    let mut proto_obj = JSObject::new_function();
34    if let Some(object_proto_ptr) = ctx.get_object_prototype() {
35        proto_obj.prototype = Some(object_proto_ptr);
36    }
37    proto_obj.set(
38        ctx.common_atoms.bind,
39        create_builtin_method(ctx, "function_bind"),
40    );
41    proto_obj.set(
42        ctx.common_atoms.call,
43        create_builtin_method(ctx, "function_call"),
44    );
45    proto_obj.set(
46        ctx.common_atoms.apply,
47        create_builtin_method(ctx, "function_apply"),
48    );
49    proto_obj.set(
50        ctx.common_atoms.to_string,
51        create_builtin_method(ctx, "function_toString"),
52    );
53    proto_obj.define_accessor(
54        ctx.common_atoms.length,
55        Some(create_builtin_method(ctx, "function_length")),
56        None,
57    );
58    proto_obj.define_accessor(
59        ctx.common_atoms.name,
60        Some(create_builtin_method(ctx, "function_name")),
61        None,
62    );
63
64    if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
65        proto_obj.prototype = Some(obj_proto_ptr);
66    }
67
68    let proto_ptr = Box::into_raw(Box::new(proto_obj)) as usize;
69    ctx.runtime_mut().gc_heap_mut().track(proto_ptr);
70    let proto_value = JSValue::new_object(proto_ptr);
71
72    ctx.set_function_prototype(proto_ptr);
73
74    let proto_obj_mut = unsafe { &mut *(proto_ptr as *mut JSObject) };
75    for atom in [
76        ctx.common_atoms.bind,
77        ctx.common_atoms.call,
78        ctx.common_atoms.apply,
79        ctx.common_atoms.to_string,
80    ] {
81        if let Some(v) = proto_obj_mut.get(atom) {
82            if v.is_function() {
83                let f = unsafe { JSValue::function_from_ptr_mut(v.get_ptr()) };
84                f.base.set_prototype_raw(proto_ptr as *mut JSObject);
85            }
86        }
87    }
88
89    let global = ctx.global();
90    if global.is_object() {
91        let global_obj = global.as_object_mut();
92        patch_function_values_with_function_prototype(global_obj, proto_ptr as *mut JSObject);
93
94        let mut nested = Vec::new();
95        global_obj.for_each_property(|_atom, value, _attrs| {
96            if value.is_object() || value.is_function() {
97                nested.push(value);
98            }
99        });
100
101        for value in nested {
102            let nested_obj = value.as_object_mut();
103            patch_function_values_with_function_prototype(nested_obj, proto_ptr as *mut JSObject);
104        }
105    }
106
107    let mut function_ctor = JSFunction::new_builtin(ctx.common_atoms.function, 1);
108    function_ctor.set_builtin_marker(ctx, "function_constructor");
109    function_ctor.base.prototype = Some(proto_ptr as *mut JSObject);
110    function_ctor
111        .base
112        .set(ctx.common_atoms.prototype, proto_value.clone());
113
114    let function_ptr = Box::into_raw(Box::new(function_ctor)) as usize;
115    ctx.runtime_mut().gc_heap_mut().track_function(function_ptr);
116    let function_value = JSValue::new_function(function_ptr);
117
118    unsafe {
119        let proto_obj_ptr = proto_ptr as *mut crate::object::object::JSObject;
120        (*proto_obj_ptr).set(ctx.common_atoms.constructor, function_value);
121    }
122
123    let global = ctx.global();
124    if global.is_object() {
125        let global_obj = global.as_object_mut();
126        global_obj.set(ctx.common_atoms.function, function_value);
127        global_obj.set(ctx.intern("FunctionPrototype"), proto_value);
128    }
129}
130
131pub fn register_builtins(ctx: &mut JSContext) {
132    ctx.register_builtin("function_bind", HostFunction::new("bind", 1, function_bind));
133    ctx.register_builtin("function_call", HostFunction::new("call", 1, function_call));
134    ctx.register_builtin(
135        "function_apply",
136        HostFunction::new("apply", 2, function_apply),
137    );
138    ctx.register_builtin(
139        "function_toString",
140        HostFunction::new("toString", 0, function_to_string),
141    );
142    ctx.register_builtin(
143        "function_length",
144        HostFunction::new("length", 0, function_length),
145    );
146    ctx.register_builtin("function_name", HostFunction::new("name", 0, function_name));
147    ctx.register_builtin(
148        "function_constructor",
149        HostFunction::new("Function", 1, function_constructor),
150    );
151    ctx.register_builtin(
152        "function_has_instance",
153        HostFunction::new(SYMBOL_HAS_INSTANCE_DISPLAY, 1, function_has_instance),
154    );
155    ctx.register_builtin(
156        "throw_type_error_callee",
157        HostFunction::new("callee", 0, throw_type_error_callee),
158    );
159}
160
161fn throw_type_error_callee(ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
162    let mut err = crate::object::object::JSObject::new();
163    err.set(
164        ctx.intern("name"),
165        JSValue::new_string(ctx.intern("TypeError")),
166    );
167    err.set(
168        ctx.intern("message"),
169        JSValue::new_string(ctx.intern("arguments.callee is not supported in strict mode")),
170    );
171    if let Some(proto) = ctx.get_type_error_prototype() {
172        err.prototype = Some(proto);
173    }
174    let ptr = Box::into_raw(Box::new(err)) as usize;
175    ctx.runtime_mut().gc_heap_mut().track(ptr);
176    ctx.pending_exception = Some(JSValue::new_object(ptr));
177    JSValue::undefined()
178}
179
180const SYMBOL_HAS_INSTANCE_DISPLAY: &str = "[Symbol.hasInstance]";
181
182fn function_has_instance(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
183    if args.len() < 2 {
184        return JSValue::bool(false);
185    }
186    let f = &args[0];
187    let v = &args[1];
188    if !f.is_function() && !f.is_object() {
189        return JSValue::bool(false);
190    }
191    if !v.is_object() && !v.is_function() {
192        return JSValue::bool(false);
193    }
194    let proto_atom = ctx.common_atoms.prototype;
195    let proto_ptr = if f.is_function() {
196        let js_func = f.as_function();
197        if !js_func.cached_prototype_ptr.is_null() {
198            js_func.cached_prototype_ptr as *const crate::object::object::JSObject
199        } else {
200            let pv = js_func.base.get(proto_atom).unwrap_or(JSValue::undefined());
201            if pv.is_object() {
202                pv.get_ptr() as *const crate::object::object::JSObject
203            } else {
204                return JSValue::bool(false);
205            }
206        }
207    } else {
208        let pv = f
209            .as_object()
210            .get(proto_atom)
211            .unwrap_or(JSValue::undefined());
212        if pv.is_object() {
213            pv.get_ptr() as *const crate::object::object::JSObject
214        } else {
215            return JSValue::bool(false);
216        }
217    };
218    let obj_ptr = v.get_ptr() as *const crate::object::object::JSObject;
219    let mut current = unsafe { (*obj_ptr).prototype };
220    while let Some(p) = current {
221        if std::ptr::eq(p, proto_ptr) {
222            return JSValue::bool(true);
223        }
224        current = unsafe { (*p).prototype };
225    }
226    JSValue::bool(false)
227}
228
229fn function_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
230    if args.is_empty() {
231        return match crate::eval(ctx, "(function(){})") {
232            Ok(val) => val,
233            Err(_) => JSValue::undefined(),
234        };
235    }
236    let body_idx = args.len() - 1;
237    let body_str = if args[body_idx].is_string() {
238        ctx.get_atom_str(args[body_idx].get_atom()).to_string()
239    } else {
240        String::new()
241    };
242    let mut params = Vec::new();
243    for i in 0..body_idx {
244        let p = if args[i].is_string() {
245            ctx.get_atom_str(args[i].get_atom()).to_string()
246        } else {
247            String::new()
248        };
249        for part in p.trim().split(',') {
250            let part = part.trim();
251            if !part.is_empty() {
252                params.push(part.to_string());
253            }
254        }
255    }
256    let params_str = params.join(",");
257    let source = format!("(function({}){{{}}})", params_str, body_str);
258    match crate::eval(ctx, &source) {
259        Ok(val) => val,
260        Err(e) => {
261            let mut err = crate::object::object::JSObject::new();
262            err.set(
263                ctx.intern("name"),
264                JSValue::new_string(ctx.intern("SyntaxError")),
265            );
266            err.set(ctx.intern("message"), JSValue::new_string(ctx.intern(&e)));
267            if let Some(proto) = ctx.get_syntax_error_prototype() {
268                err.prototype = Some(proto);
269            }
270            let ptr = Box::into_raw(Box::new(err)) as usize;
271            ctx.runtime_mut().gc_heap_mut().track(ptr);
272            ctx.pending_exception = Some(JSValue::new_object(ptr));
273            JSValue::undefined()
274        }
275    }
276}
277
278fn function_bind(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
279    if args.is_empty() {
280        return JSValue::undefined();
281    }
282
283    let this_val = &args[0];
284    if !this_val.is_function() {
285        return JSValue::undefined();
286    }
287
288    let this_arg = if args.len() > 1 {
289        args[1].clone()
290    } else {
291        JSValue::undefined()
292    };
293
294    let mut wrapper = JSObject::new();
295    wrapper.set(ctx.common_atoms.__boundFn, *this_val);
296    wrapper.set(ctx.common_atoms.__boundThis, this_arg);
297
298    let mut args_arr = JSObject::new_array();
299    let length_key = ctx.common_atoms.length;
300    let bound_count = if args.len() > 2 {
301        (args.len() - 2) as i64
302    } else {
303        0
304    };
305    args_arr.set(length_key, JSValue::new_int(bound_count));
306    let mut idx = 0;
307    for arg in args.iter().skip(2) {
308        let key = ctx.intern(&idx.to_string());
309        args_arr.set(key, arg.clone());
310        idx += 1;
311    }
312    let boxed_args_arr = Box::new(args_arr);
313    let args_ptr = Box::into_raw(boxed_args_arr) as usize;
314    ctx.runtime_mut().gc_heap_mut().track(args_ptr);
315    wrapper.set(ctx.common_atoms.__boundArgs, JSValue::new_object(args_ptr));
316
317    let boxed_wrapper = Box::new(wrapper);
318    let ptr = Box::into_raw(boxed_wrapper) as usize;
319    ctx.runtime_mut().gc_heap_mut().track(ptr);
320    JSValue::new_object(ptr)
321}
322
323fn function_call(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
324    if args.is_empty() {
325        return JSValue::undefined();
326    }
327    let this_val = &args[0];
328    if !this_val.is_function() {
329        return JSValue::undefined();
330    }
331    let this_arg = if args.len() > 1 {
332        args[1].clone()
333    } else {
334        JSValue::undefined()
335    };
336    let mut call_args = Vec::new();
337    for arg in args.iter().skip(2) {
338        call_args.push(arg.clone());
339    }
340
341    if let Some(ptr) = ctx.get_register_vm_ptr() {
342        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
343        let result = vm.call_function_with_this(ctx, *this_val, this_arg, &call_args);
344        match result {
345            Ok(val) => val,
346            Err(e) => {
347                if ctx.pending_exception.is_none() {
348                    ctx.pending_exception = Some(JSValue::new_string(ctx.intern(&e)));
349                }
350                JSValue::undefined()
351            }
352        }
353    } else {
354        JSValue::undefined()
355    }
356}
357
358fn function_apply(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
359    if args.is_empty() {
360        return JSValue::undefined();
361    }
362    let this_val = &args[0];
363    if !this_val.is_function() {
364        return JSValue::undefined();
365    }
366
367    let this_arg = if args.len() > 1 {
368        args[1].clone()
369    } else {
370        JSValue::undefined()
371    };
372
373    let mut call_args_buf = [JSValue::undefined(); 16];
374    let mut call_args_vec = Vec::new();
375    let call_args: &[JSValue];
376
377    if args.len() > 2 && args[2].is_object() {
378        let arr_obj = args[2].as_object();
379        let length_atom = ctx.common_atoms.length;
380        if let Some(len_val) = arr_obj.get(length_atom) {
381            let len = len_val.get_int() as usize;
382
383            if arr_obj.is_mapped_arguments() {
384                let fi = arr_obj.mapped_args_frame_index();
385                let param_count = arr_obj.mapped_args_param_count() as usize;
386                let vm_ptr = ctx.get_register_vm_ptr();
387                if let Some(ptr) = vm_ptr {
388                    let vm = unsafe { &*(ptr as *const crate::runtime::vm::VM) };
389                    if fi < vm.frames.len() {
390                        let frame = &vm.frames[fi];
391                        let base = frame.registers_base;
392                        let target = if len <= 16 {
393                            &mut call_args_buf[..len]
394                        } else {
395                            call_args_vec.resize(len, JSValue::undefined());
396                            &mut call_args_vec[..len]
397                        };
398                        let saved = &frame.saved_args;
399                        for i in 0..len {
400                            target[i] = if i < param_count {
401                                let reg_idx = base + 1 + i;
402                                if reg_idx < vm.registers.len() {
403                                    vm.registers[reg_idx]
404                                } else {
405                                    JSValue::undefined()
406                                }
407                            } else if i < saved.len() {
408                                saved[i]
409                            } else {
410                                arr_obj.get_indexed(i).unwrap_or(JSValue::undefined())
411                            };
412                        }
413                        call_args = if len <= 16 {
414                            &call_args_buf[..len]
415                        } else {
416                            &call_args_vec[..len]
417                        };
418                    } else {
419                        call_args = &[];
420                    }
421                } else {
422                    call_args = &[];
423                }
424            } else if len <= 16 {
425                if arr_obj.is_array() {
426                    let ptr = arr_obj as *const _ as usize;
427                    if arr_obj.is_dense_array() {
428                        let arr_ptr =
429                            unsafe { &*(ptr as *const crate::object::array_obj::JSArrayObject) };
430                        for i in 0..len {
431                            call_args_buf[i] = arr_ptr.get(i).unwrap_or(JSValue::undefined());
432                        }
433                    } else {
434                        for i in 0..len {
435                            let key = ctx.int_atom_mut(i);
436                            call_args_buf[i] = arr_obj.get(key).unwrap_or(JSValue::undefined());
437                        }
438                    }
439                } else if let Some(slice) = arr_obj.get_dense_slice(len) {
440                    call_args_buf[..len].copy_from_slice(slice);
441                } else {
442                    for i in 0..len {
443                        if let Some(val) = arr_obj.get_indexed(i) {
444                            call_args_buf[i] = val;
445                        } else {
446                            let key = ctx.int_atom_mut(i);
447                            call_args_buf[i] = arr_obj.get(key).unwrap_or(JSValue::undefined());
448                        }
449                    }
450                }
451                call_args = &call_args_buf[..len];
452            } else {
453                if arr_obj.is_array() {
454                    let ptr = arr_obj as *const _ as usize;
455                    if arr_obj.is_dense_array() {
456                        let arr_ptr =
457                            unsafe { &*(ptr as *const crate::object::array_obj::JSArrayObject) };
458                        for i in 0..len {
459                            call_args_vec.push(arr_ptr.get(i).unwrap_or(JSValue::undefined()));
460                        }
461                    } else {
462                        for i in 0..len {
463                            let key = ctx.int_atom_mut(i);
464                            call_args_vec.push(arr_obj.get(key).unwrap_or(JSValue::undefined()));
465                        }
466                    }
467                } else if let Some(slice) = arr_obj.get_dense_slice(len) {
468                    call_args_vec.extend_from_slice(slice);
469                } else {
470                    for i in 0..len {
471                        if let Some(val) = arr_obj.get_indexed(i) {
472                            call_args_vec.push(val);
473                        } else {
474                            let key = ctx.int_atom_mut(i);
475                            if let Some(val) = arr_obj.get(key) {
476                                call_args_vec.push(val);
477                            } else {
478                                call_args_vec.push(JSValue::undefined());
479                            }
480                        }
481                    }
482                }
483                call_args = &call_args_vec;
484            }
485        } else {
486            call_args = &[];
487        }
488    } else {
489        call_args = &[];
490    }
491
492    if let Some(ptr) = ctx.get_register_vm_ptr() {
493        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
494        let result = vm.call_function_with_this(ctx, *this_val, this_arg, call_args);
495        match result {
496            Ok(val) => val,
497            Err(e) => {
498                if ctx.pending_exception.is_none() {
499                    ctx.pending_exception = Some(JSValue::new_string(ctx.intern(&e)));
500                }
501                JSValue::undefined()
502            }
503        }
504    } else {
505        JSValue::undefined()
506    }
507}
508
509fn function_to_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
510    if args.is_empty() {
511        return JSValue::new_string(ctx.intern("function () { [native code] }"));
512    }
513    let this_val = &args[0];
514    if this_val.is_function() {
515        let js_func = this_val.as_function();
516
517        let func_name = ctx.get_atom_str(js_func.name).to_string();
518        let arity = js_func.arity as usize;
519
520        let params: Vec<String> = (0..arity).map(|i| format!("a{}", i)).collect();
521        let param_str = params.join(", ");
522
523        if js_func.is_builtin() {
524            if func_name.is_empty() {
525                JSValue::new_string(
526                    ctx.intern(&format!("function({}) {{ [native code] }}", param_str)),
527                )
528            } else {
529                JSValue::new_string(ctx.intern(&format!(
530                    "function {}({}) {{ [native code] }}",
531                    func_name, param_str
532                )))
533            }
534        } else {
535            let prefix = if js_func.is_async() { "async " } else { "" };
536            let suffix = if js_func.is_generator() { "*" } else { "" };
537            if func_name.is_empty() {
538                JSValue::new_string(ctx.intern(&format!(
539                    "{}function{}({}) {{ [user code] }}",
540                    prefix, suffix, param_str
541                )))
542            } else {
543                JSValue::new_string(ctx.intern(&format!(
544                    "{}function{} {}({}) {{ [user code] }}",
545                    prefix, suffix, func_name, param_str
546                )))
547            }
548        }
549    } else {
550        JSValue::new_string(ctx.intern("[object Function]"))
551    }
552}
553
554fn function_length(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
555    if args.is_empty() {
556        return JSValue::undefined();
557    }
558    let this_val = &args[0];
559    if !this_val.is_function() {
560        return JSValue::undefined();
561    }
562    let js_func = this_val.as_function();
563    JSValue::new_int(js_func.arity as i64)
564}
565
566fn function_name(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
567    if args.is_empty() {
568        return JSValue::undefined();
569    }
570    let this_val = &args[0];
571    if !this_val.is_function() {
572        return JSValue::undefined();
573    }
574    let js_func = this_val.as_function();
575    let name_str = ctx.get_atom_str(js_func.name).to_string();
576    JSValue::new_string(ctx.intern(&name_str))
577}