#![no_std]
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
#![warn(unused_import_braces)]
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::new_without_default, clippy::new_without_default)
)]
#![cfg_attr(
feature = "cargo-clippy",
warn(
clippy::float_arithmetic,
clippy::mut_mut,
clippy::nonminimal_bool,
clippy::map_unwrap_or,
clippy::print_stdout,
)
)]
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(dead_code)]
#[allow(missing_docs)]
#[allow(clippy::all)]
mod sys {
include!(concat!(env!("OUT_DIR"), "/mjs.rs"));
}
use cstr_core::CStr;
pub use sys::mjs;
use sys::*;
#[derive(Clone)]
pub struct VM {
inner: *mut mjs,
}
pub struct Val {
vm: VM,
inner: mjs_val_t,
}
#[derive(Debug)]
pub enum JSError<'a> {
NonNullTerminatedString,
NotAFunction,
TooManyArgs,
VMError(&'a str),
}
impl<'a> core::fmt::Display for JSError<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:?}", self)
}
}
impl VM {
fn get_error<'a>(&mut self, err: mjs_err_t) -> JSError<'a> {
unsafe {
let msg = mjs_strerror(self.inner, err);
JSError::VMError(CStr::from_ptr(msg).to_str().unwrap())
}
}
pub fn create() -> VM {
VM {
inner: unsafe { mjs_create() },
}
}
pub fn destroy(self) {
unsafe { mjs_destroy(self.inner) }
}
pub fn from_inner(mjs: *mut mjs) -> VM {
VM { inner: mjs }
}
fn get_inner(&self) -> *mut mjs {
self.inner
}
pub fn exec(&mut self, source: &[u8]) -> Result<Val, JSError> {
if !matches!(source.last(), Some(0)) {
return Err(JSError::NonNullTerminatedString);
}
let mut ret: mjs_val_t = 0;
let err = unsafe { mjs_exec(self.inner, source.as_ptr() as _, &mut ret) };
if err != mjs_err_MJS_OK {
Err(self.get_error(err))
} else {
Ok(self.val(ret))
}
}
pub fn global(&mut self) -> Val {
self.val(unsafe { mjs_get_global(self.inner) })
}
pub fn this(&mut self) -> Val {
self.val(unsafe { mjs_get_this(self.inner) })
}
pub fn nargs(&mut self) -> i32 {
unsafe { mjs_nargs(self.inner) }
}
pub fn arg(&mut self, i: i32) -> Option<Val> {
unsafe {
let val = mjs_arg(self.inner, i);
if mjs_is_undefined(val) > 0 {
None
} else {
Some(self.val(val))
}
}
}
pub fn make_undefined(&mut self) -> Val {
self.val(unsafe { mjs_mk_undefined() })
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn make_foreign(&mut self, f: *mut cty::c_void) -> Val {
self.val(unsafe { mjs_mk_foreign(self.inner, f) })
}
pub fn make_number(&mut self, number: f64) -> Val {
self.val(unsafe { mjs_mk_number(self.inner, number) })
}
pub fn make_boolean(&mut self, b: bool) -> Val {
self.val(unsafe { mjs_mk_boolean(self.inner, if b { 1 } else { 0 }) })
}
pub fn make_object(&mut self) -> Val {
self.val(unsafe { mjs_mk_object(self.inner) })
}
pub fn make_string(&mut self, bytes: &'static [u8]) -> Result<Val, JSError> {
let s = unsafe { mjs_mk_string(self.inner, bytes.as_ptr() as _, bytes.len() as _, 0) };
Ok(self.val(s))
}
pub fn make_string_copy(&mut self, bytes: &[u8]) -> Result<Val, JSError> {
let s = unsafe { mjs_mk_string(self.inner, bytes.as_ptr() as _, bytes.len() as _, 1) };
Ok(self.val(s))
}
fn val(&self, val: mjs_val_t) -> Val {
Val {
vm: self.clone(),
inner: val,
}
}
}
impl Val {
pub fn own(&self) {
unsafe {
mjs_own(self.vm.get_inner(), &self.inner as *const mjs_val_t as _);
}
}
pub fn disown(&self) {
unsafe {
mjs_disown(self.vm.get_inner(), &self.inner as *const mjs_val_t as _);
}
}
pub fn is_number(&self) -> bool {
unsafe { mjs_is_number(self.inner) > 0 }
}
pub fn is_object(&self) -> bool {
unsafe { mjs_is_object(self.inner) > 0 }
}
pub fn is_string(&self) -> bool {
unsafe { mjs_is_string(self.inner) > 0 }
}
pub fn is_function(&self) -> bool {
unsafe { mjs_is_function(self.inner) > 0 }
}
pub fn is_foreign(&self) -> bool {
unsafe { mjs_is_foreign(self.inner) > 0 }
}
pub fn as_int(&self) -> Option<i32> {
if self.is_number() {
Some(unsafe { mjs_get_int(self.vm.get_inner(), self.inner) })
} else {
None
}
}
pub fn as_double(&self) -> Option<f64> {
if self.is_number() {
Some(unsafe { mjs_get_double(self.vm.get_inner(), self.inner) })
} else {
None
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
if self.is_string() {
let mut len = 0;
let ptr: *const mjs_val_t = &self.inner;
unsafe {
let ptr = mjs_get_string(self.vm.get_inner(), ptr as _, &mut len);
let slice = core::slice::from_raw_parts(ptr, len as _);
let slice = &*(slice as *const _ as *const [u8]);
Some(slice)
}
} else {
None
}
}
pub fn as_str(&self) -> Option<Result<&str, core::str::Utf8Error>> {
self.as_bytes().map(|b| core::str::from_utf8(b))
}
pub fn as_ptr(&self) -> Option<*const cty::c_void> {
if self.is_foreign() {
Some(unsafe { mjs_get_ptr(self.vm.get_inner(), self.inner) as _ })
} else {
None
}
}
pub fn delete(&self, name: &[u8]) {
unsafe {
mjs_del(
self.vm.get_inner(),
self.inner,
name.as_ptr() as _,
name.len() as _,
)
};
}
pub fn set(&mut self, name: &[u8], val: Val) -> Result<(), JSError> {
let err = unsafe {
mjs_set(
self.vm.get_inner(),
self.inner,
name.as_ptr() as _,
name.len() as _,
val.inner,
)
};
if err != mjs_err_MJS_OK {
Err(self.vm.get_error(err))
} else {
Ok(())
}
}
pub fn get(&self, name: &[u8]) -> Option<Val> {
let val = unsafe {
mjs_get(
self.vm.get_inner(),
self.inner,
name.as_ptr() as _,
name.len() as _,
)
};
if unsafe { mjs_is_undefined(val) } > 0 {
None
} else {
Some(Val {
vm: self.vm.clone(),
inner: val,
})
}
}
pub fn call(&mut self, this: Option<Val>, args: &[&Val]) -> Result<Val, JSError> {
if self.is_function() {
let mut ret: mjs_val_t = 0;
let this = this.unwrap_or_else(|| self.vm.make_undefined());
let mut mjs_args: [mjs_val_t; 8] = [0; 8];
if args.len() > mjs_args.len() {
return Err(JSError::TooManyArgs);
}
for (i, val) in args.iter().enumerate() {
mjs_args[i] = val.inner;
}
let err = unsafe {
mjs_apply(
self.vm.get_inner(),
&mut ret,
self.inner,
this.inner,
args.len() as _,
mjs_args.as_mut_ptr(),
)
};
if err != mjs_err_MJS_OK {
Err(self.vm.get_error(err))
} else {
Ok(Val {
vm: self.vm.clone(),
inner: ret,
})
}
} else {
Err(JSError::NotAFunction)
}
}
}