From 880bce1114d9cefcb2b9aad49c9d9d5e0d9ef753 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 11:03:18 +0800 Subject: [PATCH 1/3] General cleanup. --- Cargo.toml | 1 + src/builtin.rs | 68 +++++++++++++------------------------- src/engine.rs | 7 ++-- src/fn_register.rs | 82 +++++++++++++++++++++++----------------------- src/optimize.rs | 8 ++++- src/parser.rs | 26 +++++++-------- src/result.rs | 43 ++++++++++++++++-------- src/scope.rs | 1 + tests/math.rs | 4 +++ 9 files changed, 124 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8fa20102..600705e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ include = [ num-traits = "*" [features] +#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] default = [] debug_msgs = [] unchecked = [] diff --git a/src/builtin.rs b/src/builtin.rs index 0e8ab757..04a290ce 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -2,45 +2,23 @@ //! _standard library_ of utility functions. use crate::any::Any; -use crate::engine::Engine; -use crate::fn_register::RegisterFn; -use crate::parser::INT; - -#[cfg(not(feature = "unchecked"))] -use crate::{parser::Position, result::EvalAltResult, RegisterResultFn}; - #[cfg(not(feature = "no_index"))] use crate::engine::Array; - -#[cfg(not(feature = "no_float"))] +use crate::engine::Engine; +use crate::fn_register::{RegisterFn, RegisterResultFn}; +use crate::parser::{Position, INT}; +use crate::result::EvalAltResult; use crate::FLOAT; +use num_traits::{ + identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, + CheckedShr, CheckedSub, +}; + use std::{ fmt::{Debug, Display}, - ops::{BitAnd, BitOr, BitXor, Range}, -}; - -#[cfg(feature = "unchecked")] -use std::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -#[cfg(not(feature = "no_float"))] -use std::{i32, i64}; - -#[cfg(not(feature = "unchecked"))] -#[cfg(not(feature = "only_i32"))] -use std::u32; - -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(not(feature = "unchecked"))] -use { - num_traits::{ - CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, - CheckedSub, - }, - std::convert::TryFrom, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, + {i32, i64, u32}, }; macro_rules! reg_op { @@ -162,12 +140,9 @@ impl Engine<'_> { #[cfg(not(feature = "unchecked"))] fn div(x: T, y: T) -> Result where - T: Display + CheckedDiv + PartialEq + TryFrom, + T: Display + CheckedDiv + PartialEq + Zero, { - if y == >::try_from(0) - .map_err(|_| ()) - .expect("zero should always succeed") - { + if y == T::zero() { return Err(EvalAltResult::ErrorArithmetic( format!("Division by zero: {} / {}", x, y), Position::none(), @@ -191,8 +166,10 @@ impl Engine<'_> { }) } #[cfg(not(feature = "unchecked"))] - fn abs>(x: T) -> Result { - if x >= 0.into() { + fn abs(x: T) -> Result { + // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics + // when the number is ::MIN instead of returning ::MIN itself. + if x >= ::zero() { Ok(x) } else { x.checked_neg().ok_or_else(|| { @@ -224,14 +201,15 @@ impl Engine<'_> { -x } #[cfg(any(feature = "unchecked", not(feature = "no_float")))] - fn abs_u>(x: T) -> T + fn abs_u(x: T) -> ::Output where - ::Output: Into, + T: Neg + PartialOrd + Default + Into<::Output>, { - if x < 0.into() { - (-x).into() + // Numbers should default to zero + if x < Default::default() { + -x } else { - x + x.into() } } fn lt(x: T, y: T) -> bool { diff --git a/src/engine.rs b/src/engine.rs index ebbf4ec7..cba45674 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,8 +4,6 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; - -#[cfg(not(feature = "no_index"))] use crate::INT; use std::{ @@ -788,6 +786,9 @@ impl Engine<'_> { .eval_index_expr(scope, lhs, idx_expr, *idx_pos) .map(|(_, _, _, x)| x), + #[cfg(feature = "no_index")] + Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"), + // Statement block Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), @@ -855,6 +856,8 @@ impl Engine<'_> { Ok(Box::new(arr)) } + #[cfg(feature = "no_index")] + Expr::Array(_, _) => panic!("encountered an array during no_index!"), Expr::FunctionCall(fn_name, args, def_val, pos) => { let mut args = args diff --git a/src/fn_register.rs b/src/fn_register.rs index 9af9ab82..84dbf150 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -120,20 +120,20 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) - } else { - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Dynamic) + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); } + + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + let r = f($(($clone)($par)),*); + Ok(Box::new(r) as Dynamic) }; self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } @@ -152,19 +152,19 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) - } else { - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - Ok(f($(($clone)($par)),*)) + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); } + + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + Ok(f($(($clone)($par)),*)) }; self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } @@ -184,23 +184,23 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) - } else { - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); - )* + return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + } - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - match f($(($clone)($par)),*) { - Ok(r) => Ok(Box::new(r) as Dynamic), - Err(mut err) => { - err.set_position(pos); - Err(err) - } + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + match f($(($clone)($par)),*) { + Ok(r) => Ok(Box::new(r) as Dynamic), + Err(mut err) => { + err.set_position(pos); + Err(err) } } }; diff --git a/src/optimize.rs b/src/optimize.rs index 085b09f9..cb92fb11 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -141,6 +141,7 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(*rhs, changed)), pos, ), + #[cfg(not(feature = "no_index"))] Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) @@ -158,6 +159,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { pos, ), }, + #[cfg(feature = "no_index")] + Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"), + #[cfg(not(feature = "no_index"))] Expr::Array(items, pos) => { let original_len = items.len(); @@ -172,6 +176,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Expr::Array(items, pos) } + #[cfg(feature = "no_index")] + Expr::Array(_, _) => panic!("encountered an array during no_index!"), + Expr::And(lhs, rhs) => match (*lhs, *rhs) { (Expr::True(_), rhs) => { *changed = true; @@ -208,7 +215,6 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(rhs, changed)), ), }, - Expr::FunctionCall(id, args, def_value, pos) => { let original_len = args.len(); diff --git a/src/parser.rs b/src/parser.rs index fcbe7e20..cdb8bac9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,13 +3,18 @@ use crate::any::Dynamic; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; + use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; -/// The system integer type +/// The system integer type. +/// +/// If the `only_i32` feature is enabled, this will be `i32` instead. #[cfg(not(feature = "only_i32"))] pub type INT = i64; /// The system integer type +/// +/// If the `only_i32` feature is not enabled, this will be `i64` instead. #[cfg(feature = "only_i32")] pub type INT = i32; @@ -179,9 +184,7 @@ pub enum Expr { FunctionCall(String, Vec, Option, Position), Assignment(Box, Box, Position), Dot(Box, Box, Position), - #[cfg(not(feature = "no_index"))] Index(Box, Box, Position), - #[cfg(not(feature = "no_index"))] Array(Vec, Position), And(Box, Box), Or(Box, Box), @@ -197,24 +200,21 @@ impl Expr { | Expr::Identifier(_, pos) | Expr::CharConstant(_, pos) | Expr::StringConstant(_, pos) - | Expr::FunctionCall(_, _, _, pos) | Expr::Stmt(_, pos) + | Expr::FunctionCall(_, _, _, pos) + | Expr::Array(_, pos) | Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => { - e.position() - } + Expr::Assignment(e, _, _) + | Expr::Dot(e, _, _) + | Expr::Index(e, _, _) + | Expr::And(e, _) + | Expr::Or(e, _) => e.position(), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => *pos, - - #[cfg(not(feature = "no_index"))] - Expr::Index(e, _, _) => e.position(), - - #[cfg(not(feature = "no_index"))] - Expr::Array(_, pos) => *pos, } } diff --git a/src/result.rs b/src/result.rs index 8034329c..e0bb3734 100644 --- a/src/result.rs +++ b/src/result.rs @@ -3,6 +3,7 @@ use crate::any::Dynamic; use crate::error::ParseError; use crate::parser::{Position, INT}; + use std::{error::Error, fmt}; /// Evaluation result. @@ -75,12 +76,12 @@ impl Error for EvalAltResult { Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } - Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", + Self::ErrorArrayBounds(0, _, _) => "Access of empty array", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorStringBounds(_, index, _) if *index < 0 => { "Indexing a string expects a non-negative index" } - Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(0, _, _) => "Indexing of empty string", Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorIfGuard(_) => "If guard expects boolean expression", Self::ErrorFor(_) => "For loop expects array or range", @@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult { write!(f, "{} '{}': {}", desc, filename, err) } Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorFunctionArgsMismatch(fun, 0, n, pos) => write!( + f, + "Function '{}' expects no argument but {} found ({})", + fun, n, pos + ), + Self::ErrorFunctionArgsMismatch(fun, 1, n, pos) => write!( + f, + "Function '{}' expects one argument but {} found ({})", + fun, n, pos + ), Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( f, "Function '{}' expects {} argument(s) but {} found ({})", @@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult { Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { write!(f, "{}: {} < 0 ({})", desc, index, pos) } - Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorArrayBounds(1, index, pos) => write!( + f, + "Array index {} is out of bounds: only one element in the array ({})", + index, pos + ), Self::ErrorArrayBounds(max, index, pos) => write!( f, - "Array index {} is out of bounds: only {} element{} in the array ({})", - index, - max, - if *max > 1 { "s" } else { "" }, - pos + "Array index {} is out of bounds: only {} elements in the array ({})", + index, max, pos ), Self::ErrorStringBounds(_, index, pos) if *index < 0 => { write!(f, "{}: {} < 0 ({})", desc, index, pos) } - Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorStringBounds(1, index, pos) => write!( + f, + "String index {} is out of bounds: only one character in the string ({})", + index, pos + ), Self::ErrorStringBounds(max, index, pos) => write!( f, - "String index {} is out of bounds: only {} character{} in the string ({})", - index, - max, - if *max > 1 { "s" } else { "" }, - pos + "String index {} is out of bounds: only {} characters in the string ({})", + index, max, pos ), } } diff --git a/src/scope.rs b/src/scope.rs index d097f422..ca3ec4d9 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Any, Dynamic}; + use std::borrow::Cow; /// A type containing information about current scope. diff --git a/tests/math.rs b/tests/math.rs index 4dd7fecc..e9565b39 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -24,6 +24,10 @@ fn test_math() -> Result<(), EvalAltResult> { { #[cfg(not(feature = "only_i32"))] { + match engine.eval::("(-9223372036854775808).abs()") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } match engine.eval::("9223372036854775807 + 1") { Err(EvalAltResult::ErrorArithmetic(_, _)) => (), r => panic!("should return overflow error: {:?}", r), From 047f064cd1081605e3003b9428f468f2dc0c302d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 11:39:15 +0800 Subject: [PATCH 2/3] Add `dump_ast` function for debugging. --- src/engine.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/engine.rs b/src/engine.rs index cba45674..c1130e14 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -27,6 +27,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; pub(crate) const KEYWORD_PRINT: &'static str = "print"; pub(crate) const KEYWORD_DEBUG: &'static str = "debug"; +pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast"; pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_SETTER: &'static str = "set$"; @@ -859,6 +860,31 @@ impl Engine<'_> { #[cfg(feature = "no_index")] Expr::Array(_, _) => panic!("encountered an array during no_index!"), + // Dump AST + Expr::FunctionCall(fn_name, args, _, pos) if fn_name == KEYWORD_DUMP_AST => { + let pos = if args.len() == 0 { + *pos + } else { + args[0].position() + }; + + // Change the argument to a debug dump of the expressions + let result = args + .into_iter() + .map(|expr| format!("{:#?}", expr)) + .collect::>() + .join("\n"); + + // Redirect call to `print` + self.call_fn_raw( + KEYWORD_PRINT, + vec![result.into_dynamic().as_mut()], + None, + pos, + ) + } + + // Normal function call Expr::FunctionCall(fn_name, args, def_val, pos) => { let mut args = args .iter() From 7c4d22d98af1856bd7d35dc322ca9adde897cc2c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 11 Mar 2020 13:28:12 +0800 Subject: [PATCH 3/3] Add `no_function` feature to disable script-defined functions. --- Cargo.toml | 18 ++++---- README.md | 99 +++++++++++++++++++++++++------------------- src/api.rs | 60 ++++++++++++++++++--------- src/engine.rs | 2 + src/parser.rs | 20 +++++++-- tests/bool_op.rs | 20 ++++----- tests/engine.rs | 1 + tests/internal_fn.rs | 2 + tests/not.rs | 1 + tests/unary_minus.rs | 3 ++ 10 files changed, 140 insertions(+), 86 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 600705e7..a5177aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,16 +18,18 @@ include = [ num-traits = "*" [features] -#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] +#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] default = [] -debug_msgs = [] -unchecked = [] -no_stdlib = [] -no_index = [] -no_float = [] -only_i32 = [] -only_i64 = [] +debug_msgs = [] # print debug messages on function registrations and calls +unchecked = [] # unchecked arithmetic +no_stdlib = [] # no standard library of utility functions +no_index = [] # no arrays and indexing +no_float = [] # no floating-point +no_function = [] # no script-defined functions +only_i32 = [] # set INT=i32 (useful for 32-bit systems) +only_i64 = [] # set INT=i64 (default) and disable support for all other integer types [profile.release] lto = "fat" codegen-units = 1 +#opt-level = "z" # optimize for size diff --git a/README.md b/README.md index 8d7724ea..c96d0a32 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Rhai's current feature set: * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Easy-to-use language similar to JS+Rust * Support for overloaded functions -* Very few additional dependencies (right now only `num-traits` to do checked arithmetic operations) +* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations) **Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. @@ -38,15 +38,16 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th Optional features ----------------- -| Feature | Description | -| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | -| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | -| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | -| `no_index` | Disable arrays and indexing features if you don't need them. | -| `no_float` | Disable floating-point numbers and math if you don't need them. | -| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | -| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | +| Feature | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | +| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | +| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +| `no_function` | Disable script-defined functions if you don't need them. | +| `no_index` | Disable arrays and indexing features if you don't need them. | +| `no_float` | Disable floating-point numbers and math if you don't need them. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. @@ -56,8 +57,8 @@ Related Other cool projects to check out: -* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. -* You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) +* [ChaiScript] - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. +* You can also check out the list of [scripting languages for Rust] on [awesome-rust]. Examples -------- @@ -195,24 +196,24 @@ Values and types The following primitive types are supported natively: -| Category | Types | -| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`](#optional-features)),
`u64`, `i64` _(default)_ | -| **Floating-point** (disabled with [`no_float`](#optional-features)) | `f32`, `f64` _(default)_ | -| **Character** | `char` | -| **Boolean** | `bool` | -| **Array** (disabled with [`no_index`](#optional-features)) | `rhai::Array` | -| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | -| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) | +| Category | Types | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| **Integer** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | +| **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | +| **Character** | `char` | +| **Boolean** | `bool` | +| **Array** (disabled with [`no_index`]) | `rhai::Array` | +| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | +| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) | All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together. -The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`](#optional-features) feature. +The default integer type is `i64`. If you do not need any other integer type, you can enable the [`only_i64`] feature. -If you only need 32-bit integers, you can enable the [`only_i32`](#optional-features) feature and remove support for all integer types other than `i32` including `i64`. +If you only need 32-bit integers, you can enable the [`only_i32`] feature and remove support for all integer types other than `i32` including `i64`. This is useful on 32-bit systems where using 64-bit integers incurs a performance penalty. -If you do not need floating-point, enable the [`no_float`](#optional-features) feature to remove support. +If you do not need floating-point, enable the [`no_float`] feature to remove support. Value conversions ----------------- @@ -303,17 +304,17 @@ use std::fmt::Display; use rhai::{Engine, RegisterFn}; -fn showit(x: &mut T) -> () { - println!("{}", x) +fn show_it(x: &mut T) -> () { + println!("put up a good show: {}!", x) } fn main() { let mut engine = Engine::new(); - engine.register_fn("print", showit as fn(x: &mut i64)->()); - engine.register_fn("print", showit as fn(x: &mut bool)->()); - engine.register_fn("print", showit as fn(x: &mut String)->()); + engine.register_fn("print", show_it as fn(x: &mut i64)->()); + engine.register_fn("print", show_it as fn(x: &mut bool)->()); + engine.register_fn("print", show_it as fn(x: &mut String)->()); } ``` @@ -615,24 +616,23 @@ Unary operators ```rust let number = -5; number = -5 - +5; -let booly = !true; +let boolean = !true; ``` Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | Description | -| ---------- | ----------------------------------- | -| `abs` | absolute value | -| `to_int` | converts an `f32` or `f64` to `i64` | -| `to_float` | converts an integer type to `f64` | +| Function | Description | +| ---------- | --------------------------------- | +| `abs` | absolute value | +| `to_float` | converts an integer type to `f64` | Floating-point functions ------------------------ -The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `f64` only: | Category | Functions | | ---------------- | ------------------------------------------------------------ | @@ -642,7 +642,8 @@ The following standard functions (defined in the standard library but excluded i | Exponential | `exp` (base _e_) | | Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) | | Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` | -| Tests | `is_nan`, `is_finite`, `is_infinite` | +| Conversion | `to_int` | +| Testing | `is_nan`, `is_finite`, `is_infinite` | Strings and Chars ----------------- @@ -684,7 +685,7 @@ record[4] = '\x58'; // 0x58 = 'X' record == "Bob X. Davis: age 42 ❤\n"; ``` -The following standard functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on strings: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings: | Function | Description | | ---------- | ------------------------------------------------------------------------ | @@ -731,7 +732,7 @@ Arrays You can create arrays of values, and then access them with numeric indices. -The following functions (defined in the standard library but excluded if [`no_stdlib`](#optional-features)) operate on arrays: +The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: | Function | Description | | ---------- | ------------------------------------------------------------------------------------- | @@ -803,7 +804,7 @@ engine.register_fn("push", The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. -Arrays are disabled via the [`no_index`](#optional-features) feature. +Arrays are disabled via the [`no_index`] feature. Comparison operators -------------------- @@ -954,7 +955,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position Functions --------- -Rhai supports defining functions in script: +Rhai supports defining functions in script (unless disabled with [`no_function`]): ```rust fn add(x, y) { @@ -1048,3 +1049,17 @@ for entry in log { println!("{}", entry); } ``` + +[ChaiScript]: http://chaiscript.com/ +[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting +[awesome-rust]: https://github.com/rust-unofficial/awesome-rust + +[`num-traits`]: https://crates.io/crates/num-traits/ +[`debug_msgs`]: #optional-features +[`unchecked`]: #optional-features +[`no_stdlib`]: #optional-features +[`no_index`]: #optional-features +[`no_float`]: #optional-features +[`no_function`]: #optional-features +[`only_i32`]: #optional-features +[`only_i64`]: #optional-features diff --git a/src/api.rs b/src/api.rs index 9d309756..f5c7e0ce 100644 --- a/src/api.rs +++ b/src/api.rs @@ -168,17 +168,25 @@ impl<'e> Engine<'e> { retain_functions: bool, ast: &AST, ) -> Result { - let AST(statements, functions) = ast; + #[cfg(feature = "no_function")] + let AST(statements) = ast; - functions.iter().for_each(|f| { - engine.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); - }); + #[cfg(not(feature = "no_function"))] + let statements = { + let AST(statements, functions) = ast; + + functions.iter().for_each(|f| { + engine.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + statements + }; let result = statements .iter() @@ -244,16 +252,26 @@ impl<'e> Engine<'e> { parse(&mut tokens.peekable(), self.optimize) .map_err(|err| EvalAltResult::ErrorParsing(err)) - .and_then(|AST(ref statements, ref functions)| { - for f in functions { - self.script_functions.insert( - FnSpec { - name: f.name.clone().into(), - args: None, - }, - Arc::new(FnIntExt::Int(f.clone())), - ); - } + .and_then(|ast| { + #[cfg(feature = "no_function")] + let AST(statements) = ast; + + #[cfg(not(feature = "no_function"))] + let statements = { + let AST(ref statements, ref functions) = ast; + + functions.iter().for_each(|f| { + self.script_functions.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + statements + }; let val = statements .iter() @@ -275,6 +293,7 @@ impl<'e> Engine<'e> { /// ```rust /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::Engine; /// @@ -289,6 +308,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(not(feature = "no_function"))] pub fn call_fn( &mut self, name: &str, diff --git a/src/engine.rs b/src/engine.rs index c1130e14..62acd255 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,6 +4,8 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; + +#[cfg(not(feature = "no_index"))] use crate::INT; use std::{ diff --git a/src/parser.rs b/src/parser.rs index cdb8bac9..5aec9d46 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -139,7 +139,10 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. -pub struct AST(pub(crate) Vec, pub(crate) Vec>); +pub struct AST( + pub(crate) Vec, + #[cfg(not(feature = "no_function"))] pub(crate) Vec>, +); #[derive(Debug, Clone)] pub struct FnDef<'a> { @@ -1820,6 +1823,7 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result, ParseError> { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, @@ -1892,11 +1896,14 @@ fn parse_top_level<'a>( input: &mut Peekable>, optimize_ast: bool, ) -> Result { - let mut statements = Vec::new(); - let mut functions = Vec::new(); + let mut statements = Vec::::new(); + + #[cfg(not(feature = "no_function"))] + let mut functions = Vec::::new(); while input.peek().is_some() { match input.peek() { + #[cfg(not(feature = "no_function"))] Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?), _ => statements.push(parse_stmt(input)?), } @@ -1910,6 +1917,7 @@ fn parse_top_level<'a>( return Ok(if optimize_ast { AST( optimize(statements), + #[cfg(not(feature = "no_function"))] functions .into_iter() .map(|mut fn_def| { @@ -1920,7 +1928,11 @@ fn parse_top_level<'a>( .collect(), ) } else { - AST(statements, functions) + AST( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ) }); } diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 7c1654bb..a7960476 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( r" - fn this() { true } - fn that() { 9/0 } + let this = true; - this() || that(); + this || { throw; }; " )?, true @@ -51,10 +50,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( r" - fn this() { false } - fn that() { 9/0 } + let this = false; - this() && that(); + this && { throw; }; " )?, false @@ -72,10 +70,9 @@ fn test_bool_op_no_short_circuit1() { engine .eval::( r" - fn this() { false } - fn that() { 9/0 } + let this = true; - this() | that(); + this | { throw; } " ) .unwrap(), @@ -92,10 +89,9 @@ fn test_bool_op_no_short_circuit2() { engine .eval::( r" - fn this() { false } - fn that() { 9/0 } + let this = false; - this() & that(); + this & { throw; } " ) .unwrap(), diff --git a/tests/engine.rs b/tests/engine.rs index a01ae916..d795b35b 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -1,4 +1,5 @@ #![cfg(not(feature = "no_stdlib"))] +#![cfg(not(feature = "no_function"))] use rhai::{Engine, EvalAltResult, INT}; #[test] diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 09bb357e..8e38f0d9 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_function"))] + use rhai::{Engine, EvalAltResult, INT}; #[test] diff --git a/tests/not.rs b/tests/not.rs index 8bade80a..52913136 100644 --- a/tests/not.rs +++ b/tests/not.rs @@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> { false ); + #[cfg(not(feature = "no_function"))] assert_eq!(engine.eval::("fn not(x) { !x } not(false)")?, true); // TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true' diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs index 8f5f6013..80170498 100644 --- a/tests/unary_minus.rs +++ b/tests/unary_minus.rs @@ -5,7 +5,10 @@ fn test_unary_minus() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!(engine.eval::("let x = -5; x")?, -5); + + #[cfg(not(feature = "no_function"))] assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5); + assert_eq!(engine.eval::("5 - -+++--+-5")?, 0); Ok(())