quickjs_oxide/engine/api/core/
convert.rs1use std::any::Any;
5use std::rc::Rc;
6
7use super::RuntimeError;
8use super::function::{HostFunction, Scope};
9use super::ops::{Js, JsResult};
10use super::property::PropertyKey;
11use super::value::{CallableRef, ObjectRef, Value};
12use crate::engine::api::error::NativeErrorKind;
13use crate::engine::value::JsString;
14
15pub trait FromJs: Sized {
19 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self>;
20}
21
22pub trait IntoJs {
24 fn into_js(self, js: &mut impl Js) -> JsResult<Value>;
25}
26
27fn type_error(js: &mut impl Js, message: &str) -> RuntimeError {
28 js.throw_error(NativeErrorKind::Type, message)
29}
30
31fn int32(number: f64) -> i32 {
33 uint32(number) as i32
34}
35
36fn uint32(number: f64) -> u32 {
38 if !number.is_finite() {
39 return 0;
40 }
41 number.trunc().rem_euclid(4_294_967_296.0) as u32
42}
43
44impl FromJs for Value {
45 fn from_js(_: &mut impl Js, value: Value) -> JsResult<Self> {
46 Ok(value)
47 }
48}
49
50impl FromJs for bool {
52 fn from_js(_: &mut impl Js, value: Value) -> JsResult<Self> {
53 Ok(value.to_boolean())
54 }
55}
56
57impl FromJs for f64 {
59 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
60 match value {
61 Value::Int(value) => Ok(f64::from(value)),
62 Value::Float(value) => Ok(value),
63 other => js.to_number(&other),
64 }
65 }
66}
67
68impl FromJs for i32 {
70 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
71 match value {
72 Value::Int(value) => Ok(value),
73 other => f64::from_js(js, other).map(int32),
74 }
75 }
76}
77
78impl FromJs for u32 {
80 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
81 match value {
82 Value::Int(value) => Ok(value as u32),
83 other => f64::from_js(js, other).map(uint32),
84 }
85 }
86}
87
88impl FromJs for JsString {
90 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
91 match value {
92 Value::String(text) => Ok(text),
93 other => js.to_js_string(&other),
94 }
95 }
96}
97
98impl FromJs for String {
100 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
101 JsString::from_js(js, value).map(|text| text.to_utf8_lossy())
102 }
103}
104
105impl<T: FromJs> FromJs for Option<T> {
107 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
108 match value {
109 Value::Undefined | Value::Null => Ok(None),
110 other => T::from_js(js, other).map(Some),
111 }
112 }
113}
114
115impl<T: FromJs> FromJs for Vec<T> {
118 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
119 let Value::Object(object) = value else {
120 return Err(type_error(js, "not an object"));
121 };
122 let key = PropertyKey::from_js_string(JsString::from_static("length"));
123 let length = js.get_property(&object, &key)?;
124 let length = f64::from_js(js, length)?;
125 let length = if length.is_nan() || length <= 0.0 {
127 0.0
128 } else {
129 length.trunc().min(9_007_199_254_740_991.0)
130 };
131 let mut items = Vec::new();
132 let mut index = 0.0;
133 while index < length {
134 let key = if index < f64::from(u32::MAX) {
135 PropertyKey::index(index as u32)
136 } else {
137 PropertyKey::from_js_string(JsString::try_from_utf8(&index.to_string())?)
138 };
139 let item = js.get_property(&object, &key)?;
140 items.push(T::from_js(js, item)?);
141 index += 1.0;
142 }
143 Ok(items)
144 }
145}
146
147impl FromJs for ObjectRef {
149 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
150 match value {
151 Value::Object(object) => Ok(object),
152 _ => Err(type_error(js, "not an object")),
153 }
154 }
155}
156
157impl FromJs for CallableRef {
159 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
160 if let Value::Object(object) = &value
161 && let Some(callable) = js.as_callable(object)?
162 {
163 return Ok(callable);
164 }
165 Err(type_error(js, "not a function"))
166 }
167}
168
169impl<T: Any> FromJs for Rc<T> {
171 fn from_js(js: &mut impl Js, value: Value) -> JsResult<Self> {
172 if let Value::Object(object) = &value
173 && let Some(data) = js.host_data::<T>(object)?
174 {
175 return Ok(data);
176 }
177 let name = std::any::type_name::<T>();
178 let name = name.rsplit("::").next().unwrap_or(name);
179 Err(type_error(js, &format!("{name} object expected")))
180 }
181}
182
183impl IntoJs for Value {
184 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
185 Ok(self)
186 }
187}
188
189impl IntoJs for () {
191 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
192 Ok(Value::Undefined)
193 }
194}
195
196impl IntoJs for bool {
197 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
198 Ok(Value::Bool(self))
199 }
200}
201
202impl IntoJs for i32 {
203 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
204 Ok(Value::Int(self))
205 }
206}
207
208impl IntoJs for u32 {
209 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
210 Ok(Value::number(f64::from(self)))
211 }
212}
213
214impl IntoJs for f64 {
215 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
216 Ok(Value::number(self))
217 }
218}
219
220impl IntoJs for JsString {
221 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
222 Ok(Value::String(self))
223 }
224}
225
226impl IntoJs for String {
227 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
228 Ok(Value::String(JsString::try_from_utf8(&self)?))
229 }
230}
231
232impl IntoJs for &str {
233 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
234 Ok(Value::String(JsString::try_from_utf8(self)?))
235 }
236}
237
238impl IntoJs for ObjectRef {
239 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
240 Ok(Value::Object(self))
241 }
242}
243
244impl IntoJs for CallableRef {
245 fn into_js(self, _: &mut impl Js) -> JsResult<Value> {
246 Ok(Value::from(self))
247 }
248}
249
250impl<T: IntoJs> IntoJs for Option<T> {
252 fn into_js(self, js: &mut impl Js) -> JsResult<Value> {
253 match self {
254 Some(value) => value.into_js(js),
255 None => Ok(Value::Undefined),
256 }
257 }
258}
259
260impl<T: IntoJs> IntoJs for Vec<T> {
262 fn into_js(self, js: &mut impl Js) -> JsResult<Value> {
263 let items = self
264 .into_iter()
265 .map(|item| item.into_js(js))
266 .collect::<JsResult<Vec<_>>>()?;
267 js.new_array_from_values(items).map(Value::Object)
268 }
269}
270
271impl<T: IntoJs> IntoJs for Result<T, RuntimeError> {
273 fn into_js(self, js: &mut impl Js) -> JsResult<Value> {
274 self.and_then(|value| value.into_js(js))
275 }
276}
277
278macro_rules! typed_host_functions {
283 ($(($($arg:ident),+))*) => {$(
284 impl<F, R, $($arg),+> HostFunction<($($arg,)+)> for F
285 where
286 F: Fn(&mut Scope<'_>, $($arg),+) -> JsResult<R> + 'static,
287 $($arg: FromJs,)+
288 R: IntoJs,
289 {
290 #[allow(non_snake_case)]
291 fn call(&self, scope: &mut Scope<'_>) -> JsResult<Value> {
292 let mut index = 0;
293 $(
294 let value = scope.arg(index);
295 let $arg = $arg::from_js(scope, value)?;
296 index += 1;
297 )+
298 let _ = index;
299 self(scope, $($arg),+)?.into_js(scope)
300 }
301 }
302 )*};
303}
304
305typed_host_functions! {
306 (A0)
307 (A0, A1)
308 (A0, A1, A2)
309 (A0, A1, A2, A3)
310 (A0, A1, A2, A3, A4)
311 (A0, A1, A2, A3, A4, A5)
312 (A0, A1, A2, A3, A4, A5, A6)
313 (A0, A1, A2, A3, A4, A5, A6, A7)
314}