From b6d839c8a9deceb92d61c99aa6cc5bdc91afc52c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 24 Apr 2020 12:39:24 +0800 Subject: [PATCH] Fix no_std build. --- Cargo.toml | 5 +- examples/no_std.rs | 6 + src/any.rs | 1 + src/api.rs | 9 +- src/engine.rs | 246 ++++++++++++++++++++--------------- src/error.rs | 2 +- src/fn_register.rs | 2 +- src/packages/arithmetic.rs | 1 + src/packages/array_basic.rs | 2 +- src/packages/iter_basic.rs | 1 + src/packages/logic.rs | 2 + src/packages/map_basic.rs | 5 + src/packages/math_basic.rs | 2 +- src/packages/mod.rs | 1 + src/packages/pkg_std.rs | 2 + src/packages/string_basic.rs | 1 + src/packages/string_more.rs | 7 +- src/packages/time_basic.rs | 97 +++++++------- src/packages/utils.rs | 6 +- src/result.rs | 7 +- 20 files changed, 238 insertions(+), 167 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 324e0a02..efaeb546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ num-traits = { version = "0.2.11", default-features = false } [features] #default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] -default = [] +default = ["no_std"] unchecked = [] # unchecked arithmetic no_index = [] # no arrays and indexing no_float = [] # no floating-point @@ -33,7 +33,7 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in sync = [] # restrict to only types that implement Send + Sync # compiling for no-std -no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] +no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] # other developer features no_stdlib = [] # do not register the standard library @@ -63,4 +63,5 @@ optional = true [dependencies.ahash] version = "0.3.2" default-features = false +features = ["compile-time-rng"] optional = true diff --git a/examples/no_std.rs b/examples/no_std.rs index ed1647ef..a6902f72 100644 --- a/examples/no_std.rs +++ b/examples/no_std.rs @@ -2,6 +2,12 @@ use rhai::{Engine, EvalAltResult, INT}; +#[cfg(feature = "no_std")] +extern crate alloc; + +#[cfg(feature = "no_std")] +use alloc::boxed::Box; + fn main() -> Result<(), Box> { let engine = Engine::new(); diff --git a/src/any.rs b/src/any.rs index f0f20938..f718b01c 100644 --- a/src/any.rs +++ b/src/any.rs @@ -12,6 +12,7 @@ use crate::stdlib::{ collections::HashMap, fmt, string::String, + vec::Vec, }; #[cfg(not(feature = "no_std"))] diff --git a/src/api.rs b/src/api.rs index 14c74fff..374fa48c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -923,10 +923,15 @@ impl Engine { ) -> Result> { let mut arg_values = args.into_vec(); let mut args: Vec<_> = arg_values.iter_mut().collect(); - let fn_lib = Some(ast.1.as_ref()); + let fn_lib = ast.1.as_ref(); let pos = Position::none(); - let result = self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?; + let fn_def = fn_lib + .get_function(name, args.len()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))?; + + let result = + self.call_fn_from_lib(Some(scope), Some(&fn_lib), fn_def, &mut args, pos, 0)?; let return_type = self.map_type_name(result.type_name()); diff --git a/src/engine.rs b/src/engine.rs index 00abbb4c..aa6a8baa 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -13,7 +13,7 @@ use crate::token::Position; use crate::stdlib::{ any::TypeId, boxed::Box, - collections::{hash_map::DefaultHasher, HashMap}, + collections::HashMap, format, hash::{Hash, Hasher}, iter::once, @@ -24,6 +24,12 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_std"))] +use crate::stdlib::collections::hash_map::DefaultHasher; + +#[cfg(feature = "no_std")] +use ahash::AHasher; + /// An dynamic array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -347,17 +353,25 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { /// Parameter types are passed in via `TypeId` values from an iterator /// which can come from any source. pub fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] let mut s = DefaultHasher::new(); - fn_name.hash(&mut s); + + s.write(fn_name.as_bytes()); params.for_each(|t| t.hash(&mut s)); s.finish() } /// Calculate a `u64` hash key from a function name and number of parameters (without regard to types). pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 { + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] let mut s = DefaultHasher::new(); - fn_name.hash(&mut s); - params.hash(&mut s); + + s.write(fn_name.as_bytes()); + s.write_usize(params); s.finish() } @@ -529,67 +543,7 @@ impl Engine { // First search in script-defined functions (can override built-in) if let Some(fn_def) = fn_lib.and_then(|lib| lib.get_function(fn_name, args.len())) { - match scope { - // Extern scope passed in which is not empty - Some(scope) if scope.len() > 0 => { - let scope_len = scope.len(); - - scope.extend( - // Put arguments into scope as variables - variable name is copied - // TODO - avoid copying variable name - fn_def - .params - .iter() - .zip(args.into_iter().map(|v| v.clone())) - .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), - ); - - // Evaluate the function at one higher level of call depth - let result = self - .eval_stmt(scope, fn_lib, &fn_def.body, level + 1) - .or_else(|err| match *err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(Box::new(err.set_position(pos))), - }); - - scope.rewind(scope_len); - - return result; - } - // No new scope - create internal scope - _ => { - let mut scope = Scope::new(); - - scope.extend( - // Put arguments into scope as variables - fn_def - .params - .iter() - .zip(args.into_iter().map(|v| v.clone())) - .map(|(name, value)| (name, ScopeEntryType::Normal, value)), - ); - - // Evaluate the function at one higher level of call depth - return self - .eval_stmt(&mut scope, fn_lib, &fn_def.body, level + 1) - .or_else(|err| match *err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(Box::new(err.set_position(pos))), - }); - } - } - } - - // Argument must be a string - fn cast_to_string(r: &Dynamic, pos: Position) -> Result<&str, Box> { - r.as_str().map_err(|type_name| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - type_name.into(), - pos, - )) - }) + return self.call_fn_from_lib(scope, fn_lib, fn_def, args, pos, level); } // Search built-in's and external functions @@ -607,10 +561,22 @@ impl Engine { // See if the function match print/debug (which requires special processing) return Ok(match fn_name { KEYWORD_PRINT if self.on_print.is_some() => { - self.on_print.as_ref().unwrap()(cast_to_string(&result, pos)?).into() + self.on_print.as_ref().unwrap()(result.as_str().map_err(|type_name| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name.into(), + pos, + )) + })?) + .into() } KEYWORD_DEBUG if self.on_debug.is_some() => { - self.on_debug.as_ref().unwrap()(cast_to_string(&result, pos)?).into() + self.on_debug.as_ref().unwrap()(result.as_str().map_err(|type_name| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name.into(), + pos, + )) + })?) + .into() } KEYWORD_PRINT | KEYWORD_DEBUG => ().into(), _ => result, @@ -651,6 +617,69 @@ impl Engine { ))) } + /// Call a script-defined function. + pub(crate) fn call_fn_from_lib( + &self, + scope: Option<&mut Scope>, + fn_lib: Option<&FunctionsLib>, + fn_def: &FnDef, + args: &mut FnCallArgs, + pos: Position, + level: usize, + ) -> Result> { + match scope { + // Extern scope passed in which is not empty + Some(scope) if scope.len() > 0 => { + let scope_len = scope.len(); + + scope.extend( + // Put arguments into scope as variables - variable name is copied + // TODO - avoid copying variable name + fn_def + .params + .iter() + .zip(args.into_iter().map(|v| v.clone())) + .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), + ); + + // Evaluate the function at one higher level of call depth + let result = self + .eval_stmt(scope, fn_lib, &fn_def.body, level + 1) + .or_else(|err| match *err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + _ => Err(EvalAltResult::set_position(err, pos)), + }); + + scope.rewind(scope_len); + + return result; + } + // No new scope - create internal scope + _ => { + let mut scope = Scope::new(); + + scope.extend( + // Put arguments into scope as variables + fn_def + .params + .iter() + .zip(args.into_iter().map(|v| v.clone())) + .map(|(name, value)| (name, ScopeEntryType::Normal, value)), + ); + + // Evaluate the function at one higher level of call depth + return self + .eval_stmt(&mut scope, fn_lib, &fn_def.body, level + 1) + .or_else(|err| match *err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + _ => Err(EvalAltResult::set_position(err, pos)), + }); + } + } + } + // Has a system function an override? fn has_override(&self, fn_lib: Option<&FunctionsLib>, name: &str) -> bool { let hash = &calc_fn_hash(name, once(TypeId::of::())); @@ -712,6 +741,49 @@ impl Engine { } } + /// Evaluate a text string as a script - used primarily for 'eval'. + fn eval_script_expr( + &self, + scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, + script: &Dynamic, + pos: Position, + ) -> Result> { + let script = script + .as_str() + .map_err(|type_name| EvalAltResult::ErrorMismatchOutputType(type_name.into(), pos))?; + + // Compile the script text + // No optimizations because we only run it once + let mut ast = self.compile_with_scope_and_optimization_level( + &Scope::new(), + script, + OptimizationLevel::None, + )?; + + // If new functions are defined within the eval string, it is an error + if ast.1.len() > 0 { + return Err(Box::new(EvalAltResult::ErrorParsing( + ParseErrorType::WrongFnDefinition.into_err(pos), + ))); + } + + if let Some(lib) = fn_lib { + #[cfg(feature = "sync")] + { + ast.1 = Arc::new(lib.clone()); + } + #[cfg(not(feature = "sync"))] + { + ast.1 = Rc::new(lib.clone()); + } + } + + // Evaluate the AST + self.eval_ast_with_scope_raw(scope, &ast) + .map_err(|err| EvalAltResult::set_position(err, pos)) + } + /// Chain-evaluate a dot setter. fn dot_get_helper( &self, @@ -1392,44 +1464,8 @@ impl Engine { && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { - // Get the script text by evaluating the expression - let script = args[0].as_str().map_err(|type_name| { - EvalAltResult::ErrorMismatchOutputType( - type_name.into(), - arg_exprs[0].position(), - ) - })?; - - // Compile the script text - // No optimizations because we only run it once - let mut ast = self.compile_with_scope_and_optimization_level( - &Scope::new(), - script, - OptimizationLevel::None, - )?; - - // If new functions are defined within the eval string, it is an error - if ast.1.len() > 0 { - return Err(Box::new(EvalAltResult::ErrorParsing( - ParseErrorType::WrongFnDefinition.into_err(*pos), - ))); - } - - if let Some(lib) = fn_lib { - #[cfg(feature = "sync")] - { - ast.1 = Arc::new(lib.clone()); - } - #[cfg(not(feature = "sync"))] - { - ast.1 = Rc::new(lib.clone()); - } - } - - // Evaluate the AST - return self - .eval_ast_with_scope_raw(scope, &ast) - .map_err(|err| Box::new(err.set_position(*pos))); + // Evaluate the text string as a script + return self.eval_script_expr(scope, fn_lib, args[0], arg_exprs[0].position()); } // Normal function call diff --git a/src/error.rs b/src/error.rs index ffda5506..d87c3722 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use crate::token::Position; -use crate::stdlib::{char, error::Error, fmt, string::String}; +use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::String}; /// Error when tokenizing the script text. #[derive(Debug, Eq, PartialEq, Hash, Clone)] diff --git a/src/fn_register.rs b/src/fn_register.rs index 4e490b67..e711ee54 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -187,7 +187,7 @@ pub fn map_result( pos: Position, ) -> Result> { data.map(|v| v.into_dynamic()) - .map_err(|err| Box::new(err.set_position(pos))) + .map_err(|err| EvalAltResult::set_position(err, pos)) } macro_rules! def_register { diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index fd7a7a5a..82618480 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -15,6 +15,7 @@ use num_traits::{ }; use crate::stdlib::{ + boxed::Box, fmt::Display, format, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 71c10e9a..e50224ac 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -6,7 +6,7 @@ use crate::engine::Array; use crate::fn_register::{map_dynamic as map, map_identity as pass}; use crate::parser::INT; -use crate::stdlib::any::TypeId; +use crate::stdlib::{any::TypeId, boxed::Box, string::String}; // Register array utility functions fn push(list: &mut Array, item: T) { diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index e554019d..af2e6f53 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -8,6 +8,7 @@ use crate::parser::INT; use crate::stdlib::{ any::TypeId, + boxed::Box, ops::{Add, Range}, }; diff --git a/src/packages/logic.rs b/src/packages/logic.rs index fbe02ee9..f993044a 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -4,6 +4,8 @@ use crate::def_package; use crate::fn_register::map_dynamic as map; use crate::parser::INT; +use crate::stdlib::string::String; + // Comparison operators pub fn lt(x: T, y: T) -> bool { x < y diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 972fce95..44009e8a 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -6,6 +6,11 @@ use crate::engine::Map; use crate::fn_register::map_dynamic as map; use crate::parser::INT; +use crate::stdlib::{ + string::{String, ToString}, + vec::Vec, +}; + fn map_get_keys(map: &mut Map) -> Vec { map.iter() .map(|(k, _)| k.to_string().into()) diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 891d3721..a39f477c 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -9,7 +9,7 @@ use crate::token::Position; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -use crate::stdlib::{i32, i64}; +use crate::stdlib::{boxed::Box, format, i32, i64}; #[cfg(feature = "only_i32")] pub const MAX_INT: INT = i32::MAX; diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 611dfef8..54c3f177 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -29,6 +29,7 @@ pub use pkg_core::CorePackage; pub use pkg_std::StandardPackage; pub use string_basic::BasicStringPackage; pub use string_more::MoreStringPackage; +#[cfg(not(feature = "no_std"))] pub use time_basic::BasicTimePackage; pub use utils::*; diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index cfb022eb..d2790d50 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -5,6 +5,7 @@ use super::map_basic::BasicMapPackage; use super::math_basic::BasicMathPackage; use super::pkg_core::CorePackage; use super::string_more::MoreStringPackage; +#[cfg(not(feature = "no_std"))] use super::time_basic::BasicTimePackage; use crate::def_package; @@ -16,6 +17,7 @@ def_package!(crate:StandardPackage:"_Standard_ package containing all built-in f BasicArrayPackage::init(lib); #[cfg(not(feature = "no_object"))] BasicMapPackage::init(lib); + #[cfg(not(feature = "no_std"))] BasicTimePackage::init(lib); MoreStringPackage::init(lib); }); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 43179f56..87807d70 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -8,6 +8,7 @@ use crate::parser::INT; use crate::stdlib::{ fmt::{Debug, Display}, format, + string::{String, ToString}, }; // Register print and debug diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 37159bda..4edef10e 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -5,7 +5,12 @@ use crate::engine::Array; use crate::fn_register::map_dynamic as map; use crate::parser::INT; -use crate::stdlib::fmt::Display; +use crate::stdlib::{ + fmt::Display, + format, + string::{String, ToString}, + vec::Vec, +}; fn prepend(x: T, y: String) -> String { format!("{}{}", x, y) diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 6fd591ee..cb0c5f8e 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -8,64 +8,63 @@ use crate::parser::INT; use crate::result::EvalAltResult; use crate::token::Position; +#[cfg(not(feature = "no_std"))] use crate::stdlib::time::Instant; +#[cfg(not(feature = "no_std"))] def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { - #[cfg(not(feature = "no_std"))] - { - // Register date/time functions - reg_none(lib, "timestamp", || Instant::now(), map); + // Register date/time functions + reg_none(lib, "timestamp", || Instant::now(), map); - reg_binary( - lib, - "-", - |ts1: Instant, ts2: Instant| { - if ts2 > ts1 { - #[cfg(not(feature = "no_float"))] - return Ok(-(ts2 - ts1).as_secs_f64()); + reg_binary( + lib, + "-", + |ts1: Instant, ts2: Instant| { + if ts2 > ts1 { + #[cfg(not(feature = "no_float"))] + return Ok(-(ts2 - ts1).as_secs_f64()); - #[cfg(feature = "no_float")] + #[cfg(feature = "no_float")] + { + let seconds = (ts2 - ts1).as_secs(); + + #[cfg(not(feature = "unchecked"))] { - let seconds = (ts2 - ts1).as_secs(); - - #[cfg(not(feature = "unchecked"))] - { - if seconds > (MAX_INT as u64) { - return Err(Box::new(EvalAltResult::ErrorArithmetic( - format!( - "Integer overflow for timestamp duration: {}", - -(seconds as i64) - ), - Position::none(), - ))); - } + if seconds > (MAX_INT as u64) { + return Err(Box::new(EvalAltResult::ErrorArithmetic( + format!( + "Integer overflow for timestamp duration: {}", + -(seconds as i64) + ), + Position::none(), + ))); } - return Ok(-(seconds as INT)); - } - } else { - #[cfg(not(feature = "no_float"))] - return Ok((ts1 - ts2).as_secs_f64()); - - #[cfg(feature = "no_float")] - { - let seconds = (ts1 - ts2).as_secs(); - - #[cfg(not(feature = "unchecked"))] - { - if seconds > (MAX_INT as u64) { - return Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: {}", seconds), - Position::none(), - ))); - } - } - return Ok(seconds as INT); } + return Ok(-(seconds as INT)); } - }, - result, - ); - } + } else { + #[cfg(not(feature = "no_float"))] + return Ok((ts1 - ts2).as_secs_f64()); + + #[cfg(feature = "no_float")] + { + let seconds = (ts1 - ts2).as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp duration: {}", seconds), + Position::none(), + ))); + } + } + return Ok(seconds as INT); + } + } + }, + result, + ); reg_binary(lib, "<", lt::, map); reg_binary(lib, "<=", lte::, map); diff --git a/src/packages/utils.rs b/src/packages/utils.rs index f3979f61..122bb63a 100644 --- a/src/packages/utils.rs +++ b/src/packages/utils.rs @@ -6,7 +6,11 @@ use crate::engine::FnCallArgs; use crate::result::EvalAltResult; use crate::token::Position; -use crate::stdlib::{any::TypeId, boxed::Box}; +use crate::stdlib::{ + any::TypeId, + boxed::Box, + string::{String, ToString}, +}; /// This macro makes it easy to define a _package_ and register functions into it. /// diff --git a/src/result.rs b/src/result.rs index 4a4cb6bc..17fe389a 100644 --- a/src/result.rs +++ b/src/result.rs @@ -6,6 +6,7 @@ use crate::parser::INT; use crate::token::Position; use crate::stdlib::{ + boxed::Box, error::Error, fmt, string::{String, ToString}, @@ -283,8 +284,8 @@ impl EvalAltResult { /// Consume the current `EvalAltResult` and return a new one /// with the specified `Position`. - pub(crate) fn set_position(mut self, new_position: Position) -> Self { - match &mut self { + pub(crate) fn set_position(mut err: Box, new_position: Position) -> Box { + match err.as_mut() { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _) => (), @@ -314,6 +315,6 @@ impl EvalAltResult { | Self::Return(_, pos) => *pos = new_position, } - self + err } }