diff --git a/README.md b/README.md index 868cfe43..8edae03a 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,20 @@ to add scripting to any application. Rhai's current features set: -* `no-std` support -* Easy integration with Rust native functions and types, including getter/setter/methods -* Easily call a script-defined function from Rust +* Easy-to-use language similar to JS+Rust +* Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods), + including [getter/setter](#getters-and-setters)/[methods](#members-and-methods) +* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust * Freely pass variables/constants into a script via an external [`Scope`] * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * 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 function overloading -* Support for operator overloading -* Compiled script is optimized for repeat evaluations -* Support for minimal builds by excluding unneeded language features +* [`no-std`](#optional-features) support +* Support for [function overloading](#function-overloading) +* Support for [operator overloading](#operator-overloading) +* Compiled script is [optimized](#script-optimization) for repeat evaluations +* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) - to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are + to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. **Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize. @@ -116,10 +117,13 @@ Opt out of as many features as possible, if they are not needed, to reduce code all code is compiled in as what a script requires cannot be predicted. If a language feature is not needed, omitting them via special features is a prudent strategy to optimize the build for size. -Start by using [`Engine::new_raw`](#raw-engine) to create a _raw_ engine which does not register the standard library of utility -functions. Secondly, omitting arrays (`no_index`) yields the most code-size savings, followed by floating-point support -(`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`). Disable script-defined -functions (`no_function`) only when the feature is not needed because code size savings is minimal. +Omitting arrays (`no_index`) yields the most code-size savings, followed by floating-point support +(`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`). +Disable script-defined functions (`no_function`) only when the feature is not needed because code size savings is minimal. + +[`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions. +This makes the scripting language quite useless as even basic arithmetic operators are not supported. +Selectively include the necessary operators by loading specific [packages](#packages) while minimizing the code footprint. Related ------- @@ -349,17 +353,41 @@ Raw `Engine` `Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). In many controlled embedded environments, however, these are not needed. -Use `Engine::new_raw` to create a _raw_ `Engine`, in which: +Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, not even basic arithmetic and logic operators! -* the `print` and `debug` statements do nothing instead of displaying to the console (see [`print` and `debug`](#print-and-debug) below) -* the _standard library_ of utility functions is _not_ loaded by default (load it using the `register_stdlib` method). +### Packages + +Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`. +Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be imported in order for +packages to be used. ```rust -let mut engine = Engine::new_raw(); // create a 'raw' Engine +use rhai::Engine; +use rhai::packages::Package // load the 'Package' trait to use packages +use rhai::packages::CorePackage; // the 'core' package contains basic functionalities (e.g. arithmetic) -engine.register_stdlib(); // register the standard library manually +let mut engine = Engine::new_raw(); // create a 'raw' Engine +let package = CorePackage::new(); // create a package + +engine.load_package(package.get()); // load the package manually ``` +The follow packages are available: + +| Package | Description | In `CorePackage` | In `StandardPackage` | +| ------------------------ | ----------------------------------------------- | :--------------: | :------------------: | +| `BasicArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes | +| `BasicIteratorPackage` | Numeric ranges | Yes | Yes | +| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes | +| `BasicStringPackage` | Basic string functions | Yes | Yes | +| `BasicTimePackage` | Basic time functions (e.g. `Instant`) | Yes | Yes | +| `MoreStringPackage` | Additional string functions | No | Yes | +| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | +| `BasicArrayPackage` | Basic [array] functions | No | Yes | +| `BasicMapPackage` | Basic [object map] functions | No | Yes | +| `CorePackage` | Basic essentials | | | +| `StandardPackage` | Standard library | | | + Evaluate expressions only ------------------------- @@ -401,7 +429,7 @@ The following primitive types are supported natively: | **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | -| **Timestamp** (implemented in standard library) | `std::time::Instant` | `"timestamp"` | _not supported_ | +| **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | | **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | @@ -1116,7 +1144,7 @@ number = -5 - +5; Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on +The following standard functions (defined in the [`BasicMathPackage`] but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: | Function | Description | @@ -1127,7 +1155,7 @@ The following standard functions (defined in the standard library but excluded i Floating-point functions ------------------------ -The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on `f64` only: +The following standard functions (defined in the [`BasicMathPackage`](#packages) but excluded if using a [raw `Engine`]) operate on `f64` only: | Category | Functions | | ---------------- | ------------------------------------------------------------ | @@ -1174,8 +1202,8 @@ Unicode characters. Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters. In Rhai, there is also no separate concepts of `String` and `&str` as in Rust. -Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded -if using a [raw `Engine`]). This is particularly useful when printing output. +Strings can be built up from other strings and types via the `+` operator (provided by the [`MoreStringPackage`](#packages) +but excluded if using a [raw `Engine`]). This is particularly useful when printing output. [`type_of()`] a string returns `"string"`. @@ -1225,7 +1253,7 @@ record == "Bob X. Davis: age 42 ❤\n"; ### Built-in functions -The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: +The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings: | Function | Parameter(s) | Description | | ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | @@ -1300,7 +1328,7 @@ Arrays are disabled via the [`no_index`] feature. ### Built-in functions -The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays: +The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays: | Function | Parameter(s) | Description | | ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | @@ -1420,7 +1448,7 @@ Object maps are disabled via the [`no_object`] feature. ### Built-in functions -The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps: +The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps: | Function | Parameter(s) | Description | | ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | @@ -1545,14 +1573,14 @@ result == 3; // the object map is successfully used i ------------- [`timestamp`]: #timestamp-s -Timestamps are provided by the standard library (excluded if using a [raw `Engine`]) via the `timestamp` +Timestamps are provided by the [`BasicTimePackage`](#packages) (excluded if using a [raw `Engine`]) via the `timestamp` function. The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp returns `"timestamp"`. ### Built-in functions -The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on timestamps: +The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps: | Function | Parameter(s) | Description | | ------------ | ---------------------------------- | -------------------------------------------------------- | @@ -1876,7 +1904,7 @@ Unlike C/C++, functions can be defined _anywhere_ within the global level. A fun prior to being used in a script; a statement in the script can freely call a function defined afterwards. This is similar to Rust and many other modern languages. -### Functions overloading +### Function overloading Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]). diff --git a/benches/engine.rs b/benches/engine.rs index e505129f..49c67a92 100644 --- a/benches/engine.rs +++ b/benches/engine.rs @@ -3,7 +3,7 @@ ///! Test evaluating expressions extern crate test; -use rhai::{Array, Engine, Map, RegisterFn, INT}; +use rhai::{Array, CorePackage, Engine, Map, Package, RegisterFn, INT}; use test::Bencher; #[bench] @@ -16,6 +16,16 @@ fn bench_engine_new_raw(bench: &mut Bencher) { bench.iter(|| Engine::new_raw()); } +#[bench] +fn bench_engine_new_raw_core(bench: &mut Bencher) { + let package = CorePackage::new(); + + bench.iter(|| { + let mut engine = Engine::new_raw(); + engine.load_package(package.get()); + }); +} + #[bench] fn bench_engine_register_fn(bench: &mut Bencher) { fn hello(a: INT, b: Array, c: Map) -> bool { diff --git a/benches/iterations.rs b/benches/iterations.rs index 073f0a91..23eb7621 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -3,7 +3,7 @@ ///! Test 1,000 iterations extern crate test; -use rhai::{Engine, OptimizationLevel, Scope, INT}; +use rhai::{Engine, OptimizationLevel, INT}; use test::Bencher; #[bench] @@ -34,6 +34,8 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) { fibonacci(n-1) + fibonacci(n-2) } } + + fibonacci(20) "#; let mut engine = Engine::new(); @@ -41,9 +43,5 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) { let ast = engine.compile(script).unwrap(); - bench.iter(|| { - engine - .call_fn::<_, INT>(&mut Scope::new(), &ast, "fibonacci", (20 as INT,)) - .unwrap() - }); + bench.iter(|| engine.eval_ast::(&ast).unwrap()); } diff --git a/examples/hello.rs b/examples/hello.rs index c72a38b5..e999861f 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,7 +1,9 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{packages::*, Engine, EvalAltResult, INT}; +use std::rc::Rc; fn main() -> Result<(), EvalAltResult> { - let engine = Engine::new(); + let mut engine = Engine::new_raw(); + engine.load_package(ArithmeticPackage::new().get()); let result = engine.eval::("40 + 2")?; diff --git a/src/builtin.rs b/src/builtin.rs index 428de9b7..9e3a6bd5 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -48,7 +48,6 @@ macro_rules! reg_op { ) } -#[cfg(not(feature = "unchecked"))] macro_rules! reg_op_result { ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( $( @@ -57,7 +56,6 @@ macro_rules! reg_op_result { ) } -#[cfg(not(feature = "unchecked"))] macro_rules! reg_op_result1 { ($self:expr, $x:expr, $op:expr, $v:ty, $( $y:ty ),*) => ( $( @@ -98,7 +96,6 @@ impl Engine { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { // Checked add - #[cfg(not(feature = "unchecked"))] fn add(x: T, y: T) -> Result { x.checked_add(&y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( @@ -108,7 +105,6 @@ impl Engine { }) } // Checked subtract - #[cfg(not(feature = "unchecked"))] fn sub(x: T, y: T) -> Result { x.checked_sub(&y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( @@ -118,7 +114,6 @@ impl Engine { }) } // Checked multiply - #[cfg(not(feature = "unchecked"))] fn mul(x: T, y: T) -> Result { x.checked_mul(&y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( @@ -128,7 +123,6 @@ impl Engine { }) } // Checked divide - #[cfg(not(feature = "unchecked"))] fn div(x: T, y: T) -> Result where T: Display + CheckedDiv + PartialEq + Zero, @@ -149,7 +143,6 @@ impl Engine { }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX - #[cfg(not(feature = "unchecked"))] fn neg(x: T) -> Result { x.checked_neg().ok_or_else(|| { EvalAltResult::ErrorArithmetic( @@ -159,7 +152,6 @@ impl Engine { }) } // Checked absolute - #[cfg(not(feature = "unchecked"))] 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. @@ -175,32 +167,26 @@ impl Engine { } } // Unchecked add - may panic on overflow - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> ::Output { x + y } // Unchecked subtract - may panic on underflow - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> ::Output { x - y } // Unchecked multiply - may panic on overflow - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> ::Output { x * y } // Unchecked divide - may panic when dividing by zero - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> ::Output { x / y } // Unchecked negative - may panic on overflow - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> ::Output { -x } // Unchecked absolute - may panic on overflow - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> ::Output where T: Neg + PartialOrd + Default + Into<::Output>, @@ -236,7 +222,6 @@ impl Engine { } // Checked left-shift - #[cfg(not(feature = "unchecked"))] fn shl(x: T, y: INT) -> Result { // Cannot shift by a negative number of bits if y < 0 { @@ -254,7 +239,6 @@ impl Engine { }) } // Checked right-shift - #[cfg(not(feature = "unchecked"))] fn shr(x: T, y: INT) -> Result { // Cannot shift by a negative number of bits if y < 0 { @@ -272,17 +256,14 @@ impl Engine { }) } // Unchecked left-shift - may panic if shifting by a negative number of bits - #[cfg(feature = "unchecked")] fn shl_u>(x: T, y: T) -> >::Output { x.shl(y) } // Unchecked right-shift - may panic if shifting by a negative number of bits - #[cfg(feature = "unchecked")] fn shr_u>(x: T, y: T) -> >::Output { x.shr(y) } // Checked modulo - #[cfg(not(feature = "unchecked"))] fn modulo(x: T, y: T) -> Result { x.checked_rem(&y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( @@ -292,12 +273,10 @@ impl Engine { }) } // Unchecked modulo - may panic if dividing by zero - #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> ::Output { x % y } // Checked power - #[cfg(not(feature = "unchecked"))] fn pow_i_i(x: INT, y: INT) -> Result { #[cfg(not(feature = "only_i32"))] { @@ -339,7 +318,6 @@ impl Engine { } } // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) - #[cfg(feature = "unchecked")] fn pow_i_i_u(x: INT, y: INT) -> INT { x.pow(y as u32) } @@ -349,7 +327,6 @@ impl Engine { x.powf(y) } // Checked power - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_float"))] fn pow_f_i(x: FLOAT, y: INT) -> Result { // Raise to power that is larger than an i32 @@ -411,34 +388,32 @@ impl Engine { reg_op!(self, "/", div_u, f32, f64); } + reg_cmp!(self, "<", lt, INT, String, char); + reg_cmp!(self, "<=", lte, INT, String, char); + reg_cmp!(self, ">", gt, INT, String, char); + reg_cmp!(self, ">=", gte, INT, String, char); + reg_cmp!(self, "==", eq, INT, String, char, bool); + reg_cmp!(self, "!=", ne, INT, String, char, bool); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - reg_cmp!(self, "<", lt, INT, String, char); - reg_cmp!(self, "<=", lte, INT, String, char); - reg_cmp!(self, ">", gt, INT, String, char); - reg_cmp!(self, ">=", gte, INT, String, char); - reg_cmp!(self, "==", eq, INT, String, char, bool); - reg_cmp!(self, "!=", ne, INT, String, char, bool); + reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - } - - #[cfg(not(feature = "no_float"))] - { - reg_cmp!(self, "<", lt, f32, f64); - reg_cmp!(self, "<=", lte, f32, f64); - reg_cmp!(self, ">", gt, f32, f64); - reg_cmp!(self, ">=", gte, f32, f64); - reg_cmp!(self, "==", eq, f32, f64); - reg_cmp!(self, "!=", ne, f32, f64); - } + #[cfg(not(feature = "no_float"))] + { + reg_cmp!(self, "<", lt, f32, f64); + reg_cmp!(self, "<=", lte, f32, f64); + reg_cmp!(self, ">", gt, f32, f64); + reg_cmp!(self, ">=", gte, f32, f64); + reg_cmp!(self, "==", eq, f32, f64); + reg_cmp!(self, "!=", ne, f32, f64); } // `&&` and `||` are treated specially as they short-circuit. @@ -517,59 +492,57 @@ impl Engine { self.register_fn("~", pow_f_i_u); } - { - macro_rules! reg_un { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y)->$y); - )* - ) - } - - #[cfg(not(feature = "unchecked"))] - macro_rules! reg_un_result { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>); - )* - ) - } - - #[cfg(not(feature = "unchecked"))] - { - reg_un_result!(self, "-", neg, INT); - reg_un_result!(self, "abs", abs, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_un_result!(self, "-", neg, i8, i16, i32, i64); - reg_un_result!(self, "abs", abs, i8, i16, i32, i64); - } - } - - #[cfg(feature = "unchecked")] - { - reg_un!(self, "-", neg_u, INT); - reg_un!(self, "abs", abs_u, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_un!(self, "-", neg_u, i8, i16, i32, i64); - reg_un!(self, "abs", abs_u, i8, i16, i32, i64); - } - } - - #[cfg(not(feature = "no_float"))] - { - reg_un!(self, "-", neg_u, f32, f64); - reg_un!(self, "abs", abs_u, f32, f64); - } - - reg_un!(self, "!", not, bool); + macro_rules! reg_un { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$y); + )* + ) } + #[cfg(not(feature = "unchecked"))] + macro_rules! reg_un_result { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>); + )* + ) + } + + #[cfg(not(feature = "unchecked"))] + { + reg_un_result!(self, "-", neg, INT); + reg_un_result!(self, "abs", abs, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un_result!(self, "-", neg, i8, i16, i32, i64, i128); + reg_un_result!(self, "abs", abs, i8, i16, i32, i64, i128); + } + } + + #[cfg(feature = "unchecked")] + { + reg_un!(self, "-", neg_u, INT); + reg_un!(self, "abs", abs_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un!(self, "-", neg_u, i8, i16, i32, i64, i128); + reg_un!(self, "abs", abs_u, i8, i16, i32, i64, i128); + } + } + + #[cfg(not(feature = "no_float"))] + { + reg_un!(self, "-", neg_u, f32, f64); + reg_un!(self, "abs", abs_u, f32, f64); + } + + reg_un!(self, "!", not, bool); + self.register_fn("+", |x: String, y: String| x + &y); // String + String self.register_fn("==", |_: (), _: ()| true); // () == () @@ -581,78 +554,76 @@ impl Engine { format!("{}", x) } + macro_rules! reg_fn1 { + ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$r); + )* + ) + } + + reg_fn1!(self, KEYWORD_PRINT, to_string, String, INT, bool); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, INT, bool); + reg_fn1!(self, KEYWORD_PRINT, to_string, String, char, String); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, char, String); + self.register_fn(KEYWORD_PRINT, || "".to_string()); + self.register_fn(KEYWORD_PRINT, |_: ()| "".to_string()); + self.register_fn(FUNC_TO_STRING, |_: ()| "".to_string()); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, INT, bool, ()); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, char, String); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - macro_rules! reg_fn1 { - ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y)->$r); - )* - ) - } + reg_fn1!(self, KEYWORD_PRINT, to_string, String, i8, u8, i16, u16); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16); + reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, u32, i64, u64); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, i32, u32, i64, u64); + reg_fn1!(self, KEYWORD_PRINT, to_string, String, i128, u128); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, i128, u128); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i32, u32, i64, u64); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i128, u128); + } - reg_fn1!(self, KEYWORD_PRINT, to_string, String, INT, bool); - reg_fn1!(self, FUNC_TO_STRING, to_string, String, INT, bool); - reg_fn1!(self, KEYWORD_PRINT, to_string, String, char, String); - reg_fn1!(self, FUNC_TO_STRING, to_string, String, char, String); - self.register_fn(KEYWORD_PRINT, || "".to_string()); - self.register_fn(KEYWORD_PRINT, |_: ()| "".to_string()); - self.register_fn(FUNC_TO_STRING, |_: ()| "".to_string()); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, INT, bool, ()); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, char, String); + #[cfg(not(feature = "no_float"))] + { + reg_fn1!(self, KEYWORD_PRINT, to_string, String, f32, f64); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, f32, f64); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, f32, f64); + } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_fn1!(self, KEYWORD_PRINT, to_string, String, i8, u8, i16, u16); - reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16); - reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, i64, u32, u64); - reg_fn1!(self, FUNC_TO_STRING, to_string, String, i32, i64, u32, u64); - reg_fn1!(self, KEYWORD_PRINT, to_string, String, i128, u128); - reg_fn1!(self, FUNC_TO_STRING, to_string, String, i128, u128); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i32, i64, u32, u64); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i128, u128); - } + #[cfg(not(feature = "no_index"))] + { + reg_fn1!(self, KEYWORD_PRINT, to_debug, String, Array); + reg_fn1!(self, FUNC_TO_STRING, to_debug, String, Array); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array); - #[cfg(not(feature = "no_float"))] - { - reg_fn1!(self, KEYWORD_PRINT, to_string, String, f32, f64); - reg_fn1!(self, FUNC_TO_STRING, to_string, String, f32, f64); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, f32, f64); - } + // Register array iterator + self.register_iterator::(|a: &Dynamic| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + as Box> + }); + } + + #[cfg(not(feature = "no_object"))] + { + self.register_fn(KEYWORD_PRINT, |x: &mut Map| format!("#{:?}", x)); + self.register_fn(FUNC_TO_STRING, |x: &mut Map| format!("#{:?}", x)); + self.register_fn(KEYWORD_DEBUG, |x: &mut Map| format!("#{:?}", x)); + + // Register map access functions + #[cfg(not(feature = "no_index"))] + self.register_fn("keys", |map: Map| { + map.iter() + .map(|(k, _)| Dynamic::from(k.clone())) + .collect::>() + }); #[cfg(not(feature = "no_index"))] - { - reg_fn1!(self, KEYWORD_PRINT, to_debug, String, Array); - reg_fn1!(self, FUNC_TO_STRING, to_debug, String, Array); - reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array); - - // Register array iterator - self.register_iterator::(|a: &Dynamic| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - as Box> - }); - } - - #[cfg(not(feature = "no_object"))] - { - self.register_fn(KEYWORD_PRINT, |x: &mut Map| format!("#{:?}", x)); - self.register_fn(FUNC_TO_STRING, |x: &mut Map| format!("#{:?}", x)); - self.register_fn(KEYWORD_DEBUG, |x: &mut Map| format!("#{:?}", x)); - - // Register map access functions - #[cfg(not(feature = "no_index"))] - self.register_fn("keys", |map: Map| { - map.iter() - .map(|(k, _)| Dynamic::from(k.clone())) - .collect::>() - }); - - #[cfg(not(feature = "no_index"))] - self.register_fn("values", |map: Map| { - map.into_iter().map(|(_, v)| v).collect::>() - }); - } + self.register_fn("values", |map: Map| { + map.into_iter().map(|(_, v)| v).collect::>() + }); } // Register range function @@ -909,6 +880,7 @@ impl Engine { reg_fn2x!(self, "push", push, &mut Array, (), String, Array, ()); reg_fn3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), INT, bool, char); reg_fn3!(self, "insert", ins, &mut Array, INT, (), String, Array, ()); self.register_fn("append", |list: &mut Array, array: Array| { diff --git a/src/engine.rs b/src/engine.rs index ad995cda..c8828aa5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Union}; use crate::error::ParseErrorType; use crate::optimize::OptimizationLevel; +use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; use crate::parser::{Expr, FnDef, ReturnType, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; @@ -41,9 +42,9 @@ pub type FnAny = pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result>; #[cfg(feature = "sync")] -type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + Sync; +pub type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + Sync; #[cfg(not(feature = "sync"))] -type IteratorFn = dyn Fn(&Dynamic) -> Box>; +pub type IteratorFn = dyn Fn(&Dynamic) -> Box>; #[cfg(debug_assertions)] pub const MAX_CALL_STACK_DEPTH: usize = 28; @@ -221,6 +222,8 @@ impl DerefMut for FunctionsLib { /// /// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. pub struct Engine { + /// A collection of all library packages loaded into the engine. + pub(crate) packages: Vec, /// A hashmap containing all compiled functions known to the engine. pub(crate) functions: HashMap>, @@ -255,7 +258,8 @@ pub struct Engine { impl Default for Engine { fn default() -> Self { // Create the new scripting Engine - let mut engine = Engine { + let mut engine = Self { + packages: Vec::new(), functions: HashMap::with_capacity(FUNCTIONS_COUNT), type_iterators: HashMap::new(), type_names: None, @@ -279,10 +283,11 @@ impl Default for Engine { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; - engine.register_core_lib(); + #[cfg(feature = "no_stdlib")] + engine.load_package(CorePackage::new().get()); #[cfg(not(feature = "no_stdlib"))] - engine.register_stdlib(); + engine.load_package(StandardPackage::new().get()); engine } @@ -442,9 +447,11 @@ impl Engine { Default::default() } - /// Create a new `Engine` with minimal configurations without the standard library etc. + /// Create a new `Engine` with _no_ built-in functions. + /// Use the `load_package` method to load packages of functions. pub fn new_raw() -> Self { - let mut engine = Engine { + Self { + packages: Vec::new(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), type_iterators: HashMap::new(), type_names: None, @@ -463,11 +470,11 @@ impl Engine { optimization_level: OptimizationLevel::Full, max_call_stack_depth: MAX_CALL_STACK_DEPTH, - }; + } + } - engine.register_core_lib(); - - engine + pub fn load_package(&mut self, package: PackageLibrary) { + self.packages.insert(0, package); } /// Control whether and how the `Engine` will optimize an AST after compilation @@ -571,7 +578,12 @@ impl Engine { // Search built-in's and external functions let fn_spec = calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())); - if let Some(func) = self.functions.get(&fn_spec) { + if let Some(func) = self.functions.get(&fn_spec).or_else(|| { + self.packages + .iter() + .find(|p| p.0.contains_key(&fn_spec)) + .and_then(|p| p.0.get(&fn_spec)) + }) { // Run external function let result = func(args, pos)?; @@ -1540,7 +1552,12 @@ impl Engine { let arr = self.eval_expr(scope, fn_lib, expr, level)?; let tid = arr.type_id(); - if let Some(iter_fn) = self.type_iterators.get(&tid) { + if let Some(iter_fn) = self.type_iterators.get(&tid).or_else(|| { + self.packages + .iter() + .find(|p| p.1.contains_key(&tid)) + .and_then(|p| p.1.get(&tid)) + }) { // Add the loop variable - variable name is copied // TODO - avoid copying variable name scope.push(name.clone(), ()); diff --git a/src/fn_register.rs b/src/fn_register.rs index 269faedd..393bda5a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -117,10 +117,16 @@ pub struct Mut(T); /// Identity dereferencing function. #[inline] -fn identity(data: T) -> T { +pub fn identity(data: &mut T) -> &mut T { data } +/// Clone dereferencing function. +#[inline] +pub fn cloned(data: &mut T) -> T { + data.clone() +} + /// This macro counts the number of arguments via recursion. macro_rules! count_args { () => { 0_usize }; @@ -128,7 +134,6 @@ macro_rules! count_args { } /// This macro creates a closure wrapping a registered function. -#[macro_export] macro_rules! make_func { ($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $clone:expr),*) => { // ^ function name @@ -254,8 +259,8 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*); - def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*); + def_register!(imp $p0 => $p0 => $p0 => cloned $(, $p => $p => $p => cloned)*); + def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => cloned)*); // handle the first parameter ^ first parameter passed through // ^ others passed by value (cloned) diff --git a/src/lib.rs b/src/lib.rs index de8e80db..a8468f0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ mod fn_call; mod fn_func; mod fn_register; mod optimize; +pub mod packages; mod parser; mod result; mod scope; diff --git a/src/optimize.rs b/src/optimize.rs index 39a5b995..83488630 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -3,6 +3,7 @@ use crate::engine::{ calc_fn_spec, Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; +use crate::packages::PackageLibrary; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; @@ -110,14 +111,23 @@ impl<'a> State<'a> { /// Call a registered function fn call_fn( + packages: &Vec, functions: &HashMap>, fn_name: &str, args: &mut FnCallArgs, pos: Position, ) -> Result, Box> { // Search built-in's and external functions + let hash = calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())); + functions - .get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id()))) + .get(&hash) + .or_else(|| { + packages + .iter() + .find(|p| p.0.contains_key(&hash)) + .and_then(|p| p.0.get(&hash)) + }) .map(|func| func(args, pos)) .transpose() } @@ -576,7 +586,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - call_fn(&state.engine.functions, &id, &mut call_args, pos).ok() + call_fn(&state.engine.packages, &state.engine.functions, &id, &mut call_args, pos).ok() .and_then(|result| result.or_else(|| { if !arg_for_type_of.is_empty() { diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs new file mode 100644 index 00000000..acc12f0d --- /dev/null +++ b/src/packages/arithmetic.rs @@ -0,0 +1,442 @@ +use super::{ + create_new_package, reg_binary, reg_unary, Package, PackageLibrary, PackageLibraryStore, +}; + +use crate::fn_register::{map_dynamic as map, map_result as result}; +use crate::parser::INT; +use crate::result::EvalAltResult; +use crate::token::Position; + +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + +use num_traits::{ + identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, + CheckedShr, CheckedSub, +}; + +use crate::stdlib::{ + fmt::Display, + format, + ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Rem, Shl, Shr, Sub}, + {i32, i64, u32}, +}; + +// Checked add +fn add(x: T, y: T) -> Result { + x.checked_add(&y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Addition overflow: {} + {}", x, y), + Position::none(), + ) + }) +} +// Checked subtract +fn sub(x: T, y: T) -> Result { + x.checked_sub(&y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Subtraction underflow: {} - {}", x, y), + Position::none(), + ) + }) +} +// Checked multiply +fn mul(x: T, y: T) -> Result { + x.checked_mul(&y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Multiplication overflow: {} * {}", x, y), + Position::none(), + ) + }) +} +// Checked divide +fn div(x: T, y: T) -> Result +where + T: Display + CheckedDiv + PartialEq + Zero, +{ + // Detect division by zero + if y == T::zero() { + return Err(EvalAltResult::ErrorArithmetic( + format!("Division by zero: {} / {}", x, y), + Position::none(), + )); + } + + x.checked_div(&y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Division overflow: {} / {}", x, y), + Position::none(), + ) + }) +} +// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX +fn neg(x: T) -> Result { + x.checked_neg().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + }) +} +// Checked absolute +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(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + }) + } +} +// Unchecked add - may panic on overflow +fn add_u(x: T, y: T) -> ::Output { + x + y +} +// Unchecked subtract - may panic on underflow +fn sub_u(x: T, y: T) -> ::Output { + x - y +} +// Unchecked multiply - may panic on overflow +fn mul_u(x: T, y: T) -> ::Output { + x * y +} +// Unchecked divide - may panic when dividing by zero +fn div_u(x: T, y: T) -> ::Output { + x / y +} +// Unchecked negative - may panic on overflow +fn neg_u(x: T) -> ::Output { + -x +} +// Unchecked absolute - may panic on overflow +fn abs_u(x: T) -> ::Output +where + T: Neg + PartialOrd + Default + Into<::Output>, +{ + // Numbers should default to zero + if x < Default::default() { + -x + } else { + x.into() + } +} +// Bit operators +fn binary_and(x: T, y: T) -> ::Output { + x & y +} +fn binary_or(x: T, y: T) -> ::Output { + x | y +} +fn binary_xor(x: T, y: T) -> ::Output { + x ^ y +} +// Checked left-shift +fn shl(x: T, y: INT) -> Result { + // Cannot shift by a negative number of bits + if y < 0 { + return Err(EvalAltResult::ErrorArithmetic( + format!("Left-shift by a negative number: {} << {}", x, y), + Position::none(), + )); + } + + CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by too many bits: {} << {}", x, y), + Position::none(), + ) + }) +} +// Checked right-shift +fn shr(x: T, y: INT) -> Result { + // Cannot shift by a negative number of bits + if y < 0 { + return Err(EvalAltResult::ErrorArithmetic( + format!("Right-shift by a negative number: {} >> {}", x, y), + Position::none(), + )); + } + + CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by too many bits: {} % {}", x, y), + Position::none(), + ) + }) +} +// Unchecked left-shift - may panic if shifting by a negative number of bits +fn shl_u>(x: T, y: T) -> >::Output { + x.shl(y) +} +// Unchecked right-shift - may panic if shifting by a negative number of bits +fn shr_u>(x: T, y: T) -> >::Output { + x.shr(y) +} +// Checked modulo +fn modulo(x: T, y: T) -> Result { + x.checked_rem(&y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Modulo division by zero or overflow: {} % {}", x, y), + Position::none(), + ) + }) +} +// Unchecked modulo - may panic if dividing by zero +fn modulo_u(x: T, y: T) -> ::Output { + x % y +} +// Checked power +fn pow_i_i(x: INT, y: INT) -> Result { + #[cfg(not(feature = "only_i32"))] + { + if y > (u32::MAX as INT) { + Err(EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), + Position::none(), + )) + } else if y < 0 { + Err(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + )) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } + } + + #[cfg(feature = "only_i32")] + { + if y < 0 { + Err(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + )) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } + } +} +// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) +fn pow_i_i_u(x: INT, y: INT) -> INT { + x.pow(y as u32) +} +// Floating-point power - always well-defined +#[cfg(not(feature = "no_float"))] +fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { + x.powf(y) +} +// Checked power +#[cfg(not(feature = "no_float"))] +fn pow_f_i(x: FLOAT, y: INT) -> Result { + // Raise to power that is larger than an i32 + if y > (i32::MAX as INT) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Number raised to too large an index: {} ~ {}", x, y), + Position::none(), + )); + } + + Ok(x.powi(y as i32)) +} +// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) +#[cfg(feature = "unchecked")] +#[cfg(not(feature = "no_float"))] +fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT { + x.powi(y as i32) +} + +pub struct ArithmeticPackage(PackageLibrary); + +impl Deref for ArithmeticPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +macro_rules! reg_unary_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_unary($lib, $op, $func::<$par>, result);)* }; +} +macro_rules! reg_unary { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_unary($lib, $op, $func::<$par>, map);)* }; +} +macro_rules! reg_op_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_binary($lib, $op, $func::<$par>, result);)* }; +} +macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_binary($lib, $op, $func::<$par>, map);)* }; +} + +impl Package for ArithmeticPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + // Checked basic arithmetic + #[cfg(not(feature = "unchecked"))] + { + reg_op_x!(lib, "+", add, INT); + reg_op_x!(lib, "-", sub, INT); + reg_op_x!(lib, "*", mul, INT); + reg_op_x!(lib, "/", div, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op_x!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_x!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_x!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_x!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + } + + // Unchecked basic arithmetic + #[cfg(feature = "unchecked")] + { + reg_op!(lib, "+", add_u, INT); + reg_op!(lib, "-", sub_u, INT); + reg_op!(lib, "*", mul_u, INT); + reg_op!(lib, "/", div_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + } + + // Basic arithmetic for floating-point - no need to check + #[cfg(not(feature = "no_float"))] + { + reg_op!(lib, "+", add_u, f32, f64); + reg_op!(lib, "-", sub_u, f32, f64); + reg_op!(lib, "*", mul_u, f32, f64); + reg_op!(lib, "/", div_u, f32, f64); + } + + // Bit operations + reg_op!(lib, "|", binary_or, INT); + reg_op!(lib, "&", binary_and, INT); + reg_op!(lib, "^", binary_xor, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + + // Checked bit shifts + #[cfg(not(feature = "unchecked"))] + { + reg_op_x!(lib, "<<", shl, INT); + reg_op_x!(lib, ">>", shr, INT); + reg_op_x!(lib, "%", modulo, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op_x!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_x!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_x!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + } + + // Unchecked bit shifts + #[cfg(feature = "unchecked")] + { + reg_op!(lib, "<<", shl_u, INT, INT); + reg_op!(lib, ">>", shr_u, INT, INT); + reg_op!(lib, "%", modulo_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + } + + // Checked power + #[cfg(not(feature = "unchecked"))] + { + reg_binary(lib, "~", pow_i_i, result); + + #[cfg(not(feature = "no_float"))] + reg_binary(lib, "~", pow_f_i, result); + } + + // Unchecked power + #[cfg(feature = "unchecked")] + { + reg_binary(lib, "~", pow_i_i_u, map); + + #[cfg(not(feature = "no_float"))] + reg_binary(lib, "~", pow_f_i_u, map); + } + + // Floating-point modulo and power + #[cfg(not(feature = "no_float"))] + { + reg_op!(lib, "%", modulo_u, f32, f64); + reg_binary(lib, "~", pow_f_f, map); + } + + // Checked unary + #[cfg(not(feature = "unchecked"))] + { + reg_unary_x!(lib, "-", neg, INT); + reg_unary_x!(lib, "abs", abs, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_unary_x!(lib, "-", neg, i8, i16, i32, i64, i128); + reg_unary_x!(lib, "abs", abs, i8, i16, i32, i64, i128); + } + } + + // Unchecked unary + #[cfg(feature = "unchecked")] + { + reg_unary!(lib, "-", neg_u, INT); + reg_unary!(lib, "abs", abs_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_unary!(lib, "-", neg_u, i8, i16, i32, i64, i128); + reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128); + } + } + + // Floating-point unary + #[cfg(not(feature = "no_float"))] + { + reg_unary!(lib, "-", neg_u, f32, f64); + reg_unary!(lib, "abs", abs_u, f32, f64); + } + } +} diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs new file mode 100644 index 00000000..5dbeecdd --- /dev/null +++ b/src/packages/array_basic.rs @@ -0,0 +1,139 @@ +use super::{ + create_new_package, reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut, Package, + PackageLibrary, PackageLibraryStore, +}; + +use crate::any::{Dynamic, Variant}; +use crate::engine::Array; +use crate::fn_register::{map_dynamic as map, map_identity as copy}; +use crate::parser::INT; + +use crate::stdlib::ops::Deref; + +// Register array utility functions +fn push(list: &mut Array, item: T) { + list.push(Dynamic::from(item)); +} +fn ins(list: &mut Array, position: INT, item: T) { + if position <= 0 { + list.insert(0, Dynamic::from(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Dynamic::from(item)); + } +} +fn pad(list: &mut Array, len: INT, item: T) { + if len >= 0 { + while list.len() < len as usize { + push(list, item.clone()); + } + } +} + +pub struct BasicArrayPackage(PackageLibrary); + +impl Deref for BasicArrayPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_binary_mut($lib, $op, $func::<$par>, map);)* }; +} +macro_rules! reg_tri { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_trinary_mut($lib, $op, $func::<$par>, map);)* }; +} + +impl Package for BasicArrayPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + #[cfg(not(feature = "no_index"))] + { + reg_op!(lib, "push", push, INT, bool, char, String, Array, ()); + reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ()); + reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ()); + + reg_binary_mut(lib, "append", |x: &mut Array, y: Array| x.extend(y), map); + reg_binary( + lib, + "+", + |mut x: Array, y: Array| { + x.extend(y); + x + }, + map, + ); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_tri!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); + reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + + #[cfg(not(feature = "no_float"))] + { + reg_op!(lib, "push", push, f32, f64); + reg_tri!(lib, "pad", pad, f32, f64); + reg_tri!(lib, "insert", ins, f32, f64); + } + + reg_unary_mut( + lib, + "pop", + |list: &mut Array| list.pop().unwrap_or_else(|| Dynamic::from_unit()), + copy, + ); + reg_unary_mut( + lib, + "shift", + |list: &mut Array| { + if !list.is_empty() { + Dynamic::from_unit() + } else { + list.remove(0) + } + }, + copy, + ); + reg_binary_mut( + lib, + "remove", + |list: &mut Array, len: INT| { + if len < 0 || (len as usize) >= list.len() { + Dynamic::from_unit() + } else { + list.remove(len as usize) + } + }, + copy, + ); + reg_unary_mut(lib, "len", |list: &mut Array| list.len() as INT, map); + reg_unary_mut(lib, "clear", |list: &mut Array| list.clear(), map); + reg_binary_mut( + lib, + "truncate", + |list: &mut Array, len: INT| { + if len >= 0 { + list.truncate(len as usize); + } + }, + map, + ); + } + } +} diff --git a/src/packages/basic.rs b/src/packages/basic.rs new file mode 100644 index 00000000..ecc619e1 --- /dev/null +++ b/src/packages/basic.rs @@ -0,0 +1,33 @@ +use super::{ + create_new_package, reg_binary, reg_binary_mut, reg_unary, Package, PackageLibrary, + PackageLibraryStore, +}; + +use crate::fn_register::map_dynamic as map; +use crate::parser::INT; + +use crate::stdlib::ops::Deref; + +pub struct BasicPackage(PackageLibrary); + +impl Deref for BasicPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for BasicPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) {} +} diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs new file mode 100644 index 00000000..a5ebd896 --- /dev/null +++ b/src/packages/iter_basic.rs @@ -0,0 +1,173 @@ +use super::{ + create_new_package, reg_binary, reg_trinary, reg_unary_mut, Package, PackageLibrary, + PackageLibraryStore, +}; + +use crate::any::{Dynamic, Union, Variant}; +use crate::engine::{Array, Map}; +use crate::fn_register::map_dynamic as map; +use crate::parser::INT; + +use crate::stdlib::{ + any::TypeId, + ops::{Add, Deref, Range}, +}; + +// Register range function +fn reg_range(lib: &mut PackageLibraryStore) +where + Range: Iterator, +{ + lib.1.insert( + TypeId::of::>(), + Box::new(|source: &Dynamic| { + Box::new( + source + .downcast_ref::>() + .cloned() + .unwrap() + .map(|x| x.into_dynamic()), + ) as Box> + }), + ); +} + +// Register range function with step +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +struct StepRange(T, T, T) +where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Variant + Clone + PartialOrd; + +impl Iterator for StepRange +where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Variant + Clone + PartialOrd, +{ + type Item = T; + + fn next(&mut self) -> Option { + if self.0 < self.1 { + let v = self.0.clone(); + self.0 = &v + &self.2; + Some(v) + } else { + None + } + } +} + +fn reg_step(lib: &mut PackageLibraryStore) +where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Variant + Clone + PartialOrd, + StepRange: Iterator, +{ + lib.1.insert( + TypeId::of::>(), + Box::new(|source: &Dynamic| { + Box::new( + source + .downcast_ref::>() + .cloned() + .unwrap() + .map(|x| x.into_dynamic()), + ) as Box> + }), + ); +} + +pub struct BasicIteratorPackage(PackageLibrary); + +impl Deref for BasicIteratorPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for BasicIteratorPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + #[cfg(not(feature = "no_index"))] + { + // Register array iterator + lib.1.insert( + TypeId::of::(), + Box::new(|a: &Dynamic| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + as Box> + }), + ); + } + + // Register map access functions + #[cfg(not(feature = "no_object"))] + { + fn map_get_keys(map: &mut Map) -> Vec { + map.iter() + .map(|(k, _)| Dynamic(Union::Str(Box::new(k.to_string())))) + .collect::>() + } + fn map_get_values(map: &mut Map) -> Vec { + map.iter().map(|(_, v)| v.clone()).collect::>() + } + + #[cfg(not(feature = "no_index"))] + reg_unary_mut(lib, "keys", map_get_keys, map); + + #[cfg(not(feature = "no_index"))] + reg_unary_mut(lib, "values", map_get_values, map); + } + + fn get_range(from: T, to: T) -> Range { + from..to + } + + reg_range::(lib); + reg_binary(lib, "range", get_range::, map); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + macro_rules! reg_range { + ($self:expr, $x:expr, $( $y:ty ),*) => ( + $( + reg_range::<$y>($self); + reg_binary($self, $x, get_range::<$y>, map); + )* + ) + } + + reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + + reg_step::(lib); + reg_trinary(lib, "range", StepRange::, map); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + macro_rules! reg_step { + ($self:expr, $x:expr, $( $y:ty ),*) => ( + $( + reg_step::<$y>($self); + reg_trinary($self, $x, StepRange::<$y>, map); + )* + ) + } + + reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + } +} diff --git a/src/packages/logic.rs b/src/packages/logic.rs new file mode 100644 index 00000000..2244558a --- /dev/null +++ b/src/packages/logic.rs @@ -0,0 +1,113 @@ +use super::{ + create_new_package, reg_binary, reg_binary_mut, reg_unary, Package, PackageLibrary, + PackageLibraryStore, +}; + +use crate::fn_register::map_dynamic as map; +use crate::parser::INT; + +use crate::stdlib::ops::Deref; + +// Comparison operators +pub fn lt(x: T, y: T) -> bool { + x < y +} +pub fn lte(x: T, y: T) -> bool { + x <= y +} +pub fn gt(x: T, y: T) -> bool { + x > y +} +pub fn gte(x: T, y: T) -> bool { + x >= y +} +pub fn eq(x: T, y: T) -> bool { + x == y +} +pub fn ne(x: T, y: T) -> bool { + x != y +} + +// Logic operators +fn and(x: bool, y: bool) -> bool { + x && y +} +fn or(x: bool, y: bool) -> bool { + x || y +} +fn not(x: bool) -> bool { + !x +} + +macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_binary($lib, $op, $func::<$par>, map);)* }; +} + +pub struct LogicPackage(PackageLibrary); + +impl Deref for LogicPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for LogicPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + reg_op!(lib, "<", lt, INT, char); + reg_op!(lib, "<=", lte, INT, char); + reg_op!(lib, ">", gt, INT, char); + reg_op!(lib, ">=", gte, INT, char); + reg_op!(lib, "==", eq, INT, char, bool, ()); + reg_op!(lib, "!=", ne, INT, char, bool, ()); + + // Special versions for strings - at least avoid copying the first string + reg_binary_mut(lib, "<", |x: &mut String, y: String| *x < y, map); + reg_binary_mut(lib, "<=", |x: &mut String, y: String| *x <= y, map); + reg_binary_mut(lib, ">", |x: &mut String, y: String| *x > y, map); + reg_binary_mut(lib, ">=", |x: &mut String, y: String| *x >= y, map); + reg_binary_mut(lib, "==", |x: &mut String, y: String| *x == y, map); + reg_binary_mut(lib, "!=", |x: &mut String, y: String| *x != y, map); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + + #[cfg(not(feature = "no_float"))] + { + reg_op!(lib, "<", lt, f32, f64); + reg_op!(lib, "<=", lte, f32, f64); + reg_op!(lib, ">", gt, f32, f64); + reg_op!(lib, ">=", gte, f32, f64); + reg_op!(lib, "==", eq, f32, f64); + reg_op!(lib, "!=", ne, f32, f64); + } + + // `&&` and `||` are treated specially as they short-circuit. + // They are implemented as special `Expr` instances, not function calls. + //reg_op!(lib, "||", or, bool); + //reg_op!(lib, "&&", and, bool); + + reg_binary(lib, "|", or, map); + reg_binary(lib, "&", and, map); + reg_unary(lib, "!", not, map); + } +} diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs new file mode 100644 index 00000000..067da61f --- /dev/null +++ b/src/packages/map_basic.rs @@ -0,0 +1,75 @@ +use super::{ + create_new_package, reg_binary, reg_binary_mut, reg_unary_mut, Package, PackageLibrary, + PackageLibraryStore, +}; + +use crate::any::Dynamic; +use crate::engine::Map; +use crate::fn_register::map_dynamic as map; +use crate::parser::INT; + +use crate::stdlib::ops::Deref; + +pub struct BasicMapPackage(PackageLibrary); + +impl Deref for BasicMapPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for BasicMapPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + // Register map functions + #[cfg(not(feature = "no_object"))] + { + reg_binary_mut( + lib, + "has", + |map: &mut Map, prop: String| map.contains_key(&prop), + map, + ); + reg_unary_mut(lib, "len", |map: &mut Map| map.len() as INT, map); + reg_unary_mut(lib, "clear", |map: &mut Map| map.clear(), map); + reg_binary_mut( + lib, + "remove", + |x: &mut Map, name: String| x.remove(&name).unwrap_or_else(|| Dynamic::from_unit()), + map, + ); + reg_binary_mut( + lib, + "mixin", + |map1: &mut Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + }, + map, + ); + reg_binary( + lib, + "+", + |mut map1: Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + map1 + }, + map, + ); + } + } +} diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs new file mode 100644 index 00000000..2d5f3fb8 --- /dev/null +++ b/src/packages/math_basic.rs @@ -0,0 +1,154 @@ +use super::{ + create_new_package, reg_binary, reg_unary, Package, PackageLibrary, PackageLibraryStore, +}; + +use crate::fn_register::{map_dynamic as map, map_result as result}; +use crate::parser::INT; +use crate::result::EvalAltResult; +use crate::token::Position; + +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + +use crate::stdlib::{i32, i64, ops::Deref}; + +#[cfg(feature = "only_i32")] +const MAX_INT: INT = i32::MAX; +#[cfg(not(feature = "only_i32"))] +const MAX_INT: INT = i64::MAX; + +pub struct BasicMathPackage(PackageLibrary); + +impl Deref for BasicMathPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for BasicMathPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + #[cfg(not(feature = "no_float"))] + { + // Advanced math functions + reg_unary(lib, "sin", |x: FLOAT| x.to_radians().sin(), map); + reg_unary(lib, "cos", |x: FLOAT| x.to_radians().cos(), map); + reg_unary(lib, "tan", |x: FLOAT| x.to_radians().tan(), map); + reg_unary(lib, "sinh", |x: FLOAT| x.to_radians().sinh(), map); + reg_unary(lib, "cosh", |x: FLOAT| x.to_radians().cosh(), map); + reg_unary(lib, "tanh", |x: FLOAT| x.to_radians().tanh(), map); + reg_unary(lib, "asin", |x: FLOAT| x.asin().to_degrees(), map); + reg_unary(lib, "acos", |x: FLOAT| x.acos().to_degrees(), map); + reg_unary(lib, "atan", |x: FLOAT| x.atan().to_degrees(), map); + reg_unary(lib, "asinh", |x: FLOAT| x.asinh().to_degrees(), map); + reg_unary(lib, "acosh", |x: FLOAT| x.acosh().to_degrees(), map); + reg_unary(lib, "atanh", |x: FLOAT| x.atanh().to_degrees(), map); + reg_unary(lib, "sqrt", |x: FLOAT| x.sqrt(), map); + reg_unary(lib, "exp", |x: FLOAT| x.exp(), map); + reg_unary(lib, "ln", |x: FLOAT| x.ln(), map); + reg_binary(lib, "log", |x: FLOAT, base: FLOAT| x.log(base), map); + reg_unary(lib, "log10", |x: FLOAT| x.log10(), map); + reg_unary(lib, "floor", |x: FLOAT| x.floor(), map); + reg_unary(lib, "ceiling", |x: FLOAT| x.ceil(), map); + reg_unary(lib, "round", |x: FLOAT| x.ceil(), map); + reg_unary(lib, "int", |x: FLOAT| x.trunc(), map); + reg_unary(lib, "fraction", |x: FLOAT| x.fract(), map); + reg_unary(lib, "is_nan", |x: FLOAT| x.is_nan(), map); + reg_unary(lib, "is_finite", |x: FLOAT| x.is_finite(), map); + reg_unary(lib, "is_infinite", |x: FLOAT| x.is_infinite(), map); + + // Register conversion functions + reg_unary(lib, "to_float", |x: INT| x as FLOAT, map); + reg_unary(lib, "to_float", |x: f32| x as FLOAT, map); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_unary(lib, "to_float", |x: i8| x as FLOAT, map); + reg_unary(lib, "to_float", |x: u8| x as FLOAT, map); + reg_unary(lib, "to_float", |x: i16| x as FLOAT, map); + reg_unary(lib, "to_float", |x: u16| x as FLOAT, map); + reg_unary(lib, "to_float", |x: i32| x as FLOAT, map); + reg_unary(lib, "to_float", |x: u32| x as FLOAT, map); + reg_unary(lib, "to_float", |x: i64| x as FLOAT, map); + reg_unary(lib, "to_float", |x: u64| x as FLOAT, map); + reg_unary(lib, "to_float", |x: i128| x as FLOAT, map); + reg_unary(lib, "to_float", |x: u128| x as FLOAT, map); + } + } + + reg_unary(lib, "to_int", |ch: char| ch as INT, map); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_unary(lib, "to_int", |x: i8| x as INT, map); + reg_unary(lib, "to_int", |x: u8| x as INT, map); + reg_unary(lib, "to_int", |x: i16| x as INT, map); + reg_unary(lib, "to_int", |x: u16| x as INT, map); + } + + #[cfg(not(feature = "only_i32"))] + { + reg_unary(lib, "to_int", |x: i32| x as INT, map); + reg_unary(lib, "to_int", |x: u64| x as INT, map); + + #[cfg(feature = "only_i64")] + reg_unary(lib, "to_int", |x: u32| x as INT, map); + } + + #[cfg(not(feature = "no_float"))] + { + #[cfg(not(feature = "unchecked"))] + { + reg_unary( + lib, + "to_int", + |x: f32| { + if x > (MAX_INT as f32) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } + + Ok(x.trunc() as INT) + }, + result, + ); + reg_unary( + lib, + "to_int", + |x: FLOAT| { + if x > (MAX_INT as FLOAT) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } + + Ok(x.trunc() as INT) + }, + result, + ); + } + + #[cfg(feature = "unchecked")] + { + reg_unary(lib, "to_int", |x: f32| x as INT, map); + reg_unary(lib, "to_int", |x: f64| x as INT, map); + } + } + } +} diff --git a/src/packages/mod.rs b/src/packages/mod.rs new file mode 100644 index 00000000..ea5ffad1 --- /dev/null +++ b/src/packages/mod.rs @@ -0,0 +1,307 @@ +use crate::any::{Dynamic, Variant}; +use crate::engine::{calc_fn_spec, FnAny, FnCallArgs, IteratorFn}; +use crate::result::EvalAltResult; +use crate::token::Position; + +use crate::stdlib::{ + any::{type_name, TypeId}, + boxed::Box, + collections::HashMap, + ops::Deref, + rc::Rc, + sync::Arc, +}; + +mod arithmetic; +mod array_basic; +mod iter_basic; +mod logic; +mod map_basic; +mod math_basic; +mod pkg_core; +mod pkg_std; +mod string_basic; +mod string_more; +mod time_basic; + +pub use arithmetic::ArithmeticPackage; +pub use array_basic::BasicArrayPackage; +pub use iter_basic::BasicIteratorPackage; +pub use logic::LogicPackage; +pub use map_basic::BasicMapPackage; +pub use math_basic::BasicMathPackage; +pub use pkg_core::CorePackage; +pub use pkg_std::StandardPackage; +pub use string_basic::BasicStringPackage; +pub use string_more::MoreStringPackage; +pub use time_basic::BasicTimePackage; + +pub trait Package: Deref { + fn new() -> Self; + fn init(lib: &mut PackageLibraryStore); + fn get(&self) -> PackageLibrary; +} + +pub type PackageLibraryStore = (HashMap>, HashMap>); + +#[cfg(not(feature = "sync"))] +pub type PackageLibrary = Rc; + +#[cfg(feature = "sync")] +pub type PackageLibrary = Arc; + +fn check_num_args( + name: &str, + num_args: usize, + args: &mut FnCallArgs, + pos: Position, +) -> Result<(), Box> { + if args.len() != num_args { + Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch( + name.to_string(), + num_args, + args.len(), + pos, + ))) + } else { + Ok(()) + } +} + +fn create_new_package() -> PackageLibraryStore { + (HashMap::new(), HashMap::new()) +} + +fn reg_none( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn() -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn() -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + let hash = calc_fn_spec(fn_name, ([] as [TypeId; 0]).iter().cloned()); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 0, args, pos)?; + + let r = func(); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} + +fn reg_unary( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn(T) -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn(T) -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + //println!("register {}({})", fn_name, type_name::()); + + let hash = calc_fn_spec(fn_name, [TypeId::of::()].iter().cloned()); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 1, args, pos)?; + + let mut drain = args.iter_mut(); + let x: &mut T = drain.next().unwrap().downcast_mut().unwrap(); + + let r = func(x.clone()); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} + +fn reg_unary_mut( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&mut T) -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut T) -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + //println!("register {}(&mut {})", fn_name, type_name::()); + + let hash = calc_fn_spec(fn_name, [TypeId::of::()].iter().cloned()); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 1, args, pos)?; + + let mut drain = args.iter_mut(); + let x: &mut T = drain.next().unwrap().downcast_mut().unwrap(); + + let r = func(x); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} + +fn reg_binary( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B) -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + //println!("register {}({}, {})", fn_name, type_name::(), type_name::()); + + let hash = calc_fn_spec( + fn_name, + [TypeId::of::(), TypeId::of::()].iter().cloned(), + ); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 2, args, pos)?; + + let mut drain = args.iter_mut(); + let x: &mut A = drain.next().unwrap().downcast_mut().unwrap(); + let y: &mut B = drain.next().unwrap().downcast_mut().unwrap(); + + let r = func(x.clone(), y.clone()); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} + +fn reg_binary_mut( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + //println!("register {}(&mut {}, {})", fn_name, type_name::(), type_name::()); + + let hash = calc_fn_spec( + fn_name, + [TypeId::of::(), TypeId::of::()].iter().cloned(), + ); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 2, args, pos)?; + + let mut drain = args.iter_mut(); + let x: &mut A = drain.next().unwrap().downcast_mut().unwrap(); + let y: &mut B = drain.next().unwrap().downcast_mut().unwrap(); + + let r = func(x, y.clone()); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} + +fn reg_trinary( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + //println!("register {}({}, {}, {})", fn_name, type_name::(), type_name::(), type_name::()); + + let hash = calc_fn_spec( + fn_name, + [TypeId::of::(), TypeId::of::(), TypeId::of::()] + .iter() + .cloned(), + ); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 3, args, pos)?; + + let mut drain = args.iter_mut(); + let x: &mut A = drain.next().unwrap().downcast_mut().unwrap(); + let y: &mut B = drain.next().unwrap().downcast_mut().unwrap(); + let z: &mut C = drain.next().unwrap().downcast_mut().unwrap(); + + let r = func(x.clone(), y.clone(), z.clone()); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} + +fn reg_trinary_mut( + lib: &mut PackageLibraryStore, + fn_name: &'static str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> R + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> R + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result> + + 'static, + #[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result> + + Send + + Sync + + 'static, +) { + //println!("register {}(&mut {}, {}, {})", fn_name, type_name::(), type_name::(), type_name::()); + + let hash = calc_fn_spec( + fn_name, + [TypeId::of::(), TypeId::of::(), TypeId::of::()] + .iter() + .cloned(), + ); + + let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { + check_num_args(fn_name, 3, args, pos)?; + + let mut drain = args.iter_mut(); + let x: &mut A = drain.next().unwrap().downcast_mut().unwrap(); + let y: &mut B = drain.next().unwrap().downcast_mut().unwrap(); + let z: &mut C = drain.next().unwrap().downcast_mut().unwrap(); + + let r = func(x, y.clone(), z.clone()); + map_result(r, pos) + }); + + lib.0.insert(hash, f); +} diff --git a/src/packages/pkg_core.rs b/src/packages/pkg_core.rs new file mode 100644 index 00000000..19f3978e --- /dev/null +++ b/src/packages/pkg_core.rs @@ -0,0 +1,37 @@ +use super::arithmetic::ArithmeticPackage; +use super::create_new_package; +use super::iter_basic::BasicIteratorPackage; +use super::logic::LogicPackage; +use super::string_basic::BasicStringPackage; +use super::{Package, PackageLibrary, PackageLibraryStore}; + +use crate::stdlib::ops::Deref; + +pub struct CorePackage(PackageLibrary); + +impl Deref for CorePackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for CorePackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn init(lib: &mut PackageLibraryStore) { + ArithmeticPackage::init(lib); + LogicPackage::init(lib); + BasicStringPackage::init(lib); + BasicIteratorPackage::init(lib); + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } +} diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs new file mode 100644 index 00000000..f002c60f --- /dev/null +++ b/src/packages/pkg_std.rs @@ -0,0 +1,40 @@ +use super::array_basic::BasicArrayPackage; +use super::map_basic::BasicMapPackage; +use super::math_basic::BasicMathPackage; +use super::pkg_core::CorePackage; +use super::string_more::MoreStringPackage; +use super::time_basic::BasicTimePackage; +use super::{create_new_package, Package, PackageLibrary, PackageLibraryStore}; + +use crate::stdlib::ops::Deref; + +pub struct StandardPackage(PackageLibrary); + +impl Deref for StandardPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for StandardPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn init(lib: &mut PackageLibraryStore) { + CorePackage::init(lib); + BasicMathPackage::init(lib); + BasicArrayPackage::init(lib); + BasicMapPackage::init(lib); + BasicTimePackage::init(lib); + MoreStringPackage::init(lib); + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } +} diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs new file mode 100644 index 00000000..20c30493 --- /dev/null +++ b/src/packages/string_basic.rs @@ -0,0 +1,124 @@ +use super::{ + create_new_package, reg_binary, reg_binary_mut, reg_none, reg_unary, reg_unary_mut, Package, + PackageLibrary, PackageLibraryStore, +}; + +use crate::engine::{Array, Map, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; +use crate::fn_register::map_dynamic as map; +use crate::parser::INT; + +use crate::stdlib::{ + fmt::{Debug, Display}, + format, + ops::Deref, +}; + +// Register print and debug +fn to_debug(x: &mut T) -> String { + format!("{:?}", x) +} +fn to_string(x: &mut T) -> String { + format!("{}", x) +} +fn format_map(x: &mut Map) -> String { + format!("#{:?}", x) +} + +macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_unary_mut($lib, $op, $func::<$par>, map);)* }; +} + +pub struct BasicStringPackage(PackageLibrary); + +impl Deref for BasicStringPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for BasicStringPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + reg_op!(lib, KEYWORD_PRINT, to_string, String, INT, bool); + reg_op!(lib, FUNC_TO_STRING, to_string, String, INT, bool); + reg_op!(lib, KEYWORD_PRINT, to_string, String, char, String); + reg_op!(lib, FUNC_TO_STRING, to_string, String, char, String); + reg_none(lib, KEYWORD_PRINT, || "".to_string(), map); + reg_unary(lib, KEYWORD_PRINT, |_: ()| "".to_string(), map); + reg_unary(lib, FUNC_TO_STRING, |_: ()| "".to_string(), map); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, INT, bool, ()); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, char, String); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, KEYWORD_PRINT, to_string, String, i8, u8, i16, u16); + reg_op!(lib, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16); + reg_op!(lib, KEYWORD_PRINT, to_string, String, i32, u32, i64, u64); + reg_op!(lib, FUNC_TO_STRING, to_string, String, i32, u32, i64, u64); + reg_op!(lib, KEYWORD_PRINT, to_string, String, i128, u128); + reg_op!(lib, FUNC_TO_STRING, to_string, String, i128, u128); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, i32, u32, i64, u64); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, i128, u128); + } + + #[cfg(not(feature = "no_float"))] + { + reg_op!(lib, KEYWORD_PRINT, to_string, String, f32, f64); + reg_op!(lib, FUNC_TO_STRING, to_string, String, f32, f64); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, f32, f64); + } + + #[cfg(not(feature = "no_index"))] + { + reg_op!(lib, KEYWORD_PRINT, to_debug, String, Array); + reg_op!(lib, FUNC_TO_STRING, to_debug, String, Array); + reg_op!(lib, KEYWORD_DEBUG, to_debug, String, Array); + } + + #[cfg(not(feature = "no_object"))] + { + reg_unary_mut(lib, KEYWORD_PRINT, format_map, map); + reg_unary_mut(lib, FUNC_TO_STRING, format_map, map); + reg_unary_mut(lib, KEYWORD_DEBUG, format_map, map); + } + + reg_binary( + lib, + "+", + |mut s: String, ch: char| { + s.push(ch); + s + }, + map, + ); + reg_binary( + lib, + "+", + |mut s: String, s2: String| { + s.push_str(&s2); + s + }, + map, + ); + reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map); + reg_binary_mut( + lib, + "append", + |s: &mut String, add: String| s.push_str(&add), + map, + ); + } +} diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs new file mode 100644 index 00000000..15d9bdac --- /dev/null +++ b/src/packages/string_more.rs @@ -0,0 +1,259 @@ +use super::{ + create_new_package, reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary, reg_unary_mut, + Package, PackageLibrary, PackageLibraryStore, +}; + +use crate::engine::Array; +use crate::fn_register::map_dynamic as map; +use crate::parser::INT; + +use crate::stdlib::{fmt::Display, ops::Deref}; + +// Register string concatenate functions +fn prepend(x: T, y: String) -> String { + format!("{}{}", x, y) +} +fn append(x: String, y: T) -> String { + format!("{}{}", x, y) +} +fn sub_string(s: &mut String, start: INT, len: INT) -> String { + let offset = if s.is_empty() || len <= 0 { + return "".to_string(); + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return "".to_string(); + } else { + start as usize + }; + + let chars: Vec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + chars[offset..][..len].into_iter().collect::() +} +fn crop_string(s: &mut String, start: INT, len: INT) { + let offset = if s.is_empty() || len <= 0 { + s.clear(); + return; + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + s.clear(); + return; + } else { + start as usize + }; + + let chars: Vec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + s.clear(); + + chars[offset..][..len] + .into_iter() + .for_each(|&ch| s.push(ch)); +} + +pub struct MoreStringPackage(PackageLibrary); + +impl Deref for MoreStringPackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $(reg_binary($lib, $op, $func::<$par>, map);)* }; +} + +impl Package for MoreStringPackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + reg_op!(lib, "+", append, INT, bool, char); + reg_binary_mut(lib, "+", |x: &mut String, _: ()| x.clone(), map); + + reg_op!(lib, "+", prepend, INT, bool, char); + reg_binary(lib, "+", |_: (), y: String| y, map); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + } + + #[cfg(not(feature = "no_float"))] + { + reg_op!(lib, "+", append, f32, f64); + reg_op!(lib, "+", prepend, f32, f64); + } + + #[cfg(not(feature = "no_index"))] + { + reg_binary(lib, "+", |x: String, y: Array| format!("{}{:?}", x, y), map); + reg_binary(lib, "+", |x: Array, y: String| format!("{:?}{}", x, y), map); + } + + reg_unary_mut(lib, "len", |s: &mut String| s.chars().count() as INT, map); + reg_binary_mut( + lib, + "contains", + |s: &mut String, ch: char| s.contains(ch), + map, + ); + reg_binary_mut( + lib, + "contains", + |s: &mut String, find: String| s.contains(&find), + map, + ); + reg_trinary_mut( + lib, + "index_of", + |s: &mut String, ch: char, start: INT| { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(ch) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + }, + map, + ); + reg_binary_mut( + lib, + "index_of", + |s: &mut String, ch: char| { + s.find(ch) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + }, + map, + ); + reg_trinary_mut( + lib, + "index_of", + |s: &mut String, find: String, start: INT| { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(&find) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + }, + map, + ); + reg_binary_mut( + lib, + "index_of", + |s: &mut String, find: String| { + s.find(&find) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + }, + map, + ); + reg_unary_mut(lib, "clear", |s: &mut String| s.clear(), map); + reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map); + reg_binary_mut( + lib, + "append", + |s: &mut String, add: String| s.push_str(&add), + map, + ); + reg_trinary_mut(lib, "sub_string", sub_string, map); + reg_binary_mut( + lib, + "sub_string", + |s: &mut String, start: INT| sub_string(s, start, s.len() as INT), + map, + ); + reg_trinary_mut(lib, "crop", crop_string, map); + reg_binary_mut( + lib, + "crop", + |s: &mut String, start: INT| crop_string(s, start, s.len() as INT), + map, + ); + reg_binary_mut( + lib, + "truncate", + |s: &mut String, len: INT| { + if len >= 0 { + let chars: Vec<_> = s.chars().take(len as usize).collect(); + s.clear(); + chars.into_iter().for_each(|ch| s.push(ch)); + } else { + s.clear(); + } + }, + map, + ); + reg_trinary_mut( + lib, + "pad", + |s: &mut String, len: INT, ch: char| { + for _ in 0..s.chars().count() - len as usize { + s.push(ch); + } + }, + map, + ); + reg_trinary_mut( + lib, + "replace", + |s: &mut String, find: String, sub: String| { + let new_str = s.replace(&find, &sub); + s.clear(); + s.push_str(&new_str); + }, + map, + ); + reg_unary_mut( + lib, + "trim", + |s: &mut String| { + let trimmed = s.trim(); + + if trimmed.len() < s.len() { + *s = trimmed.to_string(); + } + }, + map, + ); + } +} diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs new file mode 100644 index 00000000..d2a49aee --- /dev/null +++ b/src/packages/time_basic.rs @@ -0,0 +1,126 @@ +use super::logic::{eq, gt, gte, lt, lte, ne}; +use super::{ + create_new_package, reg_binary, reg_none, reg_unary, Package, PackageLibrary, + PackageLibraryStore, +}; + +use crate::fn_register::{map_dynamic as map, map_result as result}; +use crate::parser::INT; + +use crate::stdlib::{ops::Deref, time::Instant}; + +pub struct BasicTimePackage(PackageLibrary); + +impl Deref for BasicTimePackage { + type Target = PackageLibrary; + + fn deref(&self) -> &PackageLibrary { + &self.0 + } +} + +impl Package for BasicTimePackage { + fn new() -> Self { + let mut pkg = create_new_package(); + Self::init(&mut pkg); + Self(pkg.into()) + } + + fn get(&self) -> PackageLibrary { + self.0.clone() + } + + fn init(lib: &mut PackageLibraryStore) { + #[cfg(not(feature = "no_std"))] + { + // 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()); + + #[cfg(feature = "no_float")] + { + let seconds = (ts2 - ts1).as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(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(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); + reg_binary(lib, ">", gt::, map); + reg_binary(lib, ">=", gte::, map); + reg_binary(lib, "==", eq::, map); + reg_binary(lib, "!=", ne::, map); + + reg_unary( + lib, + "elapsed", + |timestamp: Instant| { + #[cfg(not(feature = "no_float"))] + return Ok(timestamp.elapsed().as_secs_f64()); + + #[cfg(feature = "no_float")] + { + let seconds = timestamp.elapsed().as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp.elapsed(): {}", seconds), + Position::none(), + )); + } + } + return Ok(seconds as INT); + } + }, + result, + ); + } +} diff --git a/src/parser.rs b/src/parser.rs index 802f8679..c77262a9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -549,6 +549,8 @@ fn parse_paren_expr<'a>( match input.next().unwrap() { // ( xxx ) (Token::RightParen, _) => Ok(expr), + // ( + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( ")".into(), @@ -568,7 +570,7 @@ fn parse_call_expr<'a, S: Into> + Display>( let mut args_expr_list = Vec::new(); match input.peek().unwrap() { - //id {EOF} + // id (Token::EOF, pos) => { return Err(PERR::MissingToken( ")".into(), @@ -576,6 +578,8 @@ fn parse_call_expr<'a, S: Into> + Display>( ) .into_err(*pos)) } + // id + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), // id() (Token::RightParen, _) => { eat_token(input, Token::RightParen); @@ -589,6 +593,13 @@ fn parse_call_expr<'a, S: Into> + Display>( args_expr_list.push(parse_expr(input, allow_stmt_expr)?); match input.peek().unwrap() { + (Token::RightParen, _) => { + eat_token(input, Token::RightParen); + return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); + } + (Token::Comma, _) => { + eat_token(input, Token::Comma); + } (Token::EOF, pos) => { return Err(PERR::MissingToken( ")".into(), @@ -596,12 +607,8 @@ fn parse_call_expr<'a, S: Into> + Display>( ) .into_err(*pos)) } - (Token::RightParen, _) => { - eat_token(input, Token::RightParen); - return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); - } - (Token::Comma, _) => { - eat_token(input, Token::Comma); + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { return Err(PERR::MissingToken( @@ -731,6 +738,7 @@ fn parse_index_expr<'a>( eat_token(input, Token::RightBracket); Ok(Expr::Index(lhs, Box::new(idx_expr), pos)) } + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), (_, pos) => Err(PERR::MissingToken( "]".into(), "for a matching [ in this index expression".into(), @@ -752,16 +760,19 @@ fn parse_array_literal<'a>( arr.push(parse_expr(input, allow_stmt_expr)?); match input.peek().unwrap() { + (Token::Comma, _) => eat_token(input, Token::Comma), + (Token::RightBracket, _) => { + eat_token(input, Token::RightBracket); + break; + } (Token::EOF, pos) => { return Err( PERR::MissingToken("]".into(), "to end this array literal".into()) .into_err(*pos), ) } - (Token::Comma, _) => eat_token(input, Token::Comma), - (Token::RightBracket, _) => { - eat_token(input, Token::RightBracket); - break; + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { return Err(PERR::MissingToken( @@ -792,6 +803,9 @@ fn parse_map_literal<'a>( let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::StringConst(s), pos) => (s, pos), + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(pos)) + } (_, pos) if map.is_empty() => { return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) } @@ -803,6 +817,9 @@ fn parse_map_literal<'a>( match input.next().unwrap() { (Token::Colon, _) => (), + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(pos)) + } (_, pos) => { return Err(PERR::MissingToken( ":".into(), @@ -834,6 +851,9 @@ fn parse_map_literal<'a>( ) .into_err(*pos)) } + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(*pos)) + } (_, pos) => { return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) } @@ -980,7 +1000,7 @@ fn parse_unary<'a>( pos, )) } - // {EOF} + // (Token::EOF, pos) => Err(PERR::UnexpectedEOF.into_err(*pos)), // All other tokens _ => parse_primary(input, allow_stmt_expr), @@ -1463,6 +1483,7 @@ fn parse_for<'a>( // for name in ... match input.next().unwrap() { (Token::In, _) => (), + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { return Err( PERR::MissingToken("in".into(), "after the iteration variable".into()) @@ -1527,6 +1548,7 @@ fn parse_block<'a>( // Must start with { let pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { return Err( PERR::MissingToken("{".into(), "to start a statement block".into()).into_err(pos), @@ -1559,7 +1581,11 @@ fn parse_block<'a>( (Token::SemiColon, _) if !need_semicolon => (), // { ... { stmt } ??? (_, _) if !need_semicolon => (), - // { ... stmt ??? - error + // { ... stmt + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(*pos)) + } + // { ... stmt ??? (_, pos) => { // Semicolons are not optional between statements return Err( @@ -1627,7 +1653,7 @@ fn parse_stmt<'a>( }; match input.peek().unwrap() { - // `return`/`throw` at {EOF} + // `return`/`throw` at (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(None, return_type, *pos)), // `return;` or `throw;` (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), @@ -1673,6 +1699,9 @@ fn parse_fn<'a>( loop { match input.next().unwrap() { (Token::Identifier(s), pos) => params.push((s, pos)), + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(pos)) + } (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), } @@ -1682,6 +1711,9 @@ fn parse_fn<'a>( (Token::Identifier(_), pos) => { return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)) } + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(pos)) + } (_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), } } @@ -1782,7 +1814,11 @@ fn parse_global_level<'a>( (Token::SemiColon, _) if !need_semicolon => (), // { stmt } ??? (_, _) if !need_semicolon => (), - // stmt ??? - error + // stmt + (Token::LexError(err), pos) => { + return Err(PERR::BadInput(err.to_string()).into_err(*pos)) + } + // stmt ??? (_, pos) => { // Semicolons are not optional between statements return Err( diff --git a/tests/increment.rs b/tests/increment.rs index 4be9397a..9642af0a 100644 --- a/tests/increment.rs +++ b/tests/increment.rs @@ -5,6 +5,7 @@ fn test_increment() -> Result<(), EvalAltResult> { let engine = Engine::new(); assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); + assert_eq!( engine.eval::("let s = \"test\"; s += \"ing\"; s")?, "testing"