use std::ffi::CString;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use failure::{Error, ResultExt};
use foreign_types::ForeignTypeRef;
use crate::{ffi, Context, ContextRef, ExtractValue, Local, Runtime, Value};
bitflags! {
pub struct Eval: u32 {
const GLOBAL = ffi::JS_EVAL_TYPE_GLOBAL;
const MODULE = ffi::JS_EVAL_TYPE_MODULE;
const DIRECT = ffi::JS_EVAL_TYPE_DIRECT;
const INDIRECT = ffi::JS_EVAL_TYPE_INDIRECT;
const STRICT = ffi::JS_EVAL_FLAG_STRICT;
const STRIP = ffi::JS_EVAL_FLAG_STRIP;
const COMPILE_ONLY = ffi::JS_EVAL_FLAG_COMPILE_ONLY;
}
}
bitflags! {
pub struct EvalBinary: u32 {
const LOAD_ONLY = ffi::JS_EVAL_BINARY_LOAD_ONLY;
}
}
pub trait Source: Sized {
type Flags;
fn default_flags() -> Self::Flags;
fn eval(self, ctxt: &'_ ContextRef, flags: Self::Flags) -> Result<Local<'_, Value>, Error>;
}
impl Source for &str {
type Flags = Eval;
fn default_flags() -> Self::Flags {
Eval::GLOBAL
}
fn eval(self, ctxt: &'_ ContextRef, flags: Self::Flags) -> Result<Local<'_, Value>, Error> {
ctxt.eval_script(self, "<evalScript>", flags)
}
}
impl Source for &Path {
type Flags = Eval;
fn default_flags() -> Self::Flags {
Eval::GLOBAL
}
fn eval(self, ctxt: &'_ ContextRef, flags: Self::Flags) -> Result<Local<'_, Value>, Error> {
ctxt.eval_file(self, flags)
}
}
impl Source for &[u8] {
type Flags = EvalBinary;
fn default_flags() -> Self::Flags {
EvalBinary::empty()
}
fn eval(self, ctxt: &'_ ContextRef, flags: Self::Flags) -> Result<Local<'_, Value>, Error> {
ctxt.eval_binary(self, flags)
}
}
pub fn eval<T: Source, V: ExtractValue>(source: T) -> Result<Option<V>, Error> {
let rt = Runtime::new();
let ctxt = Context::new(&rt);
rt.set_module_loader::<()>(None, Some(ffi::js_module_loader), None);
ctxt.std_add_helpers::<_, String>(None)?;
ctxt.init_module_std()?;
ctxt.init_module_os()?;
if cfg!(feature = "qjscalc") {
ctxt.eval_binary(&*ffi::QJSCALC, EvalBinary::empty())?;
}
let res = source.eval(&ctxt, T::default_flags()).map(|v| {
if v.is_undefined() {
None
} else {
V::extract_value(&v)
}
});
rt.std_free_handlers();
res
}
impl ContextRef {
pub fn eval<T: Source, V: ExtractValue>(
&self,
source: T,
flags: T::Flags,
) -> Result<Option<V>, Error> {
source.eval(self, flags).map(|v| {
if v.is_undefined() {
None
} else {
V::extract_value(&v)
}
})
}
pub fn eval_script<T: Into<Vec<u8>>>(
&self,
input: T,
filename: &str,
flags: Eval,
) -> Result<Local<Value>, Error> {
let input = CString::new(input).context("input")?;
trace!(
"eval `{}` {:?}: {}",
filename,
flags,
input.to_string_lossy()
);
let input = input.to_bytes_with_nul();
let filename = CString::new(filename).context("filename")?;
self.bind(unsafe {
ffi::JS_Eval(
self.as_ptr(),
input.as_ptr() as *const _,
input.len() - 1,
filename.as_ptr() as *const _,
flags.bits as i32,
)
})
.ok()
}
pub fn eval_file<P: AsRef<Path>>(&self, path: P, flags: Eval) -> Result<Local<Value>, Error> {
let filename = path.as_ref().to_string_lossy().to_string();
self.load_file(path)
.and_then(|s| self.eval_script(s, &filename, flags))
}
fn load_file<P: AsRef<Path>>(&self, path: P) -> Result<String, Error> {
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
pub fn eval_binary(&self, buf: &[u8], flags: EvalBinary) -> Result<Local<Value>, Error> {
trace!("eval {} bytes binary {:?}", buf.len(), flags,);
self.bind(unsafe {
ffi::JS_EvalBinary(self.as_ptr(), buf.as_ptr(), buf.len(), flags.bits as i32)
})
.ok()
}
pub fn parse_json<T: Into<Vec<u8>>>(
&self,
input: T,
filename: &str,
) -> Result<Local<Value>, Error> {
let input = CString::new(input)?;
let input = input.to_bytes_with_nul();
let filename = CString::new(filename)?;
self.bind(unsafe {
ffi::JS_ParseJSON(
self.as_ptr(),
input.as_ptr() as *const _,
input.len(),
filename.as_ptr(),
)
})
.ok()
}
}
#[cfg(test)]
mod tests {
use crate::{value::JS_TAG_INT, Context, ErrorKind, Runtime};
use super::*;
#[test]
fn context() {
let _ = pretty_env_logger::try_init();
let rt = Runtime::new();
let ctxt = Context::new(&rt);
assert_eq!(ctxt.runtime(), &rt);
let res = ctxt
.eval_script("1+2", "<evalScript>", Eval::GLOBAL)
.unwrap();
assert_eq!(res.tag(), JS_TAG_INT);
assert!(res.is_number());
assert_eq!(res.as_int().unwrap(), 3);
assert_eq!(
ctxt.eval::<_, ()>("foobar", Eval::GLOBAL)
.unwrap_err()
.downcast::<ErrorKind>()
.unwrap(),
ErrorKind::ReferenceError(
"foobar is not defined".into(),
Some(" at <eval> (<evalScript>)\n".into())
)
);
}
#[test]
fn str() {
assert_eq!(eval::<_, i32>("1+2").unwrap(), Some(3));
}
#[test]
fn file() {
let mut f = tempfile::NamedTempFile::new().unwrap();
write!(&mut f, "Float.PI").unwrap();
let path = f.into_temp_path();
let path: &Path = path.as_ref();
assert!((eval::<_, f64>(path).unwrap().unwrap() - 3.14).abs() < 0.01);
}
#[test]
fn binary() {
assert_eq!(eval::<_, ()>(*ffi::REPL).unwrap(), None);
}
#[test]
fn error() {
assert_eq!(
eval::<_, i32>("throw new Error('Whoops!')")
.unwrap_err()
.downcast::<ErrorKind>()
.unwrap(),
ErrorKind::Error(
"Whoops!".into(),
Some(" at <eval> (<evalScript>)\n".into())
)
);
}
#[test]
fn parse_json() {
let _ = pretty_env_logger::try_init();
let rt = Runtime::new();
let ctxt = Context::new(&rt);
let obj = ctxt
.parse_json(
r#"{ "name": "John", "age": 30, "city": "New York" }"#,
"<evalScript>",
)
.unwrap();
assert_eq!(obj.get_property("name").unwrap().to_string(), "John");
assert_eq!(obj.get_property("age").unwrap().to_int32().unwrap(), 30);
assert_eq!(obj.get_property("city").unwrap().to_string(), "New York");
}
}