mjs_sys/
lib.rs

1//! # mJS Rust bindings
2//!
3//! mJS documentation: <https://github.com/cesanta/mjs>
4//!
5//! ## Life time considerations:
6//!
7//! [Val] lifetime hasn't been properly solidified yet.
8//! Calls to [`VM::exec()`] or [`Val::call()`] might make invalidate
9//! some previously acquired values.
10//! Use [`Val::own()`] to make sure a JS value is not garbage collected.
11
12#![no_std]
13#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
14#![warn(unused_import_braces)]
15#![cfg_attr(
16  feature = "cargo-clippy",
17  allow(clippy::new_without_default, clippy::new_without_default)
18)]
19#![cfg_attr(
20  feature = "cargo-clippy",
21  warn(
22    clippy::float_arithmetic,
23    clippy::mut_mut,
24    clippy::nonminimal_bool,
25    clippy::map_unwrap_or,
26    clippy::print_stdout,
27  )
28)]
29
30#[allow(non_upper_case_globals)]
31#[allow(non_camel_case_types)]
32#[allow(non_snake_case)]
33#[allow(dead_code)]
34#[allow(missing_docs)]
35#[allow(clippy::all)]
36mod sys {
37  include!(concat!(env!("OUT_DIR"), "/mjs.rs"));
38}
39
40use cstr_core::CStr;
41// Inner mJS object
42pub use sys::mjs;
43use sys::*;
44
45/// mJS virtual machine
46#[derive(Clone)]
47pub struct VM {
48  inner: *mut mjs,
49}
50
51/// JS value
52pub struct Val {
53  vm: VM,
54  inner: mjs_val_t,
55}
56
57/// Execution error
58#[derive(Debug)]
59pub enum JSError<'a> {
60  /// String does't end with `\0`
61  NonNullTerminatedString,
62  /// Trying to exectute a Val that's not a function
63  NotAFunction,
64  /// Too many arguments
65  TooManyArgs,
66  /// VM execution error
67  VMError(&'a str),
68}
69
70impl<'a> core::fmt::Display for JSError<'a> {
71  fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
72    write!(f, "{:?}", self)
73  }
74}
75
76impl VM {
77  fn get_error<'a>(&mut self, err: mjs_err_t) -> JSError<'a> {
78    unsafe {
79      let msg = mjs_strerror(self.inner, err);
80      JSError::VMError(CStr::from_ptr(msg).to_str().unwrap())
81    }
82  }
83
84  /// Create new VM
85  pub fn create() -> VM {
86    VM {
87      inner: unsafe { mjs_create() },
88    }
89  }
90
91  /// Destroy VM
92  pub fn destroy(self) {
93    unsafe { mjs_destroy(self.inner) }
94  }
95
96  /// Create a VM from an existing instance
97  pub fn from_inner(mjs: *mut mjs) -> VM {
98    VM { inner: mjs }
99  }
100
101  fn get_inner(&self) -> *mut mjs {
102    self.inner
103  }
104
105  /// Execute code. `source` should be null terminated.
106  pub fn exec(&mut self, source: &[u8]) -> Result<Val, JSError> {
107    if !matches!(source.last(), Some(0)) {
108      return Err(JSError::NonNullTerminatedString);
109    }
110    let mut ret: mjs_val_t = 0;
111    let err = unsafe { mjs_exec(self.inner, source.as_ptr() as _, &mut ret) };
112    if err != mjs_err_MJS_OK {
113      Err(self.get_error(err))
114    } else {
115      Ok(self.val(ret))
116    }
117  }
118
119  /// Return the VM global
120  pub fn global(&mut self) -> Val {
121    self.val(unsafe { mjs_get_global(self.inner) })
122  }
123
124  /// Return scope's `this`
125  pub fn this(&mut self) -> Val {
126    self.val(unsafe { mjs_get_this(self.inner) })
127  }
128
129  /// In a function, return the number of arguments.
130  pub fn nargs(&mut self) -> i32 {
131    unsafe { mjs_nargs(self.inner) }
132  }
133
134  /// Return argument `i`
135  pub fn arg(&mut self, i: i32) -> Option<Val> {
136    unsafe {
137      let val = mjs_arg(self.inner, i);
138      if mjs_is_undefined(val) > 0 {
139        None
140      } else {
141        Some(self.val(val))
142      }
143    }
144  }
145
146  /// Create a JS `undefined` value
147  pub fn make_undefined(&mut self) -> Val {
148    self.val(unsafe { mjs_mk_undefined() })
149  }
150
151  /// Create a JS value that wraps a native pointer
152  #[allow(clippy::not_unsafe_ptr_arg_deref)]
153  pub fn make_foreign(&mut self, f: *mut cty::c_void) -> Val {
154    self.val(unsafe { mjs_mk_foreign(self.inner, f) })
155  }
156
157  /// Create a JS number
158  pub fn make_number(&mut self, number: f64) -> Val {
159    self.val(unsafe { mjs_mk_number(self.inner, number) })
160  }
161
162  /// Create a JS bool
163  pub fn make_boolean(&mut self, b: bool) -> Val {
164    self.val(unsafe { mjs_mk_boolean(self.inner, if b { 1 } else { 0 }) })
165  }
166
167  /// Create a JS object
168  pub fn make_object(&mut self) -> Val {
169    self.val(unsafe { mjs_mk_object(self.inner) })
170  }
171
172  /// Create a JS string. Bytes don't need to be null terminated.
173  /// Content of the string **is not** copied.
174  pub fn make_string(&mut self, bytes: &'static [u8]) -> Result<Val, JSError> {
175    let s = unsafe { mjs_mk_string(self.inner, bytes.as_ptr() as _, bytes.len() as _, 0) };
176    Ok(self.val(s))
177  }
178
179  /// Create a JS string. Bytes don't need to be null terminated.
180  /// Content of the string **is** copied.
181  pub fn make_string_copy(&mut self, bytes: &[u8]) -> Result<Val, JSError> {
182    let s = unsafe { mjs_mk_string(self.inner, bytes.as_ptr() as _, bytes.len() as _, 1) };
183    Ok(self.val(s))
184  }
185
186  fn val(&self, val: mjs_val_t) -> Val {
187    Val {
188      vm: self.clone(),
189      inner: val,
190    }
191  }
192}
193
194impl Val {
195  /// Make sure the value don't get garbage collected next time exec or call is
196  /// used
197  pub fn own(&self) {
198    unsafe {
199      mjs_own(self.vm.get_inner(), &self.inner as *const mjs_val_t as _);
200    }
201  }
202
203  /// Disown value
204  pub fn disown(&self) {
205    unsafe {
206      mjs_disown(self.vm.get_inner(), &self.inner as *const mjs_val_t as _);
207    }
208  }
209
210  /// Is value a number
211  pub fn is_number(&self) -> bool {
212    unsafe { mjs_is_number(self.inner) > 0 }
213  }
214
215  /// Is value an object
216  pub fn is_object(&self) -> bool {
217    unsafe { mjs_is_object(self.inner) > 0 }
218  }
219
220  /// Is value a string
221  pub fn is_string(&self) -> bool {
222    unsafe { mjs_is_string(self.inner) > 0 }
223  }
224
225  /// Is value a function
226  pub fn is_function(&self) -> bool {
227    unsafe { mjs_is_function(self.inner) > 0 }
228  }
229
230  /// Is value a wrapper pointer
231  pub fn is_foreign(&self) -> bool {
232    unsafe { mjs_is_foreign(self.inner) > 0 }
233  }
234
235  /// Get number as int
236  pub fn as_int(&self) -> Option<i32> {
237    if self.is_number() {
238      Some(unsafe { mjs_get_int(self.vm.get_inner(), self.inner) })
239    } else {
240      None
241    }
242  }
243
244  /// Get number as double
245  pub fn as_double(&self) -> Option<f64> {
246    if self.is_number() {
247      Some(unsafe { mjs_get_double(self.vm.get_inner(), self.inner) })
248    } else {
249      None
250    }
251  }
252
253  /// Get string as bytes
254  pub fn as_bytes(&self) -> Option<&[u8]> {
255    if self.is_string() {
256      let mut len = 0;
257      let ptr: *const mjs_val_t = &self.inner;
258      unsafe {
259        let ptr = mjs_get_string(self.vm.get_inner(), ptr as _, &mut len);
260        let slice = core::slice::from_raw_parts(ptr, len as _);
261        let slice = &*(slice as *const _ as *const [u8]);
262        Some(slice)
263      }
264    } else {
265      None
266    }
267  }
268
269  /// Get string as str
270  pub fn as_str(&self) -> Option<Result<&str, core::str::Utf8Error>> {
271    self.as_bytes().map(|b| core::str::from_utf8(b))
272  }
273
274  /// Get wrapper pointer
275  pub fn as_ptr(&self) -> Option<*const cty::c_void> {
276    if self.is_foreign() {
277      Some(unsafe { mjs_get_ptr(self.vm.get_inner(), self.inner) as _ })
278    } else {
279      None
280    }
281  }
282
283  /// Delete property
284  pub fn delete(&self, name: &[u8]) {
285    unsafe {
286      mjs_del(
287        self.vm.get_inner(),
288        self.inner,
289        name.as_ptr() as _,
290        name.len() as _,
291      )
292    };
293  }
294
295  /// Set property
296  pub fn set(&mut self, name: &[u8], val: Val) -> Result<(), JSError> {
297    let err = unsafe {
298      mjs_set(
299        self.vm.get_inner(),
300        self.inner,
301        name.as_ptr() as _,
302        name.len() as _,
303        val.inner,
304      )
305    };
306    if err != mjs_err_MJS_OK {
307      Err(self.vm.get_error(err))
308    } else {
309      Ok(())
310    }
311  }
312
313  /// Get property
314  pub fn get(&self, name: &[u8]) -> Option<Val> {
315    let val = unsafe {
316      mjs_get(
317        self.vm.get_inner(),
318        self.inner,
319        name.as_ptr() as _,
320        name.len() as _,
321      )
322    };
323    if unsafe { mjs_is_undefined(val) } > 0 {
324      None
325    } else {
326      Some(Val {
327        vm: self.vm.clone(),
328        inner: val,
329      })
330    }
331  }
332
333  /// Execute function
334  pub fn call(&mut self, this: Option<Val>, args: &[&Val]) -> Result<Val, JSError> {
335    if self.is_function() {
336      let mut ret: mjs_val_t = 0;
337
338      let this = this.unwrap_or_else(|| self.vm.make_undefined());
339
340      let mut mjs_args: [mjs_val_t; 8] = [0; 8];
341
342      if args.len() > mjs_args.len() {
343        return Err(JSError::TooManyArgs);
344      }
345
346      for (i, val) in args.iter().enumerate() {
347        mjs_args[i] = val.inner;
348      }
349
350      let err = unsafe {
351        mjs_apply(
352          self.vm.get_inner(),
353          &mut ret,
354          self.inner,
355          this.inner,
356          args.len() as _,
357          mjs_args.as_mut_ptr(),
358        )
359      };
360
361      if err != mjs_err_MJS_OK {
362        Err(self.vm.get_error(err))
363      } else {
364        Ok(Val {
365          vm: self.vm.clone(),
366          inner: ret,
367        })
368      }
369    } else {
370      Err(JSError::NotAFunction)
371    }
372  }
373}