From f1f8e06c4add932f484eafabae80af9d6afb2823 Mon Sep 17 00:00:00 2001 From: envis10n Date: Wed, 30 Jan 2019 00:05:20 -0600 Subject: [PATCH] Init --- .gitignore | 3 + Cargo.toml | 9 ++ src/lib.rs | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c173fa0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "duktape-rs" +version = "0.0.1" +authors = ["envis10n "] +edition = "2018"git + +[dependencies] +dukbind = "0.0.4" +serde_json = "1.0.37" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2e656fd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,245 @@ +extern crate dukbind; +extern crate serde_json; + +use serde_json::*; + +use dukbind::*; +use std::error::Error; +use std::fmt; +use std::f64; + +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(u32)] +pub enum DukErrorCode { + None = DUK_ERR_NONE, + Error = DUK_ERR_ERROR, + Eval = DUK_ERR_EVAL_ERROR, + Range = DUK_ERR_RANGE_ERROR, + Syntax = DUK_ERR_SYNTAX_ERROR, + Type = DUK_ERR_TYPE_ERROR, + URI = DUK_ERR_URI_ERROR +} + + +/// Compatibility type enum providing NaN, Infinity, and None alongside Serde JSON types as Val. +#[derive(Clone, Debug)] +pub enum DukValue { + None, + NaN, + Infinity, + Val(Value) +} + +impl DukValue { + pub fn as_str(&self) -> Option<&str> { + match self { + DukValue::None => None, + DukValue::NaN => Some("NaN"), + DukValue::Infinity => Some("Infinity"), + DukValue::Val(ref v) => v.as_str() + } + } + pub fn as_f64(&self) -> Option { + match self { + DukValue::NaN => Some(f64::NAN), + DukValue::Infinity => Some(f64::INFINITY), + DukValue::Val(ref v) => v.as_f64(), + DukValue::None => None + } + } + pub fn is_f64(&self) -> bool { + match self { + DukValue::None => false, + DukValue::NaN => true, + DukValue::Infinity => true, + DukValue::Val(ref v) => v.is_f64() + } + } + pub fn is_i64(&self) -> bool { + match self { + DukValue::Val(ref v) => v.is_i64(), + _ => false + } + } + pub fn as_i64(&self) -> Option { + match self { + DukValue::NaN => Some(f64::NAN as i64), + DukValue::Infinity => Some(f64::INFINITY as i64), + DukValue::Val(ref v) => v.as_i64(), + DukValue::None => None + } + } + pub fn as_value(&self) -> Option { + match self { + DukValue::Val(ref v) => Some(v.clone()), + _ => None + } + } + pub fn is_nan(&self) -> bool { + match self { + DukValue::NaN => true, + _ => false + } + } + pub fn is_infinity(&self) -> bool { + match self { + DukValue::Infinity => true, + _ => false + } + } + pub fn is_value(&self) -> bool { + match self { + DukValue::Val(ref v) => true, + _ => false + } + } +} + +#[derive(PartialEq, Eq, Debug)] +pub struct DukError { + /// The error code, if a specific one is available, or + /// `ErrorCode::Error` if we have nothing better. + code: DukErrorCode, + + /// Errors have some sort of internal structure, but the duktape + /// documentation always just converts them to strings. So that's all + /// we'll store for now. + message: Option +} + +impl DukError { + pub fn from_code(code: DukErrorCode) -> DukError { + DukError{code: code, message: None} + } + pub fn from_str(message: &str) -> DukError { + DukError{code: DukErrorCode::Error, message: Some(message.to_string())} + } + pub fn from(code: DukErrorCode, message: &str) -> DukError { + DukError{code: code, message: Some(message.to_string())} + } + pub fn to_string(&self) -> Option { + match &self.message { + Some(m) => Some(m.clone()), + None => None + } + } +} + +impl Error for DukError { + fn description(&self) -> &str { "script error:" } + + fn cause(&self) -> Option<&Error> { None } +} + +impl fmt::Display for DukError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match (&self.message, self.code) { + (&Some(ref msg), _) => write!(f, "{}", msg), + (&None, DukErrorCode::Error) => write!(f, "an unknown error occurred"), + (&None, code) => + write!(f, "type: {:?} code: {:?}", code, code as duk_int_t) + } + } +} + +pub type DukResult = std::result::Result; + +pub struct DukContext { + ctx: *mut duk_context, +} + +impl DukContext { + fn new() -> DukContext { + unsafe { + DukContext { ctx: duk_create_heap_default() } + } + } + fn get_value(&mut self) -> DukValue { + unsafe { + let t = duk_get_type(self.ctx, -1); + match t as u32 { + DUK_TYPE_NONE => DukValue::Val(Value::default()), + DUK_TYPE_UNDEFINED => DukValue::Val(Value::default()), + DUK_TYPE_NULL => DukValue::Val(Value::Null), + DUK_TYPE_BOOLEAN => DukValue::Val(Value::Bool(duk_get_boolean(self.ctx, -1) == 0)), + DUK_TYPE_NUMBER => { + let v = duk_get_number(self.ctx, -1); + if v.fract() > 0_f64 { + match Number::from_f64(v) { + Some(n) => DukValue::Val(Value::Number(n)), + None => { + if v.is_nan() { + DukValue::NaN + } else if v.is_infinite() { + DukValue::Infinity + } else { + DukValue::None + } + } + } + } else { + if v.is_nan() { + DukValue::NaN + } else if v.is_infinite() { + DukValue::Infinity + } else { + DukValue::Val(Value::Number(Number::from(v as i32))) + } + } + }, + DUK_TYPE_STRING => { + use std::ffi::CStr; + let v = duk_get_string(self.ctx, -1); + let t = CStr::from_ptr(v); + let cow = t.to_string_lossy(); + DukValue::Val(Value::String(String::from(cow))) + }, + DUK_TYPE_OBJECT => { + use std::ffi::CStr; + let istr = duk_json_encode(self.ctx, -1); + let t = CStr::from_ptr(istr); + let cow = t.to_string_lossy(); + DukValue::Val(serde_json::from_str(&String::from(cow)).unwrap()) + }, + _ => DukValue::None + } + } + } + fn eval_string(&mut self, code: &str) -> DukResult { + unsafe { + if duk_eval_string(self.ctx, code) == 0 { + let result = self.get_value(); + duk_pop(self.ctx); + Ok(result) + } else { + let code = duk_get_error_code(self.ctx, -1) as u32; + let name = "stack"; + duk_get_prop_lstring(self.ctx, -1, name.as_ptr() as *const i8, name.len() as duk_size_t); + let val = self.get_value(); + duk_pop(self.ctx); + match val.as_str() { + Some(v) => { + use std::mem; + let c: DukErrorCode = mem::transmute(code); + Err(DukError::from(c, v)) + }, + None => { + Err(DukError::from_code(DukErrorCode::Error)) + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_eval_ret() { + let mut ctx = DukContext::new(); + let res = ctx.eval_string("5*5").expect("Eval error!"); + assert_eq!(25, res.as_i64().expect("Value was not an integer!")) + } +}