diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..df310705 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +name: Benchmark +on: + push: + branches: + - master + +jobs: + benchmark: + name: Run Rust benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup toolchain update nightly && rustup default nightly + - name: Run benchmark + run: cargo +nightly bench | tee output.txt + - name: Store benchmark result + uses: rhysd/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false + github-token: ${{ secrets.RHAI }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@schungx' diff --git a/Cargo.toml b/Cargo.toml index 87cd6436..324e0a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.12.0" +version = "0.13.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/README.md b/README.md index c073d244..868cfe43 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,21 @@ to add scripting to any application. Rhai's current features set: * `no-std` support -* Easy integration with Rust native functions and data types, including getter/setter methods +* Easy integration with Rust native functions and types, including getter/setter/methods * Easily call a script-defined function 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 overloaded functions +* Support for function overloading +* Support for operator overloading * Compiled script is optimized for repeat evaluations * Support for minimal builds by excluding unneeded language 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 pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.12.0, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize. Installation ------------ @@ -36,7 +37,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.12.0" +rhai = "0.13.0" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): @@ -87,6 +88,39 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_std`]: #optional-features [`sync`]: #optional-features +### Performance builds + +Some features are for performance. For example, using `only_i32` or `only_i64` disables all other integer types (such as `u16`). +If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering +lots of functions related to other integer types that will never be used. As a result, performance will improve. + +If only 32-bit integers are needed - again, most of the time this is the case - using `only_i32` disables also `i64`. +On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic +requiring more CPU cycles to complete. + +Also, turning on `no_float`, and `only_i32` makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets +while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. +Making [`Dynamic`] small helps performance due to more caching efficiency. + +### Minimal builds + +In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for embedded targets, it is essential that +the correct linker flags are used in `cargo.toml`: + +```toml +[profile.release] +opt-level = "z" # optimize for size +``` + +Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default +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. + Related ------- @@ -146,6 +180,8 @@ There are also a number of examples scripts that showcase Rhai's features, all i | -------------------------------------------- | ---------------------------------------------------------------------------------- | | [`speed_test.rhai`](scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) | | [`primes.rhai`](scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit | +| [`fibonacci.rhai`](scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm | +| [`mat_mul.rhai`](scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of Rhai's interpreter | To run the scripts, either make a tiny program or use of the `rhai_runner` example: @@ -180,13 +216,18 @@ fn main() -> Result<(), EvalAltResult> ### Script evaluation The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. -Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. +Rhai is very strict here. Use [`Dynamic`] for uncertain return types. +There are two ways to specify the return type - _turbofish_ notation, or type inference. ```rust let result = engine.eval::("40 + 2")?; // return type is i64, specified using 'turbofish' notation let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 +result.is::() == true; + +let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be! + let result = engine.eval::("40 + 2")?; // returns an error because the actual return type is i64, not String ``` @@ -317,8 +358,6 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which: let mut engine = Engine::new_raw(); // create a 'raw' Engine engine.register_stdlib(); // register the standard library manually - -engine. ``` Evaluate expressions only @@ -375,7 +414,7 @@ The default integer type is `i64`. If other integer types are not needed, it is smaller build with the [`only_i64`] feature. If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. -This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. +This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty. If no floating-point is needed or supported, use the [`no_float`] feature to remove it. @@ -439,12 +478,10 @@ There is no easy way for Rust to decide, at run-time, what type the `Dynamic` va function and match against the name). A `Dynamic` value's actual type can be checked via the `is` method. -The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type. -Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. +The `cast` method then converts the value into a specific, known type. +Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails. ```rust -use rhai::AnyExt; // pull in the trait. - let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' @@ -453,14 +490,12 @@ item.is::() == true; // 'is' returns whether a 'Dynam let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics let value: i64 = item.cast(); // type can also be inferred -let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error +let value = item.try_cast::().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None' ``` The `type_name` method gets the name of the actual type as a static string slice, which you may match against. ```rust -use rhai::Any; // pull in the trait. - let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' @@ -499,8 +534,6 @@ A number of traits, under the `rhai::` module namespace, provide additional func | Trait | Description | Methods | | ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- | -| `Any` | Generic trait that represents a [`Dynamic`] type | `type_id`, `type_name`, `into_dynamic` | -| `AnyExt` | Extension trait to allows casting of a [`Dynamic`] value to Rust types | `cast`, `try_cast` | | `RegisterFn` | Trait for registering functions | `register_fn` | | `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | | `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, EvalAltResult>` | `register_result_fn` | @@ -513,9 +546,9 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from To call these functions, they need to be registered with the [`Engine`]. ```rust -use rhai::{Engine, EvalAltResult}; +use rhai::{Dynamic, Engine, EvalAltResult}; use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' -use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' +use rhai::{Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' // Normal function fn add(x: i64, y: i64) -> i64 { @@ -524,7 +557,7 @@ fn add(x: i64, y: i64) -> i64 { // Function that returns a Dynamic value fn get_an_any() -> Dynamic { - (42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait + Dynamic::from(42_i64) } fn main() -> Result<(), EvalAltResult> @@ -548,17 +581,16 @@ fn main() -> Result<(), EvalAltResult> } ``` -To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method -(under the `rhai::Any` trait) to convert it. +To return a [`Dynamic`] value from a Rust function, use the `Dynamic::from` method. ```rust -use rhai::Any; // pull in the trait +use rhai::Dynamic; fn decide(yes_no: bool) -> Dynamic { if yes_no { - (42_i64).into_dynamic() + Dynamic::from(42_i64) } else { - String::from("hello!").into_dynamic() // remember &str is not supported by Rhai + Dynamic::from(String::from("hello!")) // remember &str is not supported by Rhai } } ``` @@ -567,7 +599,7 @@ Generic functions ----------------- Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. -Essentially this is a form of function overloading as Rhai does not support generics. +This is essentially function overloading (Rhai does not natively support generics). ```rust use std::fmt::Display; @@ -641,6 +673,50 @@ fn to_int(num) { print(to_int(123)); // what happens? ``` +Operator overloading +-------------------- + +In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations. +For example, in the expression "`a + b`", the `+` operator is _not_ built-in, but calls a function named "`+`" instead! + +```rust +let x = a + b; +let x = +(a, b); // <- the above is equivalent to this function call +``` + +Similarly, comparison operators including `==`, `!=` etc. are all implemented as functions, with the stark exception of `&&` and `||`. +Because they [_short-circuit_](#boolean-operators), `&&` and `||` are handled specially and _not_ via a function; as a result, +overriding them has no effect at all. + +Operator functions cannot be defined as a script function (because operators syntax are not valid function names). +However, operator functions _can_ be registered to the [`Engine`] via `register_fn`, `register_result_fn` etc. +When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version. + +```rust +use rhai::{Engine, EvalAltResult, RegisterFn}; + +let mut engine = Engine::new(); + +fn strange_add(a: i64, b: i64) -> i64 { (a + b) * 42 } + +engine.register_fn("+", strange_add); // overload '+' operator for two integers! + +let result: i64 = engine.eval("1 + 0"); // the overloading version is used + +println!("result: {}", result); // prints 42 + +let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded + +println!("result: {}", result); // prints 1.0 +``` + +Use operator overloading for custom types (described below) only. Be very careful when overloading built-in operators because +script writers expect standard operators to behave in a consistent and predictable manner, and will be annoyed if a calculation +for '+' turns into a subtraction, for example. + +Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. +See the [relevant section](#script-optimization) for more details. + Custom types and methods ----------------------- @@ -676,7 +752,7 @@ fn main() -> Result<(), EvalAltResult> let result = engine.eval::("let x = new_ts(); x.update(); x")?; - println!("result: {}", result.field); // prints 42 + println!("result: {}", result.field); // prints 42 Ok(()) } @@ -1151,16 +1227,19 @@ record == "Bob X. Davis: age 42 ❤\n"; The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: -| Function | Parameter(s) | Description | -| ---------- | ------------------------------------- | -------------------------------------------------------------------- | -| `len` | _none_ | returns the number of characters (not number of bytes) in the string | -| `pad` | character to pad, target length | pads the string with an character to a specified length | -| `append` | character/string to append | Adds a character or a string to the end of another string | -| `clear` | _none_ | empties the string | -| `truncate` | target length | cuts off the string at exactly a specified number of characters | -| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | -| `replace` | target sub-string, replacement string | replaces a substring with another | -| `trim` | _none_ | trims the string of whitespace at the beginning and end | +| Function | Parameter(s) | Description | +| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| `len` | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | character to pad, target length | pads the string with an character to at least a specified length | +| `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | +| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | +| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | +| `replace` | target sub-string, replacement string | replaces a sub-string with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | ### Examples @@ -1176,17 +1255,30 @@ full_name.pad(15, '$'); full_name.len() == 15; full_name == "Bob C. Davis$$$"; +let n = full_name.index_of('$'); +n == 12; + +full_name.index_of("$$", n + 1) == 13; + +full_name.sub_string(n, 3) == "$$$"; + full_name.truncate(6); full_name.len() == 6; full_name == "Bob C."; full_name.replace("Bob", "John"); full_name.len() == 7; -full_name = "John C."; +full_name == "John C."; full_name.contains('C') == true; full_name.contains("John") == true; +full_name.crop(5); +full_name == "C."; + +full_name.crop(0, 1); +full_name == "C"; + full_name.clear(); full_name.len() == 0; ``` @@ -1220,7 +1312,7 @@ The following methods (defined in the standard library but excluded if using a [ | `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | | `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | | `len` | _none_ | returns the number of elements | -| `pad` | element to pad, target length | pads the array with an element until a specified length | +| `pad` | element to pad, target length | pads the array with an element to at least a specified length | | `clear` | _none_ | empties the array | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..c7418817 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/benches/engine.rs b/benches/engine.rs new file mode 100644 index 00000000..e505129f --- /dev/null +++ b/benches/engine.rs @@ -0,0 +1,29 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Array, Engine, Map, RegisterFn, INT}; +use test::Bencher; + +#[bench] +fn bench_engine_new(bench: &mut Bencher) { + bench.iter(|| Engine::new()); +} + +#[bench] +fn bench_engine_new_raw(bench: &mut Bencher) { + bench.iter(|| Engine::new_raw()); +} + +#[bench] +fn bench_engine_register_fn(bench: &mut Bencher) { + fn hello(a: INT, b: Array, c: Map) -> bool { + true + } + + bench.iter(|| { + let mut engine = Engine::new(); + engine.register_fn("hello", hello); + }); +} diff --git a/benches/eval_array.rs b/benches/eval_array.rs new file mode 100644 index 00000000..687f22f5 --- /dev/null +++ b/benches/eval_array.rs @@ -0,0 +1,63 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_array_small_get(bench: &mut Bencher) { + let script = "let x = [1, 2, 3, 4, 5]; x[3]"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_array_small_set(bench: &mut Bencher) { + let script = "let x = [1, 2, 3, 4, 5]; x[3] = 42;"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_array_large_get(bench: &mut Bencher) { + let script = r#"let x = [ 1, 2.345, "hello", true, + [ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ] + ]; + x[4][3][1][1] + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_array_large_set(bench: &mut Bencher) { + let script = r#"let x = [ 1, 2.345, "hello", true, + [ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ] + ]; + x[4] = 42 + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs new file mode 100644 index 00000000..131d3bb5 --- /dev/null +++ b/benches/eval_expression.rs @@ -0,0 +1,43 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_expression_single(bench: &mut Bencher) { + let script = "1"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_expression_number_literal(bench: &mut Bencher) { + let script = "2 > 1"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_expression_number_operators(bench: &mut Bencher) { + let script = "2 + 2 == 4"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/eval_map.rs b/benches/eval_map.rs new file mode 100644 index 00000000..afe8d676 --- /dev/null +++ b/benches/eval_map.rs @@ -0,0 +1,71 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_map_small_get(bench: &mut Bencher) { + let script = "let x = #{a:1}; x.a"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_map_small_set(bench: &mut Bencher) { + let script = "let x = #{a:1}; x.a = 42;"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_map_large_get(bench: &mut Bencher) { + let script = r#"let x = #{ + a:1, + b:2.345, + c:"hello", + d: true, + e: #{ x: 42, "y$@#%": (), z: [ 1, 2, 3, #{}, #{ "hey": "jude" }]} + }; + x["e"].z[4].hey + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_map_large_set(bench: &mut Bencher) { + let script = r#"let x = #{ + a:1, + b:2.345, + c:"hello", + d: true, + e: #{ x: 42, "y$@#%": (), z: [ 1, 2, 3, #{}, #{ "hey": "jude" }]} + }; + x["e"].z[4].hey = 42; + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/eval_scope.rs b/benches/eval_scope.rs new file mode 100644 index 00000000..5d38f7d7 --- /dev/null +++ b/benches/eval_scope.rs @@ -0,0 +1,77 @@ +#![feature(test)] + +///! Test evaluating with scope +extern crate test; + +use rhai::{Engine, OptimizationLevel, Scope, INT}; +use test::Bencher; + +#[bench] +fn bench_eval_scope_single(bench: &mut Bencher) { + let script = "requests_made == requests_made"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("requests_made", 99 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_eval_scope_multiple(bench: &mut Bencher) { + let script = "requests_made > requests_succeeded"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("requests_made", 99 as INT); + scope.push("requests_succeeded", 90 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_eval_scope_longer(bench: &mut Bencher) { + let script = "(requests_made * requests_succeeded / 100) >= 90"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("requests_made", 99 as INT); + scope.push("requests_succeeded", 90 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_eval_scope_complex(bench: &mut Bencher) { + let script = r#" + 2 > 1 && + "something" != "nothing" || + "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && + Variable_name_with_spaces <= variableName && + modifierTest + 1000 / 2 > (80 * 100 % 2) + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("Variable_name_with_spaces", 99 as INT); + scope.push("variableName", 90 as INT); + scope.push("modifierTest", 5 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} diff --git a/benches/eval_type.rs b/benches/eval_type.rs new file mode 100644 index 00000000..1e5a70c8 --- /dev/null +++ b/benches/eval_type.rs @@ -0,0 +1,100 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel, RegisterFn, Scope, INT}; +use test::Bencher; + +#[derive(Debug, Clone)] +struct Test { + x: INT, +} + +impl Test { + pub fn get_x(&mut self) -> INT { + self.x + } + pub fn action(&mut self) { + self.x = 0; + } + pub fn update(&mut self, val: INT) { + self.x = val; + } + pub fn get_nest(&mut self) -> Test { + Test { x: 9 } + } +} + +#[bench] +fn bench_type_field(bench: &mut Bencher) { + let script = "foo.field"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_get("field", Test::get_x); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_type_method(bench: &mut Bencher) { + let script = "foo.action()"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_fn("action", Test::action); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_type_method_with_params(bench: &mut Bencher) { + let script = "foo.update(1)"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_fn("update", Test::update); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_type_method_nested(bench: &mut Bencher) { + let script = "foo.nest.field"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_get("field", Test::get_x); + engine.register_get("nest", Test::get_nest); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} diff --git a/benches/iterations.rs b/benches/iterations.rs new file mode 100644 index 00000000..073f0a91 --- /dev/null +++ b/benches/iterations.rs @@ -0,0 +1,49 @@ +#![feature(test)] + +///! Test 1,000 iterations +extern crate test; + +use rhai::{Engine, OptimizationLevel, Scope, INT}; +use test::Bencher; + +#[bench] +fn bench_iterations_1000(bench: &mut Bencher) { + let script = r#" + let x = 1_000; + + while x > 0 { + x = x - 1; + } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_iterations_fibonacci(bench: &mut Bencher) { + let script = r#" + fn fibonacci(n) { + if n < 2 { + n + } else { + fibonacci(n-1) + fibonacci(n-2) + } + } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| { + engine + .call_fn::<_, INT>(&mut Scope::new(), &ast, "fibonacci", (20 as INT,)) + .unwrap() + }); +} diff --git a/benches/parsing.rs b/benches/parsing.rs new file mode 100644 index 00000000..7acf47b3 --- /dev/null +++ b/benches/parsing.rs @@ -0,0 +1,103 @@ +#![feature(test)] + +///! Test parsing expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_parse_single(bench: &mut Bencher) { + let script = "1"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_simple(bench: &mut Bencher) { + let script = "(requests_made * requests_succeeded / 100) >= 90"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_full(bench: &mut Bencher) { + let script = r#" + 2 > 1 && + "something" != "nothing" || + "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && + [array, with, spaces].len() <= #{prop:name}.len() && + modifierTest + 1000 / 2 > (80 * 100 % 2) + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_array(bench: &mut Bencher) { + let script = r#"[1, 234.789, "hello", false, [ 9, 8, 7] ]"#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_map(bench: &mut Bencher) { + let script = r#"#{a: 1, b: 42, c: "hi", "dc%$& ": "strange", x: true, y: 123.456 }"#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_primes(bench: &mut Bencher) { + let script = r#" + // This script uses the Sieve of Eratosthenes to calculate prime numbers. + + let now = timestamp(); + + const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000 + + let prime_mask = []; + prime_mask.pad(MAX_NUMBER_TO_CHECK, true); + + prime_mask[0] = prime_mask[1] = false; + + let total_primes_found = 0; + + for p in range(2, MAX_NUMBER_TO_CHECK) { + if prime_mask[p] { + print(p); + + total_primes_found += 1; + let i = 2 * p; + + while i < MAX_NUMBER_TO_CHECK { + prime_mask[i] = false; + i += p; + } + } + } + + print("Total " + total_primes_found + " primes."); + print("Run time = " + now.elapsed() + " seconds."); + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile(script).unwrap()); +} diff --git a/benches/primes.rs b/benches/primes.rs new file mode 100644 index 00000000..4144d2ad --- /dev/null +++ b/benches/primes.rs @@ -0,0 +1,44 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +// This script uses the Sieve of Eratosthenes to calculate prime numbers. + +const SCRIPT: &str = r#" +let now = timestamp(); + +const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000 + +let prime_mask = []; +prime_mask.pad(MAX_NUMBER_TO_CHECK, true); + +prime_mask[0] = prime_mask[1] = false; + +let total_primes_found = 0; + +for p in range(2, MAX_NUMBER_TO_CHECK) { + if prime_mask[p] { + total_primes_found += 1; + let i = 2 * p; + + while i < MAX_NUMBER_TO_CHECK { + prime_mask[i] = false; + i += p; + } + } +} +"#; + +#[bench] +fn bench_eval_primes(bench: &mut Bencher) { + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(SCRIPT).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/examples/repl.rs b/examples/repl.rs index 56f0ac69..4c807814 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Position, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -14,7 +14,6 @@ fn print_error(input: &str, err: EvalAltResult) { let line_no = if lines.len() > 1 { match err.position() { p if p.is_none() => "".to_string(), - p if p.is_eof() => format!("{}: ", lines.len()), p => format!("{}: ", p.line().unwrap()), } } else { @@ -25,15 +24,7 @@ fn print_error(input: &str, err: EvalAltResult) { let pos = err.position(); let pos_text = format!(" ({})", pos); - let pos = if pos.is_eof() { - let last = lines[lines.len() - 1]; - Position::new(lines.len(), last.len() + 1) - } else { - pos - }; - match pos { - p if p.is_eof() => panic!("should not be EOF"), p if p.is_none() => { // No position println!("{}", err); @@ -137,9 +128,9 @@ fn main() { _ => (), } - if let Err(err) = engine + match engine .compile_with_scope(&scope, &script) - .map_err(EvalAltResult::ErrorParsing) + .map_err(|err| err.into()) .and_then(|r| { ast_u = r.clone(); @@ -157,22 +148,21 @@ fn main() { main_ast = main_ast.merge(&ast); // Evaluate - let result = engine - .consume_ast_with_scope(&mut scope, &main_ast) - .or_else(|err| match err { - EvalAltResult::Return(_, _) => Ok(()), - err => Err(err), - }); - - // Throw away all the statements, leaving only the functions - main_ast.retain_functions(); - - result - }) - { - println!(); - print_error(&input, err); - println!(); + engine.eval_ast_with_scope::(&mut scope, &main_ast) + }) { + Ok(result) if !result.is::<()>() => { + println!("=> {:?}", result); + println!(); + } + Ok(_) => (), + Err(err) => { + println!(); + print_error(&input, err); + println!(); + } } + + // Throw away all the statements, leaving only the functions + main_ast.retain_functions(); } } diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 9a5cfe59..166e1c3e 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Position}; +use rhai::{Engine, EvalAltResult}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -23,15 +23,9 @@ fn eprint_error(input: &str, err: EvalAltResult) { let lines: Vec<_> = input.split('\n').collect(); // Print error - let pos = if err.position().is_eof() { - let last = lines[lines.len() - 1]; - Position::new(lines.len(), last.len() + 1) - } else { - err.position() - }; + let pos = err.position(); match pos { - p if p.is_eof() => panic!("should not be EOF"), p if p.is_none() => { // No position eprintln!("{}", err); diff --git a/scripts/fibonacci.rhai b/scripts/fibonacci.rhai new file mode 100644 index 00000000..a9a54c3a --- /dev/null +++ b/scripts/fibonacci.rhai @@ -0,0 +1,22 @@ +// This script calculates the n-th Fibonacci number using a really dumb algorithm +// to test the speed of the scripting engine. + +const target = 30; + +let now = timestamp(); + +fn fib(n) { + if n < 2 { + n + } else { + fib(n-1) + fib(n-2) + } +} + +print("Ready... Go!"); + +let result = fib(target); + +print("Fibonacci number #" + target + " = " + result); + +print("Finished. Run time = " + now.elapsed() + " seconds."); diff --git a/scripts/mat_mul.rhai b/scripts/mat_mul.rhai new file mode 100644 index 00000000..c7c00ae9 --- /dev/null +++ b/scripts/mat_mul.rhai @@ -0,0 +1,73 @@ +const SIZE = 50; + +fn new_mat(x, y) { + let row = []; + row.pad(y, 0.0); + + let matrix = []; + matrix.pad(x, row); + + matrix +} + +fn mat_gen(n) { + let m = new_mat(n, n); + let tmp = 1.0 / n.to_float() / n.to_float(); + + for i in range(0, n) { + for j in range(0, n) { + let foo = m[i]; + foo[j] = tmp * (i.to_float() - j.to_float()) * (i.to_float() + j.to_float()); + m[i] = foo; + } + } + + m +} + +fn mat_mul(a, b) { + let m = a.len(); + let n = a[0].len(); + let p = b[0].len(); + + let b2 = new_mat(n, p); + + for i in range(0, n) { + for j in range(0, p) { + let foo = b2[j]; + foo[i] = b[i][j]; + b2[j] = foo; + } + } + + let c = new_mat(m, p); + + for i in range(0, c.len()) { + let ci = c[i]; + for j in range(0, ci.len()) { + let b2j = b2[j]; + ci[j] = 0.0; + + for z in range(0, a[i].len()) { + let x = a[i][z]; + let y = b2j[z]; + ci[j] += x * y; + } + } + c[i] = ci; + } + + c +} + +let now = timestamp(); + +let a = mat_gen(SIZE); +let b = mat_gen(SIZE); +let c = mat_mul(a, b); + +for i in range(0, SIZE) { + print(c[i]); +} + +print("Finished. Run time = " + now.elapsed() + " seconds."); diff --git a/src/any.rs b/src/any.rs index 1a7bda22..cb60384e 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,70 +1,41 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. +use crate::engine::{Array, Map}; +use crate::parser::INT; + +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + use crate::stdlib::{ - any::{type_name, TypeId}, + any::{type_name, Any, TypeId}, boxed::Box, fmt, + string::String, }; -/// An raw value of any type. +#[cfg(not(feature = "no_std"))] +use crate::stdlib::time::Instant; + +/// A trait to represent any type. /// /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. /// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. -pub type Variant = dyn Any; - -/// A boxed dynamic type containing any value. -/// -/// Currently, `Dynamic` is not `Send` nor `Sync`, so it can practically be any type. -/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. -pub type Dynamic = Box; - -/// A trait covering any type. -#[cfg(feature = "sync")] -pub trait Any: crate::stdlib::any::Any + Send + Sync { - /// Get the `TypeId` of this type. - fn type_id(&self) -> TypeId; - - /// Get the name of this type. - fn type_name(&self) -> &'static str; - - /// Convert into `Dynamic`. - fn into_dynamic(&self) -> Dynamic; - - /// This trait may only be implemented by `rhai`. - #[doc(hidden)] - fn _closed(&self) -> _Private; -} - -#[cfg(feature = "sync")] -impl Any for T { - fn type_id(&self) -> TypeId { - TypeId::of::() - } - - fn type_name(&self) -> &'static str { - type_name::() - } - - fn into_dynamic(&self) -> Dynamic { - Box::new(self.clone()) - } - - fn _closed(&self) -> _Private { - _Private - } -} - -/// A trait covering any type. #[cfg(not(feature = "sync"))] -pub trait Any: crate::stdlib::any::Any { - /// Get the `TypeId` of this type. - fn type_id(&self) -> TypeId; +pub trait Variant: Any { + /// Convert this `Variant` trait object to `&dyn Any`. + fn as_any(&self) -> &dyn Any; + + /// Convert this `Variant` trait object to `&mut dyn Any`. + fn as_mut_any(&mut self) -> &mut dyn Any; /// Get the name of this type. fn type_name(&self) -> &'static str; /// Convert into `Dynamic`. - fn into_dynamic(&self) -> Dynamic; + fn into_dynamic(self) -> Dynamic; + + /// Clone into `Dynamic`. + fn clone_into_dynamic(&self) -> Dynamic; /// This trait may only be implemented by `rhai`. #[doc(hidden)] @@ -72,104 +43,349 @@ pub trait Any: crate::stdlib::any::Any { } #[cfg(not(feature = "sync"))] -impl Any for T { - fn type_id(&self) -> TypeId { - TypeId::of::() +impl Variant for T { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + fn as_mut_any(&mut self) -> &mut dyn Any { + self as &mut dyn Any } - fn type_name(&self) -> &'static str { type_name::() } - - fn into_dynamic(&self) -> Dynamic { - Box::new(self.clone()) + fn into_dynamic(self) -> Dynamic { + Dynamic::from(self) + } + fn clone_into_dynamic(&self) -> Dynamic { + Dynamic::from(self.clone()) } - fn _closed(&self) -> _Private { _Private } } -impl Variant { +/// A trait to represent any type. +#[cfg(feature = "sync")] +pub trait Variant: Any + Send + Sync { + /// Convert this `Variant` trait object to `&dyn Any`. + fn as_any(&self) -> &dyn Any; + + /// Convert this `Variant` trait object to `&mut dyn Any`. + fn as_mut_any(&mut self) -> &mut dyn Any; + + /// Get the name of this type. + fn type_name(&self) -> &'static str; + + /// Convert into `Dynamic`. + fn into_dynamic(self) -> Dynamic; + + /// Clone into `Dynamic`. + fn clone_into_dynamic(&self) -> Dynamic; + + /// This trait may only be implemented by `rhai`. + #[doc(hidden)] + fn _closed(&self) -> _Private; +} + +#[cfg(feature = "sync")] +impl Variant for T { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + fn as_mut_any(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } + fn type_name(&self) -> &'static str { + type_name::() + } + fn into_dynamic(self) -> Dynamic { + Dynamic::from(self) + } + fn clone_into_dynamic(&self) -> Dynamic { + Dynamic::from(self.clone()) + } + fn _closed(&self) -> _Private { + _Private + } +} + +impl dyn Variant { /// Is this `Variant` a specific type? pub fn is(&self) -> bool { - TypeId::of::() == ::type_id(self) + TypeId::of::() == self.type_id() } /// Get a reference of a specific type to the `Variant`. /// Returns `None` if the cast fails. pub fn downcast_ref(&self) -> Option<&T> { - if self.is::() { - unsafe { Some(&*(self as *const Variant as *const T)) } - } else { - None - } + Any::downcast_ref::(self.as_any()) } /// Get a mutable reference of a specific type to the `Variant`. /// Returns `None` if the cast fails. pub fn downcast_mut(&mut self) -> Option<&mut T> { - if self.is::() { - unsafe { Some(&mut *(self as *mut Variant as *mut T)) } - } else { - None + Any::downcast_mut::(self.as_mut_any()) + } +} + +/// A dynamic type containing any value. +pub struct Dynamic(pub(crate) Union); + +/// Internal `Dynamic` representation. +pub enum Union { + Unit(()), + Bool(bool), + Str(Box), + Char(char), + Int(INT), + #[cfg(not(feature = "no_float"))] + Float(FLOAT), + Array(Box), + Map(Box), + Variant(Box>), +} + +impl Dynamic { + /// Does this `Dynamic` hold a variant data type + /// instead of one of the support system primitive types? + pub fn is_variant(&self) -> bool { + match self.0 { + Union::Variant(_) => true, + _ => false, + } + } + + /// Is the value held by this `Dynamic` a particular type? + pub fn is(&self) -> bool { + self.type_id() == TypeId::of::() + } + + /// Get the TypeId of the value held by this `Dynamic`. + pub fn type_id(&self) -> TypeId { + match &self.0 { + Union::Unit(_) => TypeId::of::<()>(), + Union::Bool(_) => TypeId::of::(), + Union::Str(_) => TypeId::of::(), + Union::Char(_) => TypeId::of::(), + Union::Int(_) => TypeId::of::(), + #[cfg(not(feature = "no_float"))] + Union::Float(_) => TypeId::of::(), + Union::Array(_) => TypeId::of::(), + Union::Map(_) => TypeId::of::(), + Union::Variant(value) => (***value).type_id(), + } + } + + /// Get the name of the type of the value held by this `Dynamic`. + pub fn type_name(&self) -> &'static str { + match &self.0 { + Union::Unit(_) => "()", + Union::Bool(_) => "bool", + Union::Str(_) => "string", + Union::Char(_) => "char", + Union::Int(_) => type_name::(), + #[cfg(not(feature = "no_float"))] + Union::Float(_) => type_name::(), + Union::Array(_) => "array", + Union::Map(_) => "map", + + #[cfg(not(feature = "no_std"))] + Union::Variant(value) if value.is::() => "timestamp", + Union::Variant(value) => (***value).type_name(), } } } -impl fmt::Debug for Variant { +impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("?") + match &self.0 { + Union::Unit(_) => write!(f, ""), + Union::Bool(value) => write!(f, "{}", value), + Union::Str(value) => write!(f, "{}", value), + Union::Char(value) => write!(f, "{}", value), + Union::Int(value) => write!(f, "{}", value), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => write!(f, "{}", value), + Union::Array(value) => write!(f, "{:?}", value), + Union::Map(value) => write!(f, "{:?}", value), + Union::Variant(_) => write!(f, "?"), + } + } +} + +impl fmt::Debug for Dynamic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Union::Unit(value) => write!(f, "{:?}", value), + Union::Bool(value) => write!(f, "{:?}", value), + Union::Str(value) => write!(f, "{:?}", value), + Union::Char(value) => write!(f, "{:?}", value), + Union::Int(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => write!(f, "{:?}", value), + Union::Array(value) => write!(f, "{:?}", value), + Union::Map(value) => write!(f, "{:?}", value), + Union::Variant(_) => write!(f, ""), + } } } impl Clone for Dynamic { fn clone(&self) -> Self { - self.as_ref().into_dynamic() + match &self.0 { + Union::Unit(value) => Self(Union::Unit(value.clone())), + Union::Bool(value) => Self(Union::Bool(value.clone())), + Union::Str(value) => Self(Union::Str(value.clone())), + Union::Char(value) => Self(Union::Char(value.clone())), + Union::Int(value) => Self(Union::Int(value.clone())), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => Self(Union::Float(value.clone())), + Union::Array(value) => Self(Union::Array(value.clone())), + Union::Map(value) => Self(Union::Map(value.clone())), + Union::Variant(value) => (***value).clone_into_dynamic(), + } } } -/// An extension trait that allows down-casting a `Dynamic` value to a specific type. -pub trait AnyExt: Sized { - /// Get a copy of a `Dynamic` value as a specific type. - fn try_cast(self) -> Result; - - /// Get a copy of a `Dynamic` value as a specific type. - /// - /// # Panics - /// - /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). - fn cast(self) -> T; - - /// This trait may only be implemented by `rhai`. - #[doc(hidden)] - fn _closed(&self) -> _Private; +/// Cast a Boxed type into another type. +fn cast_box(item: Box) -> Result> { + // Only allow casting to the exact same type + if TypeId::of::() == TypeId::of::() { + // SAFETY: just checked whether we are pointing to the correct type + unsafe { + let raw: *mut dyn Any = Box::into_raw(item as Box); + Ok(*Box::from_raw(raw as *mut T)) + } + } else { + // Return the consumed item for chaining. + Err(item) + } } -impl AnyExt for Dynamic { +impl Dynamic { + /// Get a reference to the inner `Union`. + pub(crate) fn get_ref(&self) -> &Union { + &self.0 + } + + /// Get a mutable reference to the inner `Union`. + pub(crate) fn get_mut(&mut self) -> &mut Union { + &mut self.0 + } + + /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. + /// + /// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`. + /// A `Vec` does not get automatically converted to an `Array`, but will be a generic + /// restricted trait object instead, because `Vec` is not a supported standard type. + /// + /// Similarly, passing in a `HashMap` will not get a `Map` but a trait object. + /// + /// # Examples + /// + /// ``` + /// use rhai::Dynamic; + /// + /// let result = Dynamic::from(42_i64); + /// assert_eq!(result.type_name(), "i64"); + /// assert_eq!(result.to_string(), "42"); + /// + /// let result = Dynamic::from("hello".to_string()); + /// assert_eq!(result.type_name(), "string"); + /// assert_eq!(result.to_string(), "hello"); + /// + /// let new_result = Dynamic::from(result); + /// assert_eq!(new_result.type_name(), "string"); + /// assert_eq!(new_result.to_string(), "hello"); + /// ``` + pub fn from(value: T) -> Self { + let dyn_value = &value as &dyn Variant; + + if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) { + return Self(result); + } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Bool) { + return Self(result); + } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Int) { + return Self(result); + } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Char) { + return Self(result); + } + + #[cfg(not(feature = "no_float"))] + { + if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Float) { + return Self(result); + } + } + + let var = Box::new(value); + + Self( + cast_box::<_, Dynamic>(var) + .map(|x| x.0) + .or_else(|var| { + cast_box::<_, String>(var) + .map(Box::new) + .map(Union::Str) + .or_else(|var| { + cast_box::<_, Array>(var) + .map(Box::new) + .map(Union::Array) + .or_else(|var| { + cast_box::<_, Map>(var) + .map(Box::new) + .map(Union::Map) + .or_else(|var| -> Result { + Ok(Union::Variant(Box::new(var as Box))) + }) + }) + }) + }) + .unwrap(), + ) + } + /// Get a copy of the `Dynamic` value as a specific type. + /// Casting to a `Dynamic` just returns as is. + /// + /// Returns an error with the name of the value's actual type when the cast fails. /// /// # Example /// /// ``` - /// use rhai::{Dynamic, Any, AnyExt}; + /// use rhai::Dynamic; /// - /// let x: Dynamic = 42_u32.into_dynamic(); + /// let x = Dynamic::from(42_u32); /// /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - fn try_cast(self) -> Result { - if self.is::() { - unsafe { - let raw: *mut Variant = Box::into_raw(self); - Ok(*Box::from_raw(raw as *mut T)) - } - } else { - Err(self) + pub fn try_cast(self) -> Option { + if TypeId::of::() == TypeId::of::() { + return cast_box::<_, T>(Box::new(self)).ok(); + } + + match &self.0 { + Union::Unit(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Bool(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Str(value) => (value.as_ref() as &dyn Variant) + .downcast_ref::() + .cloned(), + Union::Char(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Int(value) => (value as &dyn Variant).downcast_ref::().cloned(), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Array(value) => (value.as_ref() as &dyn Variant) + .downcast_ref::() + .cloned(), + Union::Map(value) => (value.as_ref() as &dyn Variant) + .downcast_ref::() + .cloned(), + Union::Variant(value) => value.as_ref().as_ref().downcast_ref::().cloned(), } } /// Get a copy of the `Dynamic` value as a specific type. + /// Casting to a `Dynamic` just returns as is. /// /// # Panics /// @@ -178,18 +394,123 @@ impl AnyExt for Dynamic { /// # Example /// /// ``` - /// use rhai::{Dynamic, Any, AnyExt}; + /// use rhai::Dynamic; /// - /// let x: Dynamic = 42_u32.into_dynamic(); + /// let x = Dynamic::from(42_u32); /// /// assert_eq!(x.cast::(), 42); /// ``` - fn cast(self) -> T { - self.try_cast::().expect("cast failed") + pub fn cast(self) -> T { + self.try_cast::().unwrap() } - fn _closed(&self) -> _Private { - _Private + /// Get a reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a reference to it. + /// Returns `None` if the cast fails. + pub fn downcast_ref(&self) -> Option<&T> { + if TypeId::of::() == TypeId::of::() { + return (self as &dyn Variant).downcast_ref::(); + } + + match &self.0 { + Union::Unit(value) => (value as &dyn Variant).downcast_ref::(), + Union::Bool(value) => (value as &dyn Variant).downcast_ref::(), + Union::Str(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), + Union::Char(value) => (value as &dyn Variant).downcast_ref::(), + Union::Int(value) => (value as &dyn Variant).downcast_ref::(), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => (value as &dyn Variant).downcast_ref::(), + Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), + Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), + Union::Variant(value) => value.as_ref().as_ref().downcast_ref::(), + } + } + + /// Get a mutable reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a mutable reference to it. + /// Returns `None` if the cast fails. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + if TypeId::of::() == TypeId::of::() { + return (self as &mut dyn Variant).downcast_mut::(); + } + + match &mut self.0 { + Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Str(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), + Union::Char(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Int(value) => (value as &mut dyn Variant).downcast_mut::(), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), + Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), + Union::Variant(value) => value.as_mut().as_mut().downcast_mut::(), + } + } + + /// Cast the `Dynamic` as the system integer type `INT` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_int(&self) -> Result { + match self.0 { + Union::Int(n) => Ok(n), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a `bool` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_bool(&self) -> Result { + match self.0 { + Union::Bool(b) => Ok(b), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a `char` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_char(&self) -> Result { + match self.0 { + Union::Char(n) => Ok(n), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a string and return the string slice. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_str(&self) -> Result<&str, &'static str> { + match &self.0 { + Union::Str(s) => Ok(s), + _ => Err(self.type_name()), + } + } + + /// Convert the `Dynamic` into `String` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn take_string(self) -> Result { + match self.0 { + Union::Str(s) => Ok(*s), + _ => Err(self.type_name()), + } + } + + pub(crate) fn from_unit() -> Self { + Self(Union::Unit(())) + } + pub(crate) fn from_bool(value: bool) -> Self { + Self(Union::Bool(value)) + } + pub(crate) fn from_int(value: INT) -> Self { + Self(Union::Int(value)) + } + #[cfg(not(feature = "no_float"))] + pub(crate) fn from_float(value: FLOAT) -> Self { + Self(Union::Float(value)) + } + pub(crate) fn from_char(value: char) -> Self { + Self(Union::Char(value)) + } + pub(crate) fn from_string(value: String) -> Self { + Self(Union::Str(Box::new(value))) } } diff --git a/src/api.rs b/src/api.rs index fde9b5d4..4bc12510 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,14 +1,15 @@ //! Module that defines the extern API of `Engine`. -use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map}; +use crate::any::{Dynamic, Variant}; +use crate::engine::{calc_fn_spec, make_getter, make_setter, Engine, FnAny, Map}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; use crate::optimize::{optimize_into_ast, OptimizationLevel}; -use crate::parser::{lex, parse, parse_global_expr, Position, AST}; +use crate::parser::{parse, parse_global_expr, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; +use crate::token::{lex, Position}; use crate::stdlib::{ any::{type_name, TypeId}, @@ -58,18 +59,11 @@ pub trait IteratorCallback: Fn(&Dynamic) -> Box> + impl Box> + 'static> IteratorCallback for F {} /// Engine public API -impl<'e> Engine<'e> { +impl Engine { /// Register a custom function. pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec, f: Box) { - let spec = FnSpec { - name: fn_name.to_string().into(), - args, - }; - - if self.functions.is_none() { - self.functions = Some(HashMap::new()); - } - self.functions.as_mut().unwrap().insert(spec, f); + self.functions + .insert(calc_fn_spec(fn_name, args.into_iter()), f); } /// Register a custom type for use with the `Engine`. @@ -109,7 +103,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_type(&mut self) { + pub fn register_type(&mut self) { self.register_type_with_name::(type_name::()); } @@ -157,7 +151,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_type_with_name(&mut self, name: &str) { + pub fn register_type_with_name(&mut self, name: &str) { if self.type_names.is_none() { self.type_names = Some(HashMap::new()); } @@ -171,15 +165,8 @@ impl<'e> Engine<'e> { /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. - pub fn register_iterator(&mut self, f: F) { - if self.type_iterators.is_none() { - self.type_iterators = Some(HashMap::new()); - } - - self.type_iterators - .as_mut() - .unwrap() - .insert(TypeId::of::(), Box::new(f)); + pub fn register_iterator(&mut self, f: F) { + self.type_iterators.insert(TypeId::of::(), Box::new(f)); } /// Register a getter function for a member of a registered type with the `Engine`. @@ -221,8 +208,8 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_object"))] pub fn register_get(&mut self, name: &str, callback: F) where - T: Any + Clone, - U: Any + Clone, + T: Variant + Clone, + U: Variant + Clone, F: ObjectGetCallback, { self.register_fn(&make_getter(name), callback); @@ -267,8 +254,8 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_object"))] pub fn register_set(&mut self, name: &str, callback: F) where - T: Any + Clone, - U: Any + Clone, + T: Variant + Clone, + U: Variant + Clone, F: ObjectSetCallback, { self.register_fn(&make_setter(name), callback); @@ -315,8 +302,8 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_object"))] pub fn register_get_set(&mut self, name: &str, get_fn: G, set_fn: S) where - T: Any + Clone, - U: Any + Clone, + T: Variant + Clone, + U: Variant + Clone, G: ObjectGetCallback, S: ObjectSetCallback, { @@ -397,7 +384,7 @@ impl<'e> Engine<'e> { ) -> Result { let scripts = [script]; let stream = lex(&scripts); - parse(&mut stream.peekable(), self, scope, optimization_level) + parse(&mut stream.peekable(), self, scope, optimization_level).map_err(|err| *err) } /// Read the contents of a file into a string. @@ -487,7 +474,7 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// use rhai::{Engine, AnyExt}; + /// use rhai::Engine; /// /// let engine = Engine::new(); /// @@ -593,7 +580,9 @@ impl<'e> Engine<'e> { ) -> Result { let scripts = [script]; let stream = lex(&scripts); + parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level) + .map_err(|err| *err) } /// Evaluate a script file. @@ -612,7 +601,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_std"))] - pub fn eval_file(&self, path: PathBuf) -> Result { + pub fn eval_file(&self, path: PathBuf) -> Result { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } @@ -636,7 +625,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_std"))] - pub fn eval_file_with_scope( + pub fn eval_file_with_scope( &self, scope: &mut Scope, path: PathBuf, @@ -658,7 +647,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval(&self, script: &str) -> Result { + pub fn eval(&self, script: &str) -> Result { self.eval_with_scope(&mut Scope::new(), script) } @@ -684,7 +673,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_with_scope( + pub fn eval_with_scope( &self, scope: &mut Scope, script: &str, @@ -709,7 +698,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_expression(&self, script: &str) -> Result { + pub fn eval_expression(&self, script: &str) -> Result { self.eval_expression_with_scope(&mut Scope::new(), script) } @@ -731,7 +720,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_expression_with_scope( + pub fn eval_expression_with_scope( &self, scope: &mut Scope, script: &str, @@ -761,7 +750,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_ast(&self, ast: &AST) -> Result { + pub fn eval_ast(&self, ast: &AST) -> Result { self.eval_ast_with_scope(&mut Scope::new(), ast) } @@ -794,32 +783,33 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_ast_with_scope( + pub fn eval_ast_with_scope( &self, scope: &mut Scope, ast: &AST, ) -> Result { - self.eval_ast_with_scope_raw(scope, ast)? - .try_cast::() - .map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).to_string(), - Position::none(), - ) - }) + let result = self + .eval_ast_with_scope_raw(scope, ast) + .map_err(|err| *err)?; + + let return_type = self.map_type_name(result.type_name()); + + return result.try_cast::().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType(return_type.to_string(), Position::none()) + }); } pub(crate) fn eval_ast_with_scope_raw( &self, scope: &mut Scope, ast: &AST, - ) -> Result { + ) -> Result> { ast.0 .iter() - .try_fold(().into_dynamic(), |_, stmt| { + .try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) }) - .or_else(|err| match err { + .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), _ => Err(err), }) @@ -875,14 +865,16 @@ impl<'e> Engine<'e> { ) -> Result<(), EvalAltResult> { ast.0 .iter() - .try_fold(().into_dynamic(), |_, stmt| { + .try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) }) - .map(|_| ()) - .or_else(|err| match err { - EvalAltResult::Return(_, _) => Ok(()), - _ => Err(err), - }) + .map_or_else( + |err| match *err { + EvalAltResult::Return(_, _) => Ok(()), + err => Err(err), + }, + |_| Ok(()), + ) } /// Call a script function defined in an `AST` with multiple arguments. @@ -921,7 +913,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_function"))] - pub fn call_fn( + pub fn call_fn( &self, scope: &mut Scope, ast: &AST, @@ -929,18 +921,19 @@ impl<'e> Engine<'e> { args: A, ) -> Result { let mut arg_values = args.into_vec(); - let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut args: Vec<_> = arg_values.iter_mut().collect(); let fn_lib = Some(ast.1.as_ref()); let pos = Position::none(); - self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)? + let result = self + .call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0) + .map_err(|err| *err)?; + + let return_type = self.map_type_name(result.type_name()); + + return result .try_cast() - .map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).into(), - pos, - ) - }) + .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(return_type.into(), pos)); } /// Optimize the `AST` with constants defined in an external Scope. @@ -961,7 +954,11 @@ impl<'e> Engine<'e> { ast: AST, optimization_level: OptimizationLevel, ) -> AST { - let fn_lib = ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(); + let fn_lib = ast + .1 + .iter() + .map(|(_, fn_def)| fn_def.as_ref().clone()) + .collect(); optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) } @@ -972,23 +969,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_print(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume("print(40 + 2);")?; /// - /// engine.consume("print(40 + 2);")?; - /// } /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[cfg(feature = "sync")] - pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { + pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) { self.on_print = Some(Box::new(callback)); } /// Override default action of `print` (print to stdout using `println!`) @@ -998,23 +997,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_print(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume("print(40 + 2);")?; /// - /// engine.consume("print(40 + 2);")?; - /// } /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "sync"))] - pub fn on_print(&mut self, callback: impl Fn(&str) + 'e) { + pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { self.on_print = Some(Box::new(callback)); } @@ -1025,23 +1026,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_debug(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_debug(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume(r#"debug("hello");"#)?; /// - /// engine.consume(r#"debug("hello");"#)?; - /// } /// assert_eq!(*result.read().unwrap(), r#""hello""#); /// # Ok(()) /// # } /// ``` #[cfg(feature = "sync")] - pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) { self.on_debug = Some(Box::new(callback)); } /// Override default action of `debug` (print to stdout using `println!`) @@ -1051,23 +1054,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_debug(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_debug(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume(r#"debug("hello");"#)?; /// - /// engine.consume(r#"debug("hello");"#)?; - /// } /// assert_eq!(*result.read().unwrap(), r#""hello""#); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "sync"))] - pub fn on_debug(&mut self, callback: impl Fn(&str) + 'e) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { self.on_debug = Some(Box::new(callback)); } } diff --git a/src/builtin.rs b/src/builtin.rs index f77b4f4a..428de9b7 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,11 +1,12 @@ //! Helper module that allows registration of the _core library_ and //! _standard library_ of utility functions. -use crate::any::{Any, Dynamic}; +use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; -use crate::parser::{Position, INT}; +use crate::parser::INT; use crate::result::EvalAltResult; +use crate::token::Position; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -27,11 +28,18 @@ use crate::stdlib::{ format, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, string::{String, ToString}, - time::Instant, vec::Vec, {i32, i64, u32}, }; +#[cfg(not(feature = "no_std"))] +use crate::stdlib::time::Instant; + +#[cfg(feature = "only_i32")] +const MAX_INT: INT = i32::MAX; +#[cfg(not(feature = "only_i32"))] +const MAX_INT: INT = i64::MAX; + macro_rules! reg_op { ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( $( @@ -86,7 +94,7 @@ fn ne(x: T, y: T) -> bool { x != y } -impl Engine<'_> { +impl Engine { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { // Checked add @@ -371,10 +379,10 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -388,10 +396,10 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -414,12 +422,12 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); + 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"))] @@ -448,9 +456,9 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } #[cfg(not(feature = "unchecked"))] @@ -462,9 +470,13 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result1!( + self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128 + ); + reg_op_result1!( + self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128 + ); + reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -477,9 +489,9 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -595,8 +607,11 @@ impl Engine<'_> { 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_float"))] @@ -628,8 +643,8 @@ impl Engine<'_> { // Register map access functions #[cfg(not(feature = "no_index"))] self.register_fn("keys", |map: Map| { - map.into_iter() - .map(|(k, _)| k.into_dynamic()) + map.iter() + .map(|(k, _)| Dynamic::from(k.clone())) .collect::>() }); @@ -641,15 +656,16 @@ impl Engine<'_> { } // Register range function - fn reg_range(engine: &mut Engine) + fn reg_range(engine: &mut Engine) where Range: Iterator, { - engine.register_iterator::, _>(|a: &Dynamic| { + engine.register_iterator::, _>(|source: &Dynamic| { Box::new( - a.downcast_ref::>() + source + .downcast_ref::>() + .cloned() .unwrap() - .clone() .map(|x| x.into_dynamic()), ) as Box> }); @@ -670,7 +686,7 @@ impl Engine<'_> { ) } - reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } // Register range function with step @@ -678,12 +694,12 @@ impl Engine<'_> { struct StepRange(T, T, T) where for<'a> &'a T: Add<&'a T, Output = T>, - T: Any + Clone + PartialOrd; + T: Variant + Clone + PartialOrd; impl Iterator for StepRange where for<'a> &'a T: Add<&'a T, Output = T>, - T: Any + Clone + PartialOrd, + T: Variant + Clone + PartialOrd, { type Item = T; @@ -701,14 +717,15 @@ impl Engine<'_> { fn reg_step(engine: &mut Engine) where for<'a> &'a T: Add<&'a T, Output = T>, - T: Any + Clone + PartialOrd, + T: Variant + Clone + PartialOrd, StepRange: Iterator, { - engine.register_iterator::, _>(|a: &Dynamic| { + engine.register_iterator::, _>(|source: &Dynamic| { Box::new( - a.downcast_ref::>() + source + .downcast_ref::>() + .cloned() .unwrap() - .clone() .map(|x| x.into_dynamic()), ) as Box> }); @@ -731,7 +748,7 @@ impl Engine<'_> { ) } - reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } } @@ -753,7 +770,7 @@ macro_rules! reg_fn2y { } /// Register the built-in library. -impl Engine<'_> { +impl Engine { pub fn register_stdlib(&mut self) { #[cfg(not(feature = "no_float"))] { @@ -799,6 +816,8 @@ impl Engine<'_> { self.register_fn("to_float", |x: u32| x as FLOAT); self.register_fn("to_float", |x: i64| x as FLOAT); self.register_fn("to_float", |x: u64| x as FLOAT); + self.register_fn("to_float", |x: i128| x as FLOAT); + self.register_fn("to_float", |x: u128| x as FLOAT); } } @@ -827,7 +846,7 @@ impl Engine<'_> { #[cfg(not(feature = "unchecked"))] { self.register_result_fn("to_int", |x: f32| { - if x > (i64::MAX as f32) { + if x > (MAX_INT as f32) { return Err(EvalAltResult::ErrorArithmetic( format!("Integer overflow: to_int({})", x), Position::none(), @@ -837,7 +856,7 @@ impl Engine<'_> { Ok(x.trunc() as INT) }); self.register_result_fn("to_int", |x: FLOAT| { - if x > (i64::MAX as FLOAT) { + if x > (MAX_INT as FLOAT) { return Err(EvalAltResult::ErrorArithmetic( format!("Integer overflow: to_int({})", x), Position::none(), @@ -866,19 +885,19 @@ impl Engine<'_> { } // Register array utility functions - fn push(list: &mut Array, item: T) { - list.push(Box::new(item)); + fn push(list: &mut Array, item: T) { + list.push(Dynamic::from(item)); } - fn ins(list: &mut Array, position: INT, item: T) { + fn ins(list: &mut Array, position: INT, item: T) { if position <= 0 { - list.insert(0, Box::new(item)); + list.insert(0, Dynamic::from(item)); } else if (position as usize) >= list.len() - 1 { push(list, item); } else { - list.insert(position as usize, Box::new(item)); + list.insert(position as usize, Dynamic::from(item)); } } - fn pad(list: &mut Array, len: INT, item: T) { + fn pad(list: &mut Array, len: INT, item: T) { if len >= 0 { while list.len() < len as usize { push(list, item.clone()); @@ -905,10 +924,13 @@ impl Engine<'_> { { reg_fn2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_fn2x!(self, "push", push, &mut Array, (), i128, u128); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), i128, u128); reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, u64); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), i128, u128); } #[cfg(not(feature = "no_float"))] @@ -919,18 +941,18 @@ impl Engine<'_> { } self.register_dynamic_fn("pop", |list: &mut Array| { - list.pop().unwrap_or_else(|| ().into_dynamic()) + list.pop().unwrap_or_else(|| Dynamic::from_unit()) }); self.register_dynamic_fn("shift", |list: &mut Array| { if !list.is_empty() { - ().into_dynamic() + Dynamic::from_unit() } else { list.remove(0) } }); self.register_dynamic_fn("remove", |list: &mut Array, len: INT| { if len < 0 || (len as usize) >= list.len() { - ().into_dynamic() + Dynamic::from_unit() } else { list.remove(len as usize) } @@ -951,7 +973,7 @@ impl Engine<'_> { self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("clear", |map: &mut Map| map.clear()); self.register_dynamic_fn("remove", |x: &mut Map, name: String| { - x.remove(&name).unwrap_or(().into_dynamic()) + x.remove(&name).unwrap_or_else(|| Dynamic::from_unit()) }); self.register_fn("mixin", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { @@ -983,8 +1005,13 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_fn2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); - reg_fn2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + reg_fn2x!( + self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128 + ); + reg_fn2y!( + self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, i128, + u128 + ); } #[cfg(not(feature = "no_float"))] @@ -1000,17 +1027,113 @@ impl Engine<'_> { } // Register string utility functions + 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)); + } + self.register_fn("len", |s: &mut String| s.chars().count() as INT); self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); self.register_fn("contains", |s: &mut String, find: String| s.contains(&find)); + self.register_fn("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) + }); + self.register_fn("index_of", |s: &mut String, ch: char| { + s.find(ch) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + }); + self.register_fn("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) + }); + self.register_fn("index_of", |s: &mut String, find: String| { + s.find(&find) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + }); self.register_fn("clear", |s: &mut String| s.clear()); self.register_fn("append", |s: &mut String, ch: char| s.push(ch)); self.register_fn("append", |s: &mut String, add: String| s.push_str(&add)); + self.register_fn("sub_string", sub_string); + self.register_fn("sub_string", |s: &mut String, start: INT| { + sub_string(s, start, s.len() as INT) + }); + self.register_fn("crop", crop_string); + self.register_fn("crop", |s: &mut String, start: INT| { + crop_string(s, start, s.len() as INT) + }); self.register_fn("truncate", |s: &mut String, len: INT| { if len >= 0 { let chars: Vec<_> = s.chars().take(len as usize).collect(); s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); + chars.into_iter().for_each(|ch| s.push(ch)); } else { s.clear(); } @@ -1033,38 +1156,83 @@ impl Engine<'_> { } }); - // Register date/time functions - self.register_fn("timestamp", || Instant::now()); + #[cfg(not(feature = "no_std"))] + { + // Register date/time functions + self.register_fn("timestamp", || Instant::now()); - self.register_fn("-", |ts1: Instant, ts2: Instant| { - if ts2 > ts1 { + self.register_result_fn("-", |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); + } + } + }); + + reg_cmp!(self, "<", lt, Instant); + reg_cmp!(self, "<=", lte, Instant); + reg_cmp!(self, ">", gt, Instant); + reg_cmp!(self, ">=", gte, Instant); + reg_cmp!(self, "==", eq, Instant); + reg_cmp!(self, "!=", ne, Instant); + + self.register_result_fn("elapsed", |timestamp: Instant| { #[cfg(not(feature = "no_float"))] - return -(ts2 - ts1).as_secs_f64(); + return Ok(timestamp.elapsed().as_secs_f64()); #[cfg(feature = "no_float")] - return -((ts2 - ts1).as_secs() as INT); - } else { - #[cfg(not(feature = "no_float"))] - return (ts1 - ts2).as_secs_f64(); + { + let seconds = timestamp.elapsed().as_secs(); - #[cfg(feature = "no_float")] - return (ts1 - ts2).as_secs() as INT; - } - }); - - reg_cmp!(self, "<", lt, Instant); - reg_cmp!(self, "<=", lte, Instant); - reg_cmp!(self, ">", gt, Instant); - reg_cmp!(self, ">=", gte, Instant); - reg_cmp!(self, "==", eq, Instant); - reg_cmp!(self, "!=", ne, Instant); - - self.register_fn("elapsed", |timestamp: Instant| { - #[cfg(not(feature = "no_float"))] - return timestamp.elapsed().as_secs_f64(); - - #[cfg(feature = "no_float")] - return timestamp.elapsed().as_secs() as INT; - }); + #[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); + } + }); + } } } diff --git a/src/engine.rs b/src/engine.rs index 73b1e800..ad995cda 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,26 +1,24 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{Any, AnyExt, Dynamic, Variant}; +use crate::any::{Dynamic, Union}; use crate::error::ParseErrorType; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; +use crate::parser::{Expr, FnDef, ReturnType, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; +use crate::token::Position; use crate::stdlib::{ - any::{type_name, TypeId}, - borrow::Cow, + any::TypeId, boxed::Box, - cmp::Ordering, - collections::HashMap, + collections::{hash_map::DefaultHasher, HashMap}, format, + hash::{Hash, Hasher}, iter::once, ops::{Deref, DerefMut}, rc::Rc, string::{String, ToString}, sync::Arc, - time::Instant, - vec, vec::Vec, }; @@ -34,12 +32,13 @@ pub type Array = Vec; /// Not available under the `no_object` feature. pub type Map = HashMap; -pub type FnCallArgs<'a> = [&'a mut Variant]; +pub type FnCallArgs<'a> = [&'a mut Dynamic]; #[cfg(feature = "sync")] -pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result + Send + Sync; +pub type FnAny = + dyn Fn(&mut FnCallArgs, Position) -> Result> + Send + Sync; #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result; +pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result>; #[cfg(feature = "sync")] type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + Sync; @@ -47,7 +46,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + type IteratorFn = dyn Fn(&Dynamic) -> Box>; #[cfg(debug_assertions)] -pub const MAX_CALL_STACK_DEPTH: usize = 32; +pub const MAX_CALL_STACK_DEPTH: usize = 28; #[cfg(not(debug_assertions))] pub const MAX_CALL_STACK_DEPTH: usize = 256; @@ -60,13 +59,12 @@ pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_GETTER: &str = "get$"; pub const FUNC_SETTER: &str = "set$"; -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] -enum IndexSourceType { - Expression, - String, - Array, - Map, -} +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +const FUNCTIONS_COUNT: usize = 512; + +#[cfg(any(feature = "only_i32", feature = "only_i64"))] +const FUNCTIONS_COUNT: usize = 256; #[derive(Debug, Eq, PartialEq, Hash, Clone)] enum IndexValue { @@ -98,28 +96,28 @@ impl IndexValue { #[derive(Debug)] enum Target<'a> { Scope(ScopeSource<'a>), - Value(&'a mut Variant), + Value(&'a mut Dynamic), } impl<'a> Target<'a> { - fn from(value: &'a mut Variant) -> Self { - Self::Value(value) - } - fn from_src(src: ScopeSource<'a>) -> Self { - Self::Scope(src) - } - fn get_mut(self, scope: &'a mut Scope) -> &'a mut Variant { + fn get_mut(self, scope: &'a mut Scope) -> &'a mut Dynamic { match self { Self::Value(t) => t, - Self::Scope(src) => scope.get_mut(src).as_mut(), + Self::Scope(src) => scope.get_mut(src), } } } -#[derive(Debug, Eq, PartialEq, Hash, Clone)] -pub struct FnSpec<'a> { - pub name: Cow<'a, str>, - pub args: Vec, +impl<'a> From> for Target<'a> { + fn from(src: ScopeSource<'a>) -> Self { + Self::Scope(src) + } +} + +impl<'a> From<&'a mut Dynamic> for Target<'a> { + fn from(value: &'a mut Dynamic) -> Self { + Self::Value(value) + } } /// A type that holds a library of script-defined functions. @@ -134,48 +132,41 @@ pub struct FnSpec<'a> { /// So instead this is implemented as a sorted list and binary searched. #[derive(Debug, Clone)] pub struct FunctionsLib( - #[cfg(feature = "sync")] Vec>, - #[cfg(not(feature = "sync"))] Vec>, + #[cfg(feature = "sync")] HashMap>, + #[cfg(not(feature = "sync"))] HashMap>, ); -impl FnDef { - /// Function to order two FnDef records, for binary search. - pub fn compare(&self, name: &str, params_len: usize) -> Ordering { - // First order by name - match self.name.as_str().cmp(name) { - // Then by number of parameters - Ordering::Equal => self.params.len().cmp(¶ms_len), - order => order, - } - } -} - impl FunctionsLib { /// Create a new `FunctionsLib`. pub fn new() -> Self { - FunctionsLib(Vec::new()) + FunctionsLib(HashMap::new()) } /// Create a new `FunctionsLib` from a collection of `FnDef`. pub fn from_vec(vec: Vec) -> Self { - #[cfg(feature = "sync")] - { - FunctionsLib(vec.into_iter().map(Arc::new).collect()) - } - #[cfg(not(feature = "sync"))] - { - FunctionsLib(vec.into_iter().map(Rc::new).collect()) - } + FunctionsLib( + vec.into_iter() + .map(|f| { + let hash = calc_fn_def(&f.name, f.params.len()); + + #[cfg(feature = "sync")] + { + (hash, Arc::new(f)) + } + #[cfg(not(feature = "sync"))] + { + (hash, Rc::new(f)) + } + }) + .collect(), + ) } /// Does a certain function exist in the `FunctionsLib`? pub fn has_function(&self, name: &str, params: usize) -> bool { - self.0.binary_search_by(|f| f.compare(name, params)).is_ok() + self.contains_key(&calc_fn_def(name, params)) } /// Get a function definition from the `FunctionsLib`. pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { - self.0 - .binary_search_by(|f| f.compare(name, params)) - .ok() - .map(|n| self.0[n].as_ref()) + self.get(&calc_fn_def(name, params)).map(|f| f.as_ref()) } /// Merge another `FunctionsLib` into this `FunctionsLib`. pub fn merge(&self, other: &Self) -> Self { @@ -185,19 +176,7 @@ impl FunctionsLib { self.clone() } else { let mut functions = self.clone(); - - other.iter().cloned().for_each(|fn_def| { - if let Some((n, _)) = functions - .iter() - .enumerate() - .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) - { - functions[n] = fn_def; - } else { - functions.push(fn_def); - } - }); - + functions.extend(other.iter().map(|(hash, fn_def)| (*hash, fn_def.clone()))); functions } } @@ -205,9 +184,9 @@ impl FunctionsLib { impl Deref for FunctionsLib { #[cfg(feature = "sync")] - type Target = Vec>; + type Target = HashMap>; #[cfg(not(feature = "sync"))] - type Target = Vec>; + type Target = HashMap>; fn deref(&self) -> &Self::Target { &self.0 @@ -216,11 +195,11 @@ impl Deref for FunctionsLib { impl DerefMut for FunctionsLib { #[cfg(feature = "sync")] - fn deref_mut(&mut self) -> &mut Vec> { + fn deref_mut(&mut self) -> &mut HashMap> { &mut self.0 } #[cfg(not(feature = "sync"))] - fn deref_mut(&mut self) -> &mut Vec> { + fn deref_mut(&mut self) -> &mut HashMap> { &mut self.0 } } @@ -241,44 +220,44 @@ impl DerefMut for FunctionsLib { /// ``` /// /// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -pub struct Engine<'e> { +pub struct Engine { /// A hashmap containing all compiled functions known to the engine. - pub(crate) functions: Option, Box>>, + pub(crate) functions: HashMap>, /// A hashmap containing all iterators known to the engine. - pub(crate) type_iterators: Option>>, + pub(crate) type_iterators: HashMap>, /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: Option>, /// Closure for implementing the `print` command. #[cfg(feature = "sync")] - pub(crate) on_print: Option>, + pub(crate) on_print: Option>, /// Closure for implementing the `print` command. #[cfg(not(feature = "sync"))] - pub(crate) on_print: Option>, + pub(crate) on_print: Option>, /// Closure for implementing the `debug` command. #[cfg(feature = "sync")] - pub(crate) on_debug: Option>, + pub(crate) on_debug: Option>, /// Closure for implementing the `debug` command. #[cfg(not(feature = "sync"))] - pub(crate) on_debug: Option>, + pub(crate) on_debug: Option>, /// Optimize the AST after compilation. pub(crate) optimization_level: OptimizationLevel, /// Maximum levels of call-stack to prevent infinite recursion. /// - /// Defaults to 32 for debug builds and 256 for non-debug builds. + /// Defaults to 28 for debug builds and 256 for non-debug builds. pub(crate) max_call_stack_depth: usize, } -impl Default for Engine<'_> { +impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Engine { - functions: None, - type_iterators: None, + functions: HashMap::with_capacity(FUNCTIONS_COUNT), + type_iterators: HashMap::new(), type_names: None, // default print/debug implementations @@ -300,7 +279,6 @@ impl Default for Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; - engine.fill_type_names(); engine.register_core_lib(); #[cfg(not(feature = "no_stdlib"))] @@ -352,26 +330,113 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { } } -impl Engine<'_> { - fn fill_type_names(&mut self) { - // User-friendly names for built-in types - self.type_names = Some( - [ - #[cfg(not(feature = "no_index"))] - (type_name::(), "array"), - #[cfg(not(feature = "no_object"))] - (type_name::(), "map"), - (type_name::(), "string"), - (type_name::(), "timestamp"), - (type_name::(), "dynamic"), - (type_name::(), "variant"), - ] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(), - ); +pub(crate) fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { + let mut s = DefaultHasher::new(); + fn_name.hash(&mut s); + params.for_each(|t| t.hash(&mut s)); + s.finish() +} + +pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 { + let mut s = DefaultHasher::new(); + fn_name.hash(&mut s); + params.hash(&mut s); + s.finish() +} + +/// Print/debug to stdout +fn default_print(s: &str) { + #[cfg(not(feature = "no_std"))] + println!("{}", s); +} + +/// Search for a variable within the scope, returning its value and index inside the Scope +fn search_scope<'a>( + scope: &'a Scope, + id: &str, + begin: Position, +) -> Result<(ScopeSource<'a>, Dynamic), Box> { + scope + .get(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(id.into(), begin))) +} + +/// Replace a character at an index position in a mutable string +fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { + let mut chars: Vec = s.chars().collect(); + let ch = *chars.get(idx).expect("string index out of bounds"); + + // See if changed - if so, update the String + if ch != new_ch { + chars[idx] = new_ch; + s.clear(); + chars.iter().for_each(|&ch| s.push(ch)); + } +} + +/// Update the value at an index position +fn update_indexed_val( + mut target: Dynamic, + idx: IndexValue, + new_val: Dynamic, + pos: Position, +) -> Result> { + match target.get_mut() { + Union::Array(arr) => { + arr[idx.as_num()] = new_val; + } + Union::Map(map) => { + map.insert(idx.as_str(), new_val); + } + Union::Str(s) => { + // Value must be a character + let ch = new_val + .as_char() + .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; + str_replace_char(s, idx.as_num(), ch); + } + // All other variable types should be an error + _ => panic!("invalid type for indexing: {}", target.type_name()), } + Ok(target) +} + +/// Update the value at an index position in a variable inside the scope +fn update_indexed_scope_var( + scope: &mut Scope, + src: ScopeSource, + idx: IndexValue, + new_val: Dynamic, + pos: Position, +) -> Result> { + let target = scope.get_mut(src); + + match target.get_mut() { + // array_id[idx] = val + Union::Array(arr) => { + arr[idx.as_num()] = new_val; + } + // map_id[idx] = val + Union::Map(map) => { + map.insert(idx.as_str(), new_val); + } + // string_id[idx] = val + Union::Str(s) => { + // Value must be a character + let ch = new_val + .as_char() + .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; + str_replace_char(s, idx.as_num(), ch); + } + // All other variable types should be an error + _ => panic!("invalid type for indexing: {}", target.type_name()), + } + + Ok(Dynamic::from_unit()) +} + +impl Engine { /// Create a new `Engine` pub fn new() -> Self { Default::default() @@ -380,8 +445,8 @@ impl Engine<'_> { /// Create a new `Engine` with minimal configurations without the standard library etc. pub fn new_raw() -> Self { let mut engine = Engine { - functions: None, - type_iterators: None, + functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), + type_iterators: HashMap::new(), type_names: None, on_print: None, on_debug: None, @@ -400,7 +465,6 @@ impl Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; - engine.fill_type_names(); engine.register_core_lib(); engine @@ -430,10 +494,10 @@ impl Engine<'_> { def_val: Option<&Dynamic>, pos: Position, level: usize, - ) -> Result { + ) -> Result> { // Check for stack overflow if level > self.max_call_stack_depth { - return Err(EvalAltResult::ErrorStackOverflow(pos)); + return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos))); } #[cfg(feature = "no_function")] @@ -452,17 +516,17 @@ impl Engine<'_> { fn_def .params .iter() - .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .zip(args.into_iter().map(|v| v.clone())) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); // Evaluate the function at one higher level of call depth let result = self .eval_stmt(scope, fn_lib, &fn_def.body, level + 1) - .or_else(|err| match err { + .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), + err => Err(Box::new(err.set_position(pos))), }); scope.rewind(scope_len); @@ -478,81 +542,86 @@ impl Engine<'_> { fn_def .params .iter() - .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .zip(args.into_iter().map(|v| v.clone())) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); // Evaluate the function at one higher level of call depth return self .eval_stmt(&mut scope, fn_lib, &fn_def.body, level + 1) - .or_else(|err| match err { + .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), + err => Err(Box::new(err.set_position(pos))), }); } } } - let spec = FnSpec { - name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(*a)).collect(), - }; - // Argument must be a string - fn cast_to_string(r: &Variant, pos: Position) -> Result<&str, EvalAltResult> { - r.downcast_ref::() - .map(String::as_str) - .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(r.type_name().into(), pos)) + fn cast_to_string(r: &Dynamic, pos: Position) -> Result<&str, Box> { + r.as_str().map_err(|type_name| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name.into(), + pos, + )) + }) } // Search built-in's and external functions - if let Some(func) = self.functions.as_ref().and_then(|f| f.get(&spec)) { + let fn_spec = calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())); + + if let Some(func) = self.functions.get(&fn_spec) { // Run external function let result = func(args, pos)?; // See if the function match print/debug (which requires special processing) return match fn_name { - KEYWORD_PRINT if self.on_print.is_some() => Ok(self.on_print.as_ref().unwrap()( - cast_to_string(result.as_ref(), pos)?, - ) - .into_dynamic()), - KEYWORD_DEBUG if self.on_debug.is_some() => Ok(self.on_debug.as_ref().unwrap()( - cast_to_string(result.as_ref(), pos)?, - ) - .into_dynamic()), - KEYWORD_PRINT | KEYWORD_DEBUG => Ok(().into_dynamic()), + KEYWORD_PRINT if self.on_print.is_some() => { + self.on_print.as_ref().unwrap()(cast_to_string(&result, pos)?); + Ok(Dynamic::from_unit()) + } + KEYWORD_DEBUG if self.on_debug.is_some() => { + self.on_debug.as_ref().unwrap()(cast_to_string(&result, pos)?); + Ok(Dynamic::from_unit()) + } + KEYWORD_PRINT | KEYWORD_DEBUG => Ok(Dynamic::from_unit()), _ => Ok(result), }; } if let Some(prop) = extract_prop_from_getter(fn_name) { - // Map property access - if let Some(map) = args[0].downcast_ref::() { - return Ok(map.get(prop).cloned().unwrap_or_else(|| ().into_dynamic())); - } + return match args[0] { + // Map property access + Dynamic(Union::Map(map)) => Ok(map + .get(prop) + .cloned() + .unwrap_or_else(|| Dynamic::from_unit())), - // Getter function not found - return Err(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or write-only", prop), - pos, - )); + // Getter function not found + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or write-only", prop), + pos, + ))), + }; } if let Some(prop) = extract_prop_from_setter(fn_name) { - let value = args[1].into_dynamic(); + let (arg, value) = args.split_at_mut(1); - // Map property update - if let Some(map) = args[0].downcast_mut::() { - map.insert(prop.to_string(), value); - return Ok(().into_dynamic()); - } + return match arg[0] { + // Map property update + Dynamic(Union::Map(map)) => { + map.insert(prop.to_string(), value[0].clone()); + Ok(Dynamic::from_unit()) + } - // Setter function not found - return Err(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or read-only", prop), - pos, - )); + // Setter function not found + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or read-only", prop), + pos, + ))), + }; } if let Some(val) = def_val { @@ -563,25 +632,25 @@ impl Engine<'_> { // Raise error let types_list: Vec<_> = args .iter() - .map(|x| (*x).type_name()) + .map(|x| x.type_name()) .map(|name| self.map_type_name(name)) .collect(); - Err(EvalAltResult::ErrorFunctionNotFound( + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!("{} ({})", fn_name, types_list.join(", ")), pos, - )) + ))) } /// Chain-evaluate a dot setter. - fn get_dot_val_helper( + fn dot_get_helper( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, target: Target, dot_rhs: &Expr, level: usize, - ) -> Result { + ) -> Result> { match dot_rhs { // xxx.fn_name(arg_expr_list) Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { @@ -589,15 +658,10 @@ impl Engine<'_> { .iter() .map(|arg_expr| self.eval_expr(scope, fn_lib, arg_expr, level)) .collect::, _>>()?; - - let this_ptr = target.get_mut(scope); - - let mut args: Vec<_> = once(this_ptr) - .chain(values.iter_mut().map(Dynamic::as_mut)) + let mut args: Vec<_> = once(target.get_mut(scope)) + .chain(values.iter_mut()) .collect(); - let def_val = def_val.as_ref(); - self.call_fn_raw(None, fn_lib, fn_name, &mut args, def_val, *pos, 0) } @@ -609,7 +673,7 @@ impl Engine<'_> { // xxx.idx_lhs[idx_expr] Expr::Index(idx_lhs, idx_expr, op_pos) => { - let value = match idx_lhs.as_ref() { + let lhs_value = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { let mut args = [target.get_mut(scope)]; @@ -617,19 +681,20 @@ impl Engine<'_> { } // xxx.???[???][idx_expr] Expr::Index(_, _, _) => { - self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? + // Chain the indexing + self.dot_get_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error _ => { - return Err(EvalAltResult::ErrorDotExpr( + return Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_rhs.position(), - )) + ))) } }; - self.get_indexed_value(scope, fn_lib, &value, idx_expr, *op_pos, level) - .map(|(val, _, _)| val) + self.get_indexed_val(scope, fn_lib, &lhs_value, idx_expr, *op_pos, level, false) + .map(|(val, _)| val) } // xxx.dot_lhs.rhs @@ -639,8 +704,7 @@ impl Engine<'_> { let mut args = [target.get_mut(scope)]; self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { - let target = Target::from(val.as_mut()); - self.get_dot_val_helper(scope, fn_lib, target, rhs, level) + self.dot_get_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) } // xxx.idx_lhs[idx_expr].rhs @@ -654,86 +718,78 @@ impl Engine<'_> { } // xxx.???[???][idx_expr].rhs Expr::Index(_, _, _) => { - self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? + self.dot_get_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error _ => { - return Err(EvalAltResult::ErrorDotExpr( + return Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_rhs.position(), - )) + ))) } }; - self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level) - .and_then(|(mut val, _, _)| { - let target = Target::from(val.as_mut()); - self.get_dot_val_helper(scope, fn_lib, target, rhs, level) + self.get_indexed_val(scope, fn_lib, &val, idx_expr, *op_pos, level, false) + .and_then(|(mut val, _)| { + self.dot_get_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_lhs.position(), - )), + ))), }, // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_rhs.position(), - )), + ))), } } /// Evaluate a dot chain getter - fn get_dot_val( + fn dot_get( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, dot_lhs: &Expr, dot_rhs: &Expr, level: usize, - ) -> Result { + ) -> Result> { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (entry, _) = Self::search_scope(scope, id, *pos)?; + let (entry, _) = search_scope(scope, id, *pos)?; // Avoid referencing scope which is used below as mut let entry = ScopeSource { name: id, ..entry }; // This is a variable property access (potential function call). // Use a direct index into `scope` to directly mutate the variable value. - self.get_dot_val_helper(scope, fn_lib, Target::from_src(entry), dot_rhs, level) + self.dot_get_helper(scope, fn_lib, entry.into(), dot_rhs, level) } // idx_lhs[idx_expr].??? Expr::Index(idx_lhs, idx_expr, op_pos) => { - let (idx_src_type, src, index, mut val) = + let (src, index, mut val) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - let target = Target::from(val.as_mut()); - let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level); + let value = self.dot_get_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - match src.map(|s| s.typ) { - None => (), + if let Some(src) = src { + match src.typ { + ScopeEntryType::Constant => { + return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + src.name.to_string(), + idx_lhs.position(), + ))); + } - Some(ScopeEntryType::Constant) => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.unwrap().name.to_string(), - idx_lhs.position(), - )); - } - - Some(ScopeEntryType::Normal) => { - Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src.unwrap(), - index, - (val, dot_rhs.position()), - )?; + ScopeEntryType::Normal => { + update_indexed_scope_var(scope, src, index, val, dot_rhs.position())?; + } } } @@ -743,24 +799,13 @@ impl Engine<'_> { // {expr}.??? expr => { let mut val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_dot_val_helper(scope, fn_lib, Target::from(val.as_mut()), dot_rhs, level) + self.dot_get_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level) } } } - /// Search for a variable within the scope, returning its value and index inside the Scope - fn search_scope<'a>( - scope: &'a Scope, - id: &str, - begin: Position, - ) -> Result<(ScopeSource<'a>, Dynamic), EvalAltResult> { - scope - .get(id) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) - } - /// Get the value at the indexed position of a base type - fn get_indexed_value( + fn get_indexed_val( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, @@ -768,76 +813,93 @@ impl Engine<'_> { idx_expr: &Expr, op_pos: Position, level: usize, - ) -> Result<(Dynamic, IndexSourceType, IndexValue), EvalAltResult> { + only_index: bool, + ) -> Result<(Dynamic, IndexValue), Box> { let idx_pos = idx_expr.position(); + let type_name = self.map_type_name(val.type_name()); - // val_array[idx] - if let Some(arr) = val.downcast_ref::() { - let index = self - .eval_expr(scope, fn_lib, idx_expr, level)? - .try_cast::() - .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; + match val.get_ref() { + Union::Array(arr) => { + // val_array[idx] + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .as_int() + .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - return if index >= 0 { - arr.get(index as usize) - .cloned() - .map(|v| (v, IndexSourceType::Array, IndexValue::from_num(index))) - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) - }; - } + let arr_len = arr.len(); - // val_map[idx] - if let Some(map) = val.downcast_ref::() { - let index = self - .eval_expr(scope, fn_lib, idx_expr, level)? - .try_cast::() - .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; + if index >= 0 { + arr.get(index as usize) + .map(|v| { + ( + if only_index { + Dynamic::from_unit() + } else { + v.clone() + }, + IndexValue::from_num(index), + ) + }) + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) + }) + } else { + Err(Box::new(EvalAltResult::ErrorArrayBounds( + arr_len, index, idx_pos, + ))) + } + } - return Ok(( - map.get(&index) - .cloned() - .unwrap_or_else(|| ().into_dynamic()), - IndexSourceType::Map, - IndexValue::from_str(index), - )); - } + Union::Map(map) => { + // val_map[idx] + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .take_string() + .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; - // val_string[idx] - if let Some(s) = val.downcast_ref::() { - let index = self - .eval_expr(scope, fn_lib, idx_expr, level)? - .try_cast::() - .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - - return if index >= 0 { - s.chars() - .nth(index as usize) - .map(|ch| { - ( - ch.into_dynamic(), - IndexSourceType::String, - IndexValue::from_num(index), - ) - }) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), index, idx_pos) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - index, - idx_pos, + Ok(( + map.get(&index) + .map(|v| { + if only_index { + Dynamic::from_unit() + } else { + v.clone() + } + }) + .unwrap_or_else(|| Dynamic::from_unit()), + IndexValue::from_str(index), )) - }; - } + } - // Error - cannot be indexed - Err(EvalAltResult::ErrorIndexingType( - self.map_type_name(val.type_name()).to_string(), - op_pos, - )) + Union::Str(s) => { + // val_string[idx] + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .as_int() + .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; + + let num_chars = s.chars().count(); + + if index >= 0 { + s.chars() + .nth(index as usize) + .map(|ch| (Dynamic::from_char(ch), IndexValue::from_num(index))) + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorStringBounds(num_chars, index, idx_pos)) + }) + } else { + Err(Box::new(EvalAltResult::ErrorStringBounds( + num_chars, index, idx_pos, + ))) + } + } + + // Error - cannot be indexed + _ => Err(Box::new(EvalAltResult::ErrorIndexingType( + type_name.to_string(), + op_pos, + ))), + } } /// Evaluate an index expression @@ -849,149 +911,42 @@ impl Engine<'_> { idx_expr: &Expr, op_pos: Position, level: usize, - ) -> Result< - ( - IndexSourceType, - Option>, - IndexValue, - Dynamic, - ), - EvalAltResult, - > { + ) -> Result<(Option>, IndexValue, Dynamic), Box> { match lhs { // id[idx_expr] - Expr::Variable(id, _) => { - let ( - ScopeSource { - typ: src_type, - index: src_idx, - .. - }, - val, - ) = Self::search_scope(scope, &id, lhs.position())?; + Expr::Variable(name, _) => { + let (ScopeSource { typ, index, .. }, val) = + search_scope(scope, &name, lhs.position())?; + let (val, idx) = + self.get_indexed_val(scope, fn_lib, &val, idx_expr, op_pos, level, false)?; - let (val, idx_src_type, index) = - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level)?; - - Ok(( - idx_src_type, - Some(ScopeSource { - name: &id, - typ: src_type, - index: src_idx, - }), - index, - val, - )) + Ok((Some(ScopeSource { name, typ, index }), idx, val)) } // (expr)[idx_expr] expr => { let val = self.eval_expr(scope, fn_lib, expr, level)?; - - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level) - .map(|(val, _, index)| (IndexSourceType::Expression, None, index, val)) + self.get_indexed_val(scope, fn_lib, &val, idx_expr, op_pos, level, false) + .map(|(val, index)| (None, index, val)) } } } - /// Replace a character at an index position in a mutable string - fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { - let mut chars: Vec = s.chars().collect(); - let ch = *chars.get(idx).expect("string index out of bounds"); - - // See if changed - if so, update the String - if ch != new_ch { - chars[idx] = new_ch; - s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); - } - } - - /// Update the value at an index position in a variable inside the scope - fn update_indexed_var_in_scope( - idx_src_type: IndexSourceType, - scope: &mut Scope, - src: ScopeSource, - idx: IndexValue, - new_val: (Dynamic, Position), - ) -> Result { - match idx_src_type { - // array_id[idx] = val - IndexSourceType::Array => { - let arr = scope.get_mut_by_type::(src); - arr[idx.as_num()] = new_val.0; - Ok(().into_dynamic()) - } - - // map_id[idx] = val - IndexSourceType::Map => { - let arr = scope.get_mut_by_type::(src); - arr.insert(idx.as_str(), new_val.0); - Ok(().into_dynamic()) - } - - // string_id[idx] = val - IndexSourceType::String => { - let s = scope.get_mut_by_type::(src); - let pos = new_val.1; - // Value must be a character - let ch = new_val - .0 - .try_cast::() - .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Self::str_replace_char(s, idx.as_num(), ch); - Ok(().into_dynamic()) - } - - IndexSourceType::Expression => panic!("expression cannot be indexed for update"), - } - } - - /// Update the value at an index position - fn update_indexed_value( - mut target: Dynamic, - idx: IndexValue, - new_val: Dynamic, - pos: Position, - ) -> Result { - if let Some(arr) = target.downcast_mut::() { - arr[idx.as_num()] = new_val; - return Ok(target); - } - - if let Some(map) = target.downcast_mut::() { - map.insert(idx.as_str(), new_val); - return Ok(target); - } - - if let Some(s) = target.downcast_mut::() { - // Value must be a character - let ch = new_val - .try_cast::() - .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Self::str_replace_char(s, idx.as_num(), ch); - return Ok(target); - } - - // All other variable types should be an error - panic!("array, map or string source type expected for indexing") - } - /// Chain-evaluate a dot setter - fn set_dot_val_helper( + fn dot_set_helper( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, - this_ptr: &mut Variant, + this_ptr: &mut Dynamic, dot_rhs: &Expr, - new_val: (&mut Dynamic, Position), + new_val: &mut Dynamic, + val_pos: Position, level: usize, - ) -> Result { + ) -> Result> { match dot_rhs { // xxx.id Expr::Property(id, pos) => { - let mut args = [this_ptr, new_val.0.as_mut()]; + let mut args = [this_ptr, new_val]; self.call_fn_raw(None, fn_lib, &make_setter(id), &mut args, None, *pos, 0) } @@ -1003,23 +958,24 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|val| { - let (_, _, index) = self - .get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level)?; + let (_, index) = self.get_indexed_val( + scope, fn_lib, &val, idx_expr, *op_pos, level, true, + )?; - Self::update_indexed_value(val, index, new_val.0.clone(), new_val.1) + update_indexed_val(val, index, new_val.clone(), val_pos) }) .and_then(|mut val| { let fn_name = make_setter(id); - let mut args = [this_ptr, val.as_mut()]; + let mut args = [this_ptr, &mut val]; self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } // All others - syntax error for setters chain - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), *op_pos, - )), + ))), }, // xxx.lhs.{...} @@ -1029,13 +985,14 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|mut val| { - let value = val.as_mut(); - self.set_dot_val_helper(scope, fn_lib, value, rhs, new_val, level) - .map(|_| val) // Discard Ok return value + self.dot_set_helper( + scope, fn_lib, &mut val, rhs, new_val, val_pos, level, + )?; + Ok(val) }) .and_then(|mut val| { let fn_name = make_setter(id); - let mut args = [this_ptr, val.as_mut()]; + let mut args = [this_ptr, &mut val]; self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } @@ -1048,75 +1005,74 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { - let (mut value, _, index) = self.get_indexed_value( - scope, fn_lib, &v, idx_expr, *op_pos, level, + let (mut value, index) = self.get_indexed_val( + scope, fn_lib, &v, idx_expr, *op_pos, level, false, )?; - let val_pos = new_val.1; - let this_ptr = value.as_mut(); - self.set_dot_val_helper( - scope, fn_lib, this_ptr, rhs, new_val, level, + self.dot_set_helper( + scope, fn_lib, &mut value, rhs, new_val, val_pos, level, )?; // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - Self::update_indexed_value(v, index, value, val_pos) + update_indexed_val(v, index, value, val_pos) }) .and_then(|mut v| { let fn_name = make_setter(id); - let mut args = [this_ptr, v.as_mut()]; + let mut args = [this_ptr, &mut v]; self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } // All others - syntax error for setters chain - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), *op_pos, - )), + ))), }, // All others - syntax error for setters chain - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), lhs.position(), - )), + ))), }, // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), dot_rhs.position(), - )), + ))), } } // Evaluate a dot chain setter - fn set_dot_val( + fn dot_set( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, dot_lhs: &Expr, dot_rhs: &Expr, - new_val: (&mut Dynamic, Position), + new_val: &mut Dynamic, + val_pos: Position, op_pos: Position, level: usize, - ) -> Result { + ) -> Result> { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (src, mut target) = Self::search_scope(scope, id, *pos)?; + let (src, mut target) = search_scope(scope, id, *pos)?; match src.typ { - ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant( - id.to_string(), - op_pos, + ScopeEntryType::Constant => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(id.to_string(), op_pos), )), _ => { // Avoid referencing scope which is used below as mut let entry = ScopeSource { name: id, ..src }; - let this_ptr = target.as_mut(); - let value = self - .set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); + let this_ptr = &mut target; + let value = self.dot_set_helper( + scope, fn_lib, this_ptr, dot_rhs, new_val, val_pos, level, + ); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. *scope.get_mut(entry) = target; @@ -1129,32 +1085,24 @@ impl Engine<'_> { // lhs[idx_expr].??? // TODO - Allow chaining of indexing! Expr::Index(lhs, idx_expr, op_pos) => { - let (idx_src_type, src, index, mut target) = + let (src, index, mut target) = self.eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level)?; - let val_pos = new_val.1; - let this_ptr = target.as_mut(); + let this_ptr = &mut target; let value = - self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); + self.dot_set_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, val_pos, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - match src.map(|x| x.typ) { - None => (), - - Some(ScopeEntryType::Constant) => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.unwrap().name.to_string(), - lhs.position(), - )); - } - - Some(ScopeEntryType::Normal) => { - Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src.unwrap(), - index, - (target, val_pos), - )?; + if let Some(src) = src { + match src.typ { + ScopeEntryType::Constant => { + return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + src.name.to_string(), + lhs.position(), + ))); + } + ScopeEntryType::Normal => { + update_indexed_scope_var(scope, src, index, target, val_pos)?; + } } } @@ -1162,10 +1110,10 @@ impl Engine<'_> { } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), dot_lhs.position(), - )), + ))), } } @@ -1177,60 +1125,54 @@ impl Engine<'_> { lhs: &Expr, rhs: &Expr, level: usize, - ) -> Result { + ) -> Result> { let mut lhs_value = self.eval_expr(scope, fn_lib, lhs, level)?; let rhs_value = self.eval_expr(scope, fn_lib, rhs, level)?; - if rhs_value.is::() { - let mut rhs_value = rhs_value.cast::(); - let def_value = false.into_dynamic(); - let mut result = false; + match rhs_value { + Dynamic(Union::Array(mut rhs_value)) => { + let def_value = Dynamic::from_bool(false); + let mut result = false; - // Call the '==' operator to compare each value - for value in rhs_value.iter_mut() { - let args = &mut [lhs_value.as_mut(), value.as_mut()]; - let def_value = Some(&def_value); - if self - .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? - .try_cast::() - .unwrap_or(false) - { - result = true; - break; + // Call the '==' operator to compare each value + for value in rhs_value.iter_mut() { + let args = &mut [&mut lhs_value, value]; + let def_value = Some(&def_value); + if self + .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? + .as_bool() + .unwrap_or(false) + { + result = true; + break; + } + } + + Ok(Dynamic::from_bool(result)) + } + Dynamic(Union::Map(rhs_value)) => { + // Only allows String or char + match lhs_value { + Dynamic(Union::Str(s)) => { + Ok(Dynamic::from_bool(rhs_value.contains_key(s.as_ref()))) + } + Dynamic(Union::Char(c)) => { + Ok(Dynamic::from_bool(rhs_value.contains_key(&c.to_string()))) + } + _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), } } - - Ok(result.into_dynamic()) - } else if rhs_value.is::() { - let rhs_value = rhs_value.cast::(); - - // Only allows String or char - if lhs_value.is::() { - Ok(rhs_value - .contains_key(&lhs_value.cast::()) - .into_dynamic()) - } else if lhs_value.is::() { - Ok(rhs_value - .contains_key(&lhs_value.cast::().to_string()) - .into_dynamic()) - } else { - Err(EvalAltResult::ErrorInExpr(lhs.position())) + Dynamic(Union::Str(rhs_value)) => { + // Only allows String or char + match lhs_value { + Dynamic(Union::Str(s)) => { + Ok(Dynamic::from_bool(rhs_value.contains(s.as_ref()))) + } + Dynamic(Union::Char(c)) => Ok(Dynamic::from_bool(rhs_value.contains(c))), + _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), + } } - } else if rhs_value.is::() { - let rhs_value = rhs_value.cast::(); - - // Only allows String or char - if lhs_value.is::() { - Ok(rhs_value - .contains(&lhs_value.cast::()) - .into_dynamic()) - } else if lhs_value.is::() { - Ok(rhs_value.contains(lhs_value.cast::()).into_dynamic()) - } else { - Err(EvalAltResult::ErrorInExpr(lhs.position())) - } - } else { - Err(EvalAltResult::ErrorInExpr(rhs.position())) + _ => Err(Box::new(EvalAltResult::ErrorInExpr(rhs.position()))), } } @@ -1241,13 +1183,14 @@ impl Engine<'_> { fn_lib: Option<&FunctionsLib>, expr: &Expr, level: usize, - ) -> Result { + ) -> Result> { match expr { - Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), - Expr::FloatConstant(f, _) => Ok(f.into_dynamic()), - Expr::StringConstant(s, _) => Ok(s.clone().into_owned().into_dynamic()), - Expr::CharConstant(c, _) => Ok(c.into_dynamic()), - Expr::Variable(id, pos) => Self::search_scope(scope, id, *pos).map(|(_, val)| val), + Expr::IntegerConstant(i, _) => Ok(Dynamic::from_int(*i)), + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(f, _) => Ok(Dynamic::from_float(*f)), + Expr::StringConstant(s, _) => Ok(Dynamic::from_string(s.to_string())), + Expr::CharConstant(c, _) => Ok(Dynamic::from_char(*c)), + Expr::Variable(id, pos) => search_scope(scope, id, *pos).map(|(_, val)| val), Expr::Property(_, _) => panic!("unexpected property."), // Statement block @@ -1261,10 +1204,10 @@ impl Engine<'_> { // name = rhs Expr::Variable(name, pos) => match scope.get(name) { None => { - return Err(EvalAltResult::ErrorVariableNotFound( - name.clone().into_owned(), + return Err(Box::new(EvalAltResult::ErrorVariableNotFound( + name.to_string(), *pos, - )) + ))) } Some(( @@ -1288,55 +1231,60 @@ impl Engine<'_> { .. }, _, - )) => Err(EvalAltResult::ErrorAssignmentToConstant( + )) => Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( name.to_string(), *op_pos, - )), + ))), }, // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { - let (idx_src_type, src, index, _) = + let (src, index, _) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - match src.map(|x| x.typ) { - None => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( - idx_lhs.position(), - )), - - Some(ScopeEntryType::Constant) => { - Err(EvalAltResult::ErrorAssignmentToConstant( - src.unwrap().name.to_string(), - idx_lhs.position(), - )) + if let Some(src) = src { + match src.typ { + ScopeEntryType::Constant => { + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + src.name.to_string(), + idx_lhs.position(), + ))) + } + ScopeEntryType::Normal => { + let pos = rhs.position(); + Ok(update_indexed_scope_var(scope, src, index, rhs_val, pos)?) + } } - - Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src.unwrap(), - index, - (rhs_val, rhs.position()), - )?), + } else { + Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( + idx_lhs.position(), + ))) } } // dot_lhs.dot_rhs = rhs #[cfg(not(feature = "no_object"))] Expr::Dot(dot_lhs, dot_rhs, _) => { - let new_val = (&mut rhs_val, rhs.position()); - self.set_dot_val(scope, fn_lib, dot_lhs, dot_rhs, new_val, *op_pos, level) + let new_val = &mut rhs_val; + let val_pos = rhs.position(); + self.dot_set( + scope, fn_lib, dot_lhs, dot_rhs, new_val, val_pos, *op_pos, level, + ) } // Error assignment to constant - expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant( - expr.get_constant_str(), - lhs.position(), - )), + expr if expr.is_constant() => { + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + expr.get_constant_str(), + lhs.position(), + ))) + } // Syntax error - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), + _ => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( + lhs.position(), + ))), } } @@ -1344,10 +1292,10 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, op_pos) => self .eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level) - .map(|(_, _, _, x)| x), + .map(|(_, _, x)| x), #[cfg(not(feature = "no_object"))] - Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, fn_lib, lhs, rhs, level), + Expr::Dot(lhs, rhs, _) => self.dot_get(scope, fn_lib, lhs, rhs, level), #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => { @@ -1358,20 +1306,20 @@ impl Engine<'_> { .map(|val| arr.push(val)) })?; - Ok((arr).into_dynamic()) + Ok(Dynamic(Union::Array(Box::new(arr)))) } #[cfg(not(feature = "no_object"))] Expr::Map(contents, _) => { let mut map = Map::new(); - contents.into_iter().try_for_each(|item| { - self.eval_expr(scope, fn_lib, &item.1, level).map(|val| { - map.insert(item.0.clone(), val); + contents.into_iter().try_for_each(|(key, expr, _)| { + self.eval_expr(scope, fn_lib, &expr, level).map(|val| { + map.insert(key.clone(), val); }) })?; - Ok((map).into_dynamic()) + Ok(Dynamic(Union::Map(Box::new(map)))) } Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { @@ -1381,12 +1329,10 @@ impl Engine<'_> { fn_lib: Option<&FunctionsLib>, name: &str, ) -> bool { - engine.functions.as_ref().map_or(false, |lib| { - lib.contains_key(&FnSpec { - name: name.into(), - args: vec![TypeId::of::()], - }) - }) || fn_lib.map_or(false, |lib| lib.has_function(name, 1)) + engine + .functions + .contains_key(&calc_fn_spec(name, once(TypeId::of::()))) + || fn_lib.map_or(false, |lib| lib.has_function(name, 1)) } match fn_name.as_ref() { @@ -1396,10 +1342,9 @@ impl Engine<'_> { && !has_override(self, fn_lib, KEYWORD_TYPE_OF) => { let result = self.eval_expr(scope, fn_lib, &args_expr_list[0], level)?; - Ok(self - .map_type_name((*result).type_name()) - .to_string() - .into_dynamic()) + Ok(Dynamic::from_string( + self.map_type_name(result.type_name()).to_string(), + )) } // eval @@ -1411,15 +1356,9 @@ impl Engine<'_> { let result = self.eval_expr(scope, fn_lib, &args_expr_list[0], level)?; // Get the script text by evaluating the expression - let script = result - .downcast_ref::() - .map(String::as_str) - .ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - result.type_name().into(), - pos, - ) - })?; + let script = result.as_str().map_err(|type_name| { + EvalAltResult::ErrorMismatchOutputType(type_name.into(), pos) + })?; // Compile the script text // No optimizations because we only run it once @@ -1429,13 +1368,13 @@ impl Engine<'_> { script, OptimizationLevel::None, ) - .map_err(EvalAltResult::ErrorParsing)?; + .map_err(|err| EvalAltResult::ErrorParsing(Box::new(err)))?; // If new functions are defined within the eval string, it is an error if ast.1.len() > 0 { - return Err(EvalAltResult::ErrorParsing( + return Err(Box::new(EvalAltResult::ErrorParsing( ParseErrorType::WrongFnDefinition.into_err(pos), - )); + ))); } if let Some(lib) = fn_lib { @@ -1451,7 +1390,7 @@ impl Engine<'_> { // Evaluate the AST self.eval_ast_with_scope_raw(scope, &ast) - .map_err(|err| err.set_position(pos)) + .map_err(|err| Box::new(err.set_position(pos))) } // Normal function call @@ -1461,7 +1400,7 @@ impl Engine<'_> { .map(|expr| self.eval_expr(scope, fn_lib, expr, level)) .collect::, _>>()?; - let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut args: Vec<_> = arg_values.iter_mut().collect(); let def_val = def_val.as_ref(); self.call_fn_raw(None, fn_lib, fn_name, &mut args, def_val, *pos, level) } @@ -1472,41 +1411,41 @@ impl Engine<'_> { self.eval_in_expr(scope, fn_lib, lhs.as_ref(), rhs.as_ref(), level) } - Expr::And(lhs, rhs, _) => Ok(Box::new( + Expr::And(lhs, rhs, _) => Ok(Dynamic::from_bool( self .eval_expr(scope, fn_lib,lhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && self .eval_expr(scope, fn_lib,rhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?, )), - Expr::Or(lhs, rhs, _) => Ok(Box::new( + Expr::Or(lhs, rhs, _) => Ok(Dynamic::from_bool( self .eval_expr(scope,fn_lib, lhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || self .eval_expr(scope,fn_lib, rhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) })?, )), - Expr::True(_) => Ok(true.into_dynamic()), - Expr::False(_) => Ok(false.into_dynamic()), - Expr::Unit(_) => Ok(().into_dynamic()), + Expr::True(_) => Ok(Dynamic::from_bool(true)), + Expr::False(_) => Ok(Dynamic::from_bool(false)), + Expr::Unit(_) => Ok(Dynamic::from_unit()), _ => panic!("should not appear: {:?}", expr), } @@ -1519,10 +1458,10 @@ impl Engine<'_> { fn_lib: Option<&FunctionsLib>, stmt: &Stmt, level: usize, - ) -> Result { + ) -> Result> { match stmt { // No-op - Stmt::Noop(_) => Ok(().into_dynamic()), + Stmt::Noop(_) => Ok(Dynamic::from_unit()), // Expression as statement Stmt::Expr(expr) => { @@ -1532,7 +1471,7 @@ impl Engine<'_> { result } else { // If it is an assignment, erase the result at the root - ().into_dynamic() + Dynamic::from_unit() }) } @@ -1540,7 +1479,7 @@ impl Engine<'_> { Stmt::Block(block, _) => { let prev_len = scope.len(); - let result = block.iter().try_fold(().into_dynamic(), |_, stmt| { + let result = block.iter().try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, fn_lib, stmt, level) }); @@ -1552,53 +1491,56 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self .eval_expr(scope, fn_lib, guard, level)? - .try_cast::() - .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) + .as_bool() + .map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(guard.position()))) .and_then(|guard_val| { if guard_val { self.eval_stmt(scope, fn_lib, if_body, level) } else if let Some(stmt) = else_body { self.eval_stmt(scope, fn_lib, stmt.as_ref(), level) } else { - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } }), // While loop Stmt::While(guard, body) => loop { - match self - .eval_expr(scope, fn_lib, guard, level)? - .try_cast::() - { - Ok(guard_val) if guard_val => { - match self.eval_stmt(scope, fn_lib, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => { - return Ok(().into_dynamic()) + match self.eval_expr(scope, fn_lib, guard, level)?.as_bool() { + Ok(true) => match self.eval_stmt(scope, fn_lib, body, level) { + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::ErrorLoopBreak(false, _) => (), + EvalAltResult::ErrorLoopBreak(true, _) => { + return Ok(Dynamic::from_unit()) } - Err(x) => return Err(x), - } + _ => return Err(err), + }, + }, + Ok(false) => return Ok(Dynamic::from_unit()), + Err(_) => { + return Err(Box::new(EvalAltResult::ErrorLogicGuard(guard.position()))) } - Ok(_) => return Ok(().into_dynamic()), - Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } }, // Loop statement Stmt::Loop(body) => loop { match self.eval_stmt(scope, fn_lib, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), - Err(x) => return Err(x), + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::ErrorLoopBreak(false, _) => (), + EvalAltResult::ErrorLoopBreak(true, _) => return Ok(Dynamic::from_unit()), + _ => return Err(err), + }, } }, // For loop Stmt::For(name, expr, body) => { let arr = self.eval_expr(scope, fn_lib, expr, level)?; - let tid = Any::type_id(arr.as_ref()); + let tid = arr.type_id(); - if let Some(iter_fn) = self.type_iterators.as_ref().and_then(|t| t.get(&tid)) { + if let Some(iter_fn) = self.type_iterators.get(&tid) { // Add the loop variable - variable name is copied // TODO - avoid copying variable name scope.push(name.clone(), ()); @@ -1613,48 +1555,50 @@ impl Engine<'_> { *scope.get_mut(entry) = a; match self.eval_stmt(scope, fn_lib, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, - Err(x) => return Err(x), + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::ErrorLoopBreak(false, _) => (), + EvalAltResult::ErrorLoopBreak(true, _) => break, + _ => return Err(err), + }, } } scope.rewind(scope.len() - 1); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } else { - Err(EvalAltResult::ErrorFor(expr.position())) + Err(Box::new(EvalAltResult::ErrorFor(expr.position()))) } } // Continue statement - Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)), + Stmt::Continue(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(false, *pos))), // Break statement - Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)), + Stmt::Break(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(true, *pos))), // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { - Err(EvalAltResult::Return(().into_dynamic(), *pos)) + Err(Box::new(EvalAltResult::Return(Dynamic::from_unit(), *pos))) } // Return value - Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(EvalAltResult::Return( - self.eval_expr(scope, fn_lib, a, level)?, - *pos, + Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(Box::new( + EvalAltResult::Return(self.eval_expr(scope, fn_lib, a, level)?, *pos), )), // Empty throw Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => { - Err(EvalAltResult::ErrorRuntime("".into(), *pos)) + Err(Box::new(EvalAltResult::ErrorRuntime("".into(), *pos))) } // Throw value Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, fn_lib, a, level)?; - Err(EvalAltResult::ErrorRuntime( - val.try_cast::().unwrap_or_else(|_| "".to_string()), + Err(Box::new(EvalAltResult::ErrorRuntime( + val.take_string().unwrap_or_else(|_| "".to_string()), *pos, - )) + ))) } // Let statement @@ -1662,13 +1606,13 @@ impl Engine<'_> { let val = self.eval_expr(scope, fn_lib, expr, level)?; // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } Stmt::Let(name, None, _) => { // TODO - avoid copying variable name in inner block? scope.push(name.clone(), ()); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } // Const statement @@ -1676,7 +1620,7 @@ impl Engine<'_> { let val = self.eval_expr(scope, fn_lib, expr, level)?; // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } Stmt::Const(_, _, _) => panic!("constant expression not constant!"), @@ -1691,9 +1635,3 @@ impl Engine<'_> { .unwrap_or(name) } } - -/// Print/debug to stdout -fn default_print(s: &str) { - #[cfg(not(feature = "no_std"))] - println!("{}", s); -} diff --git a/src/error.rs b/src/error.rs index 6ad21bbd..4de71bbf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ //! Module containing error definitions for the parsing process. -use crate::parser::Position; +use crate::token::Position; use crate::stdlib::{char, error::Error, fmt, string::String}; @@ -107,13 +107,8 @@ pub enum ParseErrorType { impl ParseErrorType { /// Make a `ParseError` using the current type and position. - pub(crate) fn into_err(self, pos: Position) -> ParseError { - ParseError(self, pos) - } - - /// Make a `ParseError` using the current type and EOF position. - pub(crate) fn into_err_eof(self) -> ParseError { - ParseError(self, Position::eof()) + pub(crate) fn into_err(self, pos: Position) -> Box { + Box::new(ParseError(self, pos)) } } @@ -209,13 +204,11 @@ impl fmt::Display for ParseError { _ => write!(f, "{}", self.desc())?, } - if !self.1.is_eof() { - write!(f, " ({})", self.1) - } else if !self.1.is_none() { + if !self.1.is_none() { // Do not write any position if None Ok(()) } else { - write!(f, " at the end of the script but there is no more input") + write!(f, " ({})", self.1) } } } diff --git a/src/fn_call.rs b/src/fn_call.rs index c81b25bc..29a9c056 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -2,9 +2,8 @@ #![allow(non_snake_case)] -use crate::any::{Any, Dynamic}; - -use crate::stdlib::{string::String, vec, vec::Vec}; +use crate::any::{Dynamic, Variant}; +use crate::stdlib::vec::Vec; /// Trait that represent arguments to a function call. /// Any data type that can be converted into a `Vec` of `Dynamic` values can be used @@ -18,7 +17,7 @@ pub trait FuncArgs { /// converted into `Dynamic`). macro_rules! impl_args { ($($p:ident),*) => { - impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) + impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) { fn into_vec(self) -> Vec { let ($($p,)*) = self; diff --git a/src/fn_func.rs b/src/fn_func.rs index 7350d6d5..4c3544de 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -2,7 +2,7 @@ #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] -use crate::any::Any; +use crate::any::Variant; use crate::engine::Engine; use crate::error::ParseError; use crate::parser::AST; @@ -88,13 +88,13 @@ macro_rules! def_anonymous_fn { def_anonymous_fn!(imp); }; (imp $($par:ident),*) => { - impl<'e, $($par: Any + Clone,)* RET: Any + Clone> Func<($($par,)*), RET> for Engine<'e> + impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine { #[cfg(feature = "sync")] - type Output = Box Result + Send + Sync + 'e>; + type Output = Box Result + Send + Sync>; #[cfg(not(feature = "sync"))] - type Output = Box Result + 'e>; + type Output = Box Result>; fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { let name = entry_point.to_string(); diff --git a/src/fn_register.rs b/src/fn_register.rs index 9030066a..5941095e 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,10 +2,10 @@ #![allow(non_snake_case)] -use crate::any::{Any, Dynamic}; +use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, FnCallArgs}; -use crate::parser::Position; use crate::result::EvalAltResult; +use crate::token::Position; use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec}; @@ -53,7 +53,7 @@ pub trait RegisterDynamicFn { /// /// // Function that returns a Dynamic value /// fn return_the_same_as_dynamic(x: i64) -> Dynamic { - /// Box::new(x) + /// Dynamic::from(x) /// } /// /// let mut engine = Engine::new(); @@ -137,7 +137,7 @@ macro_rules! def_register { // ^ function parameter actual type (T, &T or &mut T) // ^ dereferencing function impl< - $($par: Any + Clone,)* + $($par: Variant + Clone,)* #[cfg(feature = "sync")] FN: Fn($($param),*) -> RET + Send + Sync + 'static, @@ -145,8 +145,8 @@ macro_rules! def_register { #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> RET + 'static, - RET: Any - > RegisterFn for Engine<'_> + RET: Variant + Clone + > RegisterFn for Engine { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -156,20 +156,20 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))); } #[allow(unused_variables, unused_mut)] let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); )* // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Dynamic) + Ok(r.into_dynamic()) }; self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); @@ -177,14 +177,14 @@ macro_rules! def_register { } impl< - $($par: Any + Clone,)* + $($par: Variant + Clone,)* #[cfg(feature = "sync")] FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Dynamic + 'static, - > RegisterDynamicFn for Engine<'_> + > RegisterDynamicFn for Engine { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -194,14 +194,14 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))); } #[allow(unused_variables, unused_mut)] let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); )* // Call the user-supplied function using ($clone) to @@ -213,15 +213,15 @@ macro_rules! def_register { } impl< - $($par: Any + Clone,)* + $($par: Variant + Clone,)* #[cfg(feature = "sync")] FN: Fn($($param),*) -> Result + Send + Sync + 'static, #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Result + 'static, - RET: Any - > RegisterResultFn for Engine<'_> + RET: Variant + Clone + > RegisterResultFn for Engine { fn register_result_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -231,20 +231,20 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))); } #[allow(unused_variables, unused_mut)] let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); )* // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. - f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic) - .map_err(|err| err.set_position(pos)) + f($(($clone)($par)),*).map(|r| r.into_dynamic()) + .map_err(|err| Box::new(err.set_position(pos))) }; self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); } diff --git a/src/lib.rs b/src/lib.rs index dd4155ec..de8e80db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,15 +82,17 @@ mod parser; mod result; mod scope; mod stdlib; +mod token; -pub use any::{Any, AnyExt, Dynamic, Variant}; +pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; -pub use parser::{Position, AST, INT}; +pub use parser::{AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; +pub use token::Position; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; diff --git a/src/optimize.rs b/src/optimize.rs index fc5a4577..39a5b995 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,11 +1,12 @@ -use crate::any::{Any, Dynamic}; +use crate::any::Dynamic; use crate::engine::{ - Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + calc_fn_spec, Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, + KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Position, ReturnType, Stmt, AST}; +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}; +use crate::token::Position; use crate::stdlib::{ boxed::Box, @@ -49,7 +50,7 @@ struct State<'a> { /// Collection of constants to use for eager function evaluations. constants: Vec<(String, Expr)>, /// An `Engine` instance for eager function evaluation. - engine: &'a Engine<'a>, + engine: &'a Engine, /// Library of script-defined functions. fn_lib: &'a [(&'a str, usize)], /// Optimization level. @@ -59,7 +60,7 @@ struct State<'a> { impl<'a> State<'a> { /// Create a new State. pub fn new( - engine: &'a Engine<'a>, + engine: &'a Engine, fn_lib: &'a [(&'a str, usize)], level: OptimizationLevel, ) -> Self { @@ -109,19 +110,14 @@ impl<'a> State<'a> { /// Call a registered function fn call_fn( - functions: Option<&HashMap>>, + functions: &HashMap>, fn_name: &str, args: &mut FnCallArgs, pos: Position, -) -> Result, EvalAltResult> { - let spec = FnSpec { - name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(*a)).collect(), - }; - +) -> Result, Box> { // Search built-in's and external functions functions - .and_then(|f| f.get(&spec)) + .get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id()))) .map(|func| func(args, pos)) .transpose() } @@ -570,7 +566,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); - let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut call_args: Vec<_> = arg_values.iter_mut().collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure @@ -580,12 +576,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok() + call_fn(&state.engine.functions, &id, &mut call_args, pos).ok() .and_then(|result| result.or_else(|| { if !arg_for_type_of.is_empty() { // Handle `type_of()` - Some(arg_for_type_of.to_string().into_dynamic()) + Some(Dynamic::from_string(arg_for_type_of.to_string())) } else { // Otherwise use the default value, if any def_value.clone() @@ -620,7 +616,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { fn optimize<'a>( statements: Vec, - engine: &Engine<'a>, + engine: &Engine, scope: &Scope, fn_lib: &'a [(&'a str, usize)], level: OptimizationLevel, diff --git a/src/parser.rs b/src/parser.rs index 9874edcf..802f8679 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,27 +1,25 @@ //! Main module defining the lexer and parser. -use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::{Array, Engine, FunctionsLib, Map}; +use crate::any::{Dynamic, Union}; +use crate::engine::{calc_fn_def, Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::token::{Position, Token, TokenIterator}; use crate::stdlib::{ borrow::Cow, boxed::Box, char, collections::HashMap, - fmt, fmt::Display, format, iter::Peekable, ops::Add, rc::Rc, - str::Chars, - str::FromStr, string::{String, ToString}, sync::Arc, - usize, vec, + vec, vec::Vec, }; @@ -42,125 +40,8 @@ pub type INT = i32; /// Not available under the `no_float` feature. pub type FLOAT = f64; -type LERR = LexError; type PERR = ParseErrorType; -/// A location (line number + character position) in the input script. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] -pub struct Position { - /// Line number - 0 = none, MAX = EOF - line: usize, - /// Character position - 0 = BOL, MAX = EOF - pos: usize, -} - -impl Position { - /// Create a new `Position`. - pub fn new(line: usize, position: usize) -> Self { - assert!(line != 0, "line cannot be zero"); - assert!( - line != usize::MAX || position != usize::MAX, - "invalid position" - ); - - Self { - line, - pos: position, - } - } - - /// Get the line number (1-based), or `None` if no position or EOF. - pub fn line(&self) -> Option { - if self.is_none() || self.is_eof() { - None - } else { - Some(self.line) - } - } - - /// Get the character position (1-based), or `None` if at beginning of a line. - pub fn position(&self) -> Option { - if self.is_none() || self.is_eof() || self.pos == 0 { - None - } else { - Some(self.pos) - } - } - - /// Advance by one character position. - pub(crate) fn advance(&mut self) { - self.pos += 1; - } - - /// Go backwards by one character position. - /// - /// # Panics - /// - /// Panics if already at beginning of a line - cannot rewind to a previous line. - /// - pub(crate) fn rewind(&mut self) { - assert!(self.pos > 0, "cannot rewind at position 0"); - self.pos -= 1; - } - - /// Advance to the next line. - pub(crate) fn new_line(&mut self) { - self.line += 1; - self.pos = 0; - } - - /// Create a `Position` representing no position. - pub(crate) fn none() -> Self { - Self { line: 0, pos: 0 } - } - - /// Create a `Position` at EOF. - pub(crate) fn eof() -> Self { - Self { - line: usize::MAX, - pos: usize::MAX, - } - } - - /// Is there no `Position`? - pub fn is_none(&self) -> bool { - self.line == 0 && self.pos == 0 - } - - /// Is the `Position` at EOF? - pub fn is_eof(&self) -> bool { - self.line == usize::MAX && self.pos == usize::MAX - } -} - -impl Default for Position { - fn default() -> Self { - Self::new(1, 0) - } -} - -impl fmt::Display for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_eof() { - write!(f, "EOF") - } else if self.is_none() { - write!(f, "none") - } else { - write!(f, "line {}, position {}", self.line, self.pos) - } - } -} - -impl fmt::Debug for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_eof() { - write!(f, "(EOF)") - } else { - write!(f, "({}:{})", self.line, self.pos) - } - } -} - /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. @@ -440,25 +321,32 @@ impl Expr { /// Panics when the expression is not constant. pub fn get_constant_value(&self) -> Dynamic { match self { - Self::IntegerConstant(i, _) => i.into_dynamic(), - Self::FloatConstant(f, _) => f.into_dynamic(), - Self::CharConstant(c, _) => c.into_dynamic(), - Self::StringConstant(s, _) => s.clone().into_owned().into_dynamic(), - Self::True(_) => true.into_dynamic(), - Self::False(_) => false.into_dynamic(), - Self::Unit(_) => ().into_dynamic(), + Self::IntegerConstant(i, _) => Dynamic::from_int(*i), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(f, _) => Dynamic::from_float(*f), + Self::CharConstant(c, _) => Dynamic::from_char(*c), + Self::StringConstant(s, _) => Dynamic::from_string(s.to_string()), + Self::True(_) => Dynamic::from_bool(true), + Self::False(_) => Dynamic::from_bool(false), + Self::Unit(_) => Dynamic::from_unit(), - Self::Array(items, _) if items.iter().all(Self::is_constant) => items - .iter() - .map(Self::get_constant_value) - .collect::>() - .into_dynamic(), + Self::Array(items, _) if items.iter().all(Self::is_constant) => { + Dynamic(Union::Array(Box::new( + items + .iter() + .map(Self::get_constant_value) + .collect::>(), + ))) + } - Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items - .iter() - .map(|(k, v, _)| (k.clone(), v.get_constant_value())) - .collect::>() - .into_dynamic(), + Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => { + Dynamic(Union::Map(Box::new( + items + .iter() + .map(|(k, v, _)| (k.clone(), v.get_constant_value())) + .collect::>(), + ))) + } _ => panic!("cannot get value of non-constant expression"), } @@ -551,7 +439,9 @@ impl Expr { Self::Stmt(stmt, _) => stmt.is_pure(), - expr => expr.is_constant() || matches!(expr, Self::Variable(_, _)), + Self::Variable(_, _) => true, + + expr => expr.is_constant(), } } @@ -582,851 +472,65 @@ impl Expr { _ => false, } } -} - -/// Tokens. -#[derive(Debug, PartialEq, Clone)] -pub enum Token { - IntegerConstant(INT), - FloatConstant(FLOAT), - Identifier(String), - CharConstant(char), - StringConst(String), - LeftBrace, - RightBrace, - LeftParen, - RightParen, - LeftBracket, - RightBracket, - Plus, - UnaryPlus, - Minus, - UnaryMinus, - Multiply, - Divide, - Modulo, - PowerOf, - LeftShift, - RightShift, - SemiColon, - Colon, - Comma, - Period, - #[cfg(not(feature = "no_object"))] - MapStart, - Equals, - True, - False, - Let, - Const, - If, - Else, - While, - Loop, - For, - In, - LessThan, - GreaterThan, - LessThanEqualsTo, - GreaterThanEqualsTo, - EqualsTo, - NotEqualsTo, - Bang, - Pipe, - Or, - XOr, - Ampersand, - And, - #[cfg(not(feature = "no_function"))] - Fn, - Continue, - Break, - Return, - Throw, - PlusAssign, - MinusAssign, - MultiplyAssign, - DivideAssign, - LeftShiftAssign, - RightShiftAssign, - AndAssign, - OrAssign, - XOrAssign, - ModuloAssign, - PowerOfAssign, - LexError(Box), -} - -impl Token { - /// Get the syntax of the token. - pub fn syntax(&self) -> Cow { - use Token::*; + /// Is a particular token allowed as a postfix operator to this expression? + pub fn is_valid_postfix(&self, token: &Token) -> bool { match self { - IntegerConstant(i) => i.to_string().into(), - FloatConstant(f) => f.to_string().into(), - Identifier(s) => s.into(), - CharConstant(c) => c.to_string().into(), - LexError(err) => err.to_string().into(), + Expr::IntegerConstant(_, _) + | Expr::FloatConstant(_, _) + | Expr::CharConstant(_, _) + | Expr::In(_, _, _) + | Expr::And(_, _, _) + | Expr::Or(_, _, _) + | Expr::True(_) + | Expr::False(_) + | Expr::Unit(_) => false, - token => (match token { - StringConst(_) => "string", - LeftBrace => "{", - RightBrace => "}", - LeftParen => "(", - RightParen => ")", - LeftBracket => "[", - RightBracket => "]", - Plus => "+", - UnaryPlus => "+", - Minus => "-", - UnaryMinus => "-", - Multiply => "*", - Divide => "/", - SemiColon => ";", - Colon => ":", - Comma => ",", - Period => ".", - #[cfg(not(feature = "no_object"))] - MapStart => "#{", - Equals => "=", - True => "true", - False => "false", - Let => "let", - Const => "const", - If => "if", - Else => "else", - While => "while", - Loop => "loop", - LessThan => "<", - GreaterThan => ">", - Bang => "!", - LessThanEqualsTo => "<=", - GreaterThanEqualsTo => ">=", - EqualsTo => "==", - NotEqualsTo => "!=", - Pipe => "|", - Or => "||", - Ampersand => "&", - And => "&&", - #[cfg(not(feature = "no_function"))] - Fn => "fn", - Continue => "continue", - Break => "break", - Return => "return", - Throw => "throw", - PlusAssign => "+=", - MinusAssign => "-=", - MultiplyAssign => "*=", - DivideAssign => "/=", - LeftShiftAssign => "<<=", - RightShiftAssign => ">>=", - AndAssign => "&=", - OrAssign => "|=", - XOrAssign => "^=", - LeftShift => "<<", - RightShift => ">>", - XOr => "^", - Modulo => "%", - ModuloAssign => "%=", - PowerOf => "~", - PowerOfAssign => "~=", - For => "for", - In => "in", - _ => panic!("operator should be match in outer scope"), - }) - .into(), - } - } + Expr::StringConstant(_, _) + | Expr::Stmt(_, _) + | Expr::FunctionCall(_, _, _, _) + | Expr::Assignment(_, _, _) + | Expr::Dot(_, _, _) + | Expr::Index(_, _, _) + | Expr::Array(_, _) + | Expr::Map(_, _) => match token { + Token::LeftBracket => true, + _ => false, + }, - // If another operator is after these, it's probably an unary operator - // (not sure about fn name). - pub fn is_next_unary(&self) -> bool { - use Token::*; - - match self { - LexError(_) | - LeftBrace | // (+expr) - is unary - // RightBrace | {expr} - expr not unary & is closing - LeftParen | // {-expr} - is unary - // RightParen | (expr) - expr not unary & is closing - LeftBracket | // [-expr] - is unary - // RightBracket | [expr] - expr not unary & is closing - Plus | - UnaryPlus | - Minus | - UnaryMinus | - Multiply | - Divide | - Colon | - Comma | - Period | - Equals | - LessThan | - GreaterThan | - Bang | - LessThanEqualsTo | - GreaterThanEqualsTo | - EqualsTo | - NotEqualsTo | - Pipe | - Or | - Ampersand | - And | - If | - While | - PlusAssign | - MinusAssign | - MultiplyAssign | - DivideAssign | - LeftShiftAssign | - RightShiftAssign | - AndAssign | - OrAssign | - XOrAssign | - LeftShift | - RightShift | - XOr | - Modulo | - ModuloAssign | - Return | - Throw | - PowerOf | - In | - PowerOfAssign => true, - - _ => false, - } - } - - /// Get the precedence number of the token. - pub fn precedence(&self) -> u8 { - use Token::*; - - match self { - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign - | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign - | PowerOfAssign => 10, - - Or | XOr | Pipe => 40, - - And | Ampersand => 50, - - LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo - | NotEqualsTo => 60, - - In => 70, - - Plus | Minus => 80, - - Divide | Multiply | PowerOf => 90, - - LeftShift | RightShift => 100, - - Modulo => 110, - - Period => 120, - - _ => 0, - } - } - - /// Does an expression bind to the right (instead of left)? - pub fn is_bind_right(&self) -> bool { - use Token::*; - - match self { - // Assignments bind to the right - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign - | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign - | PowerOfAssign => true, - - // Property access binds to the right - Period => true, - - _ => false, + Expr::Variable(_, _) | Expr::Property(_, _) => match token { + Token::LeftBracket | Token::LeftParen => true, + _ => false, + }, } } } -/// An iterator on a `Token` stream. -pub struct TokenIterator<'a> { - /// Can the next token be a unary operator? - can_be_unary: bool, - /// Current position. - pos: Position, - /// The input character streams. - streams: Vec>>, -} - -impl<'a> TokenIterator<'a> { - /// Consume the next character. - fn eat_next(&mut self) { - self.get_next(); - self.advance(); - } - /// Get the next character - fn get_next(&mut self) -> Option { - loop { - if self.streams.is_empty() { - return None; - } else if let Some(ch) = self.streams[0].next() { - return Some(ch); - } else { - let _ = self.streams.remove(0); - } +/// Consume a particular token, checking that it is the expected one. +fn eat_token(input: &mut Peekable, token: Token) -> Position { + if let Some((t, pos)) = input.next() { + if t != token { + panic!( + "expecting {} (found {}) at {}", + token.syntax(), + t.syntax(), + pos + ); } - } - /// Peek the next character - fn peek_next(&mut self) -> Option { - loop { - if self.streams.is_empty() { - return None; - } else if let Some(ch) = self.streams[0].peek() { - return Some(*ch); - } else { - let _ = self.streams.remove(0); - } - } - } - /// Move the current position one character ahead. - fn advance(&mut self) { - self.pos.advance(); - } - /// Move the current position back one character. - /// - /// # Panics - /// - /// Panics if already at the beginning of a line - cannot rewind to the previous line. - fn rewind(&mut self) { - self.pos.rewind(); - } - /// Move the current position to the next line. - fn new_line(&mut self) { - self.pos.new_line() - } - - /// Parse a string literal wrapped by `enclosing_char`. - pub fn parse_string_literal( - &mut self, - enclosing_char: char, - ) -> Result { - let mut result = Vec::new(); - let mut escape = String::with_capacity(12); - - loop { - let next_char = self.get_next(); - self.advance(); - - match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { - // \... - '\\' if escape.is_empty() => { - escape.push('\\'); - } - // \\ - '\\' if !escape.is_empty() => { - escape.clear(); - result.push('\\'); - } - // \t - 't' if !escape.is_empty() => { - escape.clear(); - result.push('\t'); - } - // \n - 'n' if !escape.is_empty() => { - escape.clear(); - result.push('\n'); - } - // \r - 'r' if !escape.is_empty() => { - escape.clear(); - result.push('\r'); - } - // \x??, \u????, \U???????? - ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { - let mut seq = escape.clone(); - seq.push(ch); - escape.clear(); - - let mut out_val: u32 = 0; - let len = match ch { - 'x' => 2, - 'u' => 4, - 'U' => 8, - _ => panic!("should be 'x', 'u' or 'U'"), - }; - - for _ in 0..len { - let c = self.get_next().ok_or_else(|| { - (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) - })?; - - seq.push(c); - self.advance(); - - out_val *= 16; - out_val += c.to_digit(16).ok_or_else(|| { - (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) - })?; - } - - result.push( - char::from_u32(out_val) - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, - ); - } - - // \{enclosing_char} - escaped - ch if enclosing_char == ch && !escape.is_empty() => { - escape.clear(); - result.push(ch) - } - - // Close wrapper - ch if enclosing_char == ch && escape.is_empty() => break, - - // Unknown escape sequence - _ if !escape.is_empty() => { - return Err((LERR::MalformedEscapeSequence(escape), self.pos)) - } - - // Cannot have new-lines inside string literals - '\n' => { - self.rewind(); - return Err((LERR::UnterminatedString, self.pos)); - } - - // All other characters - ch => { - escape.clear(); - result.push(ch); - } - } - } - - Ok(result.iter().collect()) - } - - /// Get the next token. - fn inner_next(&mut self) -> Option<(Token, Position)> { - let mut negated = false; - - while let Some(c) = self.get_next() { - self.advance(); - - let pos = self.pos; - - match (c, self.peek_next().unwrap_or('\0')) { - // \n - ('\n', _) => self.new_line(), - - // digit ... - ('0'..='9', _) => { - let mut result = Vec::new(); - let mut radix_base: Option = None; - result.push(c); - - while let Some(next_char) = self.peek_next() { - match next_char { - '0'..='9' | '_' => { - result.push(next_char); - self.eat_next(); - } - #[cfg(not(feature = "no_float"))] - '.' => { - result.push(next_char); - self.eat_next(); - while let Some(next_char_in_float) = self.peek_next() { - match next_char_in_float { - '0'..='9' | '_' => { - result.push(next_char_in_float); - self.eat_next(); - } - _ => break, - } - } - } - // 0x????, 0o????, 0b???? - ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' - if c == '0' => - { - result.push(next_char); - self.eat_next(); - - let valid = match ch { - 'x' | 'X' => [ - 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', - ], - 'o' | 'O' => [ - '0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_', - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - ], - 'b' | 'B' => [ - '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - ], - _ => panic!("unexpected character {}", ch), - }; - - radix_base = Some(match ch { - 'x' | 'X' => 16, - 'o' | 'O' => 8, - 'b' | 'B' => 2, - _ => panic!("unexpected character {}", ch), - }); - - while let Some(next_char_in_hex) = self.peek_next() { - if !valid.contains(&next_char_in_hex) { - break; - } - - result.push(next_char_in_hex); - self.eat_next(); - } - } - - _ => break, - } - } - - if negated { - result.insert(0, '-'); - } - - // Parse number - if let Some(radix) = radix_base { - let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); - - return Some(( - INT::from_str_radix(&out, radix) - .map(Token::IntegerConstant) - .unwrap_or_else(|_| { - Token::LexError(Box::new(LERR::MalformedNumber( - result.iter().collect(), - ))) - }), - pos, - )); - } else { - let out: String = result.iter().filter(|&&c| c != '_').collect(); - let num = INT::from_str(&out).map(Token::IntegerConstant); - - // If integer parsing is unnecessary, try float instead - #[cfg(not(feature = "no_float"))] - let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); - - return Some(( - num.unwrap_or_else(|_| { - Token::LexError(Box::new(LERR::MalformedNumber( - result.iter().collect(), - ))) - }), - pos, - )); - } - } - - // letter or underscore ... - ('A'..='Z', _) | ('a'..='z', _) | ('_', _) => { - let mut result = Vec::new(); - result.push(c); - - while let Some(next_char) = self.peek_next() { - match next_char { - x if x.is_ascii_alphanumeric() || x == '_' => { - result.push(x); - self.eat_next(); - } - _ => break, - } - } - - let is_valid_identifier = result - .iter() - .find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character - .map(char::is_ascii_alphabetic) // is a letter - .unwrap_or(false); // if no alpha-numeric at all - syntax error - - let identifier: String = result.iter().collect(); - - if !is_valid_identifier { - return Some(( - Token::LexError(Box::new(LERR::MalformedIdentifier(identifier))), - pos, - )); - } - - return Some(( - match identifier.as_str() { - "true" => Token::True, - "false" => Token::False, - "let" => Token::Let, - "const" => Token::Const, - "if" => Token::If, - "else" => Token::Else, - "while" => Token::While, - "loop" => Token::Loop, - "continue" => Token::Continue, - "break" => Token::Break, - "return" => Token::Return, - "throw" => Token::Throw, - "for" => Token::For, - "in" => Token::In, - - #[cfg(not(feature = "no_function"))] - "fn" => Token::Fn, - - _ => Token::Identifier(identifier), - }, - pos, - )); - } - - // " - string literal - ('"', _) => { - return self.parse_string_literal('"').map_or_else( - |err| Some((Token::LexError(Box::new(err.0)), err.1)), - |out| Some((Token::StringConst(out), pos)), - ); - } - - // ' - character literal - ('\'', '\'') => { - return Some(( - Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), - pos, - )); - } - ('\'', _) => { - return Some(self.parse_string_literal('\'').map_or_else( - |err| (Token::LexError(Box::new(err.0)), err.1), - |result| { - let mut chars = result.chars(); - let first = chars.next(); - - if chars.next().is_some() { - (Token::LexError(Box::new(LERR::MalformedChar(result))), pos) - } else { - (Token::CharConstant(first.expect("should be Some")), pos) - } - }, - )); - } - - // Braces - ('{', _) => return Some((Token::LeftBrace, pos)), - ('}', _) => return Some((Token::RightBrace, pos)), - - // Parentheses - ('(', _) => return Some((Token::LeftParen, pos)), - (')', _) => return Some((Token::RightParen, pos)), - - // Indexing - ('[', _) => return Some((Token::LeftBracket, pos)), - (']', _) => return Some((Token::RightBracket, pos)), - - // Map literal - #[cfg(not(feature = "no_object"))] - ('#', '{') => { - self.eat_next(); - return Some((Token::MapStart, pos)); - } - - // Operators - ('+', '=') => { - self.eat_next(); - return Some((Token::PlusAssign, pos)); - } - ('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)), - ('+', _) => return Some((Token::Plus, pos)), - - ('-', '0'..='9') if self.can_be_unary => negated = true, - ('-', '0'..='9') => return Some((Token::Minus, pos)), - ('-', '=') => { - self.eat_next(); - return Some((Token::MinusAssign, pos)); - } - ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), - ('-', _) => return Some((Token::Minus, pos)), - - ('*', '=') => { - self.eat_next(); - return Some((Token::MultiplyAssign, pos)); - } - ('*', _) => return Some((Token::Multiply, pos)), - - // Comments - ('/', '/') => { - self.eat_next(); - - while let Some(c) = self.get_next() { - if c == '\n' { - self.new_line(); - break; - } - - self.advance(); - } - } - ('/', '*') => { - let mut level = 1; - - self.eat_next(); - - while let Some(c) = self.get_next() { - self.advance(); - - match c { - '/' => { - if self.get_next() == Some('*') { - level += 1; - } - self.advance(); - } - '*' => { - if self.get_next() == Some('/') { - level -= 1; - } - self.advance(); - } - '\n' => self.new_line(), - _ => (), - } - - if level == 0 { - break; - } - } - } - - ('/', '=') => { - self.eat_next(); - return Some((Token::DivideAssign, pos)); - } - ('/', _) => return Some((Token::Divide, pos)), - - (';', _) => return Some((Token::SemiColon, pos)), - (':', _) => return Some((Token::Colon, pos)), - (',', _) => return Some((Token::Comma, pos)), - ('.', _) => return Some((Token::Period, pos)), - - ('=', '=') => { - self.eat_next(); - return Some((Token::EqualsTo, pos)); - } - ('=', _) => return Some((Token::Equals, pos)), - - ('<', '=') => { - self.eat_next(); - return Some((Token::LessThanEqualsTo, pos)); - } - ('<', '<') => { - self.eat_next(); - - return Some(( - if self.peek_next() == Some('=') { - self.eat_next(); - Token::LeftShiftAssign - } else { - Token::LeftShift - }, - pos, - )); - } - ('<', _) => return Some((Token::LessThan, pos)), - - ('>', '=') => { - self.eat_next(); - return Some((Token::GreaterThanEqualsTo, pos)); - } - ('>', '>') => { - self.eat_next(); - - return Some(( - if self.peek_next() == Some('=') { - self.eat_next(); - Token::RightShiftAssign - } else { - Token::RightShift - }, - pos, - )); - } - ('>', _) => return Some((Token::GreaterThan, pos)), - - ('!', '=') => { - self.eat_next(); - return Some((Token::NotEqualsTo, pos)); - } - ('!', _) => return Some((Token::Bang, pos)), - - ('|', '|') => { - self.eat_next(); - return Some((Token::Or, pos)); - } - ('|', '=') => { - self.eat_next(); - return Some((Token::OrAssign, pos)); - } - ('|', _) => return Some((Token::Pipe, pos)), - - ('&', '&') => { - self.eat_next(); - return Some((Token::And, pos)); - } - ('&', '=') => { - self.eat_next(); - return Some((Token::AndAssign, pos)); - } - ('&', _) => return Some((Token::Ampersand, pos)), - - ('^', '=') => { - self.eat_next(); - return Some((Token::XOrAssign, pos)); - } - ('^', _) => return Some((Token::XOr, pos)), - - ('%', '=') => { - self.eat_next(); - return Some((Token::ModuloAssign, pos)); - } - ('%', _) => return Some((Token::Modulo, pos)), - - ('~', '=') => { - self.eat_next(); - return Some((Token::PowerOfAssign, pos)); - } - ('~', _) => return Some((Token::PowerOf, pos)), - - (ch, _) if ch.is_whitespace() => (), - (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)), - } - } - - None + pos + } else { + panic!("expecting {} but already EOF", token.syntax()); } } -impl<'a> Iterator for TokenIterator<'a> { - type Item = (Token, Position); - - fn next(&mut self) -> Option { - self.inner_next().map(|x| { - // Save the last token - self.can_be_unary = x.0.is_next_unary(); - x - }) - } -} - -/// Tokenize an input text stream. -pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { - TokenIterator { - can_be_unary: true, - pos: Position::new(1, 0), - streams: input.iter().map(|s| s.chars().peekable()).collect(), +/// Match a particular token, consuming it if matched. +fn match_token(input: &mut Peekable, token: Token) -> Result> { + let (t, _) = input.peek().unwrap(); + if *t == token { + eat_token(input, token); + Ok(true) + } else { + Ok(false) } } @@ -1435,28 +539,22 @@ fn parse_paren_expr<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { - if matches!(input.peek(), Some((Token::RightParen, _))) { - input.next(); +) -> Result> { + if match_token(input, Token::RightParen)? { return Ok(Expr::Unit(begin)); } let expr = parse_expr(input, allow_stmt_expr)?; - match input.next() { + match input.next().unwrap() { // ( xxx ) - Some((Token::RightParen, _)) => Ok(expr), + (Token::RightParen, _) => Ok(expr), // ( xxx ??? - Some((_, pos)) => Err(PERR::MissingToken( + (_, pos) => Err(PERR::MissingToken( ")".into(), "for a matching ( in this expression".into(), ) .into_err(pos)), - // ( xxx - None => Err( - PERR::MissingToken(")".into(), "for a matching ( in this expression".into()) - .into_err_eof(), - ), } } @@ -1466,21 +564,21 @@ fn parse_call_expr<'a, S: Into> + Display>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut args_expr_list = Vec::new(); - match input.peek() { + match input.peek().unwrap() { //id {EOF} - None => { + (Token::EOF, pos) => { return Err(PERR::MissingToken( ")".into(), format!("to close the arguments list of this function call '{}'", id), ) - .into_err_eof()) + .into_err(*pos)) } // id() - Some((Token::RightParen, _)) => { - input.next(); + (Token::RightParen, _) => { + eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } // id... @@ -1490,20 +588,22 @@ fn parse_call_expr<'a, S: Into> + Display>( loop { args_expr_list.push(parse_expr(input, allow_stmt_expr)?); - match input.peek() { - None => { + match input.peek().unwrap() { + (Token::EOF, pos) => { return Err(PERR::MissingToken( ")".into(), format!("to close the arguments list of this function call '{}'", id), ) - .into_err_eof()) + .into_err(*pos)) } - Some((Token::RightParen, _)) => { - input.next(); + (Token::RightParen, _) => { + eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } - Some((Token::Comma, _)) => (), - Some((_, pos)) => { + (Token::Comma, _) => { + eat_token(input, Token::Comma); + } + (_, pos) => { return Err(PERR::MissingToken( ",".into(), format!("to separate the arguments to function call '{}'", id), @@ -1511,8 +611,6 @@ fn parse_call_expr<'a, S: Into> + Display>( .into_err(*pos)) } } - - input.next(); } } @@ -1522,7 +620,7 @@ fn parse_index_expr<'a>( input: &mut Peekable>, pos: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let idx_expr = parse_expr(input, allow_stmt_expr)?; // Check type of indexing - must be integer or string @@ -1628,15 +726,9 @@ fn parse_index_expr<'a>( } // Check if there is a closing bracket - match input.peek().ok_or_else(|| { - PERR::MissingToken( - "]".into(), - "for a matching [ in this index expression".into(), - ) - .into_err_eof() - })? { + match input.peek().unwrap() { (Token::RightBracket, _) => { - input.next(); + eat_token(input, Token::RightBracket); Ok(Expr::Index(lhs, Box::new(idx_expr), pos)) } (_, pos) => Err(PERR::MissingToken( @@ -1647,55 +739,30 @@ fn parse_index_expr<'a>( } } -/// Parse an expression that begins with an identifier. -fn parse_ident_expr<'a, S: Into> + Display>( - id: S, - input: &mut Peekable>, - begin: Position, - allow_stmt_expr: bool, -) -> Result { - match input.peek() { - // id(...) - function call - Some((Token::LeftParen, _)) => { - input.next(); - parse_call_expr(id, input, begin, allow_stmt_expr) - } - // id[...] - indexing - #[cfg(not(feature = "no_index"))] - Some((Token::LeftBracket, pos)) => { - let pos = *pos; - input.next(); - parse_index_expr( - Box::new(Expr::Variable(id.into(), begin)), - input, - pos, - allow_stmt_expr, - ) - } - // id - variable - Some(_) => Ok(Expr::Variable(id.into(), begin)), - // EOF - None => Ok(Expr::Variable(id.into(), begin)), - } -} - /// Parse an array literal. fn parse_array_literal<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut arr = Vec::new(); - if !matches!(input.peek(), Some((Token::RightBracket, _))) { - while input.peek().is_some() { + if !match_token(input, Token::RightBracket)? { + while !input.peek().unwrap().0.is_eof() { arr.push(parse_expr(input, allow_stmt_expr)?); - match input.peek().ok_or_else(|| { - PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof() - })? { - (Token::Comma, _) => input.next(), - (Token::RightBracket, _) => break, + match input.peek().unwrap() { + (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; + } (_, pos) => { return Err(PERR::MissingToken( ",".into(), @@ -1707,17 +774,7 @@ fn parse_array_literal<'a>( } } - match input.peek().ok_or_else(|| { - PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof() - })? { - (Token::RightBracket, _) => { - input.next(); - Ok(Expr::Array(arr, begin)) - } - (_, pos) => { - Err(PERR::MissingToken("]".into(), "to end this array literal".into()).into_err(*pos)) - } - } + Ok(Expr::Array(arr, begin)) } /// Parse a map literal. @@ -1725,37 +782,26 @@ fn parse_map_literal<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut map = Vec::new(); - if !matches!(input.peek(), Some((Token::RightBrace, _))) { - while input.peek().is_some() { - let (name, pos) = match input.next().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this object map literal".into()) - .into_err_eof() - })? { + if !match_token(input, Token::RightBrace)? { + while !input.peek().unwrap().0.is_eof() { + const MISSING_RBRACE: &str = "to end this object map literal"; + + let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::StringConst(s), pos) => (s, pos), (_, pos) if map.is_empty() => { - return Err(PERR::MissingToken( - "}".into(), - "to end this object map literal".into(), - ) - .into_err(pos)) + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + } + (Token::EOF, pos) => { + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) } (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; - match input.next().ok_or_else(|| { - PERR::MissingToken( - ":".into(), - format!( - "to follow the property '{}' in this object map literal", - name - ), - ) - .into_err_eof() - })? { + match input.next().unwrap() { (Token::Colon, _) => (), (_, pos) => { return Err(PERR::MissingToken( @@ -1773,14 +819,14 @@ fn parse_map_literal<'a>( map.push((name, expr, pos)); - match input.peek().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this object map literal".into()) - .into_err_eof() - })? { + match input.peek().unwrap() { (Token::Comma, _) => { - input.next(); + eat_token(input, Token::Comma); + } + (Token::RightBrace, _) => { + eat_token(input, Token::RightBrace); + break; } - (Token::RightBrace, _) => break, (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( ",".into(), @@ -1789,11 +835,7 @@ fn parse_map_literal<'a>( .into_err(*pos)) } (_, pos) => { - return Err(PERR::MissingToken( - "}".into(), - "to end this object map literal".into(), - ) - .into_err(*pos)) + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) } } } @@ -1810,83 +852,67 @@ fn parse_map_literal<'a>( }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; - // Ending brace - match input.peek().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this object map literal".into()).into_err_eof() - })? { - (Token::RightBrace, _) => { - input.next(); - Ok(Expr::Map(map, begin)) - } - (_, pos) => Err( - PERR::MissingToken("]".into(), "to end this object map literal".into()).into_err(*pos), - ), - } + Ok(Expr::Map(map, begin)) } /// Parse a primary expression. fn parse_primary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { - let token = match input - .peek() - .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? - { +) -> Result> { + let (token, pos) = match input.peek().unwrap() { // { - block statement as expression (Token::LeftBrace, pos) if allow_stmt_expr => { let pos = *pos; return parse_block(input, false, allow_stmt_expr) .map(|block| Expr::Stmt(Box::new(block), pos)); } - _ => input.next().expect("should be a token"), + (Token::EOF, pos) => return Err(PERR::UnexpectedEOF.into_err(*pos)), + _ => input.next().unwrap(), }; - let mut can_be_indexed = false; - let mut root_expr = match token { - (Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)), - (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), - (Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)), - (Token::StringConst(s), pos) => { - can_be_indexed = true; - Ok(Expr::StringConstant(s.into(), pos)) - } - (Token::Identifier(s), pos) => { - can_be_indexed = true; - parse_ident_expr(s, input, pos, allow_stmt_expr) - } - (Token::LeftParen, pos) => { - can_be_indexed = true; - parse_paren_expr(input, pos, allow_stmt_expr) - } + Token::IntegerConstant(x) => Expr::IntegerConstant(x, pos), + #[cfg(not(feature = "no_float"))] + Token::FloatConstant(x) => Expr::FloatConstant(x, pos), + Token::CharConstant(c) => Expr::CharConstant(c, pos), + Token::StringConst(s) => Expr::StringConstant(s.into(), pos), + Token::Identifier(s) => Expr::Variable(s.into(), pos), + Token::LeftParen => parse_paren_expr(input, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] - (Token::LeftBracket, pos) => { - can_be_indexed = true; - parse_array_literal(input, pos, allow_stmt_expr) - } + Token::LeftBracket => parse_array_literal(input, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_object"))] - (Token::MapStart, pos) => { - can_be_indexed = true; - parse_map_literal(input, pos, allow_stmt_expr) + Token::MapStart => parse_map_literal(input, pos, allow_stmt_expr)?, + Token::True => Expr::True(pos), + Token::False => Expr::False(pos), + Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + token => { + return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) } - (Token::True, pos) => Ok(Expr::True(pos)), - (Token::False, pos) => Ok(Expr::False(pos)), - (Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(pos)), - (token, pos) => { - Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) + }; + + // Tail processing all possible postfix operators + loop { + let (token, _) = input.peek().unwrap(); + + if !root_expr.is_valid_postfix(token) { + break; } - }?; - #[cfg(feature = "no_index")] - let can_be_indexed = false; + let (token, pos) = input.next().unwrap(); - if can_be_indexed { - // Tail processing all possible indexing - while let Some((Token::LeftBracket, pos)) = input.peek() { - let pos = *pos; - input.next(); - root_expr = parse_index_expr(Box::new(root_expr), input, pos, allow_stmt_expr)?; + root_expr = match (root_expr, token) { + // Function call + (Expr::Variable(id, pos), Token::LeftParen) + | (Expr::Property(id, pos), Token::LeftParen) => { + parse_call_expr(id, input, pos, allow_stmt_expr)? + } + // Indexing + (expr, Token::LeftBracket) => { + parse_index_expr(Box::new(expr), input, pos, allow_stmt_expr)? + } + // Unknown postfix operator + (expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), } } @@ -1897,11 +923,8 @@ fn parse_primary<'a>( fn parse_unary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { - match input - .peek() - .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? - { +) -> Result> { + match input.peek().unwrap() { // If statement is allowed to act as expressions (Token::If, pos) => { let pos = *pos; @@ -1911,10 +934,8 @@ fn parse_unary<'a>( )) } // -expr - (Token::UnaryMinus, pos) => { - let pos = *pos; - - input.next(); + (Token::UnaryMinus, _) => { + let pos = eat_token(input, Token::UnaryMinus); match parse_unary(input, allow_stmt_expr)? { // Negative integer @@ -1932,7 +953,7 @@ fn parse_unary<'a>( } }) .ok_or_else(|| { - PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()) + PERR::BadInput(LexError::MalformedNumber(format!("-{}", i)).to_string()) .into_err(pos) }), @@ -1946,31 +967,30 @@ fn parse_unary<'a>( } // +expr (Token::UnaryPlus, _) => { - input.next(); + eat_token(input, Token::UnaryPlus); parse_unary(input, allow_stmt_expr) } // !expr - (Token::Bang, pos) => { - let pos = *pos; - - input.next(); - + (Token::Bang, _) => { + let pos = eat_token(input, Token::Bang); Ok(Expr::FunctionCall( "!".into(), vec![parse_primary(input, allow_stmt_expr)?], - Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false + Some(Dynamic::from_bool(false)), // NOT operator, when operating on invalid operand, defaults to false pos, )) } + // {EOF} + (Token::EOF, pos) => Err(PERR::UnexpectedEOF.into_err(*pos)), // All other tokens _ => parse_primary(input, allow_stmt_expr), } } /// Parse an assignment. -fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result { +fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result> { // Is the LHS in a valid format for an assignment target? - fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option { + fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option> { match expr { // var Expr::Variable(_, _) => { @@ -1983,45 +1003,41 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result { - assert!(is_top, "property expected but gets variable"); - None - } - // property[...] - Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => { - assert!(!is_top, "variable expected but gets property"); - None - } - // idx_lhs[...] Expr::Index(idx_lhs, _, pos) => match idx_lhs.as_ref() { + // var[...] + Expr::Variable(_, _) => { + assert!(is_top, "property expected but gets variable"); + None + } + // property[...] + Expr::Property(_, _) => { + assert!(!is_top, "variable expected but gets property"); + None + } + // ???[...][...] Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)), + // idx_lhs[...] _ => Some(ParseErrorType::AssignmentToInvalidLHS.into_err(*pos)), }, // dot_lhs.dot_rhs - Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() { + Expr::Dot(dot_lhs, dot_rhs, pos) => match dot_lhs.as_ref() { // var.dot_rhs Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), // property.dot_rhs Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false), - // var[...] - Expr::Index(idx_lhs, _, _) - if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top => - { - valid_assignment_chain(dot_rhs, false) - } - // property[...] - Expr::Index(idx_lhs, _, _) - if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top => - { - valid_assignment_chain(dot_rhs, false) - } - // idx_lhs[...] - Expr::Index(idx_lhs, _, _) => { - Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())) - } + // idx_lhs[...].dot_rhs + Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { + // var[...].dot_rhs + Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), + // property[...].dot_rhs + Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false), + // ???[...][...].dot_rhs + Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)), + // idx_lhs[...].dot_rhs + _ => Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())), + }, expr => panic!("unexpected dot expression {:#?}", expr), }, @@ -2042,7 +1058,7 @@ fn parse_op_assignment>>( lhs: Expr, rhs: Expr, pos: Position, -) -> Result { +) -> Result> { let lhs_copy = lhs.clone(); // lhs op= rhs -> lhs = op(lhs, rhs) @@ -2054,7 +1070,7 @@ fn parse_op_assignment>>( } /// Parse an 'in' expression. -fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { +fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result> { match (&lhs, &rhs) { (_, Expr::IntegerConstant(_, pos)) | (_, Expr::FloatConstant(_, pos)) @@ -2188,7 +1204,7 @@ fn parse_binary_op<'a>( parent_precedence: u8, lhs: Expr, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut current_lhs = lhs; loop { @@ -2206,8 +1222,6 @@ fn parse_binary_op<'a>( } if let Some((op_token, pos)) = input.next() { - input.peek(); - let rhs = parse_unary(input, allow_stmt_expr)?; let next_precedence = if let Some((next_op, _)) = input.peek() { @@ -2241,7 +1255,7 @@ fn parse_binary_op<'a>( #[cfg(not(feature = "no_object"))] Token::Period => { - fn check_property(expr: Expr) -> Result { + fn check_property(expr: Expr) -> Result> { match expr { // xxx.lhs.rhs Expr::Dot(lhs, rhs, pos) => Ok(Expr::Dot( @@ -2270,37 +1284,37 @@ fn parse_binary_op<'a>( Token::EqualsTo => Expr::FunctionCall( "==".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::NotEqualsTo => Expr::FunctionCall( "!=".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::LessThan => Expr::FunctionCall( "<".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::LessThanEqualsTo => Expr::FunctionCall( "<=".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::GreaterThan => Expr::FunctionCall( ">".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::GreaterThanEqualsTo => Expr::FunctionCall( ">=".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), @@ -2341,7 +1355,7 @@ fn parse_binary_op<'a>( fn parse_expr<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // Parse a real expression let lhs = parse_unary(input, allow_stmt_expr)?; parse_binary_op(input, 1, lhs, allow_stmt_expr) @@ -2351,13 +1365,12 @@ fn parse_expr<'a>( fn ensure_not_statement_expr<'a>( input: &mut Peekable>, type_name: &str, -) -> Result<(), ParseError> { - match input - .peek() - .ok_or_else(|| PERR::ExprExpected(type_name.to_string()).into_err_eof())? - { +) -> Result<(), Box> { + match input.peek().unwrap() { // Disallow statement expressions - (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), + (Token::LeftBrace, pos) | (Token::EOF, pos) => { + Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)) + } // No need to check for others at this time - leave it for the expr parser _ => Ok(()), } @@ -2368,9 +1381,9 @@ fn parse_if<'a>( input: &mut Peekable>, breakable: bool, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // if ... - input.next(); + eat_token(input, Token::If); // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; @@ -2378,9 +1391,7 @@ fn parse_if<'a>( let if_body = parse_block(input, breakable, allow_stmt_expr)?; // if guard { if_body } else ... - let else_body = if matches!(input.peek(), Some((Token::Else, _))) { - input.next(); - + let else_body = if match_token(input, Token::Else).unwrap_or(false) { Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) { // if guard { if_body } else if ... parse_if(input, breakable, allow_stmt_expr)? @@ -2403,9 +1414,9 @@ fn parse_if<'a>( fn parse_while<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // while ... - input.next(); + eat_token(input, Token::While); // while guard { body } ensure_not_statement_expr(input, "a boolean")?; @@ -2419,9 +1430,9 @@ fn parse_while<'a>( fn parse_loop<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // loop ... - input.next(); + eat_token(input, Token::Loop); // loop { body } let body = parse_block(input, true, allow_stmt_expr)?; @@ -2433,27 +1444,24 @@ fn parse_loop<'a>( fn parse_for<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // for ... - input.next(); + eat_token(input, Token::For); // for name ... - let name = match input - .next() - .ok_or_else(|| PERR::VariableExpected.into_err_eof())? - { + let name = match input.next().unwrap() { // Variable name (Token::Identifier(s), _) => s, // Bad identifier (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + // EOF + (Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)), // Not a variable name (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // for name in ... - match input.next().ok_or_else(|| { - PERR::MissingToken("in".into(), "after the iteration variable".into()).into_err_eof() - })? { + match input.next().unwrap() { (Token::In, _) => (), (_, pos) => { return Err( @@ -2476,24 +1484,19 @@ fn parse_let<'a>( input: &mut Peekable>, var_type: ScopeEntryType, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // let/const... (specified in `var_type`) input.next(); // let name ... - let (name, pos) = match input - .next() - .ok_or_else(|| PERR::VariableExpected.into_err_eof())? - { + let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // let name = ... - if matches!(input.peek(), Some((Token::Equals, _))) { - input.next(); - + if match_token(input, Token::Equals)? { // let name = expr let init_value = parse_expr(input, allow_stmt_expr)?; @@ -2520,12 +1523,9 @@ fn parse_block<'a>( input: &mut Peekable>, breakable: bool, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // Must start with { - let pos = match input - .next() - .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? - { + let pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, (_, pos) => { return Err( @@ -2536,7 +1536,7 @@ fn parse_block<'a>( let mut statements = Vec::new(); - while !matches!(input.peek(), Some((Token::RightBrace, _))) { + while !match_token(input, Token::RightBrace)? { // Parse statements inside the block let stmt = parse_stmt(input, breakable, allow_stmt_expr)?; @@ -2545,21 +1545,22 @@ fn parse_block<'a>( statements.push(stmt); - match input.peek() { - // EOF - None => break, + match input.peek().unwrap() { // { ... stmt } - Some((Token::RightBrace, _)) => break, + (Token::RightBrace, _) => { + eat_token(input, Token::RightBrace); + break; + } // { ... stmt; - Some((Token::SemiColon, _)) if need_semicolon => { - input.next(); + (Token::SemiColon, _) if need_semicolon => { + eat_token(input, Token::SemiColon); } // { ... { stmt } ; - Some((Token::SemiColon, _)) if !need_semicolon => (), + (Token::SemiColon, _) if !need_semicolon => (), // { ... { stmt } ??? - Some((_, _)) if !need_semicolon => (), + (_, _) if !need_semicolon => (), // { ... stmt ??? - error - Some((_, pos)) => { + (_, pos) => { // Semicolons are not optional between statements return Err( PERR::MissingToken(";".into(), "to terminate this statement".into()) @@ -2569,24 +1570,14 @@ fn parse_block<'a>( } } - match input.peek().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this statement block".into()).into_err_eof() - })? { - (Token::RightBrace, _) => { - input.next(); - Ok(Stmt::Block(statements, pos)) - } - (_, pos) => { - Err(PERR::MissingToken("}".into(), "to end this statement block".into()).into_err(*pos)) - } - } + Ok(Stmt::Block(statements, pos)) } /// Parse an expression as a statement. fn parse_expr_stmt<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { Ok(Stmt::Expr(Box::new(parse_expr(input, allow_stmt_expr)?))) } @@ -2595,56 +1586,53 @@ fn parse_stmt<'a>( input: &mut Peekable>, breakable: bool, allow_stmt_expr: bool, -) -> Result { - let token = match input.peek() { - Some(token) => token, - None => return Ok(Stmt::Noop(Position::eof())), +) -> Result> { + let (token, pos) = match input.peek().unwrap() { + (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), + x => x, }; match token { // Semicolon - empty statement - (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), + Token::SemiColon => Ok(Stmt::Noop(*pos)), - (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + Token::LeftBrace => parse_block(input, breakable, allow_stmt_expr), // fn ... #[cfg(not(feature = "no_function"))] - (Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), + Token::Fn => Err(PERR::WrongFnDefinition.into_err(*pos)), - (Token::If, _) => parse_if(input, breakable, allow_stmt_expr), - (Token::While, _) => parse_while(input, allow_stmt_expr), - (Token::Loop, _) => parse_loop(input, allow_stmt_expr), - (Token::For, _) => parse_for(input, allow_stmt_expr), + Token::If => parse_if(input, breakable, allow_stmt_expr), + Token::While => parse_while(input, allow_stmt_expr), + Token::Loop => parse_loop(input, allow_stmt_expr), + Token::For => parse_for(input, allow_stmt_expr), - (Token::Continue, pos) if breakable => { - let pos = *pos; - input.next(); + Token::Continue if breakable => { + let pos = eat_token(input, Token::Continue); Ok(Stmt::Continue(pos)) } - (Token::Break, pos) if breakable => { - let pos = *pos; - input.next(); + Token::Break if breakable => { + let pos = eat_token(input, Token::Break); Ok(Stmt::Break(pos)) } - (Token::Continue, pos) | (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(*pos)), - (token @ Token::Return, pos) | (token @ Token::Throw, pos) => { - let return_type = match token { - Token::Return => ReturnType::Return, - Token::Throw => ReturnType::Exception, + Token::Return | Token::Throw => { + let pos = *pos; + + let return_type = match input.next() { + Some((Token::Return, _)) => ReturnType::Return, + Some((Token::Throw, _)) => ReturnType::Exception, _ => panic!("token should be return or throw"), }; - let pos = *pos; - input.next(); - - match input.peek() { - // `return`/`throw` at EOF - None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), + match input.peek().unwrap() { + // `return`/`throw` at {EOF} + (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(None, return_type, *pos)), // `return;` or `throw;` - Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), + (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), // `return` or `throw` with expression - Some((_, _)) => { + (_, _) => { let expr = parse_expr(input, allow_stmt_expr)?; let pos = expr.position(); Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos)) @@ -2652,8 +1640,8 @@ fn parse_stmt<'a>( } } - (Token::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), - (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), + Token::Let => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), + Token::Const => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), _ => parse_expr_stmt(input, allow_stmt_expr), } @@ -2663,46 +1651,32 @@ fn parse_stmt<'a>( fn parse_fn<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let pos = input.next().expect("should be fn").1; - let name = match input - .next() - .ok_or_else(|| PERR::FnMissingName.into_err_eof())? - { + let name = match input.next().unwrap() { (Token::Identifier(s), _) => s, (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), }; - match input - .peek() - .ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())? - { - (Token::LeftParen, _) => input.next(), + match input.peek().unwrap() { + (Token::LeftParen, _) => eat_token(input, Token::LeftParen), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), }; let mut params = Vec::new(); - if matches!(input.peek(), Some((Token::RightParen, _))) { - input.next(); - } else { + if !match_token(input, Token::RightParen)? { let end_err = format!("to close the parameters list of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name); loop { - match input - .next() - .ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())? - { + match input.next().unwrap() { (Token::Identifier(s), pos) => params.push((s, pos)), (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), } - match input - .next() - .ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())? - { + match input.next().unwrap() { (Token::RightParen, _) => break, (Token::Comma, _) => (), (Token::Identifier(_), pos) => { @@ -2729,10 +1703,9 @@ fn parse_fn<'a>( })?; // Parse function body - let body = match input.peek() { - Some((Token::LeftBrace, _)) => parse_block(input, false, allow_stmt_expr)?, - Some((_, pos)) => return Err(PERR::FnMissingBody(name).into_err(*pos)), - None => return Err(PERR::FnMissingBody(name).into_err_eof()), + let body = match input.peek().unwrap() { + (Token::LeftBrace, _) => parse_block(input, false, allow_stmt_expr)?, + (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; let params = params.into_iter().map(|(p, _)| p).collect(); @@ -2745,17 +1718,20 @@ fn parse_fn<'a>( }) } -pub fn parse_global_expr<'a, 'e>( +pub fn parse_global_expr<'a>( input: &mut Peekable>, - engine: &Engine<'e>, + engine: &Engine, scope: &Scope, optimization_level: OptimizationLevel, -) -> Result { +) -> Result> { let expr = parse_expr(input, false)?; - if let Some((token, pos)) = input.peek() { + match input.peek().unwrap() { + (Token::EOF, _) => (), // Return error if the expression doesn't end - return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos)); + (token, pos) => { + return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos)) + } } Ok( @@ -2773,23 +1749,17 @@ pub fn parse_global_expr<'a, 'e>( /// Parse the global level statements. fn parse_global_level<'a>( input: &mut Peekable>, -) -> Result<(Vec, Vec), ParseError> { +) -> Result<(Vec, HashMap), Box> { let mut statements = Vec::::new(); - let mut functions = Vec::::new(); + let mut functions = HashMap::::new(); - while input.peek().is_some() { + while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions #[cfg(not(feature = "no_function"))] { if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) { let f = parse_fn(input, true)?; - - // Ensure list is sorted - match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) { - Ok(n) => functions[n] = f, // Override previous definition - Err(n) => functions.insert(n, f), // New function definition - } - + functions.insert(calc_fn_def(&f.name, f.params.len()), f); continue; } } @@ -2801,19 +1771,19 @@ fn parse_global_level<'a>( statements.push(stmt); - match input.peek() { + match input.peek().unwrap() { // EOF - None => break, + (Token::EOF, _) => break, // stmt ; - Some((Token::SemiColon, _)) if need_semicolon => { - input.next(); + (Token::SemiColon, _) if need_semicolon => { + eat_token(input, Token::SemiColon); } // stmt ; - Some((Token::SemiColon, _)) if !need_semicolon => (), + (Token::SemiColon, _) if !need_semicolon => (), // { stmt } ??? - Some((_, _)) if !need_semicolon => (), + (_, _) if !need_semicolon => (), // stmt ??? - error - Some((_, pos)) => { + (_, pos) => { // Semicolons are not optional between statements return Err( PERR::MissingToken(";".into(), "to terminate this statement".into()) @@ -2827,17 +1797,18 @@ fn parse_global_level<'a>( } /// Run the parser on an input stream, returning an AST. -pub fn parse<'a, 'e>( +pub fn parse<'a>( input: &mut Peekable>, - engine: &Engine<'e>, + engine: &Engine, scope: &Scope, optimization_level: OptimizationLevel, -) -> Result { +) -> Result> { let (statements, functions) = parse_global_level(input)?; + let fn_lib = functions.into_iter().map(|(_, v)| v).collect(); Ok( // Optimize AST - optimize_into_ast(engine, scope, statements, functions, optimization_level), + optimize_into_ast(engine, scope, statements, fn_lib, optimization_level), ) } @@ -2845,67 +1816,50 @@ pub fn parse<'a, 'e>( /// /// Returns Some(expression) if conversion is successful. Otherwise None. pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { - if value.is::() { - Some(Expr::IntegerConstant(value.cast(), pos)) - } else if value.is::() { - Some(Expr::CharConstant(value.cast(), pos)) - } else if value.is::() { - Some(Expr::StringConstant(value.cast::().into(), pos)) - } else if value.is::() { - Some(if value.cast::() { - Expr::True(pos) - } else { - Expr::False(pos) - }) - } else { + match value.0 { + Union::Unit(_) => Some(Expr::Unit(pos)), + Union::Int(value) => Some(Expr::IntegerConstant(value, pos)), + Union::Char(value) => Some(Expr::CharConstant(value, pos)), + Union::Str(value) => Some(Expr::StringConstant((*value).into(), pos)), + Union::Bool(true) => Some(Expr::True(pos)), + Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] - { - if value.is::() { - let array = value.cast::(); - let items: Vec<_> = array - .into_iter() - .map(|x| map_dynamic_to_expr(x, pos)) - .collect(); - if items.iter().all(Option::is_some) { - return Some(Expr::Array( - items.into_iter().map(Option::unwrap).collect(), - pos, - )); - } else { - return None; - } + Union::Array(array) => { + let items: Vec<_> = array + .into_iter() + .map(|x| map_dynamic_to_expr(x, pos)) + .collect(); + + if items.iter().all(Option::is_some) { + Some(Expr::Array( + items.into_iter().map(Option::unwrap).collect(), + pos, + )) + } else { + None } } - #[cfg(not(feature = "no_object"))] - { - if value.is::() { - let map = value.cast::(); - let items: Vec<_> = map - .into_iter() - .map(|(k, v)| (k, map_dynamic_to_expr(v, pos), pos)) - .collect(); - if items.iter().all(|(_, expr, _)| expr.is_some()) { - return Some(Expr::Map( - items - .into_iter() - .map(|(k, expr, pos)| (k, expr.unwrap(), pos)) - .collect(), - pos, - )); - } else { - return None; - } + Union::Map(map) => { + let items: Vec<_> = map + .into_iter() + .map(|(k, v)| (k, map_dynamic_to_expr(v, pos), pos)) + .collect(); + if items.iter().all(|(_, expr, _)| expr.is_some()) { + Some(Expr::Map( + items + .into_iter() + .map(|(k, expr, pos)| (k, expr.unwrap(), pos)) + .collect(), + pos, + )) + } else { + None } } - #[cfg(not(feature = "no_float"))] - { - if value.is::() { - return Some(Expr::FloatConstant(value.cast(), pos)); - } - } + Union::Float(value) => Some(Expr::FloatConstant(value, pos)), - None + _ => None, } } diff --git a/src/result.rs b/src/result.rs index ddb56f0b..21fe7ef8 100644 --- a/src/result.rs +++ b/src/result.rs @@ -2,7 +2,8 @@ use crate::any::Dynamic; use crate::error::ParseError; -use crate::parser::{Position, INT}; +use crate::parser::INT; +use crate::token::Position; use crate::stdlib::{ error::Error, @@ -21,7 +22,7 @@ use crate::stdlib::path::PathBuf; #[derive(Debug)] pub enum EvalAltResult { /// Syntax error. - ErrorParsing(ParseError), + ErrorParsing(Box), /// Error reading from a script file. Wrapped value is the path of the script file. /// @@ -229,6 +230,11 @@ impl fmt::Display for EvalAltResult { impl From for EvalAltResult { fn from(err: ParseError) -> Self { + Self::ErrorParsing(Box::new(err)) + } +} +impl From> for EvalAltResult { + fn from(err: Box) -> Self { Self::ErrorParsing(err) } } @@ -279,8 +285,9 @@ impl EvalAltResult { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _) => (), - Self::ErrorParsing(ParseError(_, pos)) - | Self::ErrorFunctionNotFound(_, pos) + Self::ErrorParsing(err) => err.1 = new_position, + + Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionArgsMismatch(_, _, _, pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) diff --git a/src/scope.rs b/src/scope.rs index b5e22170..a9fd579c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,14 +1,10 @@ //! Module that defines the `Scope` type representing a function call-stack scope. -use crate::any::{Any, Dynamic}; -use crate::parser::{map_dynamic_to_expr, Expr, Position}; +use crate::any::{Dynamic, Variant}; +use crate::parser::{map_dynamic_to_expr, Expr}; +use crate::token::Position; -use crate::stdlib::{ - borrow::Cow, - iter, - string::{String, ToString}, - vec::Vec, -}; +use crate::stdlib::{borrow::Cow, iter, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -20,7 +16,7 @@ pub enum EntryType { } /// An entry in the Scope. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Entry<'a> { /// Name of the entry. pub name: Cow<'a, str>, @@ -68,7 +64,7 @@ pub(crate) struct EntryRef<'a> { /// allowing for automatic _shadowing_. /// /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { @@ -157,8 +153,8 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push>, T: Any + Clone>(&mut self, name: K, value: T) { - self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); + pub fn push>, T: Variant + Clone>(&mut self, name: K, value: T) { + self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false); } /// Add (push) a new `Dynamic` entry to the Scope. @@ -166,11 +162,11 @@ impl<'a> Scope<'a> { /// # Examples /// /// ``` - /// use rhai::{Any, Scope}; + /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// - /// my_scope.push_dynamic("x", (42_i64).into_dynamic()); + /// my_scope.push_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) { @@ -195,8 +191,8 @@ impl<'a> Scope<'a> { /// my_scope.push_constant("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push_constant>, T: Any + Clone>(&mut self, name: K, value: T) { - self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); + pub fn push_constant>, T: Variant + Clone>(&mut self, name: K, value: T) { + self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true); } /// Add (push) a new constant with a `Dynamic` value to the Scope. @@ -211,11 +207,11 @@ impl<'a> Scope<'a> { /// # Examples /// /// ``` - /// use rhai::{Any, Scope}; + /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// - /// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic()); + /// my_scope.push_constant_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn push_constant_dynamic>>(&mut self, name: K, value: Dynamic) { @@ -336,13 +332,12 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn get_value(&self, name: &str) -> Option { + pub fn get_value(&self, name: &str) -> Option { self.0 .iter() .rev() .find(|Entry { name: key, .. }| name == key) - .and_then(|Entry { value, .. }| value.downcast_ref::()) - .map(T::clone) + .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } /// Update the value of the named entry. @@ -366,7 +361,7 @@ impl<'a> Scope<'a> { /// my_scope.set_value("x", 0_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` - pub fn set_value(&mut self, name: &'a str, value: T) { + pub fn set_value(&mut self, name: &'a str, value: T) { match self.get(name) { Some(( EntryRef { @@ -382,8 +377,8 @@ impl<'a> Scope<'a> { .. }, _, - )) => self.0.get_mut(index).unwrap().value = value.into_dynamic(), - None => self.push(name, value.into_dynamic()), + )) => self.0.get_mut(index).unwrap().value = Dynamic::from(value), + None => self.push(name, value), } } @@ -402,13 +397,6 @@ impl<'a> Scope<'a> { &mut entry.value } - /// Get a mutable reference to an entry in the Scope and downcast it to a specific type - pub(crate) fn get_mut_by_type(&mut self, key: EntryRef) -> &mut T { - self.get_mut(key) - .downcast_mut::() - .expect("wrong type cast") - } - /// Get an iterator to entries in the Scope. pub fn iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order diff --git a/src/stdlib.rs b/src/stdlib.rs index 4ec71d21..1d1397d5 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -8,7 +8,7 @@ mod inner { panic, pin, prelude, ptr, result, slice, str, task, time, u128, u16, u32, u64, u8, usize, }; - pub use alloc::{borrow, boxed, format, string, sync, vec}; + pub use alloc::{borrow, boxed, format, rc, string, sync, vec}; pub use core_error as error; diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 00000000..aa11a3c6 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,987 @@ +//! Main module defining the lexer and parser. + +use crate::error::LexError; +use crate::parser::INT; + +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + +use crate::stdlib::{ + borrow::Cow, + boxed::Box, + char, fmt, + iter::Peekable, + str::{Chars, FromStr}, + string::{String, ToString}, + usize, + vec::Vec, +}; + +type LERR = LexError; + +/// A location (line number + character position) in the input script. +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +pub struct Position { + /// Line number - 0 = none + line: usize, + /// Character position - 0 = BOL + pos: usize, +} + +impl Position { + /// Create a new `Position`. + pub fn new(line: usize, position: usize) -> Self { + assert!(line != 0, "line cannot be zero"); + assert!( + line != usize::MAX || position != usize::MAX, + "invalid position" + ); + + Self { + line, + pos: position, + } + } + + /// Get the line number (1-based), or `None` if no position. + pub fn line(&self) -> Option { + if self.is_none() { + None + } else { + Some(self.line) + } + } + + /// Get the character position (1-based), or `None` if at beginning of a line. + pub fn position(&self) -> Option { + if self.is_none() || self.pos == 0 { + None + } else { + Some(self.pos) + } + } + + /// Advance by one character position. + pub(crate) fn advance(&mut self) { + self.pos += 1; + } + + /// Go backwards by one character position. + /// + /// # Panics + /// + /// Panics if already at beginning of a line - cannot rewind to a previous line. + /// + pub(crate) fn rewind(&mut self) { + assert!(self.pos > 0, "cannot rewind at position 0"); + self.pos -= 1; + } + + /// Advance to the next line. + pub(crate) fn new_line(&mut self) { + self.line += 1; + self.pos = 0; + } + + /// Create a `Position` representing no position. + pub(crate) fn none() -> Self { + Self { line: 0, pos: 0 } + } + + /// Is there no `Position`? + pub fn is_none(&self) -> bool { + self.line == 0 && self.pos == 0 + } +} + +impl Default for Position { + fn default() -> Self { + Self::new(1, 0) + } +} + +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_none() { + write!(f, "none") + } else { + write!(f, "line {}, position {}", self.line, self.pos) + } + } +} + +impl fmt::Debug for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({}:{})", self.line, self.pos) + } +} + +/// Tokens. +#[derive(Debug, PartialEq, Clone)] +pub enum Token { + IntegerConstant(INT), + #[cfg(not(feature = "no_float"))] + FloatConstant(FLOAT), + Identifier(String), + CharConstant(char), + StringConst(String), + LeftBrace, + RightBrace, + LeftParen, + RightParen, + LeftBracket, + RightBracket, + Plus, + UnaryPlus, + Minus, + UnaryMinus, + Multiply, + Divide, + Modulo, + PowerOf, + LeftShift, + RightShift, + SemiColon, + Colon, + Comma, + Period, + #[cfg(not(feature = "no_object"))] + MapStart, + Equals, + True, + False, + Let, + Const, + If, + Else, + While, + Loop, + For, + In, + LessThan, + GreaterThan, + LessThanEqualsTo, + GreaterThanEqualsTo, + EqualsTo, + NotEqualsTo, + Bang, + Pipe, + Or, + XOr, + Ampersand, + And, + #[cfg(not(feature = "no_function"))] + Fn, + Continue, + Break, + Return, + Throw, + PlusAssign, + MinusAssign, + MultiplyAssign, + DivideAssign, + LeftShiftAssign, + RightShiftAssign, + AndAssign, + OrAssign, + XOrAssign, + ModuloAssign, + PowerOfAssign, + LexError(Box), + EOF, +} + +impl Token { + /// Get the syntax of the token. + pub fn syntax(&self) -> Cow { + use Token::*; + + match self { + IntegerConstant(i) => i.to_string().into(), + #[cfg(not(feature = "no_float"))] + FloatConstant(f) => f.to_string().into(), + Identifier(s) => s.into(), + CharConstant(c) => c.to_string().into(), + LexError(err) => err.to_string().into(), + + token => (match token { + StringConst(_) => "string", + LeftBrace => "{", + RightBrace => "}", + LeftParen => "(", + RightParen => ")", + LeftBracket => "[", + RightBracket => "]", + Plus => "+", + UnaryPlus => "+", + Minus => "-", + UnaryMinus => "-", + Multiply => "*", + Divide => "/", + SemiColon => ";", + Colon => ":", + Comma => ",", + Period => ".", + #[cfg(not(feature = "no_object"))] + MapStart => "#{", + Equals => "=", + True => "true", + False => "false", + Let => "let", + Const => "const", + If => "if", + Else => "else", + While => "while", + Loop => "loop", + LessThan => "<", + GreaterThan => ">", + Bang => "!", + LessThanEqualsTo => "<=", + GreaterThanEqualsTo => ">=", + EqualsTo => "==", + NotEqualsTo => "!=", + Pipe => "|", + Or => "||", + Ampersand => "&", + And => "&&", + #[cfg(not(feature = "no_function"))] + Fn => "fn", + Continue => "continue", + Break => "break", + Return => "return", + Throw => "throw", + PlusAssign => "+=", + MinusAssign => "-=", + MultiplyAssign => "*=", + DivideAssign => "/=", + LeftShiftAssign => "<<=", + RightShiftAssign => ">>=", + AndAssign => "&=", + OrAssign => "|=", + XOrAssign => "^=", + LeftShift => "<<", + RightShift => ">>", + XOr => "^", + Modulo => "%", + ModuloAssign => "%=", + PowerOf => "~", + PowerOfAssign => "~=", + For => "for", + In => "in", + EOF => "{EOF}", + _ => panic!("operator should be match in outer scope"), + }) + .into(), + } + } + + // Is this token EOF? + pub fn is_eof(&self) -> bool { + use Token::*; + + match self { + EOF => true, + _ => false, + } + } + + // If another operator is after these, it's probably an unary operator + // (not sure about fn name). + pub fn is_next_unary(&self) -> bool { + use Token::*; + + match self { + LexError(_) | + LeftBrace | // (+expr) - is unary + // RightBrace | {expr} - expr not unary & is closing + LeftParen | // {-expr} - is unary + // RightParen | (expr) - expr not unary & is closing + LeftBracket | // [-expr] - is unary + // RightBracket | [expr] - expr not unary & is closing + Plus | + UnaryPlus | + Minus | + UnaryMinus | + Multiply | + Divide | + Colon | + Comma | + Period | + Equals | + LessThan | + GreaterThan | + Bang | + LessThanEqualsTo | + GreaterThanEqualsTo | + EqualsTo | + NotEqualsTo | + Pipe | + Or | + Ampersand | + And | + If | + While | + PlusAssign | + MinusAssign | + MultiplyAssign | + DivideAssign | + LeftShiftAssign | + RightShiftAssign | + AndAssign | + OrAssign | + XOrAssign | + LeftShift | + RightShift | + XOr | + Modulo | + ModuloAssign | + Return | + Throw | + PowerOf | + In | + PowerOfAssign => true, + + _ => false, + } + } + + /// Get the precedence number of the token. + pub fn precedence(&self) -> u8 { + use Token::*; + + match self { + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => 10, + + Or | XOr | Pipe => 40, + + And | Ampersand => 50, + + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo + | NotEqualsTo => 60, + + In => 70, + + Plus | Minus => 80, + + Divide | Multiply | PowerOf => 90, + + LeftShift | RightShift => 100, + + Modulo => 110, + + Period => 120, + + _ => 0, + } + } + + /// Does an expression bind to the right (instead of left)? + pub fn is_bind_right(&self) -> bool { + use Token::*; + + match self { + // Assignments bind to the right + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => true, + + // Property access binds to the right + Period => true, + + _ => false, + } + } +} + +/// An iterator on a `Token` stream. +pub struct TokenIterator<'a> { + /// Can the next token be a unary operator? + can_be_unary: bool, + /// Current position. + pos: Position, + /// The input character streams. + streams: Vec>>, +} + +impl<'a> TokenIterator<'a> { + /// Consume the next character. + fn eat_next(&mut self) { + self.get_next(); + self.advance(); + } + /// Get the next character + fn get_next(&mut self) -> Option { + loop { + if self.streams.is_empty() { + // No more streams + return None; + } else if let Some(ch) = self.streams[0].next() { + // Next character in current stream + return Some(ch); + } else { + // Jump to the next stream + let _ = self.streams.remove(0); + } + } + } + /// Peek the next character + fn peek_next(&mut self) -> Option { + loop { + if self.streams.is_empty() { + // No more streams + return None; + } else if let Some(ch) = self.streams[0].peek() { + // Next character in current stream + return Some(*ch); + } else { + // Jump to the next stream + let _ = self.streams.remove(0); + } + } + } + /// Move the current position one character ahead. + fn advance(&mut self) { + self.pos.advance(); + } + /// Move the current position back one character. + /// + /// # Panics + /// + /// Panics if already at the beginning of a line - cannot rewind to the previous line. + fn rewind(&mut self) { + self.pos.rewind(); + } + /// Move the current position to the next line. + fn new_line(&mut self) { + self.pos.new_line() + } + + /// Parse a string literal wrapped by `enclosing_char`. + pub fn parse_string_literal( + &mut self, + enclosing_char: char, + ) -> Result { + let mut result = Vec::new(); + let mut escape = String::with_capacity(12); + + loop { + let next_char = self + .get_next() + .ok_or((LERR::UnterminatedString, self.pos))?; + + self.advance(); + + match next_char { + // \... + '\\' if escape.is_empty() => { + escape.push('\\'); + } + // \\ + '\\' if !escape.is_empty() => { + escape.clear(); + result.push('\\'); + } + // \t + 't' if !escape.is_empty() => { + escape.clear(); + result.push('\t'); + } + // \n + 'n' if !escape.is_empty() => { + escape.clear(); + result.push('\n'); + } + // \r + 'r' if !escape.is_empty() => { + escape.clear(); + result.push('\r'); + } + // \x??, \u????, \U???????? + ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push(ch); + escape.clear(); + + let mut out_val: u32 = 0; + let len = match ch { + 'x' => 2, + 'u' => 4, + 'U' => 8, + _ => panic!("should be 'x', 'u' or 'U'"), + }; + + for _ in 0..len { + let c = self.get_next().ok_or_else(|| { + (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) + })?; + + seq.push(c); + self.advance(); + + out_val *= 16; + out_val += c.to_digit(16).ok_or_else(|| { + (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) + })?; + } + + result.push( + char::from_u32(out_val) + .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, + ); + } + + // \{enclosing_char} - escaped + ch if enclosing_char == ch && !escape.is_empty() => { + escape.clear(); + result.push(ch) + } + + // Close wrapper + ch if enclosing_char == ch && escape.is_empty() => break, + + // Unknown escape sequence + _ if !escape.is_empty() => { + return Err((LERR::MalformedEscapeSequence(escape), self.pos)) + } + + // Cannot have new-lines inside string literals + '\n' => { + self.rewind(); + return Err((LERR::UnterminatedString, self.pos)); + } + + // All other characters + ch => { + escape.clear(); + result.push(ch); + } + } + } + + Ok(result.iter().collect()) + } + + /// Get the next token. + fn inner_next(&mut self) -> Option<(Token, Position)> { + let mut negated = false; + + while let Some(c) = self.get_next() { + self.advance(); + + let pos = self.pos; + + match (c, self.peek_next().unwrap_or('\0')) { + // \n + ('\n', _) => self.new_line(), + + // digit ... + ('0'..='9', _) => { + let mut result = Vec::new(); + let mut radix_base: Option = None; + result.push(c); + + while let Some(next_char) = self.peek_next() { + match next_char { + '0'..='9' | '_' => { + result.push(next_char); + self.eat_next(); + } + #[cfg(not(feature = "no_float"))] + '.' => { + result.push(next_char); + self.eat_next(); + while let Some(next_char_in_float) = self.peek_next() { + match next_char_in_float { + '0'..='9' | '_' => { + result.push(next_char_in_float); + self.eat_next(); + } + _ => break, + } + } + } + // 0x????, 0o????, 0b???? + ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' + if c == '0' => + { + result.push(next_char); + self.eat_next(); + + let valid = match ch { + 'x' | 'X' => [ + 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', + ], + 'o' | 'O' => [ + '0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + ], + 'b' | 'B' => [ + '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + ], + _ => panic!("unexpected character {}", ch), + }; + + radix_base = Some(match ch { + 'x' | 'X' => 16, + 'o' | 'O' => 8, + 'b' | 'B' => 2, + _ => panic!("unexpected character {}", ch), + }); + + while let Some(next_char_in_hex) = self.peek_next() { + if !valid.contains(&next_char_in_hex) { + break; + } + + result.push(next_char_in_hex); + self.eat_next(); + } + } + + _ => break, + } + } + + if negated { + result.insert(0, '-'); + } + + // Parse number + if let Some(radix) = radix_base { + let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); + + return Some(( + INT::from_str_radix(&out, radix) + .map(Token::IntegerConstant) + .unwrap_or_else(|_| { + Token::LexError(Box::new(LERR::MalformedNumber( + result.iter().collect(), + ))) + }), + pos, + )); + } else { + let out: String = result.iter().filter(|&&c| c != '_').collect(); + let num = INT::from_str(&out).map(Token::IntegerConstant); + + // If integer parsing is unnecessary, try float instead + #[cfg(not(feature = "no_float"))] + let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); + + return Some(( + num.unwrap_or_else(|_| { + Token::LexError(Box::new(LERR::MalformedNumber( + result.iter().collect(), + ))) + }), + pos, + )); + } + } + + // letter or underscore ... + ('A'..='Z', _) | ('a'..='z', _) | ('_', _) => { + let mut result = Vec::new(); + result.push(c); + + while let Some(next_char) = self.peek_next() { + match next_char { + x if x.is_ascii_alphanumeric() || x == '_' => { + result.push(x); + self.eat_next(); + } + _ => break, + } + } + + let is_valid_identifier = result + .iter() + .find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character + .map(char::is_ascii_alphabetic) // is a letter + .unwrap_or(false); // if no alpha-numeric at all - syntax error + + let identifier: String = result.iter().collect(); + + if !is_valid_identifier { + return Some(( + Token::LexError(Box::new(LERR::MalformedIdentifier(identifier))), + pos, + )); + } + + return Some(( + match identifier.as_str() { + "true" => Token::True, + "false" => Token::False, + "let" => Token::Let, + "const" => Token::Const, + "if" => Token::If, + "else" => Token::Else, + "while" => Token::While, + "loop" => Token::Loop, + "continue" => Token::Continue, + "break" => Token::Break, + "return" => Token::Return, + "throw" => Token::Throw, + "for" => Token::For, + "in" => Token::In, + + #[cfg(not(feature = "no_function"))] + "fn" => Token::Fn, + + _ => Token::Identifier(identifier), + }, + pos, + )); + } + + // " - string literal + ('"', _) => { + return self.parse_string_literal('"').map_or_else( + |err| Some((Token::LexError(Box::new(err.0)), err.1)), + |out| Some((Token::StringConst(out), pos)), + ); + } + + // ' - character literal + ('\'', '\'') => { + return Some(( + Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), + pos, + )); + } + ('\'', _) => { + return Some(self.parse_string_literal('\'').map_or_else( + |err| (Token::LexError(Box::new(err.0)), err.1), + |result| { + let mut chars = result.chars(); + let first = chars.next(); + + if chars.next().is_some() { + (Token::LexError(Box::new(LERR::MalformedChar(result))), pos) + } else { + (Token::CharConstant(first.expect("should be Some")), pos) + } + }, + )); + } + + // Braces + ('{', _) => return Some((Token::LeftBrace, pos)), + ('}', _) => return Some((Token::RightBrace, pos)), + + // Parentheses + ('(', _) => return Some((Token::LeftParen, pos)), + (')', _) => return Some((Token::RightParen, pos)), + + // Indexing + ('[', _) => return Some((Token::LeftBracket, pos)), + (']', _) => return Some((Token::RightBracket, pos)), + + // Map literal + #[cfg(not(feature = "no_object"))] + ('#', '{') => { + self.eat_next(); + return Some((Token::MapStart, pos)); + } + + // Operators + ('+', '=') => { + self.eat_next(); + return Some((Token::PlusAssign, pos)); + } + ('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)), + ('+', _) => return Some((Token::Plus, pos)), + + ('-', '0'..='9') if self.can_be_unary => negated = true, + ('-', '0'..='9') => return Some((Token::Minus, pos)), + ('-', '=') => { + self.eat_next(); + return Some((Token::MinusAssign, pos)); + } + ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), + ('-', _) => return Some((Token::Minus, pos)), + + ('*', '=') => { + self.eat_next(); + return Some((Token::MultiplyAssign, pos)); + } + ('*', _) => return Some((Token::Multiply, pos)), + + // Comments + ('/', '/') => { + self.eat_next(); + + while let Some(c) = self.get_next() { + if c == '\n' { + self.new_line(); + break; + } + + self.advance(); + } + } + ('/', '*') => { + let mut level = 1; + + self.eat_next(); + + while let Some(c) = self.get_next() { + self.advance(); + + match c { + '/' => { + if self.get_next() == Some('*') { + level += 1; + } + self.advance(); + } + '*' => { + if self.get_next() == Some('/') { + level -= 1; + } + self.advance(); + } + '\n' => self.new_line(), + _ => (), + } + + if level == 0 { + break; + } + } + } + + ('/', '=') => { + self.eat_next(); + return Some((Token::DivideAssign, pos)); + } + ('/', _) => return Some((Token::Divide, pos)), + + (';', _) => return Some((Token::SemiColon, pos)), + (':', _) => return Some((Token::Colon, pos)), + (',', _) => return Some((Token::Comma, pos)), + ('.', _) => return Some((Token::Period, pos)), + + ('=', '=') => { + self.eat_next(); + return Some((Token::EqualsTo, pos)); + } + ('=', _) => return Some((Token::Equals, pos)), + + ('<', '=') => { + self.eat_next(); + return Some((Token::LessThanEqualsTo, pos)); + } + ('<', '<') => { + self.eat_next(); + + return Some(( + if self.peek_next() == Some('=') { + self.eat_next(); + Token::LeftShiftAssign + } else { + Token::LeftShift + }, + pos, + )); + } + ('<', _) => return Some((Token::LessThan, pos)), + + ('>', '=') => { + self.eat_next(); + return Some((Token::GreaterThanEqualsTo, pos)); + } + ('>', '>') => { + self.eat_next(); + + return Some(( + if self.peek_next() == Some('=') { + self.eat_next(); + Token::RightShiftAssign + } else { + Token::RightShift + }, + pos, + )); + } + ('>', _) => return Some((Token::GreaterThan, pos)), + + ('!', '=') => { + self.eat_next(); + return Some((Token::NotEqualsTo, pos)); + } + ('!', _) => return Some((Token::Bang, pos)), + + ('|', '|') => { + self.eat_next(); + return Some((Token::Or, pos)); + } + ('|', '=') => { + self.eat_next(); + return Some((Token::OrAssign, pos)); + } + ('|', _) => return Some((Token::Pipe, pos)), + + ('&', '&') => { + self.eat_next(); + return Some((Token::And, pos)); + } + ('&', '=') => { + self.eat_next(); + return Some((Token::AndAssign, pos)); + } + ('&', _) => return Some((Token::Ampersand, pos)), + + ('^', '=') => { + self.eat_next(); + return Some((Token::XOrAssign, pos)); + } + ('^', _) => return Some((Token::XOr, pos)), + + ('%', '=') => { + self.eat_next(); + return Some((Token::ModuloAssign, pos)); + } + ('%', _) => return Some((Token::Modulo, pos)), + + ('~', '=') => { + self.eat_next(); + return Some((Token::PowerOfAssign, pos)); + } + ('~', _) => return Some((Token::PowerOf, pos)), + + (ch, _) if ch.is_whitespace() => (), + (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)), + } + } + + self.advance(); + Some((Token::EOF, self.pos)) + } +} + +impl<'a> Iterator for TokenIterator<'a> { + type Item = (Token, Position); + + fn next(&mut self) -> Option { + self.inner_next().map(|x| { + // Save the last token + self.can_be_unary = x.0.is_next_unary(); + x + }) + } +} + +/// Tokenize an input text stream. +pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { + TokenIterator { + can_be_unary: true, + pos: Position::new(1, 0), + streams: input.iter().map(|s| s.chars().peekable()).collect(), + } +} diff --git a/tests/maps.rs b/tests/maps.rs index ac591966..c1b91187 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_object"))] -use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT}; +use rhai::{Engine, EvalAltResult, Map, Scope, INT}; #[test] fn test_map_indexing() -> Result<(), EvalAltResult> { diff --git a/tests/side_effects.rs b/tests/side_effects.rs index 0375d1af..259d1f63 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -81,18 +81,18 @@ fn test_side_effects_command() -> Result<(), EvalAltResult> { #[test] fn test_side_effects_print() -> Result<(), EvalAltResult> { + use std::sync::Arc; use std::sync::RwLock; - let result = RwLock::new(String::from("")); + let result = Arc::new(RwLock::new(String::from(""))); - { - let mut engine = Engine::new(); + let mut engine = Engine::new(); - // Override action of 'print' function - engine.on_print(|s| result.write().unwrap().push_str(s)); + // Override action of 'print' function + let logger = result.clone(); + engine.on_print(move |s| logger.write().unwrap().push_str(s)); - engine.consume("print(40 + 2);")?; - } + engine.consume("print(40 + 2);")?; assert_eq!(*result.read().unwrap(), "42"); Ok(()) diff --git a/tests/stack.rs b/tests/stack.rs index 79a16221..8efc759e 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -9,10 +9,10 @@ fn test_stack_overflow() -> Result<(), EvalAltResult> { engine.eval::( r" fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } } - foo(30) + foo(25) ", )?, - 465 + 325 ); match engine.eval::<()>( diff --git a/tests/string.rs b/tests/string.rs index 7454dcf2..1ec77e2e 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_string() -> Result<(), EvalAltResult> { @@ -33,3 +33,110 @@ fn test_string() -> Result<(), EvalAltResult> { Ok(()) } + +#[cfg(not(feature = "no_stdlib"))] +#[cfg(not(feature = "no_object"))] +#[test] +fn test_string_substring() -> Result<(), EvalAltResult> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(-1, 2)"# + )?, + "❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 5)"# + )?, + "❤❤ he" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1)"# + )?, + "❤❤ hello! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(99)"# + )?, + "" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, -1)"# + )?, + "" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 999)"# + )?, + "❤❤ hello! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, -1); x"# + )?, + "" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(4, 6); x"# + )?, + "hello!" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, 999); x"# + )?, + "❤❤ hello! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764')"# + )?, + 0 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 5)"# + )?, + 11 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', -1)"# + )?, + 0 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 999)"# + )?, + -1 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('x')"# + )?, + -1 + ); + + Ok(()) +} diff --git a/tests/time.rs b/tests/time.rs index 10b232fe..eddb652d 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,4 +1,5 @@ #![cfg(not(feature = "no_stdlib"))] +#![cfg(not(feature = "no_std"))] use rhai::{Engine, EvalAltResult, INT};