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}