use crate::prelude::{Box, Rc, String, ToString, Vec, format, vec};
use crate::ast::{Expression, Statement};
use crate::compiler::Compiler;
use crate::error::JsError;
use crate::gc::Gc;
use crate::interpreter::Interpreter;
use crate::parser::Parser;
use crate::value::{
BoundFunctionData, BytecodeFunction, CheapClone, ExoticObject, Guarded, JsFunction, JsObject,
JsString, JsSymbol, JsValue, NativeFunction, PropertyKey,
};
pub fn function_prototype_fn(
_interp: &mut Interpreter,
_this: JsValue,
_args: &[JsValue],
) -> Result<Guarded, JsError> {
Ok(Guarded::unguarded(JsValue::Undefined))
}
pub fn init_function_prototype(interp: &mut Interpreter) {
let proto = interp.function_prototype.clone();
{
let mut proto_ref = proto.borrow_mut();
let name = interp.intern("");
proto_ref.exotic = ExoticObject::Function(JsFunction::Native(NativeFunction {
name: name.cheap_clone(),
func: function_prototype_fn,
arity: 0,
ffi_id: 0,
}));
let length_key = PropertyKey::String(interp.intern("length"));
let name_key = PropertyKey::String(interp.intern("name"));
proto_ref.set_property(length_key, JsValue::Number(0.0));
proto_ref.set_property(name_key, JsValue::String(name));
}
interp.register_method(&proto, "call", function_call, 1);
interp.register_method(&proto, "apply", function_apply, 2);
interp.register_method(&proto, "bind", function_bind, 1);
let well_known = interp.well_known_symbols;
let has_instance_symbol = JsSymbol::new(
well_known.has_instance,
Some(interp.intern("Symbol.hasInstance")),
);
let has_instance_key = PropertyKey::Symbol(Box::new(has_instance_symbol));
let has_instance_fn =
interp.create_native_function("[Symbol.hasInstance]", function_has_instance, 1);
proto
.borrow_mut()
.set_property(has_instance_key, JsValue::Object(has_instance_fn));
}
fn function_has_instance(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let value = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj) = value else {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
};
let JsValue::Object(func) = this else {
return Err(JsError::type_error(
"Function.prototype[Symbol.hasInstance] called on non-function",
));
};
let prototype_key = PropertyKey::String(interp.intern("prototype"));
let prototype_value = func.borrow().get_property(&prototype_key);
let Some(JsValue::Object(prototype)) = prototype_value else {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
};
let mut current = obj.borrow().prototype.clone();
while let Some(proto) = current {
if proto == prototype {
return Ok(Guarded::unguarded(JsValue::Boolean(true)));
}
current = proto.borrow().prototype.clone();
}
Ok(Guarded::unguarded(JsValue::Boolean(false)))
}
pub fn create_function_constructor(interp: &mut Interpreter) -> Gc<JsObject> {
let constructor = interp.create_native_function("Function", function_constructor_fn, 1);
let proto_key = PropertyKey::String(interp.intern("prototype"));
constructor.borrow_mut().set_property(
proto_key,
JsValue::Object(interp.function_prototype.clone()),
);
let ctor_key = PropertyKey::String(interp.intern("constructor"));
interp
.function_prototype
.borrow_mut()
.set_property(ctor_key, JsValue::Object(constructor.clone()));
constructor
}
fn function_constructor_fn(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let (param_strings, body_string) = if args.is_empty() {
(Vec::new(), String::new())
} else if args.len() == 1 {
let body = args.first().map(js_value_to_string).unwrap_or_default();
(Vec::new(), body)
} else {
let body = args.last().map(js_value_to_string).unwrap_or_default();
let params: Vec<String> = args
.iter()
.take(args.len() - 1)
.map(js_value_to_string)
.collect();
(params, body)
};
let param_source = build_param_string(¶m_strings);
let source = format!(
"(function anonymous({}) {{ {} }})",
param_source, body_string
);
let mut parser = Parser::new(&source, &mut interp.string_dict);
let program = parser
.parse_program()
.map_err(|e| JsError::syntax_error_simple(format!("Invalid function body: {}", e)))?;
let func_expr = extract_function_expression(&program)?;
let chunk = Compiler::compile_function_body_direct(
&func_expr.params,
&func_expr.body.body,
Some(JsString::from("anonymous")),
false, false, )
.map_err(|e| JsError::syntax_error_simple(format!("Failed to compile function: {}", e)))?;
let bc_func = BytecodeFunction {
chunk: Rc::new(chunk),
closure: interp.global_env.clone(),
captured_this: None,
};
let guard = interp.heap.create_guard();
let func_obj = interp.create_bytecode_function(&guard, bc_func);
Ok(Guarded::with_guard(JsValue::Object(func_obj), guard))
}
fn js_value_to_string(value: &JsValue) -> String {
match value {
JsValue::String(s) => s.to_string(),
JsValue::Number(n) => {
if n.is_nan() {
"NaN".to_string()
} else if n.is_infinite() {
if *n > 0.0 {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}
} else {
n.to_string()
}
}
JsValue::Boolean(b) => b.to_string(),
JsValue::Undefined => "undefined".to_string(),
JsValue::Null => "null".to_string(),
JsValue::Object(_) => "[object Object]".to_string(),
JsValue::Symbol(_) => {
"".to_string()
}
}
}
fn build_param_string(param_strings: &[String]) -> String {
param_strings
.iter()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(", ")
}
fn extract_function_expression(
program: &crate::ast::Program,
) -> Result<&crate::ast::FunctionExpression, JsError> {
let stmt = program
.body
.first()
.ok_or_else(|| JsError::syntax_error_simple("Failed to parse function"))?;
let expr_stmt = match stmt {
Statement::Expression(expr_stmt) => expr_stmt,
_ => return Err(JsError::syntax_error_simple("Expected function expression")),
};
let inner = match &*expr_stmt.expression {
Expression::Parenthesized(inner, _) => inner,
Expression::Function(f) => return Ok(f),
_ => return Err(JsError::syntax_error_simple("Expected function expression")),
};
match &**inner {
Expression::Function(f) => Ok(f),
_ => Err(JsError::syntax_error_simple("Expected function expression")),
}
}
pub fn function_call(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let this_arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let call_args: Vec<JsValue> = args.iter().skip(1).cloned().collect();
interp.call_function(this, this_arg, &call_args)
}
pub fn function_apply(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let this_arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let args_array = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let call_args: Vec<JsValue> = match args_array {
JsValue::Object(arr_ref) => {
let arr = arr_ref.borrow();
if let Some(elements) = arr.array_elements() {
elements.to_vec()
} else {
vec![]
}
}
JsValue::Undefined | JsValue::Null => vec![],
_ => {
return Err(JsError::type_error(
"Second argument to apply must be an array",
));
}
};
interp.call_function(this, this_arg, &call_args)
}
pub fn function_bind(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let JsValue::Object(target_fn) = this else {
return Err(JsError::type_error("Bind must be called on a function"));
};
if !target_fn.borrow().is_callable() {
return Err(JsError::type_error("Bind must be called on a function"));
}
let this_arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let bound_args: Vec<JsValue> = args.iter().skip(1).cloned().collect();
let guard = interp.heap.create_guard();
let bound_fn = interp.create_js_function(
&guard,
JsFunction::Bound(Box::new(BoundFunctionData {
target: target_fn,
this_arg: this_arg.clone(),
bound_args: bound_args.clone(),
})),
);
Ok(Guarded::with_guard(JsValue::Object(bound_fn), guard))
}