quickjs_oxide/engine/api/core/
function.rs1use std::rc::{Rc, Weak};
6
7use oxide_core::vm::native::{HostFn, NativeCall};
8use oxide_core::{Intrinsic, NativeCx};
9
10use super::RuntimeError;
11use super::ops::{JsResult, View};
12use super::runtime::Shared;
13use super::value::{CallableRef, Value};
14
15pub struct Scope<'a> {
19 pub(crate) core: NativeCx<'a>,
20 pub(crate) shared: Rc<Shared>,
21 call: NativeCall,
22}
23
24impl Scope<'_> {
25 pub(crate) fn view(&mut self) -> View<'_> {
27 View::new(self.core.reborrow(), &self.shared)
28 }
29
30 fn lift(&mut self, value: oxide_core::Value) -> Value {
31 self.view().lift(value).unwrap_or(Value::Undefined)
34 }
35
36 pub fn this(&mut self) -> Value {
38 let this = self.call.this(&self.core);
39 self.lift(this)
40 }
41
42 pub fn arg(&mut self, index: usize) -> Value {
44 let arg = self.call.arg(&self.core, index);
45 self.lift(arg)
46 }
47
48 pub fn argc(&self) -> usize {
50 self.call.argc()
51 }
52
53 pub fn args(&mut self) -> Vec<Value> {
55 (0..self.argc()).map(|index| self.arg(index)).collect()
56 }
57
58 pub fn new_target(&mut self) -> Value {
60 let target = self.call.new_target();
61 self.lift(target)
62 }
63
64 pub fn is_construct(&self) -> bool {
66 self.call.is_construct()
67 }
68
69 pub fn callee(&mut self) -> CallableRef {
71 let callee = self.call.callee().as_object().expect("a host function");
72 CallableRef(self.view().lift_object(callee))
73 }
74
75 pub fn data(&mut self, slot: usize) -> Value {
78 if slot >= self.call.data_len(&self.core) {
79 return Value::Undefined;
80 }
81 let value = self.call.data(&self.core, slot);
82 self.lift(value)
83 }
84
85 pub fn set_data(&mut self, slot: usize, value: Value) -> JsResult<()> {
87 if slot >= self.call.data_len(&self.core) {
88 return Err(RuntimeError::Invariant("a captured value's slot"));
89 }
90 let call = self.call;
91 let mut view = self.view();
92 let value = view.lower(&value)?;
93 call.set_data(&mut view.core, slot, value);
94 Ok(())
95 }
96
97 pub fn this_host_data<T: 'static>(&mut self) -> JsResult<Option<Rc<T>>> {
100 match self.this() {
101 Value::Object(object) => self.host_data::<T>(&object),
102 _ => Ok(None),
103 }
104 }
105}
106
107#[derive(Clone, Debug, Default)]
111pub struct FunctionOptions {
112 pub name: String,
113 pub length: u32,
114 pub constructor: bool,
115 pub data: Vec<Value>,
116}
117
118impl FunctionOptions {
119 pub fn new(name: impl Into<String>) -> Self {
120 FunctionOptions {
121 name: name.into(),
122 ..FunctionOptions::default()
123 }
124 }
125
126 #[must_use]
127 pub fn length(mut self, length: u32) -> Self {
128 self.length = length;
129 self
130 }
131
132 #[must_use]
135 pub fn constructor(mut self) -> Self {
136 self.constructor = true;
137 self
138 }
139
140 #[must_use]
141 pub fn data(mut self, data: Vec<Value>) -> Self {
142 self.data = data;
143 self
144 }
145}
146
147pub trait HostFunction<Args>: 'static {
150 fn call(&self, scope: &mut Scope<'_>) -> JsResult<Value>;
151}
152
153pub struct Raw;
155
156impl<F> HostFunction<Raw> for F
157where
158 F: Fn(&mut Scope<'_>) -> JsResult<Value> + 'static,
159{
160 fn call(&self, scope: &mut Scope<'_>) -> JsResult<Value> {
161 self(scope)
162 }
163}
164
165struct PoisonGuard<'a>(&'a Shared);
167
168impl Drop for PoisonGuard<'_> {
169 fn drop(&mut self) {
170 if std::thread::panicking() {
171 self.0.poisoned.set(true);
172 }
173 }
174}
175
176pub(crate) fn host_body<Args>(
180 shared: &Rc<Shared>,
181 function: impl HostFunction<Args>,
182) -> Rc<HostFn> {
183 let weak: Weak<Shared> = Rc::downgrade(shared);
184 Rc::new(move |core: &mut NativeCx<'_>, call: NativeCall| {
185 let shared = weak
186 .upgrade()
187 .expect("a host function runs inside its live runtime");
188 let _guard = PoisonGuard(&shared);
189 let mut scope = Scope {
190 core: core.reborrow(),
191 shared: Rc::clone(&shared),
192 call,
193 };
194 let result = function.call(&mut scope);
195 drop(scope);
196 let mut view = View::new(core.reborrow(), &shared);
197 let completion = match result {
198 Ok(value) => view
201 .lower(&value)
202 .map_err(|error| error_throw(&mut view, error)),
203 Err(error) => Err(error_throw(&mut view, error)),
204 };
205 drop(view);
206 shared.drain_released(core);
209 completion
210 })
211}
212
213fn error_throw(view: &mut View<'_>, error: RuntimeError) -> oxide_core::Throw {
215 let thrown = match error {
216 RuntimeError::Exception(value) => view.lower(&value),
217 RuntimeError::Interrupted => view
220 .core
221 .new_error(oxide_core::ErrorKind::Internal, "interrupted")
222 .map_err(RuntimeError::from),
223 other => Err(other),
224 };
225 match thrown {
226 Ok(value) => oxide_core::Throw(value),
227 Err(RuntimeError::OutOfMemory) => out_of_memory(view),
228 Err(other) => {
229 let message = other.to_string();
230 match view
231 .core
232 .new_error(oxide_core::ErrorKind::Internal, &message)
233 {
234 Ok(error) => oxide_core::Throw(error),
235 Err(_) => out_of_memory(view),
236 }
237 }
238 }
239}
240
241fn out_of_memory(view: &View<'_>) -> oxide_core::Throw {
242 let error = view.core.intrinsic(Intrinsic::OutOfMemoryError);
243 oxide_core::Throw(oxide_core::Value::object(error))
244}