use super::{
Native, define_name_length, getter, install_constructor, link_constructor, methods,
string_text, symbol_method,
};
use crate::heap::HeapError;
use crate::heap::object::{InternalTag, ObjectKind, flags};
use crate::runtime::{Intrinsic, Names};
use crate::shape::PropertyFlags;
use crate::value::Value;
use crate::vm::native::CallSite;
use crate::vm::object as internal;
use crate::vm::{Cx, Throw, arguments, error, ops};
pub(super) fn install(cx: &mut Cx<'_>) -> Result<(), HeapError> {
let prototype = cx.realm.intrinsic(Intrinsic::FunctionPrototype);
let names = cx.realm.names;
define_name_length(cx.heap, &names, prototype, Value::string(names.empty), 0)?;
let thrower = super::native_function(cx, Native::ThrowTypeError)?;
for key in [names.length, names.name] {
cx.heap.set_property_flags(
thrower,
Names::key(key),
PropertyFlags::data(false, false, false),
)?;
}
cx.heap.clear_object_flag(thrower, flags::EXTENSIBLE);
cx.set_intrinsic(Intrinsic::ThrowTypeError, thrower);
for name in [names.caller, names.arguments] {
cx.heap.add_accessor(
prototype,
Names::key(name),
PropertyFlags::accessor(false, true),
Value::object(thrower),
Value::object(thrower),
)?;
}
methods(
cx,
prototype,
&[
Native::FunctionCall,
Native::FunctionApply,
Native::FunctionBind,
Native::FunctionToString,
],
)?;
let has_instance = cx.realm.symbols.has_instance;
symbol_method(
cx,
prototype,
has_instance,
Native::FunctionHasInstance,
PropertyFlags::data(false, false, false),
)?;
getter(cx, prototype, "fileName", Native::FunctionFileName)?;
getter(cx, prototype, "lineNumber", Native::FunctionLineNumber)?;
getter(cx, prototype, "columnNumber", Native::FunctionColumnNumber)?;
let function = install_constructor(cx, Native::FunctionConstructor)?;
cx.set_intrinsic(Intrinsic::Function, function);
link_constructor(cx, function, prototype)
}
fn debug_unit(cx: &Cx<'_>, site: &CallSite) -> Option<crate::bytecode::CodeUnitId> {
let object = site.this(cx).as_object()?;
if cx.heap.object_kind(object) != crate::ObjectKind::Function {
return None;
}
let bytecode = cx.heap.function_bytecode(object)?;
let unit = cx.heap.bytecode_unit(bytecode);
cx.unit(unit).debug.filename.is_some().then_some(unit)
}
pub(super) fn file_name(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let Some(unit) = debug_unit(cx, &site) else {
return Ok(Value::UNDEFINED);
};
let file = cx.unit(unit).debug.filename.clone().unwrap_or_default();
string_text(cx, &file)
}
pub(super) fn line_number(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
Ok(debug_unit(cx, &site)
.and_then(|unit| cx.unit(unit).debug.definition)
.map_or(Value::UNDEFINED, |(line, _)| Value::int32(line as i32)))
}
pub(super) fn column_number(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
Ok(debug_unit(cx, &site)
.and_then(|unit| cx.unit(unit).debug.definition)
.map_or(Value::UNDEFINED, |(_, column)| Value::int32(column as i32)))
}
pub(super) fn prototype(_: &mut Cx<'_>, _: CallSite) -> Result<Value, Throw> {
Ok(Value::UNDEFINED)
}
pub(super) fn throw_type_error(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
use crate::bytecode::flags as unit;
let this = site.this(cx);
let sloppy = this.as_object().is_some_and(|object| {
cx.heap.object_kind(object) == crate::ObjectKind::Function
&& cx.heap.function_bytecode(object).is_some_and(|bytecode| {
let unit_id = cx.heap.bytecode_unit(bytecode);
let code = cx.unit(unit_id);
!code.has(unit::STRICT)
&& code.has(unit::PROTOTYPE)
&& !code.has(unit::GENERATOR)
&& !code.has(unit::ASYNC)
})
});
if sloppy && site.argc == 0 {
return Ok(Value::UNDEFINED);
}
Err(error::type_error(cx, "invalid property access"))
}
pub(super) fn constructor(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
crate::vm::compile::function_constructor(cx, site, crate::vm::compile::FunctionKind::Normal)
}
pub(super) fn to_string(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let this = site.this(cx);
if !ops::is_callable(cx.heap, this) {
return Err(error::type_error(cx, "not a function"));
}
let object = this.as_object().unwrap();
if let Some(text) = crate::vm::compile::function_source(cx, object) {
return super::string_units(cx, &text);
}
let prefix = match crate::vm::compile::function_kind_flags(cx, object) {
Some(flags) => {
use crate::bytecode::flags as unit;
match (flags & unit::ASYNC != 0, flags & unit::GENERATOR != 0) {
(false, false) => "function ",
(false, true) => "function *",
(true, false) => "async function ",
(true, true) => "async function *",
}
}
None => "function ",
};
let mut text: Vec<u16> = prefix.encode_utf16().collect();
if cx.heap.object_kind(object) != crate::ObjectKind::Proxy {
let name = internal::get_own_property(cx, object, Names::key(cx.realm.names.name))?
.and_then(|descriptor| descriptor.value)
.and_then(|value| value.as_string());
if let Some(name) = name {
let units = cx.heap.string(name).repr().units().collect::<Vec<u16>>();
if is_native_function_name(&units) {
text.extend(units);
}
}
}
text.extend("() {\n [native code]\n}".encode_utf16());
super::string_units(cx, &text)
}
fn is_native_function_name(units: &[u16]) -> bool {
let name = units
.strip_prefix(&[
u16::from(b'g'),
u16::from(b'e'),
u16::from(b't'),
u16::from(b' '),
])
.or_else(|| {
units.strip_prefix(&[
u16::from(b's'),
u16::from(b'e'),
u16::from(b't'),
u16::from(b' '),
])
})
.unwrap_or(units);
let symbol: Vec<u16> = "[Symbol.".encode_utf16().collect();
match name.strip_prefix(symbol.as_slice()) {
Some(rest) => rest
.strip_suffix(&[u16::from(b']')])
.is_some_and(is_identifier_name),
None => is_identifier_name(name),
}
}
fn is_identifier_name(units: &[u16]) -> bool {
let mut code_points = char::decode_utf16(units.iter().copied());
let start = |c: char| c == '$' || c == '_' || oxide_regexp::unicode::is_id_start(c as u32);
match code_points.next() {
Some(Ok(first)) if start(first) => code_points.all(|c| {
c.is_ok_and(|c| {
start(c)
|| c == '\u{200c}'
|| c == '\u{200d}'
|| oxide_regexp::unicode::is_id_continue(c as u32)
})
}),
_ => false,
}
}
pub(super) fn call(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let function = site.this(cx);
let this = site.arg(cx, 0);
let args: Vec<Value> = (1..site.argc).map(|index| site.arg(cx, index)).collect();
if !ops::is_callable(cx.heap, function) {
return Err(error::type_error(cx, "not a function"));
}
crate::vm::call(cx, function, this, &args)
}
pub(super) fn apply(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let function = site.this(cx);
let this = site.arg(cx, 0);
let list = site.arg(cx, 1);
if !ops::is_callable(cx.heap, function) {
return Err(error::type_error(cx, "not a function"));
}
if list.is_undefined() || list.is_null() {
return crate::vm::call(cx, function, this, &[]);
}
let mark = cx.heap.roots().mark();
let result = arguments::list_from_array_like(cx, list).and_then(|start| {
let args = cx.heap.roots().values()[start..].to_vec();
crate::vm::call(cx, function, this, &args)
});
cx.heap.roots().truncate(mark);
result
}
pub(super) fn bind(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let target = site.this(cx);
if !ops::is_callable(cx.heap, target) {
return Err(error::type_error(cx, "not a function"));
}
let target_object = target.as_object().unwrap();
let prototype = internal::get_prototype_of(cx, target_object)?;
cx.heap.roots().push(prototype);
let mut slots = vec![target, site.arg(cx, 0)];
slots.extend((1..site.argc).map(|index| site.arg(cx, index)));
let callable = cx.heap.header(target_object).flags & (flags::CALLABLE | flags::CONSTRUCTOR);
let bound = cx
.heap
.alloc_internal(
ObjectKind::BoundFunction,
InternalTag::None,
prototype.as_object(),
2,
&slots,
Default::default(),
callable,
)
.map_err(|error| error::heap_error(cx, error))?;
cx.heap.roots().push(Value::object(bound));
let names = cx.realm.names;
let mut length = 0.0;
let length_key = Names::key(names.length);
if internal::has_own_property(cx, target_object, length_key)? {
let target_length = internal::get_property(cx, target_object, length_key)?;
if let Some(number) = target_length.as_f64() {
if number == f64::INFINITY {
length = f64::INFINITY;
} else if number.is_finite() {
let integer = number.trunc();
length = (integer - site.argc.saturating_sub(1) as f64).max(0.0);
}
}
}
let name = internal::get_property(cx, target_object, Names::key(names.name))?;
let mut text: Vec<u16> = "bound ".encode_utf16().collect();
if let Some(name) = name.as_string() {
text.extend(cx.heap.string(name).repr().units());
}
let name = super::string_units(cx, &text)?;
cx.heap.roots().push(name);
let property = PropertyFlags::data(false, false, true);
let heap = &mut *cx.heap;
let length_value = crate::vm::number::number(length);
heap.add_property(bound, length_key, property, length_value)
.and_then(|()| heap.add_property(bound, Names::key(names.name), property, name))
.map_err(|error| error::heap_error(cx, error))?;
Ok(Value::object(bound))
}
pub(super) fn has_instance(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let function = site.this(cx);
let value = site.arg(cx, 0);
internal::ordinary_has_instance(cx, function, value).map(Value::bool)
}