[oden] Grab my incomplete QuickJS wrapper
This commit is contained in:
parent
aa70df41a3
commit
898b1fe129
114 changed files with 244181 additions and 0 deletions
322
oden-js/Cargo.lock
generated
Normal file
322
oden-js/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 1.0.109",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.146"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oden-js"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"oden-js-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oden-js-sys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
|
||||
dependencies = [
|
||||
"either",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
12
oden-js/Cargo.toml
Normal file
12
oden-js/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "oden-js"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
bitflags = "1"
|
||||
thiserror = "1"
|
||||
oden-js-sys = {path = "../oden-js-sys"}
|
||||
65
oden-js/src/atom.rs
Normal file
65
oden-js/src/atom.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use crate::{ContextRef, Runtime, ValueResult};
|
||||
use oden_js_sys as sys;
|
||||
use std::marker;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub struct AtomRef<'r> {
|
||||
pub(crate) atom: sys::JSAtom,
|
||||
_marker: marker::PhantomData<&'r Runtime>,
|
||||
}
|
||||
|
||||
impl<'r> AtomRef<'r> {
|
||||
pub(crate) fn from_raw(atom: sys::JSAtom, _ctx: &ContextRef<'r>) -> AtomRef<'r> {
|
||||
AtomRef {
|
||||
atom,
|
||||
_marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dup(&self, context: &ContextRef<'r>) -> Atom<'r> {
|
||||
unsafe {
|
||||
sys::JS_DupAtom(context.ctx, self.atom);
|
||||
}
|
||||
Atom::from_raw(self.atom, context)
|
||||
}
|
||||
|
||||
pub fn to_value(&self, context: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
context.check_exception(unsafe { sys::JS_AtomToValue(context.ctx, self.atom) })
|
||||
}
|
||||
|
||||
pub fn to_string_value(&self, context: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
context.check_exception(unsafe { sys::JS_AtomToString(context.ctx, self.atom) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Atom<'r> {
|
||||
atom: AtomRef<'r>,
|
||||
rt: *mut sys::JSRuntime,
|
||||
_phantom: marker::PhantomData<&'r Runtime>,
|
||||
}
|
||||
|
||||
impl<'r> Atom<'r> {
|
||||
pub(crate) fn from_raw(atom: sys::JSAtom, ctx: &ContextRef<'r>) -> Self {
|
||||
Atom {
|
||||
atom: AtomRef::from_raw(atom, ctx),
|
||||
rt: unsafe { sys::JS_GetRuntime(ctx.ctx) },
|
||||
_phantom: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Deref for Atom<'r> {
|
||||
type Target = AtomRef<'r>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.atom
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Drop for Atom<'r> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::JS_FreeAtomRT(self.rt, self.atom.atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
180
oden-js/src/callback.rs
Normal file
180
oden-js/src/callback.rs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
use crate::{Class, ClassID, ContextRef, Error, Runtime, ValueRef, ValueResult};
|
||||
use oden_js_sys as sys;
|
||||
use std::ffi::{c_int, CString};
|
||||
use std::marker;
|
||||
use std::panic::catch_unwind;
|
||||
|
||||
pub trait Callback<'rt>:
|
||||
Fn(&ContextRef<'rt>, &ValueRef<'rt>, &[&ValueRef<'rt>]) -> ValueResult<'rt>
|
||||
{
|
||||
}
|
||||
|
||||
impl<'rt, T> Callback<'rt> for T where
|
||||
T: Fn(&ContextRef<'rt>, &ValueRef<'rt>, &[&ValueRef<'rt>]) -> ValueResult<'rt>
|
||||
{
|
||||
}
|
||||
|
||||
struct CallbackObject<'rt, T: Callback<'rt>> {
|
||||
callback: T,
|
||||
_phantom: marker::PhantomData<&'rt Runtime>,
|
||||
}
|
||||
|
||||
impl<'rt, T: Callback<'rt>> CallbackObject<'rt, T> {
|
||||
fn new(callback: T, context: &ContextRef<'rt>) -> ValueResult<'rt> {
|
||||
let obj = CallbackObject {
|
||||
callback,
|
||||
_phantom: marker::PhantomData,
|
||||
};
|
||||
obj.into_value(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'rt, T: Callback<'rt>> Class for CallbackObject<'rt, T> {
|
||||
fn class_id() -> &'static ClassID {
|
||||
static ID: ClassID = ClassID::new("CallbackObject");
|
||||
&ID
|
||||
}
|
||||
}
|
||||
|
||||
fn throw_string<'rt>(context: &ContextRef<'rt>, message: String) -> sys::JSValue {
|
||||
let ctx = context.ctx;
|
||||
match context.new_string(&message) {
|
||||
Ok(e) => unsafe {
|
||||
// Because context.new_string yields an owned Value, and will
|
||||
// clean it up on the way out, we need to explicitly DupValue a
|
||||
// reference for the `Throw` to own.
|
||||
let err = sys::JS_NewError(ctx);
|
||||
if sys::JS_ValueGetTag(err) == sys::JS_TAG_EXCEPTION {
|
||||
// GIVE UP; this is out of memory anyway things probably went
|
||||
// wrong because of that.
|
||||
return err;
|
||||
}
|
||||
|
||||
sys::JS_DupValue(ctx, e.val); // SetProperty takes ownership.
|
||||
let prop = CString::new("message").unwrap();
|
||||
if sys::JS_SetPropertyStr(ctx, err, prop.as_ptr(), e.val) == -1 {
|
||||
// Also an out of memory but we need to free the error object
|
||||
// on our way out.
|
||||
sys::JS_FreeValue(ctx, err);
|
||||
return sys::JS_MakeException(); // JS_EXCEPTION
|
||||
}
|
||||
|
||||
sys::JS_Throw(ctx, err)
|
||||
},
|
||||
Err(_) => unsafe {
|
||||
sys::JS_Throw(
|
||||
ctx,
|
||||
sys::JS_NewString(ctx, "Errors within errors: embedded nulls in the description of the error that occurred".as_bytes().as_ptr() as *const i8),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn callback_impl<'rt, F>(
|
||||
ctx: *mut sys::JSContext,
|
||||
_this: sys::JSValue,
|
||||
argc: c_int,
|
||||
argv: *mut sys::JSValue,
|
||||
_magic: c_int,
|
||||
data: *mut sys::JSValue,
|
||||
) -> sys::JSValue
|
||||
where
|
||||
F: Callback<'rt>,
|
||||
{
|
||||
let context: ContextRef<'_> = ContextRef::from_raw(ctx);
|
||||
let this: ValueRef = ValueRef::from_raw(_this, &context);
|
||||
|
||||
let mut actual_args = Vec::new();
|
||||
unsafe {
|
||||
for arg in std::slice::from_raw_parts(argv, argc.try_into().unwrap()) {
|
||||
actual_args.push(ValueRef::from_raw(*arg, &context));
|
||||
}
|
||||
}
|
||||
|
||||
let mut args = Vec::new();
|
||||
for aa in &actual_args {
|
||||
args.push(aa);
|
||||
}
|
||||
|
||||
// Grab the callback that we stashed in the value.
|
||||
let data_ref = unsafe { ValueRef::from_raw(*data, &context) };
|
||||
let result = match CallbackObject::<F>::try_from_value(&data_ref) {
|
||||
Ok(closure) => (closure.callback)(&context, &this, &args),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(v) => {
|
||||
// `result` is owned; make sure to dup the value on the way out. Ideally
|
||||
// we would just have a `take` or something on `Value` but I don't quite
|
||||
// know how to implement it correctly.
|
||||
unsafe {
|
||||
let ret = &v.val;
|
||||
sys::JS_DupValue(ctx, *ret);
|
||||
*ret
|
||||
}
|
||||
}
|
||||
Err(Error::Exception(e)) => unsafe {
|
||||
// If we returned `Error::Exception` then we're propagating an
|
||||
// exception through the JS stack, just flip it.
|
||||
let exc = &e.val;
|
||||
sys::JS_DupValue(ctx, *exc);
|
||||
sys::JS_Throw(ctx, *exc)
|
||||
},
|
||||
Err(err) => throw_string(&context, err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn trampoline<'rt, F>(
|
||||
ctx: *mut sys::JSContext,
|
||||
_this: sys::JSValue,
|
||||
argc: c_int,
|
||||
argv: *mut sys::JSValue,
|
||||
_magic: c_int,
|
||||
data: *mut sys::JSValue,
|
||||
) -> sys::JSValue
|
||||
where
|
||||
F: Callback<'rt>,
|
||||
{
|
||||
match catch_unwind(|| callback_impl::<'rt, F>(ctx, _this, argc, argv, _magic, data)) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
let message = if let Some(e) = e.downcast_ref::<&'static str>() {
|
||||
format!("{e}")
|
||||
} else {
|
||||
format!("Unknown error")
|
||||
};
|
||||
|
||||
let context: ContextRef<'_> = ContextRef::from_raw(ctx);
|
||||
throw_string(&context, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an unsafe pointer to the trampoline for a closure. We need a generic
|
||||
/// function here to intuit the type of the closure, otherwise otherwise
|
||||
/// there's nothing we can put in the `<>` for `trampoline`. This also
|
||||
/// ensures that the rust compiler actually generates the trampoline.
|
||||
fn get_trampoline<'rt, F>(_closure: &F) -> sys::JSCFunctionData
|
||||
where
|
||||
F: Callback<'rt>,
|
||||
{
|
||||
Some(trampoline::<F>)
|
||||
}
|
||||
|
||||
/// Construct a new value that wraps a closure.
|
||||
pub(crate) fn new_fn<'rt, F>(ctx: &ContextRef<'rt>, func: F) -> sys::JSValue
|
||||
where
|
||||
F: Callback<'rt>,
|
||||
{
|
||||
let closure = func;
|
||||
let callback = get_trampoline(&closure);
|
||||
let value = CallbackObject::new(closure, ctx).expect("Unable to create");
|
||||
|
||||
unsafe {
|
||||
// Just some silly pointer magic; they want an array but we only have
|
||||
// one value so.
|
||||
let mut val = value.val;
|
||||
sys::JS_NewCFunctionData(ctx.ctx, callback, 0, 0, 1, &mut val)
|
||||
}
|
||||
}
|
||||
309
oden-js/src/class.rs
Normal file
309
oden-js/src/class.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
use crate::{ContextRef, Error, Result, Value, ValueRef, ValueResult};
|
||||
use oden_js_sys as sys;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub struct ClassID {
|
||||
name: &'static str,
|
||||
value: Mutex<Option<sys::JSClassID>>,
|
||||
}
|
||||
|
||||
impl ClassID {
|
||||
pub const fn new(name: &'static str) -> Self {
|
||||
ClassID {
|
||||
name,
|
||||
value: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
pub fn get(&self) -> sys::JSClassID {
|
||||
let mut locked = self.value.lock().unwrap();
|
||||
match *locked {
|
||||
None => {
|
||||
let id = unsafe {
|
||||
let mut id: sys::JSClassID = 0;
|
||||
sys::JS_NewClassID(&mut id);
|
||||
id
|
||||
};
|
||||
*locked = Some(id);
|
||||
id
|
||||
}
|
||||
Some(id) => id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClassID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let id_str = {
|
||||
let locked = self.value.lock().unwrap();
|
||||
match *locked {
|
||||
None => format!("none"),
|
||||
Some(id) => format!("{id}"),
|
||||
}
|
||||
};
|
||||
|
||||
f.debug_struct("ClassID")
|
||||
.field("id", &id_str)
|
||||
.field("name", &self.name)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the `Class` trait on rust types that you want to embed into
|
||||
/// JavaScript values.
|
||||
pub trait Class: Sized {
|
||||
fn class_id() -> &'static ClassID;
|
||||
|
||||
fn into_value<'r>(self, context: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
let class_id = Self::class_id();
|
||||
|
||||
// Check to see if the class is registered with the runtime. If not,
|
||||
// register it.
|
||||
if !context.is_registered_class(class_id) {
|
||||
let class_name = match CString::new(class_id.name()) {
|
||||
Ok(cs) => Ok(cs),
|
||||
Err(_) => Err(Error::UnexpectedNul),
|
||||
}?;
|
||||
|
||||
let class_def = sys::JSClassDef {
|
||||
class_name: class_name.as_ptr(),
|
||||
finalizer: Some(Self::finalizer),
|
||||
gc_mark: if Self::needs_mark() {
|
||||
Some(Self::gc_mark)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
call: None,
|
||||
exotic: std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
if 0 != unsafe {
|
||||
sys::JS_NewClass(sys::JS_GetRuntime(context.ctx), class_id.get(), &class_def)
|
||||
} {
|
||||
return Err(Error::TooManyClasses);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if the class proto is registered. We always register
|
||||
// the prototype as an object, so if quickjs says the prototype is
|
||||
// NULL then we can go ahead and register our own object.
|
||||
unsafe {
|
||||
let pval =
|
||||
Value::from_raw(sys::JS_GetClassProto(context.ctx, class_id.get()), &context);
|
||||
if pval.is_null() {
|
||||
let proto = Self::prototype(context)?;
|
||||
sys::JS_DupValue(context.ctx, proto.val);
|
||||
sys::JS_SetClassProto(context.ctx, class_id.get(), proto.val);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the class is registered and the prototype is registered
|
||||
// we can just ask quickjs to create the object for us, referencing
|
||||
// the existing prototype.
|
||||
let val = context.check_exception(unsafe {
|
||||
sys::JS_NewObjectClass(context.ctx, class_id.get() as i32)
|
||||
})?;
|
||||
|
||||
// Jam the value onto the heap, and thence into the opaque slot of
|
||||
// the JS value. We'll be sure to free the box when the value is
|
||||
// destroyed, because of the finalizer that we register in the class
|
||||
// description above.
|
||||
let b = Box::new(RefCell::new(self));
|
||||
unsafe {
|
||||
sys::JS_SetOpaque(val.val, Box::into_raw(b) as *mut _);
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn try_from_value_mut<'r>(value: &ValueRef<'r>) -> Result<'r, RefMut<'r, Self>> {
|
||||
let class = Self::class_id();
|
||||
|
||||
// SAFETY: value.val is known to be valid, from the ValueRef.
|
||||
let ptr = unsafe { sys::JS_GetOpaque(value.val, class.get()) as *const RefCell<Self> };
|
||||
if ptr.is_null() {
|
||||
return Err(Error::WrongClass(class.name().to_string()));
|
||||
}
|
||||
|
||||
// SAFETY: The pointer will live as long as the value, which we have
|
||||
// a reference to and whose lifetime we propagate into the
|
||||
// reference to Self.
|
||||
Ok(unsafe { ptr.as_ref() }
|
||||
.expect("already checked for null")
|
||||
.borrow_mut())
|
||||
}
|
||||
|
||||
fn from_value_mut<'r>(value: &ValueRef<'r>) -> RefMut<'r, Self> {
|
||||
Self::try_from_value_mut(value).expect("Wrong type for value")
|
||||
}
|
||||
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>) -> Result<'r, Ref<'r, Self>> {
|
||||
let class = Self::class_id();
|
||||
|
||||
// SAFETY: value.val is known to be valid, from the ValueRef.
|
||||
let ptr = unsafe { sys::JS_GetOpaque(value.val, class.get()) as *const RefCell<Self> };
|
||||
if ptr.is_null() {
|
||||
return Err(Error::WrongClass(class.name().to_string()));
|
||||
}
|
||||
|
||||
// SAFETY: The pointer will live as long as the value, which we have
|
||||
// a reference to and whose lifetime we propagate into the
|
||||
// reference to Self.
|
||||
Ok(unsafe { ptr.as_ref() }
|
||||
.expect("already checked for null")
|
||||
.borrow())
|
||||
}
|
||||
|
||||
fn from_value<'r>(value: &ValueRef<'r>) -> Ref<'r, Self> {
|
||||
match Self::try_from_value(value) {
|
||||
Ok(r) => r,
|
||||
Err(_) => panic!(
|
||||
"Expected a {} but got a value {:?}",
|
||||
Self::class_id().name(),
|
||||
value
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn prototype<'r>(context: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
context.new_object()
|
||||
}
|
||||
|
||||
/// If your class contains a Value or ValueRef, `needs_mark` must return
|
||||
/// true and you must implement `mark`. (See the documentation for `mark`
|
||||
/// for more information.)
|
||||
///
|
||||
/// This method returns `true` by default. If you know that you do not
|
||||
/// contain any Value or ValueRefs then you may override this to return
|
||||
/// `false`, as an optimization.
|
||||
fn needs_mark() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// If your class contians a Value or ValueRef then you *must* implement
|
||||
/// this method and return `true` from `needs_mark`. Call the provided
|
||||
/// closure with each `ValueRef` that you store, so that the garbage
|
||||
/// collector won't collect it.
|
||||
fn mark<F>(&self, _mark: F)
|
||||
where
|
||||
F: for<'r> Fn(&ValueRef<'r>) -> (),
|
||||
{
|
||||
// By default, nothing.
|
||||
}
|
||||
|
||||
unsafe extern "C" fn gc_mark(
|
||||
_rt: *mut sys::JSRuntime,
|
||||
val: sys::JSValue,
|
||||
mark_func: sys::JS_MarkFunc,
|
||||
) {
|
||||
let class = Self::class_id();
|
||||
|
||||
// SAFETY: value.val is known to be valid, from the ValueRef.
|
||||
let ptr = unsafe { sys::JS_GetOpaque(val, class.get()) as *const RefCell<Self> };
|
||||
if !ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFETY: The pointer will live as long as the value, which we have
|
||||
// a reference to and whose lifetime we propagate into the
|
||||
// reference to Self.
|
||||
let self_ref = unsafe { ptr.as_ref() }
|
||||
.expect("already checked for null")
|
||||
.borrow();
|
||||
|
||||
self_ref.mark(|v| sys::JS_MarkValue(_rt, v.val, mark_func));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn finalizer(_rt: *mut sys::JSRuntime, val: sys::JSValue) {
|
||||
unsafe {
|
||||
let opaque = sys::JS_GetOpaque(val, Self::class_id().get());
|
||||
if !opaque.is_null() {
|
||||
// Just let the system drop it here.
|
||||
let _ = Box::from_raw(opaque as *mut RefCell<Self>);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Context, Runtime};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct X {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
impl Class for X {
|
||||
fn class_id() -> &'static ClassID {
|
||||
static ID: ClassID = ClassID::new("X");
|
||||
&ID
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Y {
|
||||
y: i64,
|
||||
}
|
||||
|
||||
impl Class for Y {
|
||||
fn class_id() -> &'static ClassID {
|
||||
static ID: ClassID = ClassID::new("Y");
|
||||
&ID
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_ids_distinct() {
|
||||
assert_ne!(X::class_id().get(), Y::class_id().get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let x = X { x: 76 };
|
||||
let val = x.into_value(&ctx).expect("Unable to create value!");
|
||||
|
||||
let r = X::try_from_value(&val).expect("Should be able to get an X back out!");
|
||||
assert_eq!(r.x, 76);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_create() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let x = X { x: 76 };
|
||||
let _v1 = x.into_value(&ctx).expect("Unable to create value!");
|
||||
|
||||
let x = X { x: 76 };
|
||||
let _v2 = x.into_value(&ctx).expect("Unable to create value!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_class_id() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let y = Y { y: 110 };
|
||||
let val = y.into_value(&ctx).expect("Unable to create value!");
|
||||
|
||||
match X::try_from_value(&val) {
|
||||
Err(Error::WrongClass(_)) => (),
|
||||
Err(e) => assert!(false, "Got an error but of the wrong type: {e}"),
|
||||
Ok(_) => assert!(false, "Expected to have an error when I unwrap"),
|
||||
};
|
||||
|
||||
let r = Y::try_from_value(&val).expect("Should be able to get a Y back out!");
|
||||
assert_eq!(r.y, 110);
|
||||
}
|
||||
}
|
||||
488
oden-js/src/context.rs
Normal file
488
oden-js/src/context.rs
Normal file
|
|
@ -0,0 +1,488 @@
|
|||
use crate::{
|
||||
callback::new_fn, conversion::RustFunction, Atom, ClassID, Error, Result, Runtime, Value,
|
||||
ValueRef, ValueResult,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use oden_js_sys as sys;
|
||||
use std::ffi::CString;
|
||||
use std::marker;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Different ways to evaluate JavaScript. See the various `eval` methods on
|
||||
/// `ContextRef`.
|
||||
pub enum EvalType {
|
||||
/// Global code, i.e., just plain old JavaScript running in the most
|
||||
/// boring, traditional context.
|
||||
Global,
|
||||
|
||||
/// Module code, i.e., code running under the context of an `import`, or
|
||||
/// referenced in HTML as `type=module`.
|
||||
Module,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct EvalFlags: u32 {
|
||||
const NONE = 0;
|
||||
|
||||
/// Force evaluation in "strict" mode, as if "use strict" were at the
|
||||
/// top of the file.
|
||||
const STRICT = sys::JS_EVAL_FLAG_STRICT;
|
||||
|
||||
/// Force "strip" mode.
|
||||
const STRIP = sys::JS_EVAL_FLAG_STRIP;
|
||||
|
||||
/// Compile but do not run. The result is a value with a value type
|
||||
/// of `ValueType::Module` or `ValueType::FunctionBytecode`, and
|
||||
/// which can be executed with `value.eval_function`.
|
||||
const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY;
|
||||
|
||||
/// Don't include the stack frames before this eval in the Error()
|
||||
/// backtraces.
|
||||
const BACKTRACE_BARRIER = sys::JS_EVAL_FLAG_BACKTRACE_BARRIER;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContextRef<'rt> {
|
||||
pub(crate) ctx: *mut sys::JSContext,
|
||||
_marker: marker::PhantomData<&'rt Runtime>,
|
||||
}
|
||||
|
||||
impl<'rt> ContextRef<'rt> {
|
||||
pub(crate) fn from_raw(ctx: *mut sys::JSContext) -> Self {
|
||||
ContextRef {
|
||||
ctx,
|
||||
_marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_registered_class(&self, id: &ClassID) -> bool {
|
||||
let id = id.get();
|
||||
let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) };
|
||||
is_registered != 0
|
||||
}
|
||||
|
||||
pub fn add_intrinsic_base_objects(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicBaseObjects(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_date(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicDate(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_eval(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicEval(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_string_normalize(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicStringNormalize(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_regexp_compiler(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicRegExpCompiler(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_regexp(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicRegExp(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_json(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicJSON(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_proxy(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicProxy(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_map_set(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicMapSet(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_typed_arrays(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicTypedArrays(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_promise(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicPromise(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_bigint(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicBigInt(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_bigfloat(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicBigFloat(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_bigdecimal(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicBigDecimal(self.ctx) }
|
||||
}
|
||||
pub fn add_intrinsic_operators(&mut self) {
|
||||
unsafe { sys::JS_AddIntrinsicOperators(self.ctx) }
|
||||
}
|
||||
pub fn enable_bignum_ext(&mut self, enable: bool) {
|
||||
unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) }
|
||||
}
|
||||
|
||||
/// Evaluate the specified JavaScript code.
|
||||
pub fn eval(
|
||||
&self,
|
||||
input: &str,
|
||||
filename: &str,
|
||||
eval_type: EvalType,
|
||||
flags: EvalFlags,
|
||||
) -> ValueResult<'rt> {
|
||||
let c_input = match CString::new(input) {
|
||||
Ok(cs) => Ok(cs),
|
||||
Err(_) => Err(Error::UnexpectedNul),
|
||||
}?;
|
||||
let c_filename = match CString::new(filename) {
|
||||
Ok(cs) => Ok(cs),
|
||||
Err(_) => Err(Error::UnexpectedNul),
|
||||
}?;
|
||||
|
||||
let eval_type = match eval_type {
|
||||
EvalType::Global => sys::JS_EVAL_TYPE_GLOBAL,
|
||||
EvalType::Module => sys::JS_EVAL_TYPE_MODULE,
|
||||
};
|
||||
let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap();
|
||||
|
||||
unsafe {
|
||||
self.check_exception(sys::JS_Eval(
|
||||
self.ctx,
|
||||
c_input.into_raw(),
|
||||
input.len(),
|
||||
c_filename.into_raw(),
|
||||
flags_bits,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new string atom.
|
||||
pub fn new_atom(&self, value: &str) -> Result<'rt, Atom<'rt>> {
|
||||
let c_value = match CString::new(value) {
|
||||
Ok(cs) => Ok(cs),
|
||||
Err(_) => Err(Error::UnexpectedNul),
|
||||
}?;
|
||||
|
||||
let atom = unsafe { sys::JS_NewAtomLen(self.ctx, c_value.into_raw(), value.len()) };
|
||||
if atom == sys::JS_ATOM_NULL {
|
||||
return Err(Error::Exception(self.exception()));
|
||||
}
|
||||
|
||||
Ok(Atom::from_raw(atom, self))
|
||||
}
|
||||
|
||||
/// Construct a new value of type object.
|
||||
pub fn new_object(&self) -> ValueResult<'rt> {
|
||||
self.check_exception(unsafe { sys::JS_NewObject(self.ctx) })
|
||||
}
|
||||
|
||||
/// Construct a new value from a boolean.
|
||||
pub fn new_bool<T>(&self, value: T) -> ValueResult<'rt>
|
||||
where
|
||||
T: Into<bool>,
|
||||
{
|
||||
self.check_exception(unsafe { sys::JS_NewBool(self.ctx, value.into()) })
|
||||
}
|
||||
|
||||
/// Construct a new value that wraps a strongly-typed closure.
|
||||
pub fn new_fn<F>(&self, func: impl RustFunction<'rt, F>) -> ValueResult<'rt> {
|
||||
self.new_dynamic_fn(|c, _, a| func.call(c, a))
|
||||
}
|
||||
|
||||
/// Construct a new value that wraps a dynamically-typed closure.
|
||||
pub fn new_dynamic_fn<F>(&self, func: F) -> ValueResult<'rt>
|
||||
where
|
||||
F: Fn(&ContextRef<'rt>, &ValueRef<'rt>, &[&ValueRef<'rt>]) -> ValueResult<'rt>,
|
||||
{
|
||||
// Constructing a new function is complicated enough that it needs to
|
||||
// be out of line.
|
||||
self.check_exception(new_fn(self, func))
|
||||
}
|
||||
|
||||
/// Construct a new value from an int32.
|
||||
pub fn new_i32<T>(&self, value: T) -> ValueResult<'rt>
|
||||
where
|
||||
T: Into<i32>,
|
||||
{
|
||||
self.check_exception(unsafe { sys::JS_NewInt32(self.ctx, value.into()) })
|
||||
}
|
||||
|
||||
/// Construct a new value from a u32.
|
||||
///
|
||||
/// This returns a value of type Int32 if the value fits into an int32,
|
||||
/// otherwise it returns a value of type Float64.
|
||||
pub fn new_u32<T>(&self, value: T) -> ValueResult<'rt>
|
||||
where
|
||||
T: Into<u32>,
|
||||
{
|
||||
self.check_exception(unsafe { sys::JS_NewUint32(self.ctx, value.into()) })
|
||||
}
|
||||
|
||||
/// Construct a new value from a float64.
|
||||
pub fn new_f64<T>(&self, value: T) -> ValueResult<'rt>
|
||||
where
|
||||
T: Into<f64>,
|
||||
{
|
||||
self.check_exception(unsafe { sys::JS_NewFloat64(self.ctx, value.into()) })
|
||||
}
|
||||
|
||||
/// Construct a new BigInt from an i64
|
||||
pub fn new_i64<T>(&self, value: T) -> ValueResult<'rt>
|
||||
where
|
||||
T: Into<i64>,
|
||||
{
|
||||
self.check_exception(unsafe { sys::JS_NewBigInt64(self.ctx, value.into()) })
|
||||
}
|
||||
|
||||
/// Construct a new BigInt from an u64
|
||||
pub fn new_u64<T>(&self, value: T) -> ValueResult<'rt>
|
||||
where
|
||||
T: Into<u64>,
|
||||
{
|
||||
self.check_exception(unsafe { sys::JS_NewBigUint64(self.ctx, value.into()) })
|
||||
}
|
||||
|
||||
/// Construct a new array value.
|
||||
pub fn new_array(&self) -> ValueResult<'rt> {
|
||||
self.check_exception(unsafe { sys::JS_NewArray(self.ctx) })
|
||||
}
|
||||
|
||||
/// Construct a new value from a string.
|
||||
pub fn new_string(&self, value: &str) -> ValueResult<'rt> {
|
||||
let c_value = match CString::new(value) {
|
||||
Ok(cs) => Ok(cs),
|
||||
Err(_) => Err(Error::UnexpectedNul),
|
||||
}?;
|
||||
|
||||
self.check_exception(unsafe {
|
||||
sys::JS_NewStringLen(self.ctx, c_value.into_raw(), value.len())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the null value.
|
||||
pub fn null(&self) -> Value<'rt> {
|
||||
let v = sys::JSValue {
|
||||
u: sys::JSValueUnion {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
tag: sys::JS_TAG_NULL as i64,
|
||||
};
|
||||
Value::from_raw(v, self)
|
||||
}
|
||||
|
||||
/// Get the undefined value.
|
||||
pub fn undefined(&self) -> Value<'rt> {
|
||||
let v = sys::JSValue {
|
||||
u: sys::JSValueUnion {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
tag: sys::JS_TAG_UNDEFINED as i64,
|
||||
};
|
||||
Value::from_raw(v, self)
|
||||
}
|
||||
|
||||
/// Fetch the global object for the context.
|
||||
pub fn global_object(&self) -> ValueResult<'rt> {
|
||||
self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) })
|
||||
}
|
||||
|
||||
/// Check the value to see if it is the special marker for an exception,
|
||||
/// and if so grab the exception from the context and return it in an
|
||||
/// error value. Otherwise, just return success with the value, wrapped.
|
||||
pub(crate) fn check_exception(&self, val: sys::JSValue) -> ValueResult<'rt> {
|
||||
if unsafe { sys::JS_ValueGetTag(val) } == sys::JS_TAG_EXCEPTION {
|
||||
Err(Error::Exception(self.exception()))
|
||||
} else {
|
||||
Ok(Value::from_raw(val, self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the exception value from the context, if any. This is not
|
||||
/// public because anything that might raise an exception should be
|
||||
/// returning a Result<> instead, to separate the exception flow from the
|
||||
/// value flow.
|
||||
pub(crate) fn exception(&self) -> Value<'rt> {
|
||||
Value::from_raw(unsafe { sys::JS_GetException(self.ctx) }, self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context<'rt> {
|
||||
value: ContextRef<'rt>,
|
||||
}
|
||||
|
||||
impl<'rt> Context<'rt> {
|
||||
/// Construct a new JavaScript context with many of the useful instrinisc
|
||||
/// functions defined. Objects created by this context can be shared
|
||||
/// across contexts belonging to the same runtime.
|
||||
///
|
||||
/// This function behaves as if the following methods have been called:
|
||||
///
|
||||
/// - `add_intrinsic_base_objects`
|
||||
/// - `add_intrinsic_date`
|
||||
/// - `add_intrinsic_eval`
|
||||
/// - `add_intrinsic_string_normalize`
|
||||
/// - `add_intrinsic_regexp`
|
||||
/// - `add_intrinsic_json`
|
||||
/// - `add_intrinsic_proxy`
|
||||
/// - `add_intrinsic_map_set`
|
||||
/// - `add_intrinsic_typed_arrays`
|
||||
/// - `add_intrinsic_promise`
|
||||
/// - `add_intrinsic_bignum`
|
||||
///
|
||||
/// If you don't want those objects, call `new_raw`, and then add the
|
||||
/// intrinsics that you want.
|
||||
pub fn new(runtime: &Runtime) -> Context {
|
||||
let value = unsafe { ContextRef::from_raw(sys::JS_NewContext(runtime.rt)) };
|
||||
Context { value }
|
||||
}
|
||||
|
||||
/// Construct a new JavaScript context without any intrinsic
|
||||
/// objects. Objects created by this context can be shared across
|
||||
/// contexts belonging to the same runtime.
|
||||
///
|
||||
/// You will probably want to call one or more of the `add_intrinsic_`
|
||||
/// functions to add useful types to the runtime.
|
||||
pub fn new_raw(runtime: &Runtime) -> Context {
|
||||
let value = unsafe { ContextRef::from_raw(sys::JS_NewContextRaw(runtime.rt)) };
|
||||
Context { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'rt> Deref for Context<'rt> {
|
||||
type Target = ContextRef<'rt>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'rt> DerefMut for Context<'rt> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'rt> Drop for Context<'rt> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::JS_FreeContext(self.value.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ValueRef, ValueType};
|
||||
|
||||
#[test]
|
||||
fn basic_evaluation() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let val = ctx
|
||||
.eval("1+1", "script", EvalType::Global, EvalFlags::NONE)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(String::from("2"), val.to_string(&ctx).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compiled() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let compiled = ctx
|
||||
.eval("1 + 1", "script", EvalType::Global, EvalFlags::COMPILE_ONLY)
|
||||
.expect("Unable to compile code");
|
||||
let result = compiled
|
||||
.eval_function(&ctx)
|
||||
.expect("Unable to execute compiled function");
|
||||
|
||||
assert_eq!(String::from("2"), result.to_string(&ctx).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_object() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let mut go = ctx.global_object().unwrap();
|
||||
assert_eq!(ValueType::Object, go.value_type());
|
||||
|
||||
let val = ctx.new_i32(12).unwrap();
|
||||
go.set_property(&ctx, "foo", &val).unwrap();
|
||||
|
||||
let result = ctx
|
||||
.eval("foo + 3", "script", EvalType::Global, EvalFlags::NONE)
|
||||
.unwrap();
|
||||
assert_eq!(String::from("15"), result.to_string(&ctx).unwrap());
|
||||
}
|
||||
|
||||
fn cb<'c>(
|
||||
ctx: &ContextRef<'c>,
|
||||
_this: &ValueRef<'c>,
|
||||
args: &[&ValueRef<'c>],
|
||||
) -> ValueResult<'c> {
|
||||
Ok(args[1].dup(ctx))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_objects() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let mut go = ctx.global_object().unwrap();
|
||||
|
||||
let tests = &[
|
||||
(ctx.new_u32(300u32), "val - 2", "298"),
|
||||
(ctx.new_i32(12), "val + 10", "22"),
|
||||
(ctx.new_f64(12.3), "val + .2", "12.5"),
|
||||
(ctx.new_string("asdf"), "val.toUpperCase()", "ASDF"),
|
||||
(ctx.new_bool(true), "val ? 'yes' : 'no'", "yes"),
|
||||
(ctx.new_bool(false), "val ? 'yes' : 'no'", "no"),
|
||||
(ctx.new_i64(-222), "val", "-222"),
|
||||
(ctx.new_u64(222u64), "val", "222"),
|
||||
(ctx.new_array(), "val.length", "0"),
|
||||
(
|
||||
ctx.new_dynamic_fn(|c, _, a: &[&ValueRef]| Ok(a[1].dup(c))),
|
||||
"val(1,2)",
|
||||
"2",
|
||||
),
|
||||
(ctx.new_dynamic_fn(cb), "val(1,2)", "2"),
|
||||
(
|
||||
ctx.new_fn(|_: &ContextRef, x: i32, y: i32| x * y),
|
||||
"val(3,3)",
|
||||
"9",
|
||||
),
|
||||
];
|
||||
|
||||
for (vr, expr, expected) in tests.into_iter() {
|
||||
let val = vr.as_ref().unwrap();
|
||||
go.set_property(&ctx, "val", val).unwrap();
|
||||
|
||||
let result = ctx
|
||||
.eval(expr, "script", EvalType::Global, EvalFlags::NONE)
|
||||
.unwrap();
|
||||
assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_closures() {
|
||||
let rt = Runtime::new();
|
||||
let ctx = Context::new(&rt);
|
||||
|
||||
let return_value = ctx.new_string("unsafe").unwrap();
|
||||
{
|
||||
let mut go = ctx.global_object().unwrap();
|
||||
go.set_property(
|
||||
&ctx,
|
||||
"foo",
|
||||
&ctx.new_dynamic_fn(|c, _, _| Ok(return_value.dup(c)))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let result = ctx
|
||||
.eval(
|
||||
"foo().toUpperCase()",
|
||||
"script",
|
||||
EvalType::Global,
|
||||
EvalFlags::NONE,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap());
|
||||
}
|
||||
}
|
||||
107
oden-js/src/conversion/from.rs
Normal file
107
oden-js/src/conversion/from.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
use crate::{ContextRef, Error, Result, ValueRef};
|
||||
use std::num::TryFromIntError;
|
||||
|
||||
pub trait TryFromValue: Sized {
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self>;
|
||||
}
|
||||
|
||||
impl TryFromValue for u8 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
let v = value.to_u32(&ctx)?;
|
||||
v.try_into()
|
||||
.map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for u16 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
let v = value.to_u32(&ctx)?;
|
||||
v.try_into()
|
||||
.map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for u32 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
value.to_u32(&ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'c,'d> TryFrom<&'c ValueRef<'d>> for u64 {
|
||||
// #[inline]
|
||||
// fn try_from_value<'r>(value: &ValueRef<'r>, ctx:&ContextRef<'r>) -> Result<'r, Self> {
|
||||
// value.to_u64()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl TryFromValue for i8 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
let v = value.to_i32(&ctx)?;
|
||||
v.try_into()
|
||||
.map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for i16 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
let v = value.to_i32(&ctx)?;
|
||||
v.try_into()
|
||||
.map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for i32 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
value.to_i32(&ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for i64 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
value.to_i64(&ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for f32 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
let v = value.to_float64(&ctx)?;
|
||||
Ok(v as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for f64 {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
value.to_float64(&ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for bool {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, _ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
value.to_bool()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue for String {
|
||||
#[inline]
|
||||
fn try_from_value<'r>(value: &ValueRef<'r>, ctx: &ContextRef<'r>) -> Result<'r, Self> {
|
||||
value.to_string(&ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'c, T: Class> TryFrom<&'c ValueRef<'_>> for T {
|
||||
//
|
||||
// #[inline]
|
||||
// fn try_from_value<'r>(value: &ValueRef<'r>, ctx:&ContextRef<'r>) -> Result<'r, Self> {
|
||||
// T::from_value(value)
|
||||
// }
|
||||
// }
|
||||
191
oden-js/src/conversion/function.rs
Normal file
191
oden-js/src/conversion/function.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
use super::{TryFromValue, TryIntoValue};
|
||||
use crate::{ContextRef, Error, ValueRef, ValueResult};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub trait IntoRustFunctionResult<'r> {
|
||||
fn into_res(self, ctx: &ContextRef<'r>) -> ValueResult<'r>;
|
||||
}
|
||||
|
||||
impl<'r, T: TryIntoValue<'r>> IntoRustFunctionResult<'r> for T {
|
||||
fn into_res(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
self.try_into_value(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T: TryIntoValue<'r>, E: std::fmt::Display> IntoRustFunctionResult<'r>
|
||||
for core::result::Result<T, E>
|
||||
{
|
||||
fn into_res(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
match self {
|
||||
Ok(v) => v.try_into_value(ctx),
|
||||
Err(e) => Err(Error::RustFunctionError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RustFunction<'r, F> {
|
||||
fn argument_count() -> usize;
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r>;
|
||||
}
|
||||
|
||||
impl<'r, R, F> RustFunction<'r, PhantomData<(&R, &F)>> for F
|
||||
where
|
||||
R: IntoRustFunctionResult<'r>,
|
||||
F: Fn(&ContextRef<'r>) -> R + Sized,
|
||||
{
|
||||
fn argument_count() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r> {
|
||||
if args.len() != 0 {
|
||||
return Err(Error::ArgumentCountMismatch {
|
||||
expected: Self::argument_count(),
|
||||
received: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let res = self(context);
|
||||
res.into_res(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, R, A, F> RustFunction<'r, PhantomData<(&R, &A, &F)>> for F
|
||||
where
|
||||
R: IntoRustFunctionResult<'r>,
|
||||
A: TryFromValue,
|
||||
F: Fn(&ContextRef<'r>, A) -> R + Sized,
|
||||
{
|
||||
fn argument_count() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r> {
|
||||
if args.len() != Self::argument_count() {
|
||||
return Err(Error::ArgumentCountMismatch {
|
||||
expected: Self::argument_count(),
|
||||
received: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let va = A::try_from_value(args[0], &context)?;
|
||||
let res = self(context, va);
|
||||
res.into_res(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, R, A, B, F> RustFunction<'r, PhantomData<(&R, &A, &B, &F)>> for F
|
||||
where
|
||||
R: IntoRustFunctionResult<'r>,
|
||||
A: TryFromValue,
|
||||
B: TryFromValue,
|
||||
F: Fn(&ContextRef<'r>, A, B) -> R + Sized,
|
||||
{
|
||||
fn argument_count() -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r> {
|
||||
if args.len() != Self::argument_count() {
|
||||
return Err(Error::ArgumentCountMismatch {
|
||||
expected: Self::argument_count(),
|
||||
received: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let va = A::try_from_value(args[0], &context)?;
|
||||
let vb = B::try_from_value(args[1], &context)?;
|
||||
let res = self(context, va, vb);
|
||||
res.into_res(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, R, A, B, C, F> RustFunction<'r, PhantomData<(&R, &A, &B, &C, &F)>> for F
|
||||
where
|
||||
R: IntoRustFunctionResult<'r>,
|
||||
A: TryFromValue,
|
||||
B: TryFromValue,
|
||||
C: TryFromValue,
|
||||
F: Fn(&ContextRef<'r>, A, B, C) -> R + Sized,
|
||||
{
|
||||
fn argument_count() -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r> {
|
||||
if args.len() != Self::argument_count() {
|
||||
return Err(Error::ArgumentCountMismatch {
|
||||
expected: Self::argument_count(),
|
||||
received: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let va = A::try_from_value(args[0], &context)?;
|
||||
let vb = B::try_from_value(args[1], &context)?;
|
||||
let vc = C::try_from_value(args[2], &context)?;
|
||||
let res = self(context, va, vb, vc);
|
||||
res.into_res(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, R, A, B, C, D, F> RustFunction<'r, PhantomData<(&R, &A, &B, &C, &D, &F)>> for F
|
||||
where
|
||||
R: IntoRustFunctionResult<'r>,
|
||||
A: TryFromValue,
|
||||
B: TryFromValue,
|
||||
C: TryFromValue,
|
||||
D: TryFromValue,
|
||||
F: Fn(&ContextRef<'r>, A, B, C, D) -> R + Sized,
|
||||
{
|
||||
fn argument_count() -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r> {
|
||||
if args.len() != Self::argument_count() {
|
||||
return Err(Error::ArgumentCountMismatch {
|
||||
expected: Self::argument_count(),
|
||||
received: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let va = A::try_from_value(args[0], &context)?;
|
||||
let vb = B::try_from_value(args[1], &context)?;
|
||||
let vc = C::try_from_value(args[2], &context)?;
|
||||
let vd = D::try_from_value(args[3], &context)?;
|
||||
let res = self(context, va, vb, vc, vd);
|
||||
res.into_res(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, R, A, B, C, D, E, F> RustFunction<'r, PhantomData<(&R, &A, &B, &C, &D, &E, &F)>> for F
|
||||
where
|
||||
R: IntoRustFunctionResult<'r>,
|
||||
A: TryFromValue,
|
||||
B: TryFromValue,
|
||||
C: TryFromValue,
|
||||
D: TryFromValue,
|
||||
E: TryFromValue,
|
||||
F: Fn(&ContextRef<'r>, A, B, C, D, E) -> R + Sized,
|
||||
{
|
||||
fn argument_count() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
fn call(&self, context: &ContextRef<'r>, args: &[&ValueRef<'r>]) -> ValueResult<'r> {
|
||||
if args.len() != Self::argument_count() {
|
||||
return Err(Error::ArgumentCountMismatch {
|
||||
expected: Self::argument_count(),
|
||||
received: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let va = A::try_from_value(args[0], &context)?;
|
||||
let vb = B::try_from_value(args[1], &context)?;
|
||||
let vc = C::try_from_value(args[2], &context)?;
|
||||
let vd = D::try_from_value(args[3], &context)?;
|
||||
let ve = E::try_from_value(args[4], &context)?;
|
||||
let res = self(context, va, vb, vc, vd, ve);
|
||||
res.into_res(context)
|
||||
}
|
||||
}
|
||||
129
oden-js/src/conversion/into.rs
Normal file
129
oden-js/src/conversion/into.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use crate::{Class, ContextRef, Error, Value, ValueRef, ValueResult};
|
||||
|
||||
pub trait TryIntoValue<'r> {
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r>;
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for u8 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_u64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for u16 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_u64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for u32 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_u64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for u64 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_u64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for i8 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_i32(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for i16 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_i32(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for i32 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_i32(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for i64 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_i64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for f32 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_f64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for f64 {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_f64(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for bool {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_bool(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for &str {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
ctx.new_string(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for &ValueRef<'r> {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
Ok(self.dup(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for Value<'r> {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
Ok(self.dup(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> TryIntoValue<'r> for Error<'r> {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
match self {
|
||||
Error::TooManyClasses => Err(Error::TooManyClasses),
|
||||
Error::WrongClass(c) => Err(Error::WrongClass(c)),
|
||||
Error::UnexpectedNul => Err(Error::UnexpectedNul),
|
||||
Error::DifferentRuntime => Err(Error::DifferentRuntime),
|
||||
Error::InvalidType { expected, found } => Err(Error::InvalidType { expected, found }),
|
||||
Error::ArgumentCountMismatch { expected, received } => {
|
||||
Err(Error::ArgumentCountMismatch { expected, received })
|
||||
}
|
||||
Error::ConversionError(e) => Err(Error::ConversionError(e)),
|
||||
Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
|
||||
Error::Exception(v) => Err(Error::Exception(v.dup(ctx))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T: Class> TryIntoValue<'r> for T {
|
||||
#[inline]
|
||||
fn try_into_value(self, ctx: &ContextRef<'r>) -> ValueResult<'r> {
|
||||
self.into_value(ctx)
|
||||
}
|
||||
}
|
||||
8
oden-js/src/conversion/mod.rs
Normal file
8
oden-js/src/conversion/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
mod into;
|
||||
pub use into::*;
|
||||
|
||||
mod from;
|
||||
pub use from::*;
|
||||
|
||||
mod function;
|
||||
pub use function::*;
|
||||
43
oden-js/src/lib.rs
Normal file
43
oden-js/src/lib.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use thiserror::Error;
|
||||
|
||||
mod atom;
|
||||
mod callback;
|
||||
mod class;
|
||||
mod context;
|
||||
mod conversion;
|
||||
mod runtime;
|
||||
mod value;
|
||||
|
||||
pub use atom::{Atom, AtomRef};
|
||||
pub use class::{Class, ClassID};
|
||||
pub use context::{Context, ContextRef, EvalFlags, EvalType};
|
||||
pub use conversion::*;
|
||||
pub use runtime::Runtime;
|
||||
pub use value::{Value, ValueRef, ValueType};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error<'ctx> {
|
||||
#[error("too many classes have been registered")]
|
||||
TooManyClasses,
|
||||
#[error("the specified value is not an instance of the class {0}")]
|
||||
WrongClass(String),
|
||||
#[error("input script contained an embedded NUL byte")]
|
||||
UnexpectedNul,
|
||||
#[error("the target context is from a different runtime")]
|
||||
DifferentRuntime,
|
||||
#[error("the specified value had the wrong type (expected {expected:?}, found {found:?})")]
|
||||
InvalidType {
|
||||
expected: ValueType,
|
||||
found: ValueType,
|
||||
},
|
||||
#[error("argument count mismatch, expected {expected} but received {received}")]
|
||||
ArgumentCountMismatch { expected: usize, received: usize },
|
||||
#[error("a conversion error occurred: {0}")]
|
||||
ConversionError(String),
|
||||
#[error("an error occurred calling a rust function: {0}")]
|
||||
RustFunctionError(String),
|
||||
#[error("an exception was thrown during evaluation")]
|
||||
Exception(Value<'ctx>),
|
||||
}
|
||||
pub type Result<'ctx, T> = core::result::Result<T, Error<'ctx>>;
|
||||
pub type ValueResult<'ctx> = core::result::Result<Value<'ctx>, Error<'ctx>>;
|
||||
47
oden-js/src/runtime.rs
Normal file
47
oden-js/src/runtime.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use oden_js_sys as sys;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Runtime {
|
||||
pub(crate) rt: *mut sys::JSRuntime,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn new() -> Runtime {
|
||||
let rt = unsafe { sys::JS_NewRuntime() };
|
||||
Runtime { rt }
|
||||
}
|
||||
|
||||
pub fn set_memory_limit(&mut self, limit: usize) {
|
||||
unsafe {
|
||||
sys::JS_SetMemoryLimit(self.rt, limit);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_gc_threshold(&mut self, threshold: usize) {
|
||||
unsafe {
|
||||
sys::JS_SetGCThreshold(self.rt, threshold);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass in 0 to disable the maximum size check.
|
||||
pub fn set_max_stack_size(&mut self, max_stack: usize) {
|
||||
unsafe {
|
||||
sys::JS_SetMaxStackSize(self.rt, max_stack);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_gc(&mut self) {
|
||||
unsafe {
|
||||
sys::JS_RunGC(self.rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Runtime {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::JS_RunGC(self.rt);
|
||||
sys::JS_FreeRuntime(self.rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
471
oden-js/src/value.rs
Normal file
471
oden-js/src/value.rs
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction};
|
||||
use oden_js_sys as sys;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ValueType {
|
||||
BigDecimal,
|
||||
BigInt,
|
||||
BigFloat,
|
||||
Symbol,
|
||||
Module,
|
||||
FunctionBytecode,
|
||||
String,
|
||||
Object,
|
||||
Int,
|
||||
Bool,
|
||||
Null,
|
||||
Undefined,
|
||||
Float64,
|
||||
}
|
||||
|
||||
impl From<i32> for ValueType {
|
||||
fn from(v: i32) -> Self {
|
||||
match v {
|
||||
sys::JS_TAG_BIG_DECIMAL => ValueType::BigDecimal,
|
||||
sys::JS_TAG_BIG_INT => ValueType::BigInt,
|
||||
sys::JS_TAG_BIG_FLOAT => ValueType::BigFloat,
|
||||
sys::JS_TAG_SYMBOL => ValueType::Symbol,
|
||||
sys::JS_TAG_MODULE => ValueType::Module,
|
||||
sys::JS_TAG_FUNCTION_BYTECODE => ValueType::FunctionBytecode,
|
||||
sys::JS_TAG_STRING => ValueType::String,
|
||||
sys::JS_TAG_OBJECT => ValueType::Object,
|
||||
sys::JS_TAG_INT => ValueType::Int,
|
||||
sys::JS_TAG_BOOL => ValueType::Bool,
|
||||
sys::JS_TAG_NULL => ValueType::Null,
|
||||
sys::JS_TAG_UNDEFINED => ValueType::Undefined,
|
||||
// sys::JS_TAG_UNINITIALIZED => ValueType::Uninitialized,
|
||||
// sys::JS_TAG_CATCH_OFFSET => ValueType::CatchOffset,
|
||||
// sys::JS_TAG_EXCEPTION => ValueType::Exception,
|
||||
sys::JS_TAG_FLOAT64 => ValueType::Float64,
|
||||
_ => panic!("Unknown or unsupported value type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A borrowed JavaScript value.
|
||||
///
|
||||
/// Contrast this structure to `Value`, which is the owned equivalent. Mostly
|
||||
/// you will want to create and manipulate `Value`, but you will occasionally
|
||||
/// receive `ValueRef` in calls that originate from JavaScript
|
||||
/// code. (`ValueRef` is to `Value` as `str` is to `String`, if that helps.)
|
||||
pub struct ValueRef<'r> {
|
||||
pub(crate) val: sys::JSValue,
|
||||
_phantom: marker::PhantomData<&'r Runtime>,
|
||||
}
|
||||
|
||||
// pub type Callback<'r, 'ctx> = fn(
|
||||
// ctx: &'ctx Context<'r>,
|
||||
// this_val: &ValueRef<'r, 'ctx>,
|
||||
// argc: usize,
|
||||
// argvc: &[&ValueRef<'r, 'ctx>],
|
||||
// ) -> Value<'r, 'ctx>;
|
||||
|
||||
impl<'r> ValueRef<'r> {
|
||||
/// Wrap a value in a specific context.
|
||||
///
|
||||
/// **WARNING**: The specified value *must* be associated with at least
|
||||
/// the runtime of the specified context, if not the context itself. This
|
||||
/// function makes no attempt to validate this.
|
||||
pub fn from_raw(val: sys::JSValue, _ctx: &ContextRef<'r>) -> Self {
|
||||
ValueRef {
|
||||
val,
|
||||
_phantom: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type of JavaScript value that this is.
|
||||
pub fn value_type(&self) -> ValueType {
|
||||
// SAFETY: Our lifetime guarantees ensure that this value is still live.
|
||||
unsafe { sys::JS_ValueGetTag(self.val) }.into()
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Null.
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.value_type() == ValueType::Null
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::BigDecimal.
|
||||
pub fn is_big_decimal(&self) -> bool {
|
||||
self.value_type() == ValueType::BigDecimal
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::BigInt.
|
||||
pub fn is_big_int(&self) -> bool {
|
||||
self.value_type() == ValueType::BigInt
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::BigFloat.
|
||||
pub fn is_big_float(&self) -> bool {
|
||||
self.value_type() == ValueType::BigFloat
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Symbol.
|
||||
pub fn is_symbol(&self) -> bool {
|
||||
self.value_type() == ValueType::Symbol
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Module.
|
||||
pub fn is_module(&self) -> bool {
|
||||
self.value_type() == ValueType::Module
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::FunctionBytecode.
|
||||
pub fn is_functionbytecode(&self) -> bool {
|
||||
self.value_type() == ValueType::FunctionBytecode
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::String.
|
||||
pub fn is_string(&self) -> bool {
|
||||
self.value_type() == ValueType::String
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Object.
|
||||
///
|
||||
/// NOTE! This also returns true for arrays and functions the like; it
|
||||
/// has all the problems of JavaScript's typeof() operator. I wish I
|
||||
/// could fix the world but I can't.
|
||||
pub fn is_object(&self) -> bool {
|
||||
self.value_type() == ValueType::Object
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Int.
|
||||
pub fn is_int(&self) -> bool {
|
||||
self.value_type() == ValueType::Int
|
||||
}
|
||||
|
||||
pub fn to_i32(&self, ctx: &ContextRef<'r>) -> Result<'r, i32> {
|
||||
unsafe {
|
||||
let mut res: i32 = 0;
|
||||
let ret = sys::JS_ToInt32(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_u32(&self, ctx: &ContextRef<'r>) -> Result<'r, u32> {
|
||||
unsafe {
|
||||
let mut res: u32 = 0;
|
||||
let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_i64(&self, ctx: &ContextRef<'r>) -> Result<'r, i64> {
|
||||
unsafe {
|
||||
let mut res: i64 = 0;
|
||||
let ret = sys::JS_ToInt64(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: tou64.
|
||||
|
||||
/// True if ValueType is ValueType::Bool.
|
||||
pub fn is_bool(&self) -> bool {
|
||||
self.value_type() == ValueType::Bool
|
||||
}
|
||||
|
||||
pub fn to_bool(&self) -> Result<'r, bool> {
|
||||
if self.value_type() == ValueType::Bool {
|
||||
Ok(unsafe { self.val.u.int32 } > 0)
|
||||
} else {
|
||||
Err(Error::InvalidType {
|
||||
expected: ValueType::Bool,
|
||||
found: self.value_type(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Undefined.
|
||||
pub fn is_undefined(&self) -> bool {
|
||||
self.value_type() == ValueType::Undefined
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Float64.
|
||||
pub fn is_float64(&self) -> bool {
|
||||
self.value_type() == ValueType::Float64
|
||||
}
|
||||
|
||||
pub fn to_float64(&self, ctx: &ContextRef<'r>) -> Result<'r, f64> {
|
||||
unsafe {
|
||||
let mut res: f64 = 0.0;
|
||||
let ret = sys::JS_ToFloat64(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a reference to this value so that it outlives this current reference.
|
||||
///
|
||||
/// (This is a very cheap operation: at most it increments a reference count.)
|
||||
pub fn dup(&self, ctx: &ContextRef<'r>) -> Value<'r> {
|
||||
unsafe {
|
||||
sys::JS_DupValue(ctx.ctx, self.val);
|
||||
}
|
||||
Value::from_raw(self.val, ctx)
|
||||
}
|
||||
|
||||
/// Evaluate a previously-compiled function or module. This value must be
|
||||
/// of type `FunctionBytecode` or `Module`, otherwise an error will be
|
||||
/// returned.
|
||||
///
|
||||
/// JavsScript exceptions are are returned as values where `value_type`
|
||||
/// is `ValueType::Exception`.
|
||||
pub fn eval_function(&self, ctx: &ContextRef<'r>) -> Result<'r, Value<'r>> {
|
||||
match self.value_type() {
|
||||
ValueType::Module | ValueType::FunctionBytecode => (),
|
||||
found => {
|
||||
return Err(Error::InvalidType {
|
||||
expected: ValueType::FunctionBytecode,
|
||||
found,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
// NOTE: JS_EvalFunction consumes its value but that's not what
|
||||
// we want here.
|
||||
sys::JS_DupValue(ctx.ctx, self.val);
|
||||
sys::JS_EvalFunction(ctx.ctx, self.val)
|
||||
};
|
||||
Ok(Value::from_raw(result, ctx))
|
||||
}
|
||||
|
||||
pub fn get_property(&self, ctx: &ContextRef<'r>, prop: &str) -> Result<'r, Value<'r>> {
|
||||
let atom = ctx.new_atom(prop)?;
|
||||
self.get_property_atom(ctx, &atom)
|
||||
}
|
||||
|
||||
pub fn get_property_atom(
|
||||
&self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &AtomRef<'r>,
|
||||
) -> Result<'r, Value<'r>> {
|
||||
ctx.check_exception(unsafe { sys::JS_GetProperty(ctx.ctx, self.val, prop.atom) })
|
||||
}
|
||||
|
||||
pub fn set_property(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &str,
|
||||
val: &ValueRef,
|
||||
) -> Result<'r, ()> {
|
||||
// TODO: Consume API
|
||||
let atom = ctx.new_atom(prop)?;
|
||||
self.set_property_atom(ctx, &atom, val)
|
||||
}
|
||||
|
||||
pub fn set_property_atom(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &AtomRef<'r>,
|
||||
val: &ValueRef,
|
||||
) -> Result<'r, ()> {
|
||||
unsafe {
|
||||
sys::JS_DupValue(ctx.ctx, val.val);
|
||||
let result = sys::JS_SetProperty(ctx.ctx, self.val, prop.atom, val.val);
|
||||
if result == -1 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fn<F>(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &str,
|
||||
func: impl RustFunction<'r, F>,
|
||||
) -> Result<'r, ()> {
|
||||
let vr: Value<'r> = ctx.new_fn(func)?;
|
||||
self.set_property(ctx, prop, &vr)
|
||||
}
|
||||
|
||||
pub fn set_dynamic_method<F>(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &str,
|
||||
func: F,
|
||||
) -> Result<'r, ()>
|
||||
where
|
||||
F: Fn(&ContextRef<'r>, &ValueRef<'r>, &[&ValueRef<'r>]) -> Result<'r, Value<'r>>,
|
||||
{
|
||||
let vr: Value<'r> = ctx.new_dynamic_fn(func)?;
|
||||
self.set_property(ctx, prop, &vr)
|
||||
}
|
||||
|
||||
/// Convert this value into a string representation of the same value.
|
||||
pub fn to_string(&self, ctx: &ContextRef<'r>) -> Result<'r, String> {
|
||||
// SAFETY: ctx's life is bound by the lifetime of our Context, and
|
||||
// sys::JS_ToCStringLen2 will return non-null, unless we're out of
|
||||
// memory.
|
||||
let cstr = unsafe {
|
||||
let ptr = sys::JS_ToCStringLen2(ctx.ctx, std::ptr::null_mut(), self.val, 0);
|
||||
if ptr.is_null() {
|
||||
return Err(Error::Exception(ctx.exception()));
|
||||
}
|
||||
CStr::from_ptr(ptr)
|
||||
};
|
||||
|
||||
// NOTE: Javascript only traffics in unicode strings, and QuickJS
|
||||
// ensures that the result here will be UTF-8, so we can safely
|
||||
// "unwrap" as there's no JavaScript value that should cause it to
|
||||
// fail.
|
||||
let result: String = String::from(cstr.to_str().unwrap());
|
||||
|
||||
// SAFETY: ctx's life is bound by the lifetime of our Context, and
|
||||
// cstr just came from the call above.
|
||||
unsafe {
|
||||
sys::JS_FreeCString(ctx.ctx, cstr.as_ptr());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> fmt::Debug for ValueRef<'ctx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Value")
|
||||
.field("v", &self.val)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned Value from JavaScript. Unlike ValueRef, this type stands in for
|
||||
/// values that you construct in Rust code; otherwise they are identical.
|
||||
pub struct Value<'r> {
|
||||
value: ValueRef<'r>,
|
||||
rt: *mut sys::JSRuntime,
|
||||
_phantom: marker::PhantomData<&'r Runtime>,
|
||||
}
|
||||
|
||||
impl<'r> Value<'r> {
|
||||
/// Take ownership of a value in a specific context.
|
||||
///
|
||||
/// **WARNING**: The specified value *must* be associated with at least
|
||||
/// the runtime of the specified context, if not the context itself. This
|
||||
/// function makes no attempt to validate this.
|
||||
pub(crate) fn from_raw(val: sys::JSValue, ctx: &ContextRef<'r>) -> Self {
|
||||
Value {
|
||||
value: ValueRef::from_raw(val, ctx),
|
||||
rt: unsafe { sys::JS_GetRuntime(ctx.ctx) },
|
||||
_phantom: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Deref for Value<'r> {
|
||||
type Target = ValueRef<'r>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> DerefMut for Value<'r> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Drop for Value<'r> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::JS_FreeValueRT(self.rt, self.val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> fmt::Debug for Value<'r> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.value.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Context, EvalFlags, EvalType, Runtime};
|
||||
|
||||
#[test]
|
||||
fn value_type() {
|
||||
let rt = Runtime::new();
|
||||
let mut ctx = Context::new(&rt);
|
||||
ctx.add_intrinsic_bigfloat();
|
||||
ctx.add_intrinsic_bigdecimal();
|
||||
|
||||
let tests = &[
|
||||
("123.333m", ValueType::BigDecimal),
|
||||
("BigInt(9007199254740991)", ValueType::BigInt),
|
||||
("222.23444l", ValueType::BigFloat),
|
||||
("Symbol('hello')", ValueType::Symbol),
|
||||
("\"hello\"", ValueType::String),
|
||||
("(function() { return {'a':123}; })()", ValueType::Object),
|
||||
("123", ValueType::Int),
|
||||
("true", ValueType::Bool),
|
||||
("null", ValueType::Null),
|
||||
("undefined", ValueType::Undefined),
|
||||
("123.45", ValueType::Float64),
|
||||
];
|
||||
|
||||
for (expr, expected) in tests.into_iter() {
|
||||
let val = ctx
|
||||
.eval(expr, "script", EvalType::Global, EvalFlags::STRICT)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(*expected, val.value_type(), "for {}", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: We can happily create and return values, like this:
|
||||
/// ```
|
||||
/// use oden_js::*;
|
||||
/// fn my_test<'a>(ctx: &'a Context<'a>) -> Value<'a> {
|
||||
/// ctx.new_i32(123).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// But our lifetime bounds on values keep them from outliving their runtimes:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oden_js::*;
|
||||
/// fn my_test<'r>() -> oden_js::Value<'r> {
|
||||
/// let rt = Runtime::new();
|
||||
/// let ctx = Context::new(&rt);
|
||||
///
|
||||
/// ctx.new_i32(123).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Contexts are fundamentally transient, though:
|
||||
///
|
||||
/// ```
|
||||
/// use oden_js::*;
|
||||
/// fn my_test<'r>(runtime: &'r Runtime) -> sheetland_js::Value<'r> {
|
||||
/// let ctx = Context::new(runtime);
|
||||
///
|
||||
/// ctx.new_i32(123).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(doctest)]
|
||||
pub struct ValuesObeyLifetimes {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue