use std::rc::{Rc, Weak};
use oxide_core::vm::native::{HostFn, NativeCall};
use oxide_core::{Intrinsic, NativeCx};
use super::RuntimeError;
use super::ops::{JsResult, View};
use super::runtime::Shared;
use super::value::{CallableRef, Value};
pub struct Scope<'a> {
pub(crate) core: NativeCx<'a>,
pub(crate) shared: Rc<Shared>,
call: NativeCall,
}
impl Scope<'_> {
pub(crate) fn view(&mut self) -> View<'_> {
View::new(self.core.reborrow(), &self.shared)
}
fn lift(&mut self, value: oxide_core::Value) -> Value {
self.view().lift(value).unwrap_or(Value::Undefined)
}
pub fn this(&mut self) -> Value {
let this = self.call.this(&self.core);
self.lift(this)
}
pub fn arg(&mut self, index: usize) -> Value {
let arg = self.call.arg(&self.core, index);
self.lift(arg)
}
pub fn argc(&self) -> usize {
self.call.argc()
}
pub fn args(&mut self) -> Vec<Value> {
(0..self.argc()).map(|index| self.arg(index)).collect()
}
pub fn new_target(&mut self) -> Value {
let target = self.call.new_target();
self.lift(target)
}
pub fn is_construct(&self) -> bool {
self.call.is_construct()
}
pub fn callee(&mut self) -> CallableRef {
let callee = self.call.callee().as_object().expect("a host function");
CallableRef(self.view().lift_object(callee))
}
pub fn data(&mut self, slot: usize) -> Value {
if slot >= self.call.data_len(&self.core) {
return Value::Undefined;
}
let value = self.call.data(&self.core, slot);
self.lift(value)
}
pub fn set_data(&mut self, slot: usize, value: Value) -> JsResult<()> {
if slot >= self.call.data_len(&self.core) {
return Err(RuntimeError::Invariant("a captured value's slot"));
}
let call = self.call;
let mut view = self.view();
let value = view.lower(&value)?;
call.set_data(&mut view.core, slot, value);
Ok(())
}
pub fn this_host_data<T: 'static>(&mut self) -> JsResult<Option<Rc<T>>> {
match self.this() {
Value::Object(object) => self.host_data::<T>(&object),
_ => Ok(None),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FunctionOptions {
pub name: String,
pub length: u32,
pub constructor: bool,
pub data: Vec<Value>,
}
impl FunctionOptions {
pub fn new(name: impl Into<String>) -> Self {
FunctionOptions {
name: name.into(),
..FunctionOptions::default()
}
}
#[must_use]
pub fn length(mut self, length: u32) -> Self {
self.length = length;
self
}
#[must_use]
pub fn constructor(mut self) -> Self {
self.constructor = true;
self
}
#[must_use]
pub fn data(mut self, data: Vec<Value>) -> Self {
self.data = data;
self
}
}
pub trait HostFunction<Args>: 'static {
fn call(&self, scope: &mut Scope<'_>) -> JsResult<Value>;
}
pub struct Raw;
impl<F> HostFunction<Raw> for F
where
F: Fn(&mut Scope<'_>) -> JsResult<Value> + 'static,
{
fn call(&self, scope: &mut Scope<'_>) -> JsResult<Value> {
self(scope)
}
}
struct PoisonGuard<'a>(&'a Shared);
impl Drop for PoisonGuard<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
self.0.poisoned.set(true);
}
}
}
pub(crate) fn host_body<Args>(
shared: &Rc<Shared>,
function: impl HostFunction<Args>,
) -> Rc<HostFn> {
let weak: Weak<Shared> = Rc::downgrade(shared);
Rc::new(move |core: &mut NativeCx<'_>, call: NativeCall| {
let shared = weak
.upgrade()
.expect("a host function runs inside its live runtime");
let _guard = PoisonGuard(&shared);
let mut scope = Scope {
core: core.reborrow(),
shared: Rc::clone(&shared),
call,
};
let result = function.call(&mut scope);
drop(scope);
let mut view = View::new(core.reborrow(), &shared);
let completion = match result {
Ok(value) => view
.lower(&value)
.map_err(|error| error_throw(&mut view, error)),
Err(error) => Err(error_throw(&mut view, error)),
};
drop(view);
shared.drain_released(core);
completion
})
}
fn error_throw(view: &mut View<'_>, error: RuntimeError) -> oxide_core::Throw {
let thrown = match error {
RuntimeError::Exception(value) => view.lower(&value),
RuntimeError::Interrupted => view
.core
.new_error(oxide_core::ErrorKind::Internal, "interrupted")
.map_err(RuntimeError::from),
other => Err(other),
};
match thrown {
Ok(value) => oxide_core::Throw(value),
Err(RuntimeError::OutOfMemory) => out_of_memory(view),
Err(other) => {
let message = other.to_string();
match view
.core
.new_error(oxide_core::ErrorKind::Internal, &message)
{
Ok(error) => oxide_core::Throw(error),
Err(_) => out_of_memory(view),
}
}
}
}
fn out_of_memory(view: &View<'_>) -> oxide_core::Throw {
let error = view.core.intrinsic(Intrinsic::OutOfMemoryError);
oxide_core::Throw(oxide_core::Value::object(error))
}