commit
40004ec361
16
CHANGELOG.md
16
CHANGELOG.md
@ -6,6 +6,12 @@ Version 1.5.0
|
|||||||
|
|
||||||
This version adds a debugging interface, which can be used to integrate a debugger.
|
This version adds a debugging interface, which can be used to integrate a debugger.
|
||||||
|
|
||||||
|
Based on popular demand, an option is added to throw exceptions when invalid properties are accessed
|
||||||
|
on object maps (default is to return `()`).
|
||||||
|
|
||||||
|
Also based on popular demand, the `REPL` tool now uses
|
||||||
|
[`rustyline`](https://crates.io/crates/rustyline) for line editing and history.
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@ -15,11 +21,14 @@ Bug fixes
|
|||||||
* Globally-defined constants are now encapsulated correctly inside a loaded module and no longer spill across call boundaries.
|
* Globally-defined constants are now encapsulated correctly inside a loaded module and no longer spill across call boundaries.
|
||||||
* Type names display is fixed.
|
* Type names display is fixed.
|
||||||
* Exceptions thrown inside function calls now unwrap correctly when `catch`-ed.
|
* Exceptions thrown inside function calls now unwrap correctly when `catch`-ed.
|
||||||
|
* Error messages for certain invalid property accesses are fixed.
|
||||||
|
|
||||||
Script-breaking changes
|
Script-breaking changes
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
* For consistency with the `import` statement, the `export` statement no longer exports multiple variables.
|
* For consistency with the `import` statement, the `export` statement no longer exports multiple variables.
|
||||||
|
* Appending a BLOB to a string (via `+`, `+=`, `append` or string interpolation) now treats the BLOB as a UTF-8 encoded string.
|
||||||
|
* Appending a string/character to a BLOB (via `+=` or `append`) now adds the string/character as a UTF-8 encoded byte stream.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
@ -27,8 +36,10 @@ New features
|
|||||||
* A debugging interface is added.
|
* A debugging interface is added.
|
||||||
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
||||||
* A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
|
* A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
|
||||||
|
* `Engine::set_fail_on_invalid_map_property` is added to control whether to raise an error (new `EvalAltResult::ErrorPropertyNotFound`) when invalid properties are accessed on object maps.
|
||||||
* `Engine::set_allow_shadowing` is added to allow/disallow variables _shadowing_, with new errors `EvalAltResult::ErrorVariableExists` and `ParseErrorType::VariableExists`.
|
* `Engine::set_allow_shadowing` is added to allow/disallow variables _shadowing_, with new errors `EvalAltResult::ErrorVariableExists` and `ParseErrorType::VariableExists`.
|
||||||
* `Engine::on_def_var` allows registering a closure which can decide whether a variable definition is allow to continue, or should fail with an error.
|
* `Engine::on_def_var` allows registering a closure which can decide whether a variable definition is allow to continue, during compilation or runtime, or should fail with an error (`ParseErrorType::ForbiddenVariable` or `EvalAltResult::ErrorForbiddenVariable`).
|
||||||
|
* A new syntax for defining custom packages is introduced that removes the need to specify the Rhai crate name (internally uses the `$crate` meta variable).
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
@ -42,6 +53,9 @@ Enhancements
|
|||||||
* `Expr::start_position` is added to give the beginning of the expression (not the operator's position).
|
* `Expr::start_position` is added to give the beginning of the expression (not the operator's position).
|
||||||
* `StmtBlock` and `Stmt::Block` now keep the position of the closing `}` as well.
|
* `StmtBlock` and `Stmt::Block` now keep the position of the closing `}` as well.
|
||||||
* `EvalAltResult::unwrap_inner` is added to access the base error inside multiple layers of wrappings (e.g. `EvalAltResult::ErrorInFunction`).
|
* `EvalAltResult::unwrap_inner` is added to access the base error inside multiple layers of wrappings (e.g. `EvalAltResult::ErrorInFunction`).
|
||||||
|
* Yet another new syntax is introduced for `def_package!` that further simplifies the old syntax.
|
||||||
|
* A new method `to_blob` is added to convert a string into a BLOB as UTF-8 encoded bytes.
|
||||||
|
* A new method `to_array` is added to convert a BLOB into array of integers.
|
||||||
|
|
||||||
REPL tool changes
|
REPL tool changes
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -31,7 +31,7 @@ unicode-xid = { version = "0.2", default-features = false, optional = true }
|
|||||||
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
|
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
|
||||||
# notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows
|
# notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows
|
||||||
# this can be moved to the official version when bracketed paste is added
|
# this can be moved to the official version when bracketed paste is added
|
||||||
rustyline = { version = "9", optional = true, git = "https://github.com/schungx/rustyline", branch = "bracketed_paste" }
|
rustyline = { version = "9", optional = true, git = "https://github.com/schungx/rustyline" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_bytes = "0.11"
|
serde_bytes = "0.11"
|
||||||
|
@ -1,12 +1,40 @@
|
|||||||
Sample Applications
|
Sample Applications
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Sample applications that use the Rhai scripting engine.
|
Standard Examples
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
| Example | Description |
|
||||||
|
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| [`arrays_and_structs`](arrays_and_structs.rs) | shows how to register a Rust type and using it with arrays |
|
||||||
|
| [`callback`](callback.rs) | shows how to store a Rhai closure and call it later within Rust |
|
||||||
|
| [`custom_types_and_methods`](custom_types_and_methods.rs) | shows how to register a Rust type and methods/getters/setters for it |
|
||||||
|
| [`hello`](hello.rs) | simple example that evaluates an expression and prints the result |
|
||||||
|
| [`reuse_scope`](reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common `Scope` |
|
||||||
|
| [`serde`](serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde) (requires the `serde` feature) |
|
||||||
|
| [`simple_fn`](simple_fn.rs) | shows how to register a simple Rust function |
|
||||||
|
| [`strings`](strings.rs) | shows different ways to register Rust functions taking string arguments |
|
||||||
|
| [`threading`](threading.rs) | shows how to communicate with an `Engine` running in a separate thread via an MPSC channel |
|
||||||
|
|
||||||
|
|
||||||
How to Run
|
Scriptable Event Handler With State Examples
|
||||||
----------
|
-------------------------------------------
|
||||||
|
|
||||||
```bash
|
Because of its popularity, included are sample implementations for the pattern
|
||||||
cargo run --example sample_app_to_run
|
[_Scriptable Event Handler With State_](https://rhai.rs/book/patterns/events.html) in different styles.
|
||||||
|
|
||||||
|
| Example | Handler Script | Description |
|
||||||
|
| ------------------------------------------ | ------------------------------------------------------------------ | :---------------------------------------------------------: |
|
||||||
|
| [`event_handler_main`](event_handler_main) | [`event_handler_main/script.rhai`](event_handler_main/script.rhai) | [_Main Style_](https://rhai.rs/book/patterns/events-1.html) |
|
||||||
|
| [`event_handler_js`](event_handler_js) | [`event_handler_js/script.rhai`](event_handler_js/script.rhai) | [_JS Style_](https://rhai.rs/book/patterns/events-2.html) |
|
||||||
|
| [`event_handler_map`](event_handler_map) | [`event_handler_map/script.rhai`](event_handler_map/script.rhai) | [_Map Style_](https://rhai.rs/book/patterns/events-3.html) |
|
||||||
|
|
||||||
|
|
||||||
|
Running Examples
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Examples can be run with the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --example {example_name}
|
||||||
```
|
```
|
||||||
|
@ -1,29 +1,43 @@
|
|||||||
|
//! An example showing how to register a Rust type and use it with arrays.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct TestStruct {
|
|
||||||
x: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestStruct {
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
self.x += 1000;
|
|
||||||
}
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { x: 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct TestStruct {
|
||||||
|
x: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestStruct {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { x: 1 }
|
||||||
|
}
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
self.x += 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_type::<TestStruct>()
|
.register_type_with_name::<TestStruct>("TestStruct")
|
||||||
.register_fn("new_ts", TestStruct::new)
|
.register_fn("new_ts", TestStruct::new)
|
||||||
.register_fn("update", TestStruct::update);
|
.register_fn("update", TestStruct::update);
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
{
|
||||||
|
println!("Functions registered:");
|
||||||
|
|
||||||
|
engine
|
||||||
|
.gen_fn_signatures(false)
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|func| println!("{}", func));
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
let result = engine.eval::<TestStruct>(
|
let result = engine.eval::<TestStruct>(
|
||||||
"
|
"
|
||||||
let x = new_ts();
|
let x = new_ts();
|
||||||
|
34
examples/callback.rs
Normal file
34
examples/callback.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//! This example stores a Rhai closure for later use as a callback.
|
||||||
|
|
||||||
|
use rhai::{Engine, EvalAltResult, FnPtr};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// This script creates a closure which may capture variables.
|
||||||
|
let script = "
|
||||||
|
let x = 20;
|
||||||
|
|
||||||
|
// The following closure captures 'x'
|
||||||
|
return |a, b| (x + a) * b;
|
||||||
|
";
|
||||||
|
|
||||||
|
// To call a Rhai closure at a later time, you'd need three things:
|
||||||
|
// 1) an `Engine` (with all needed functions registered),
|
||||||
|
// 2) a compiled `AST`,
|
||||||
|
// 3) the closure (of type `FnPtr`).
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
let ast = engine.compile(script)?;
|
||||||
|
|
||||||
|
let closure = engine.eval_ast::<FnPtr>(&ast)?;
|
||||||
|
|
||||||
|
// Create a closure that we can call any time, encapsulating the
|
||||||
|
// `Engine`, `AST` and `FnPtr`.
|
||||||
|
let func = move |x: i64, y: i64| -> Result<i64, _> { closure.call(&engine, &ast, (x, y)) };
|
||||||
|
|
||||||
|
// Now we can call `func` anywhere just like a normal function!
|
||||||
|
let result = func(1, 2)?;
|
||||||
|
|
||||||
|
println!("The Answer: {}", result); // prints 42
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,38 +1,63 @@
|
|||||||
|
//! An example showing how to register a Rust type and methods/getters/setters for it.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct TestStruct {
|
|
||||||
x: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestStruct {
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
self.x += 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { x: 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct TestStruct {
|
||||||
|
x: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestStruct {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { x: 1 }
|
||||||
|
}
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
self.x += 1000;
|
||||||
|
}
|
||||||
|
pub fn calculate(&mut self, data: i64) -> i64 {
|
||||||
|
self.x * data
|
||||||
|
}
|
||||||
|
pub fn get_x(&mut self) -> i64 {
|
||||||
|
self.x
|
||||||
|
}
|
||||||
|
pub fn set_x(&mut self, value: i64) {
|
||||||
|
self.x = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.register_type::<TestStruct>()
|
.register_type_with_name::<TestStruct>("TestStruct")
|
||||||
.register_fn("new_ts", TestStruct::new)
|
.register_fn("new_ts", TestStruct::new)
|
||||||
.register_fn("update", TestStruct::update);
|
.register_fn("update", TestStruct::update)
|
||||||
|
.register_fn("calc", TestStruct::calculate)
|
||||||
|
.register_get_set("x", TestStruct::get_x, TestStruct::set_x);
|
||||||
|
|
||||||
let result = engine.eval::<TestStruct>(
|
#[cfg(feature = "metadata")]
|
||||||
|
{
|
||||||
|
println!("Functions registered:");
|
||||||
|
|
||||||
|
engine
|
||||||
|
.gen_fn_signatures(false)
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|func| println!("{}", func));
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = engine.eval::<i64>(
|
||||||
"
|
"
|
||||||
let x = new_ts();
|
let x = new_ts();
|
||||||
|
x.x = 42;
|
||||||
x.update();
|
x.update();
|
||||||
x
|
x.calc(x.x)
|
||||||
",
|
",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("result: {}", result.x); // prints 1001
|
println!("result: {}", result); // prints 1085764
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! A simple example that evaluates an expression and prints the result.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -7,7 +9,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let result = engine.eval::<i64>("40 + 2")?;
|
let result = engine.eval::<i64>("40 + 2")?;
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("The Answer: {}", result); // prints 42
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! An example that evaluates two pieces of code in separate runs, but using a common `Scope`.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, Scope};
|
use rhai::{Engine, EvalAltResult, Scope};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! An example to serialize and deserialize Rust types.
|
||||||
|
|
||||||
#[cfg(any(not(feature = "serde"), feature = "no_object"))]
|
#[cfg(any(not(feature = "serde"), feature = "no_object"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("This example requires the 'serde' feature to run.");
|
println!("This example requires the 'serde' feature to run.");
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! An example showing how to register a simple Rust function.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
///! This example registers a variety of functions that operate on strings.
|
//! An example that registers a variety of functions that operate on strings.
|
||||||
///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters.
|
//! Remember to use `ImmutableString` or `&str` instead of `String` as parameters.
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, ImmutableString, Scope};
|
use rhai::{Engine, EvalAltResult, ImmutableString, Scope};
|
||||||
use std::io::{stdin, stdout, Write};
|
use std::io::{stdin, stdout, Write};
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ fn trim_string(s: &mut ImmutableString) {
|
|||||||
|
|
||||||
/// Notice this is different from the built-in Rhai 'len' function for strings
|
/// Notice this is different from the built-in Rhai 'len' function for strings
|
||||||
/// which counts the actual number of Unicode _characters_ in a string.
|
/// which counts the actual number of Unicode _characters_ in a string.
|
||||||
|
///
|
||||||
/// This version simply counts the number of _bytes_ in the UTF-8 representation.
|
/// This version simply counts the number of _bytes_ in the UTF-8 representation.
|
||||||
///
|
///
|
||||||
/// This version uses `&str`.
|
/// This version uses `&str`.
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
//! An advanced example showing how to communicate with an `Engine` running in a separate thread via
|
||||||
|
//! an MPSC channel.
|
||||||
|
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
|
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
|
@ -5,6 +5,19 @@ use crate::{Dynamic, Engine, EvalContext, Position, RhaiResultOf};
|
|||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
|
/// Information on a variable definition.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct VarDefInfo<'a> {
|
||||||
|
/// Name of the variable to be defined.
|
||||||
|
pub name: &'a str,
|
||||||
|
/// `true` if the statement is `const`, otherwise it is `let`.
|
||||||
|
pub is_const: bool,
|
||||||
|
/// The current nesting level, with zero being the global level.
|
||||||
|
pub nesting_level: usize,
|
||||||
|
/// Will the variable _shadow_ an existing variable?
|
||||||
|
pub will_shadow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Provide a callback that will be invoked before each variable access.
|
/// Provide a callback that will be invoked before each variable access.
|
||||||
///
|
///
|
||||||
@ -65,23 +78,25 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
/// Provide a callback that will be invoked before the definition of each variable .
|
/// Provide a callback that will be invoked before the definition of each variable .
|
||||||
///
|
///
|
||||||
|
/// # WARNING - Unstable API
|
||||||
|
///
|
||||||
|
/// This API is volatile and may change in the future.
|
||||||
|
///
|
||||||
/// # Callback Function Signature
|
/// # Callback Function Signature
|
||||||
///
|
///
|
||||||
/// The callback function signature takes the following form:
|
/// The callback function signature takes the following form:
|
||||||
///
|
///
|
||||||
/// > `Fn(name: &str, is_const: bool, block_level: usize, will_shadow: bool, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
|
/// > `Fn(is_runtime: bool, info: VarInfo, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
|
||||||
///
|
///
|
||||||
/// where:
|
/// where:
|
||||||
/// * `name`: name of the variable to be defined.
|
/// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation.
|
||||||
/// * `is_const`: `true` if the statement is `const`, otherwise it is `let`.
|
/// * `info`: information on the variable.
|
||||||
/// * `block_level`: the current nesting level of statement blocks, with zero being the global level
|
|
||||||
/// * `will_shadow`: will the variable _shadow_ an existing variable?
|
|
||||||
/// * `context`: the current [evaluation context][`EvalContext`].
|
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||||
///
|
///
|
||||||
/// ## Return value
|
/// ## Return value
|
||||||
///
|
///
|
||||||
/// * `Ok(true)`: continue with normal variable definition.
|
/// * `Ok(true)`: continue with normal variable definition.
|
||||||
/// * `Ok(false)`: deny the variable definition with an [runtime error][EvalAltResult::ErrorRuntime].
|
/// * `Ok(false)`: deny the variable definition with an [runtime error][crate::EvalAltResult::ErrorRuntime].
|
||||||
///
|
///
|
||||||
/// ## Raising errors
|
/// ## Raising errors
|
||||||
///
|
///
|
||||||
@ -96,9 +111,9 @@ impl Engine {
|
|||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Register a variable definition filter.
|
/// // Register a variable definition filter.
|
||||||
/// engine.on_def_var(|name, is_const, _, _, _| {
|
/// engine.on_def_var(|_, info, _| {
|
||||||
/// // Disallow defining MYSTIC_NUMBER as a constant
|
/// // Disallow defining MYSTIC_NUMBER as a constant
|
||||||
/// if name == "MYSTIC_NUMBER" && is_const {
|
/// if info.name == "MYSTIC_NUMBER" && info.is_const {
|
||||||
/// Ok(false)
|
/// Ok(false)
|
||||||
/// } else {
|
/// } else {
|
||||||
/// Ok(true)
|
/// Ok(true)
|
||||||
@ -114,12 +129,11 @@ impl Engine {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[deprecated = "This API is volatile and may change in the future."]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_def_var(
|
pub fn on_def_var(
|
||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>
|
callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + SendSync + 'static,
|
||||||
+ SendSync
|
|
||||||
+ 'static,
|
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.def_var_filter = Some(Box::new(callback));
|
self.def_var_filter = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
@ -321,8 +335,13 @@ impl Engine {
|
|||||||
self.debug = Some(Box::new(callback));
|
self.debug = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// _(debugging)_ Register callbacks for debugging.
|
/// _(debugging)_ Register a callback for debugging.
|
||||||
/// Exported under the `debugging` feature only.
|
/// Exported under the `debugging` feature only.
|
||||||
|
///
|
||||||
|
/// # WARNING - Unstable API
|
||||||
|
///
|
||||||
|
/// This API is volatile and may change in the future.
|
||||||
|
#[deprecated = "This API is volatile and may change in the future."]
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn register_debugger(
|
pub fn register_debugger(
|
||||||
|
@ -18,10 +18,14 @@ pub struct LanguageOptions {
|
|||||||
pub allow_anonymous_fn: bool,
|
pub allow_anonymous_fn: bool,
|
||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
pub allow_looping: bool,
|
pub allow_looping: bool,
|
||||||
/// Strict variables mode?
|
|
||||||
pub strict_var: bool,
|
|
||||||
/// Is variables shadowing allowed?
|
/// Is variables shadowing allowed?
|
||||||
pub allow_shadowing: bool,
|
pub allow_shadowing: bool,
|
||||||
|
/// Strict variables mode?
|
||||||
|
pub strict_var: bool,
|
||||||
|
/// Raise error if an object map property does not exist?
|
||||||
|
/// Returns `()` if `false`.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub fail_on_invalid_map_property: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageOptions {
|
impl LanguageOptions {
|
||||||
@ -37,6 +41,8 @@ impl LanguageOptions {
|
|||||||
allow_looping: true,
|
allow_looping: true,
|
||||||
strict_var: false,
|
strict_var: false,
|
||||||
allow_shadowing: true,
|
allow_shadowing: true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
fail_on_invalid_map_property: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,6 +55,7 @@ impl Default for LanguageOptions {
|
|||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Is `if`-expression allowed?
|
/// Is `if`-expression allowed?
|
||||||
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_if_expression(&self) -> bool {
|
pub fn allow_if_expression(&self) -> bool {
|
||||||
self.options.allow_if_expr
|
self.options.allow_if_expr
|
||||||
@ -59,6 +66,7 @@ impl Engine {
|
|||||||
self.options.allow_if_expr = enable;
|
self.options.allow_if_expr = enable;
|
||||||
}
|
}
|
||||||
/// Is `switch` expression allowed?
|
/// Is `switch` expression allowed?
|
||||||
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_switch_expression(&self) -> bool {
|
pub fn allow_switch_expression(&self) -> bool {
|
||||||
self.options.allow_switch_expr
|
self.options.allow_switch_expr
|
||||||
@ -69,6 +77,7 @@ impl Engine {
|
|||||||
self.options.allow_switch_expr = enable;
|
self.options.allow_switch_expr = enable;
|
||||||
}
|
}
|
||||||
/// Is statement-expression allowed?
|
/// Is statement-expression allowed?
|
||||||
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_statement_expression(&self) -> bool {
|
pub fn allow_statement_expression(&self) -> bool {
|
||||||
self.options.allow_stmt_expr
|
self.options.allow_stmt_expr
|
||||||
@ -79,6 +88,7 @@ impl Engine {
|
|||||||
self.options.allow_stmt_expr = enable;
|
self.options.allow_stmt_expr = enable;
|
||||||
}
|
}
|
||||||
/// Is anonymous function allowed?
|
/// Is anonymous function allowed?
|
||||||
|
/// Default is `true`.
|
||||||
///
|
///
|
||||||
/// Not available under `no_function`.
|
/// Not available under `no_function`.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -95,6 +105,7 @@ impl Engine {
|
|||||||
self.options.allow_anonymous_fn = enable;
|
self.options.allow_anonymous_fn = enable;
|
||||||
}
|
}
|
||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_looping(&self) -> bool {
|
pub fn allow_looping(&self) -> bool {
|
||||||
self.options.allow_looping
|
self.options.allow_looping
|
||||||
@ -104,17 +115,8 @@ impl Engine {
|
|||||||
pub fn set_allow_looping(&mut self, enable: bool) {
|
pub fn set_allow_looping(&mut self, enable: bool) {
|
||||||
self.options.allow_looping = enable;
|
self.options.allow_looping = enable;
|
||||||
}
|
}
|
||||||
/// Is strict variables mode enabled?
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn strict_variables(&self) -> bool {
|
|
||||||
self.options.strict_var
|
|
||||||
}
|
|
||||||
/// Set whether strict variables mode is enabled.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn set_strict_variables(&mut self, enable: bool) {
|
|
||||||
self.options.strict_var = enable;
|
|
||||||
}
|
|
||||||
/// Is variables shadowing allowed?
|
/// Is variables shadowing allowed?
|
||||||
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_shadowing(&self) -> bool {
|
pub fn allow_shadowing(&self) -> bool {
|
||||||
self.options.allow_shadowing
|
self.options.allow_shadowing
|
||||||
@ -124,4 +126,32 @@ impl Engine {
|
|||||||
pub fn set_allow_shadowing(&mut self, enable: bool) {
|
pub fn set_allow_shadowing(&mut self, enable: bool) {
|
||||||
self.options.allow_shadowing = enable;
|
self.options.allow_shadowing = enable;
|
||||||
}
|
}
|
||||||
|
/// Is strict variables mode enabled?
|
||||||
|
/// Default is `false`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn strict_variables(&self) -> bool {
|
||||||
|
self.options.strict_var
|
||||||
|
}
|
||||||
|
/// Set whether strict variables mode is enabled.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_strict_variables(&mut self, enable: bool) {
|
||||||
|
self.options.strict_var = enable;
|
||||||
|
}
|
||||||
|
/// Raise error if an object map property does not exist?
|
||||||
|
/// Default is `false`.
|
||||||
|
///
|
||||||
|
/// Not available under `no_object`.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn fail_on_invalid_map_property(&self) -> bool {
|
||||||
|
self.options.fail_on_invalid_map_property
|
||||||
|
}
|
||||||
|
/// Set whether to raise error if an object map property does not exist.
|
||||||
|
///
|
||||||
|
/// Not available under `no_object`.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_fail_on_invalid_map_property(&mut self, enable: bool) {
|
||||||
|
self.options.fail_on_invalid_map_property = enable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -879,7 +879,7 @@ impl Eq for ASTNode<'_> {}
|
|||||||
|
|
||||||
impl ASTNode<'_> {
|
impl ASTNode<'_> {
|
||||||
/// Get the [`Position`] of this [`ASTNode`].
|
/// Get the [`Position`] of this [`ASTNode`].
|
||||||
pub const fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
ASTNode::Stmt(stmt) => stmt.position(),
|
ASTNode::Stmt(stmt) => stmt.position(),
|
||||||
ASTNode::Expr(expr) => expr.position(),
|
ASTNode::Expr(expr) => expr.position(),
|
||||||
|
@ -440,7 +440,7 @@ impl Default for Expr {
|
|||||||
|
|
||||||
impl fmt::Debug for Expr {
|
impl fmt::Debug for Expr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut display_pos = self.start_position();
|
let mut display_pos = format!(" @ {:?}", self.start_position());
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::DynamicConstant(value, ..) => write!(f, "{:?}", value),
|
Self::DynamicConstant(value, ..) => write!(f, "{:?}", value),
|
||||||
@ -470,24 +470,34 @@ impl fmt::Debug for Expr {
|
|||||||
f.write_str("Variable(")?;
|
f.write_str("Variable(")?;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if let Some((.., ref namespace)) = x.1 {
|
if let Some((ref namespace, ..)) = x.1 {
|
||||||
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?
|
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?;
|
||||||
|
let pos = namespace.position();
|
||||||
|
if !pos.is_none() {
|
||||||
|
display_pos = format!(" @ {:?}", pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f.write_str(&x.2)?;
|
f.write_str(&x.2)?;
|
||||||
if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
||||||
write!(f, " #{}", n)?
|
write!(f, " #{}", n)?;
|
||||||
}
|
}
|
||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
Self::Property(x, ..) => write!(f, "Property({})", x.2),
|
Self::Property(x, ..) => write!(f, "Property({})", x.2),
|
||||||
Self::Stack(x, ..) => write!(f, "ConstantArg#{}", x),
|
Self::Stack(x, ..) => write!(f, "ConstantArg[{}]", x),
|
||||||
Self::Stmt(x) => {
|
Self::Stmt(x) => {
|
||||||
|
let pos = x.span();
|
||||||
|
if !pos.is_none() {
|
||||||
|
display_pos = format!(" @ {:?}", pos);
|
||||||
|
}
|
||||||
f.write_str("ExprStmtBlock")?;
|
f.write_str("ExprStmtBlock")?;
|
||||||
f.debug_list().entries(x.iter()).finish()
|
f.debug_list().entries(x.iter()).finish()
|
||||||
}
|
}
|
||||||
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
||||||
Self::Index(x, term, pos) => {
|
Self::Index(x, term, pos) => {
|
||||||
display_pos = *pos;
|
if !pos.is_none() {
|
||||||
|
display_pos = format!(" @ {:?}", pos);
|
||||||
|
}
|
||||||
|
|
||||||
f.debug_struct("Index")
|
f.debug_struct("Index")
|
||||||
.field("lhs", &x.lhs)
|
.field("lhs", &x.lhs)
|
||||||
@ -506,7 +516,9 @@ impl fmt::Debug for Expr {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
display_pos = *pos;
|
if !pos.is_none() {
|
||||||
|
display_pos = format!(" @ {:?}", pos);
|
||||||
|
}
|
||||||
|
|
||||||
f.debug_struct(op_name)
|
f.debug_struct(op_name)
|
||||||
.field("lhs", &x.lhs)
|
.field("lhs", &x.lhs)
|
||||||
@ -516,7 +528,7 @@ impl fmt::Debug for Expr {
|
|||||||
Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(),
|
Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
display_pos.debug_print(f)
|
f.write_str(&display_pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,8 +720,16 @@ impl Expr {
|
|||||||
/// For a binary expression, this will be the left-most LHS instead of the operator.
|
/// For a binary expression, this will be the left-most LHS instead of the operator.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn start_position(&self) -> Position {
|
pub fn start_position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Self::Variable(.., x) => {
|
||||||
|
if let Some((ref namespace, ..)) = x.1 {
|
||||||
|
namespace.position()
|
||||||
|
} else {
|
||||||
|
self.position()
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::And(x, ..) | Self::Or(x, ..) | Self::Index(x, ..) | Self::Dot(x, ..) => {
|
Self::And(x, ..) | Self::Or(x, ..) | Self::Index(x, ..) | Self::Dot(x, ..) => {
|
||||||
x.lhs.start_position()
|
x.lhs.start_position()
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
|
use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
|
||||||
use crate::engine::KEYWORD_EVAL;
|
use crate::engine::KEYWORD_EVAL;
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::{Span, Token};
|
||||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -134,7 +134,7 @@ pub struct TryCatchBlock {
|
|||||||
/// _(internals)_ A scoped block of statements.
|
/// _(internals)_ A scoped block of statements.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Clone, Hash, Default)]
|
#[derive(Clone, Hash, Default)]
|
||||||
pub struct StmtBlock(StaticVec<Stmt>, (Position, Position));
|
pub struct StmtBlock(StaticVec<Stmt>, Span);
|
||||||
|
|
||||||
impl StmtBlock {
|
impl StmtBlock {
|
||||||
/// A [`StmtBlock`] that does not exist.
|
/// A [`StmtBlock`] that does not exist.
|
||||||
@ -149,13 +149,13 @@ impl StmtBlock {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let mut statements: StaticVec<_> = statements.into_iter().collect();
|
let mut statements: StaticVec<_> = statements.into_iter().collect();
|
||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
Self(statements, (start_pos, end_pos))
|
Self(statements, Span::new(start_pos, end_pos))
|
||||||
}
|
}
|
||||||
/// Create an empty [`StmtBlock`].
|
/// Create an empty [`StmtBlock`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn empty(pos: Position) -> Self {
|
pub const fn empty(pos: Position) -> Self {
|
||||||
Self(StaticVec::new_const(), (pos, pos))
|
Self(StaticVec::new_const(), Span::new(pos, pos))
|
||||||
}
|
}
|
||||||
/// Is this statements block empty?
|
/// Is this statements block empty?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -191,38 +191,34 @@ impl StmtBlock {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn position(&self) -> Position {
|
pub const fn position(&self) -> Position {
|
||||||
(self.1).0
|
(self.1).start()
|
||||||
}
|
}
|
||||||
/// Get the end position (location of the ending `}`) of this statements block.
|
/// Get the end position (location of the ending `}`) of this statements block.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn end_position(&self) -> Position {
|
pub const fn end_position(&self) -> Position {
|
||||||
(self.1).1
|
(self.1).end()
|
||||||
}
|
}
|
||||||
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block.
|
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn positions(&self) -> (Position, Position) {
|
pub const fn span(&self) -> Span {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block
|
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block
|
||||||
/// or a default.
|
/// or a default.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn positions_or_else(
|
pub const fn span_or_else(&self, def_start_pos: Position, def_end_pos: Position) -> Span {
|
||||||
&self,
|
Span::new(
|
||||||
def_start_pos: Position,
|
(self.1).start().or_else(def_start_pos),
|
||||||
def_end_pos: Position,
|
(self.1).end().or_else(def_end_pos),
|
||||||
) -> (Position, Position) {
|
|
||||||
(
|
|
||||||
(self.1).0.or_else(def_start_pos),
|
|
||||||
(self.1).1.or_else(def_end_pos),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Set the positions of this statements block.
|
/// Set the positions of this statements block.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_position(&mut self, start_pos: Position, end_pos: Position) {
|
pub fn set_position(&mut self, start_pos: Position, end_pos: Position) {
|
||||||
self.1 = (start_pos, end_pos);
|
self.1 = Span::new(start_pos, end_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,10 +256,8 @@ impl fmt::Debug for StmtBlock {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str("Block")?;
|
f.write_str("Block")?;
|
||||||
fmt::Debug::fmt(&self.0, f)?;
|
fmt::Debug::fmt(&self.0, f)?;
|
||||||
(self.1).0.debug_print(f)?;
|
if !self.1.is_none() {
|
||||||
#[cfg(not(feature = "no_position"))]
|
write!(f, " @ {:?}", self.1)?;
|
||||||
if !(self.1).1.is_none() {
|
|
||||||
write!(f, "-{:?}", (self.1).1)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -273,11 +267,11 @@ impl From<Stmt> for StmtBlock {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn from(stmt: Stmt) -> Self {
|
fn from(stmt: Stmt) -> Self {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
Stmt::Block(mut block, span) => Self(block.iter_mut().map(mem::take).collect(), span),
|
||||||
Stmt::Noop(pos) => Self(StaticVec::new_const(), (pos, pos)),
|
Stmt::Noop(pos) => Self(StaticVec::new_const(), Span::new(pos, pos)),
|
||||||
_ => {
|
_ => {
|
||||||
let pos = stmt.position();
|
let pos = stmt.position();
|
||||||
Self(vec![stmt].into(), (pos, Position::NONE))
|
Self(vec![stmt].into(), Span::new(pos, Position::NONE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,7 +338,7 @@ pub enum Stmt {
|
|||||||
/// function call forming one statement.
|
/// function call forming one statement.
|
||||||
FnCall(Box<FnCallExpr>, Position),
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
/// `{` stmt`;` ... `}`
|
/// `{` stmt`;` ... `}`
|
||||||
Block(Box<[Stmt]>, (Position, Position)),
|
Block(Box<[Stmt]>, Span),
|
||||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||||
TryCatch(Box<TryCatchBlock>, Position),
|
TryCatch(Box<TryCatchBlock>, Position),
|
||||||
/// [expression][Expr]
|
/// [expression][Expr]
|
||||||
@ -382,7 +376,7 @@ pub enum Stmt {
|
|||||||
/// This variant does not map to any language structure. It is currently only used only to
|
/// This variant does not map to any language structure. It is currently only used only to
|
||||||
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
|
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Share(crate::Identifier),
|
Share(crate::Identifier, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Stmt {
|
impl Default for Stmt {
|
||||||
@ -408,11 +402,10 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
/// Get the [position][Position] of this statement.
|
/// Get the [position][Position] of this statement.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Self::Noop(pos)
|
Self::Noop(pos)
|
||||||
| Self::BreakLoop(.., pos)
|
| Self::BreakLoop(.., pos)
|
||||||
| Self::Block(.., (pos, ..))
|
|
||||||
| Self::Assignment(.., pos)
|
| Self::Assignment(.., pos)
|
||||||
| Self::FnCall(.., pos)
|
| Self::FnCall(.., pos)
|
||||||
| Self::If(.., pos)
|
| Self::If(.., pos)
|
||||||
@ -424,6 +417,8 @@ impl Stmt {
|
|||||||
| Self::Var(.., pos)
|
| Self::Var(.., pos)
|
||||||
| Self::TryCatch(.., pos) => *pos,
|
| Self::TryCatch(.., pos) => *pos,
|
||||||
|
|
||||||
|
Self::Block(.., span) => span.start(),
|
||||||
|
|
||||||
Self::Expr(x) => x.start_position(),
|
Self::Expr(x) => x.start_position(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -432,7 +427,7 @@ impl Stmt {
|
|||||||
Self::Export(.., pos) => *pos,
|
Self::Export(.., pos) => *pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::Share(..) => Position::NONE,
|
Self::Share(.., pos) => *pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Override the [position][Position] of this statement.
|
/// Override the [position][Position] of this statement.
|
||||||
@ -440,7 +435,6 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Self::Noop(pos)
|
Self::Noop(pos)
|
||||||
| Self::BreakLoop(.., pos)
|
| Self::BreakLoop(.., pos)
|
||||||
| Self::Block(.., (pos, ..))
|
|
||||||
| Self::Assignment(.., pos)
|
| Self::Assignment(.., pos)
|
||||||
| Self::FnCall(.., pos)
|
| Self::FnCall(.., pos)
|
||||||
| Self::If(.., pos)
|
| Self::If(.., pos)
|
||||||
@ -452,6 +446,8 @@ impl Stmt {
|
|||||||
| Self::Var(.., pos)
|
| Self::Var(.., pos)
|
||||||
| Self::TryCatch(.., pos) => *pos = new_pos,
|
| Self::TryCatch(.., pos) => *pos = new_pos,
|
||||||
|
|
||||||
|
Self::Block(.., span) => *span = Span::new(new_pos, span.end()),
|
||||||
|
|
||||||
Self::Expr(x) => {
|
Self::Expr(x) => {
|
||||||
x.set_position(new_pos);
|
x.set_position(new_pos);
|
||||||
}
|
}
|
||||||
@ -462,7 +458,7 @@ impl Stmt {
|
|||||||
Self::Export(.., pos) => *pos = new_pos,
|
Self::Export(.., pos) => *pos = new_pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::Share(..) => (),
|
Self::Share(.., pos) => *pos = new_pos,
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -10,28 +10,45 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Pretty-print source line.
|
/// Pretty-print source line.
|
||||||
fn print_source(lines: &[String], pos: Position, offset: usize) {
|
fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, usize)) {
|
||||||
let line_no = if lines.len() > 1 {
|
|
||||||
if pos.is_none() {
|
|
||||||
"".to_string()
|
|
||||||
} else {
|
|
||||||
format!("{}: ", pos.line().unwrap())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Print error position
|
|
||||||
if pos.is_none() {
|
if pos.is_none() {
|
||||||
// No position
|
// No position
|
||||||
println!();
|
println!();
|
||||||
} else {
|
return;
|
||||||
// Specific position - print line text
|
}
|
||||||
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
|
|
||||||
|
|
||||||
// Display position marker
|
let line = pos.line().unwrap() - 1;
|
||||||
|
let start = if line >= window.0 { line - window.0 } else { 0 };
|
||||||
|
let end = usize::min(line + window.1, lines.len() - 1);
|
||||||
|
let line_no_len = format!("{}", end).len();
|
||||||
|
|
||||||
|
// Print error position
|
||||||
|
if start >= end {
|
||||||
|
println!("{}: {}", start + 1, lines[start]);
|
||||||
if let Some(pos) = pos.position() {
|
if let Some(pos) = pos.position() {
|
||||||
println!("{0:>1$}", "^", line_no.len() + pos + offset);
|
println!("{0:>1$}", "^", pos + offset + line_no_len + 2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for n in start..=end {
|
||||||
|
let marker = if n == line { "> " } else { " " };
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{0}{1}{2:>3$}{5}│ {0}{4}{5}",
|
||||||
|
if n == line { "\x1b[33m" } else { "" },
|
||||||
|
marker,
|
||||||
|
n + 1,
|
||||||
|
line_no_len,
|
||||||
|
lines[n],
|
||||||
|
if n == line { "\x1b[39m" } else { "" },
|
||||||
|
);
|
||||||
|
|
||||||
|
if n == line {
|
||||||
|
if let Some(pos) = pos.position() {
|
||||||
|
let shift = offset + line_no_len + marker.len() + 2;
|
||||||
|
|
||||||
|
println!("{0:>1$}{2:>3$}", "│ ", shift, "\x1b[36m^\x1b[39m", pos + 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,7 +57,8 @@ fn print_current_source(
|
|||||||
context: &mut rhai::EvalContext,
|
context: &mut rhai::EvalContext,
|
||||||
source: Option<&str>,
|
source: Option<&str>,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
lines: &Vec<String>,
|
lines: &[String],
|
||||||
|
window: (usize, usize),
|
||||||
) {
|
) {
|
||||||
let current_source = &mut *context
|
let current_source = &mut *context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -58,7 +76,7 @@ fn print_current_source(
|
|||||||
println!("{} @ {:?}", src, pos);
|
println!("{} @ {:?}", src, pos);
|
||||||
} else {
|
} else {
|
||||||
// Print the current source line
|
// Print the current source line
|
||||||
print_source(lines, pos, 0);
|
print_source(lines, pos, 0, window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,11 +119,13 @@ fn print_debug_help() {
|
|||||||
println!("quit, q, exit, kill => quit");
|
println!("quit, q, exit, kill => quit");
|
||||||
println!("scope => print the scope");
|
println!("scope => print the scope");
|
||||||
println!("print, p => print all variables de-duplicated");
|
println!("print, p => print all variables de-duplicated");
|
||||||
|
println!("print/p this => print the 'this' pointer");
|
||||||
println!("print/p <variable> => print the current value of a variable");
|
println!("print/p <variable> => print the current value of a variable");
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
println!("imports => print all imported modules");
|
println!("imports => print all imported modules");
|
||||||
println!("node => print the current AST node");
|
println!("node => print the current AST node");
|
||||||
println!("list, l => print the current source line");
|
println!("list, l => print the current source line");
|
||||||
|
println!("list/l <line#> => print a source line");
|
||||||
println!("backtrace, bt => print the current call-stack");
|
println!("backtrace, bt => print the current call-stack");
|
||||||
println!("info break, i b => print all break-points");
|
println!("info break, i b => print all break-points");
|
||||||
println!("enable/en <bp#> => enable a break-point");
|
println!("enable/en <bp#> => enable a break-point");
|
||||||
@ -122,17 +142,19 @@ fn print_debug_help() {
|
|||||||
println!(
|
println!(
|
||||||
"break/b <func> <#args> => set a new break-point for a function call with #args arguments"
|
"break/b <func> <#args> => set a new break-point for a function call with #args arguments"
|
||||||
);
|
);
|
||||||
println!("throw [message] => throw an exception (message optional)");
|
println!("throw => throw a runtime exception");
|
||||||
|
println!("throw <message...> => throw an exception with string data");
|
||||||
|
println!("throw <#> => throw an exception with numeric data");
|
||||||
println!("run, r => restart the script evaluation from beginning");
|
println!("run, r => restart the script evaluation from beginning");
|
||||||
println!("step, s => go to the next expression, diving into functions");
|
println!("step, s => go to the next expression, diving into functions");
|
||||||
println!("over => go to the next expression, skipping oer functions");
|
println!("over, o => go to the next expression, skipping oer functions");
|
||||||
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
||||||
println!("finish, f => continue until the end of the current function call");
|
println!("finish, f => continue until the end of the current function call");
|
||||||
println!("continue, c => continue normal execution");
|
println!("continue, c => continue normal execution");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the scope.
|
/// Display the current scope.
|
||||||
fn print_scope(scope: &Scope, dedup: bool) {
|
fn print_scope(scope: &Scope, dedup: bool) {
|
||||||
let flattened_clone;
|
let flattened_clone;
|
||||||
let scope = if dedup {
|
let scope = if dedup {
|
||||||
@ -167,27 +189,19 @@ fn print_scope(scope: &Scope, dedup: bool) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
// Load script to debug.
|
||||||
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION"));
|
fn load_script(engine: &Engine) -> (rhai::AST, String) {
|
||||||
println!("{}", title);
|
|
||||||
println!("{0:=<1$}", "", title.len());
|
|
||||||
|
|
||||||
// Initialize scripting engine
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
let mut script = String::new();
|
|
||||||
let main_ast;
|
|
||||||
|
|
||||||
{
|
|
||||||
// Load init scripts
|
|
||||||
if let Some(filename) = env::args().skip(1).next() {
|
if let Some(filename) = env::args().skip(1).next() {
|
||||||
|
let mut contents = String::new();
|
||||||
|
|
||||||
let filename = match Path::new(&filename).canonicalize() {
|
let filename = match Path::new(&filename).canonicalize() {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error script file path: {}\n{}", filename, err);
|
eprintln!(
|
||||||
|
"\x1b[31mError script file path: {}\n{}\x1b[39m",
|
||||||
|
filename, err
|
||||||
|
);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
@ -201,7 +215,7 @@ fn main() {
|
|||||||
let mut f = match File::open(&filename) {
|
let mut f = match File::open(&filename) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Error reading script file: {}\n{}",
|
"\x1b[31mError reading script file: {}\n{}\x1b[39m",
|
||||||
filename.to_string_lossy(),
|
filename.to_string_lossy(),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
@ -210,7 +224,7 @@ fn main() {
|
|||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = f.read_to_string(&mut script) {
|
if let Err(err) = f.read_to_string(&mut contents) {
|
||||||
println!(
|
println!(
|
||||||
"Error reading script file: {}\n{}",
|
"Error reading script file: {}\n{}",
|
||||||
filename.to_string_lossy(),
|
filename.to_string_lossy(),
|
||||||
@ -219,40 +233,43 @@ fn main() {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let script = if script.starts_with("#!") {
|
let script = if contents.starts_with("#!") {
|
||||||
// Skip shebang
|
// Skip shebang
|
||||||
&script[script.find('\n').unwrap_or(0)..]
|
&contents[contents.find('\n').unwrap_or(0)..]
|
||||||
} else {
|
} else {
|
||||||
&script[..]
|
&contents[..]
|
||||||
};
|
};
|
||||||
|
|
||||||
main_ast = match engine
|
let ast = match engine
|
||||||
.compile(&script)
|
.compile(script)
|
||||||
.map_err(Into::<Box<EvalAltResult>>::into)
|
.map_err(Into::<Box<EvalAltResult>>::into)
|
||||||
{
|
{
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
print_error(&script, *err);
|
print_error(script, *err);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
Ok(ast) => ast,
|
Ok(ast) => ast,
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("Script '{}' loaded.", filename.to_string_lossy());
|
println!("Script '{}' loaded.", filename.to_string_lossy());
|
||||||
println!();
|
|
||||||
|
(ast, contents)
|
||||||
} else {
|
} else {
|
||||||
eprintln!("No script file specified.");
|
eprintln!("\x1b[31mNo script file specified.\x1b[39m");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook up debugger
|
// Main callback for debugging.
|
||||||
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
fn debug_callback(
|
||||||
|
context: &mut rhai::EvalContext,
|
||||||
engine.register_debugger(
|
event: DebuggerEvent,
|
||||||
// Store the current source in the debugger state
|
node: rhai::ASTNode,
|
||||||
|| "".into(),
|
source: Option<&str>,
|
||||||
// Main debugging interface
|
pos: Position,
|
||||||
move |context, event, node, source, pos| {
|
lines: &[String],
|
||||||
|
) -> Result<DebuggerCommand, Box<EvalAltResult>> {
|
||||||
|
// Check event
|
||||||
match event {
|
match event {
|
||||||
DebuggerEvent::Step => (),
|
DebuggerEvent::Step => (),
|
||||||
DebuggerEvent::BreakPoint(n) => {
|
DebuggerEvent::BreakPoint(n) => {
|
||||||
@ -298,13 +315,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print current source line
|
// Print current source line
|
||||||
print_current_source(context, source, pos, &lines);
|
print_current_source(context, source, pos, lines, (0, 0));
|
||||||
|
|
||||||
// Read stdin for commands
|
// Read stdin for commands
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("rhai-dbg> ");
|
print!("rhai-dbg> ");
|
||||||
|
|
||||||
stdout().flush().expect("couldn't flush stdout");
|
stdout().flush().expect("couldn't flush stdout");
|
||||||
|
|
||||||
input.clear();
|
input.clear();
|
||||||
@ -317,36 +335,59 @@ fn main() {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.as_slice()
|
.as_slice()
|
||||||
{
|
{
|
||||||
["help" | "h", ..] => print_debug_help(),
|
["help" | "h"] => print_debug_help(),
|
||||||
["exit" | "quit" | "q" | "kill", ..] => {
|
["exit" | "quit" | "q" | "kill", ..] => {
|
||||||
println!("Script terminated. Bye!");
|
println!("Script terminated. Bye!");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
["node", ..] => {
|
["node"] => {
|
||||||
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
if pos.is_none() {
|
||||||
|
println!("{:?}", node);
|
||||||
|
} else if let Some(source) = source {
|
||||||
|
println!("{:?} {} @ {:?}", node, source, pos);
|
||||||
|
} else {
|
||||||
|
println!("{:?} @ {:?}", node, pos);
|
||||||
|
}
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
["list" | "l", ..] => print_current_source(context, source, pos, &lines),
|
["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)),
|
||||||
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
|
["list" | "l", n] if n.parse::<usize>().is_ok() => {
|
||||||
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
|
let num = n.parse::<usize>().unwrap();
|
||||||
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
|
if num <= 0 || num > lines.len() {
|
||||||
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num);
|
||||||
["next" | "n", ..] => break Ok(DebuggerCommand::Next),
|
|
||||||
["scope", ..] => print_scope(context.scope(), false),
|
|
||||||
["print" | "p", var_name, ..] => {
|
|
||||||
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
|
||||||
if value.is::<()>() {
|
|
||||||
println!("=> ()");
|
|
||||||
} else {
|
} else {
|
||||||
println!("=> {}", value);
|
let pos = Position::new(num as u16, 0);
|
||||||
|
print_current_source(context, source, pos, &lines, (3, 6));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
["continue" | "c"] => break Ok(DebuggerCommand::Continue),
|
||||||
|
["finish" | "f"] => break Ok(DebuggerCommand::FunctionExit),
|
||||||
|
[] | ["step" | "s"] => break Ok(DebuggerCommand::StepInto),
|
||||||
|
["over" | "o"] => break Ok(DebuggerCommand::StepOver),
|
||||||
|
["next" | "n"] => break Ok(DebuggerCommand::Next),
|
||||||
|
["scope"] => print_scope(context.scope(), false),
|
||||||
|
["print" | "p", "this"] => {
|
||||||
|
if let Some(value) = context.this_ptr() {
|
||||||
|
println!("=> {:?}", value);
|
||||||
|
} else {
|
||||||
|
println!("'this' pointer is unbound.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["print" | "p", var_name] => {
|
||||||
|
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
||||||
|
println!("=> {:?}", value);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Variable not found: {}", var_name);
|
eprintln!("Variable not found: {}", var_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["print" | "p"] => print_scope(context.scope(), true),
|
["print" | "p"] => {
|
||||||
|
print_scope(context.scope(), true);
|
||||||
|
if let Some(value) = context.this_ptr() {
|
||||||
|
println!("this = {:?}", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
["imports", ..] => {
|
["imports"] => {
|
||||||
for (i, (name, module)) in context
|
for (i, (name, module)) in context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.scan_imports_raw()
|
.scan_imports_raw()
|
||||||
@ -363,7 +404,7 @@ fn main() {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
["backtrace" | "bt", ..] => {
|
["backtrace" | "bt"] => {
|
||||||
for frame in context
|
for frame in context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.debugger
|
.debugger
|
||||||
@ -374,7 +415,7 @@ fn main() {
|
|||||||
println!("{}", frame)
|
println!("{}", frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
|
["info" | "i", "break" | "b"] => Iterator::for_each(
|
||||||
context
|
context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.debugger
|
.debugger
|
||||||
@ -386,12 +427,12 @@ fn main() {
|
|||||||
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
|
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
|
||||||
let line_num = format!("[{}] line ", i + 1);
|
let line_num = format!("[{}] line ", i + 1);
|
||||||
print!("{}", line_num);
|
print!("{}", line_num);
|
||||||
print_source(&lines, *pos, line_num.len());
|
print_source(&lines, *pos, line_num.len(), (0, 0));
|
||||||
}
|
}
|
||||||
_ => println!("[{}] {}", i + 1, bp),
|
_ => println!("[{}] {}", i + 1, bp),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
["enable" | "en", n, ..] => {
|
["enable" | "en", n] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -408,13 +449,13 @@ fn main() {
|
|||||||
.enable(true);
|
.enable(true);
|
||||||
println!("Break-point #{} enabled.", n)
|
println!("Break-point #{} enabled.", n)
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid break-point: {}", n);
|
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["disable" | "dis", n, ..] => {
|
["disable" | "dis", n] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -431,13 +472,13 @@ fn main() {
|
|||||||
.enable(false);
|
.enable(false);
|
||||||
println!("Break-point #{} disabled.", n)
|
println!("Break-point #{} disabled.", n)
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid break-point: {}", n);
|
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["delete" | "d", n, ..] => {
|
["delete" | "d", n] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -452,13 +493,13 @@ fn main() {
|
|||||||
.remove(n - 1);
|
.remove(n - 1);
|
||||||
println!("Break-point #{} deleted.", n)
|
println!("Break-point #{} deleted.", n)
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid break-point: {}", n);
|
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["delete" | "d", ..] => {
|
["delete" | "d"] => {
|
||||||
context
|
context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
.debugger
|
.debugger
|
||||||
@ -466,7 +507,7 @@ fn main() {
|
|||||||
.clear();
|
.clear();
|
||||||
println!("All break-points deleted.");
|
println!("All break-points deleted.");
|
||||||
}
|
}
|
||||||
["break" | "b", fn_name, args, ..] => {
|
["break" | "b", fn_name, args] => {
|
||||||
if let Ok(args) = args.parse::<usize>() {
|
if let Ok(args) = args.parse::<usize>() {
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
||||||
name: fn_name.trim().into(),
|
name: fn_name.trim().into(),
|
||||||
@ -480,7 +521,7 @@ fn main() {
|
|||||||
.break_points_mut()
|
.break_points_mut()
|
||||||
.push(bp);
|
.push(bp);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid number of arguments: '{}'", args);
|
eprintln!("\x1b[31mInvalid number of arguments: '{}'\x1b[39m", args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Property name
|
// Property name
|
||||||
@ -520,7 +561,7 @@ fn main() {
|
|||||||
.break_points_mut()
|
.break_points_mut()
|
||||||
.push(bp);
|
.push(bp);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Invalid line number: {}", n);
|
eprintln!("\x1b[31mInvalid line number: '{}'\x1b[39m", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Function name parameter
|
// Function name parameter
|
||||||
@ -550,9 +591,7 @@ fn main() {
|
|||||||
.break_points_mut()
|
.break_points_mut()
|
||||||
.push(bp);
|
.push(bp);
|
||||||
}
|
}
|
||||||
["throw"] => {
|
["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()),
|
||||||
break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
|
|
||||||
}
|
|
||||||
["throw", num] if num.trim().parse::<INT>().is_ok() => {
|
["throw", num] if num.trim().parse::<INT>().is_ok() => {
|
||||||
let value = num.trim().parse::<INT>().unwrap().into();
|
let value = num.trim().parse::<INT>().unwrap().into();
|
||||||
break Err(EvalAltResult::ErrorRuntime(value, pos).into());
|
break Err(EvalAltResult::ErrorRuntime(value, pos).into());
|
||||||
@ -566,15 +605,42 @@ fn main() {
|
|||||||
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
||||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||||
}
|
}
|
||||||
["run" | "r", ..] => {
|
["run" | "r"] => {
|
||||||
println!("Restarting script...");
|
println!("Restarting script...");
|
||||||
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
||||||
}
|
}
|
||||||
[cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd),
|
_ => eprintln!(
|
||||||
|
"\x1b[31mInvalid debugger command: '{}'\x1b[39m",
|
||||||
|
input.trim()
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Err(err) => panic!("input error: {}", err),
|
Err(err) => panic!("input error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION"));
|
||||||
|
println!("{}", title);
|
||||||
|
println!("{0:=<1$}", "", title.len());
|
||||||
|
|
||||||
|
// Initialize scripting engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||||
|
|
||||||
|
let (ast, script) = load_script(&engine);
|
||||||
|
|
||||||
|
// Hook up debugger
|
||||||
|
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
engine.register_debugger(
|
||||||
|
// Store the current source in the debugger state
|
||||||
|
|| "".into(),
|
||||||
|
// Main debugging interface
|
||||||
|
move |context, event, node, source, pos| {
|
||||||
|
debug_callback(context, event, node, source, pos, &lines)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -587,10 +653,11 @@ fn main() {
|
|||||||
engine.set_module_resolver(resolver);
|
engine.set_module_resolver(resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
print_debug_help();
|
println!("Type 'help' for commands list.");
|
||||||
|
println!();
|
||||||
|
|
||||||
// Evaluate
|
// Evaluate
|
||||||
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &main_ast) {
|
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &ast) {
|
||||||
match *err {
|
match *err {
|
||||||
// Loop back to restart
|
// Loop back to restart
|
||||||
EvalAltResult::ErrorTerminated(..) => (),
|
EvalAltResult::ErrorTerminated(..) => (),
|
||||||
|
@ -10,7 +10,7 @@ const HISTORY_FILE: &str = ".rhai-repl-history";
|
|||||||
|
|
||||||
/// Pretty-print error.
|
/// Pretty-print error.
|
||||||
fn print_error(input: &str, mut err: EvalAltResult) {
|
fn print_error(input: &str, mut err: EvalAltResult) {
|
||||||
let lines: Vec<_> = input.trim().split('\n').collect();
|
let lines: Vec<_> = input.split('\n').collect();
|
||||||
let pos = err.take_position();
|
let pos = err.take_position();
|
||||||
|
|
||||||
let line_no = if lines.len() > 1 {
|
let line_no = if lines.len() > 1 {
|
||||||
@ -60,6 +60,7 @@ fn print_help() {
|
|||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
println!("json => output all functions in JSON format");
|
println!("json => output all functions in JSON format");
|
||||||
println!("ast => print the last AST (optimized)");
|
println!("ast => print the last AST (optimized)");
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
println!("astu => print the last raw, un-optimized AST");
|
println!("astu => print the last raw, un-optimized AST");
|
||||||
println!();
|
println!();
|
||||||
println!("press Ctrl-Enter or end a line with `\\`");
|
println!("press Ctrl-Enter or end a line with `\\`");
|
||||||
@ -84,6 +85,7 @@ fn print_keys() {
|
|||||||
println!("Ctrl-R => reverse search history");
|
println!("Ctrl-R => reverse search history");
|
||||||
println!(" (Ctrl-S forward, Ctrl-G cancel)");
|
println!(" (Ctrl-S forward, Ctrl-G cancel)");
|
||||||
println!("Ctrl-L => clear screen");
|
println!("Ctrl-L => clear screen");
|
||||||
|
#[cfg(target_family = "windows")]
|
||||||
println!("Escape => clear all input");
|
println!("Escape => clear all input");
|
||||||
println!("Ctrl-C => exit");
|
println!("Ctrl-C => exit");
|
||||||
println!("Ctrl-D => EOF (when line empty)");
|
println!("Ctrl-D => EOF (when line empty)");
|
||||||
@ -94,7 +96,10 @@ fn print_keys() {
|
|||||||
println!("Ctrl-T => transpose characters");
|
println!("Ctrl-T => transpose characters");
|
||||||
println!("Ctrl-V => insert special character");
|
println!("Ctrl-V => insert special character");
|
||||||
println!("Ctrl-Y => paste yank");
|
println!("Ctrl-Y => paste yank");
|
||||||
println!("Ctrl-Z => suspend (Unix), undo (Windows)");
|
#[cfg(target_family = "unix")]
|
||||||
|
println!("Ctrl-Z => suspend");
|
||||||
|
#[cfg(target_family = "windows")]
|
||||||
|
println!("Ctrl-Z => undo");
|
||||||
println!("Ctrl-_ => undo");
|
println!("Ctrl-_ => undo");
|
||||||
println!("Enter => run code");
|
println!("Enter => run code");
|
||||||
println!("Shift-Ctrl-Enter => continue to next line");
|
println!("Shift-Ctrl-Enter => continue to next line");
|
||||||
@ -311,6 +316,7 @@ fn main() {
|
|||||||
let mut history_offset = 1;
|
let mut history_offset = 1;
|
||||||
|
|
||||||
let mut main_ast = AST::empty();
|
let mut main_ast = AST::empty();
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
let mut ast_u = AST::empty();
|
let mut ast_u = AST::empty();
|
||||||
let mut ast = AST::empty();
|
let mut ast = AST::empty();
|
||||||
|
|
||||||
@ -344,13 +350,13 @@ fn main() {
|
|||||||
// Line continuation
|
// Line continuation
|
||||||
Ok(mut line) if line.ends_with("\\") => {
|
Ok(mut line) if line.ends_with("\\") => {
|
||||||
line.pop();
|
line.pop();
|
||||||
input += line.trim_end();
|
input += &line;
|
||||||
input.push('\n');
|
input.push('\n');
|
||||||
}
|
}
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
input += line.trim_end();
|
input += &line;
|
||||||
if !input.is_empty() && !input.starts_with('!') && input.trim() != "history"
|
let cmd = input.trim();
|
||||||
{
|
if !cmd.is_empty() && !cmd.starts_with('!') && cmd.trim() != "history" {
|
||||||
if rl.add_history_entry(input.clone()) {
|
if rl.add_history_entry(input.clone()) {
|
||||||
history_offset += 1;
|
history_offset += 1;
|
||||||
}
|
}
|
||||||
@ -368,14 +374,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let script = input.trim();
|
let cmd = input.trim();
|
||||||
|
|
||||||
if script.is_empty() {
|
if cmd.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement standard commands
|
// Implement standard commands
|
||||||
match script {
|
match cmd {
|
||||||
"help" => {
|
"help" => {
|
||||||
print_help();
|
print_help();
|
||||||
continue;
|
continue;
|
||||||
@ -429,6 +435,7 @@ fn main() {
|
|||||||
print_scope(&scope);
|
print_scope(&scope);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
"astu" => {
|
"astu" => {
|
||||||
// print the last un-optimized AST
|
// print the last un-optimized AST
|
||||||
println!("{:#?}\n", ast_u);
|
println!("{:#?}\n", ast_u);
|
||||||
@ -473,8 +480,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ if script.starts_with("!?") => {
|
_ if cmd.starts_with("!?") => {
|
||||||
let text = script[2..].trim();
|
let text = cmd[2..].trim();
|
||||||
if let Some((n, line)) = rl
|
if let Some((n, line)) = rl
|
||||||
.history()
|
.history()
|
||||||
.iter()
|
.iter()
|
||||||
@ -489,8 +496,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ if script.starts_with('!') => {
|
_ if cmd.starts_with('!') => {
|
||||||
if let Ok(num) = script[1..].parse::<usize>() {
|
if let Ok(num) = cmd[1..].parse::<usize>() {
|
||||||
if num >= history_offset {
|
if num >= history_offset {
|
||||||
if let Some(line) = rl.history().get(num - history_offset) {
|
if let Some(line) = rl.history().get(num - history_offset) {
|
||||||
replacement = Some(line.clone());
|
replacement = Some(line.clone());
|
||||||
@ -499,7 +506,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let prefix = script[1..].trim();
|
let prefix = cmd[1..].trim();
|
||||||
if let Some((n, line)) = rl
|
if let Some((n, line)) = rl
|
||||||
.history()
|
.history()
|
||||||
.iter()
|
.iter()
|
||||||
@ -512,20 +519,20 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!("History line not found: {}", &script[1..]);
|
eprintln!("History line not found: {}", &cmd[1..]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match engine
|
match engine
|
||||||
.compile_with_scope(&scope, &script)
|
.compile_with_scope(&scope, &input)
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
ast_u = r.clone();
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
{
|
{
|
||||||
|
ast_u = r.clone();
|
||||||
|
|
||||||
ast = engine.optimize_ast(&scope, r, optimize_level);
|
ast = engine.optimize_ast(&scope, r, optimize_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,7 @@
|
|||||||
use super::{EvalState, GlobalRuntimeState, Target};
|
use super::{EvalState, GlobalRuntimeState, Target};
|
||||||
use crate::ast::{Expr, OpAssignment};
|
use crate::ast::{Expr, OpAssignment};
|
||||||
use crate::types::dynamic::Union;
|
use crate::types::dynamic::Union;
|
||||||
use crate::{
|
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
|
||||||
Dynamic, Engine, Module, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR,
|
|
||||||
};
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -149,7 +147,6 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ChainType::Indexing => {
|
ChainType::Indexing => {
|
||||||
let pos = rhs.start_position();
|
let pos = rhs.start_position();
|
||||||
let root_pos = idx_val.position();
|
|
||||||
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
||||||
|
|
||||||
match rhs {
|
match rhs {
|
||||||
@ -185,20 +182,15 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(mut new_val) = try_setter {
|
if let Some(mut new_val) = try_setter {
|
||||||
// Try to call index setter if value is changed
|
// Try to call index setter if value is changed
|
||||||
let hash_set =
|
let idx = &mut idx_val_for_setter;
|
||||||
crate::ast::FnCallHashes::from_native(global.hash_idx_set());
|
let new_val = &mut new_val;
|
||||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
self.call_indexer_set(
|
||||||
let fn_name = crate::engine::FN_IDX_SET;
|
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||||
|
)
|
||||||
if let Err(err) = self.exec_fn_call(
|
.or_else(|idx_err| match *idx_err {
|
||||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)),
|
||||||
true, root_pos, level,
|
_ => Err(idx_err),
|
||||||
) {
|
})?;
|
||||||
// Just ignore if there is no index setter
|
|
||||||
if !matches!(*err, ERR::ErrorFunctionNotFound(..)) {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@ -234,14 +226,10 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(mut new_val) = try_setter {
|
if let Some(mut new_val) = try_setter {
|
||||||
// Try to call index setter
|
// Try to call index setter
|
||||||
let hash_set =
|
let idx = &mut idx_val_for_setter;
|
||||||
crate::ast::FnCallHashes::from_native(global.hash_idx_set());
|
let new_val = &mut new_val;
|
||||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
self.call_indexer_set(
|
||||||
let fn_name = crate::engine::FN_IDX_SET;
|
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||||
|
|
||||||
self.exec_fn_call(
|
|
||||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
|
||||||
true, root_pos, level,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,12 +329,10 @@ impl Engine {
|
|||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
ERR::ErrorDotExpr(..) => {
|
ERR::ErrorDotExpr(..) => {
|
||||||
let prop = name.into();
|
let mut prop = name.into();
|
||||||
self.get_indexed_mut(
|
self.call_indexer_get(
|
||||||
global, state, lib, target, prop, *pos, false, true,
|
global, state, lib, target, &mut prop, level,
|
||||||
level,
|
|
||||||
)
|
)
|
||||||
.map(|v| (v.take_or_clone(), false))
|
|
||||||
.map_err(
|
.map_err(
|
||||||
|idx_err| match *idx_err {
|
|idx_err| match *idx_err {
|
||||||
ERR::ErrorIndexingType(..) => err,
|
ERR::ErrorIndexingType(..) => err,
|
||||||
@ -382,15 +368,10 @@ impl Engine {
|
|||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
ERR::ErrorDotExpr(..) => {
|
ERR::ErrorDotExpr(..) => {
|
||||||
let args = &mut [target, &mut name.into(), &mut new_val];
|
let idx = &mut name.into();
|
||||||
let fn_name = crate::engine::FN_IDX_SET;
|
let new_val = &mut new_val;
|
||||||
let hash_set =
|
self.call_indexer_set(
|
||||||
crate::ast::FnCallHashes::from_native(global.hash_idx_set());
|
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||||
let pos = Position::NONE;
|
|
||||||
|
|
||||||
self.exec_fn_call(
|
|
||||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
|
||||||
true, pos, level,
|
|
||||||
)
|
)
|
||||||
.map_err(
|
.map_err(
|
||||||
|idx_err| match *idx_err {
|
|idx_err| match *idx_err {
|
||||||
@ -418,11 +399,10 @@ impl Engine {
|
|||||||
|err| match *err {
|
|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
ERR::ErrorDotExpr(..) => {
|
ERR::ErrorDotExpr(..) => {
|
||||||
let prop = name.into();
|
let mut prop = name.into();
|
||||||
self.get_indexed_mut(
|
self.call_indexer_get(
|
||||||
global, state, lib, target, prop, *pos, false, true, level,
|
global, state, lib, target, &mut prop, level,
|
||||||
)
|
)
|
||||||
.map(|v| (v.take_or_clone(), false))
|
|
||||||
.map_err(|idx_err| {
|
.map_err(|idx_err| {
|
||||||
match *idx_err {
|
match *idx_err {
|
||||||
ERR::ErrorIndexingType(..) => err,
|
ERR::ErrorIndexingType(..) => err,
|
||||||
@ -517,12 +497,10 @@ impl Engine {
|
|||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
ERR::ErrorDotExpr(..) => {
|
ERR::ErrorDotExpr(..) => {
|
||||||
let prop = name.into();
|
let mut prop = name.into();
|
||||||
self.get_indexed_mut(
|
self.call_indexer_get(
|
||||||
global, state, lib, target, prop, pos, false, true,
|
global, state, lib, target, &mut prop, level,
|
||||||
level,
|
|
||||||
)
|
)
|
||||||
.map(|v| (v.take_or_clone(), false))
|
|
||||||
.map_err(
|
.map_err(
|
||||||
|idx_err| match *idx_err {
|
|idx_err| match *idx_err {
|
||||||
ERR::ErrorIndexingType(..) => err,
|
ERR::ErrorIndexingType(..) => err,
|
||||||
@ -566,21 +544,16 @@ impl Engine {
|
|||||||
|err| match *err {
|
|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
ERR::ErrorDotExpr(..) => {
|
ERR::ErrorDotExpr(..) => {
|
||||||
let args =
|
let idx = &mut name.into();
|
||||||
&mut [target.as_mut(), &mut name.into(), val];
|
let new_val = val;
|
||||||
let fn_name = crate::engine::FN_IDX_SET;
|
self.call_indexer_set(
|
||||||
let hash_set =
|
global, state, lib, target, idx, new_val,
|
||||||
crate::ast::FnCallHashes::from_native(
|
is_ref_mut, level,
|
||||||
global.hash_idx_set(),
|
|
||||||
);
|
|
||||||
self.exec_fn_call(
|
|
||||||
None, global, state, lib, fn_name, hash_set,
|
|
||||||
args, is_ref_mut, true, pos, level,
|
|
||||||
)
|
)
|
||||||
.or_else(|idx_err| match *idx_err {
|
.or_else(|idx_err| match *idx_err {
|
||||||
|
// If there is no setter, no need to feed it
|
||||||
|
// back because the property is read-only
|
||||||
ERR::ErrorIndexingType(..) => {
|
ERR::ErrorIndexingType(..) => {
|
||||||
// If there is no setter, no need to feed it back because
|
|
||||||
// the property is read-only
|
|
||||||
Ok((Dynamic::UNIT, false))
|
Ok((Dynamic::UNIT, false))
|
||||||
}
|
}
|
||||||
_ => Err(idx_err),
|
_ => Err(idx_err),
|
||||||
@ -743,7 +716,7 @@ impl Engine {
|
|||||||
pos = arg_pos;
|
pos = arg_pos;
|
||||||
}
|
}
|
||||||
values.push(value.flatten());
|
values.push(value.flatten());
|
||||||
Ok::<_, RhaiError>((values, pos))
|
Ok::<_, crate::RhaiError>((values, pos))
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -789,7 +762,7 @@ impl Engine {
|
|||||||
pos = arg_pos
|
pos = arg_pos
|
||||||
}
|
}
|
||||||
values.push(value.flatten());
|
values.push(value.flatten());
|
||||||
Ok::<_, RhaiError>((values, pos))
|
Ok::<_, crate::RhaiError>((values, pos))
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
super::ChainArgument::from_fn_call_args(values, pos)
|
super::ChainArgument::from_fn_call_args(values, pos)
|
||||||
@ -842,6 +815,52 @@ impl Engine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call a get indexer.
|
||||||
|
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
||||||
|
#[inline(always)]
|
||||||
|
fn call_indexer_get(
|
||||||
|
&self,
|
||||||
|
global: &mut GlobalRuntimeState,
|
||||||
|
state: &mut EvalState,
|
||||||
|
lib: &[&Module],
|
||||||
|
target: &mut Dynamic,
|
||||||
|
idx: &mut Dynamic,
|
||||||
|
level: usize,
|
||||||
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
|
let args = &mut [target, idx];
|
||||||
|
let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get());
|
||||||
|
let fn_name = crate::engine::FN_IDX_GET;
|
||||||
|
let pos = Position::NONE;
|
||||||
|
|
||||||
|
self.exec_fn_call(
|
||||||
|
None, global, state, lib, fn_name, hash_get, args, true, true, pos, level,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a set indexer.
|
||||||
|
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
||||||
|
#[inline(always)]
|
||||||
|
fn call_indexer_set(
|
||||||
|
&self,
|
||||||
|
global: &mut GlobalRuntimeState,
|
||||||
|
state: &mut EvalState,
|
||||||
|
lib: &[&Module],
|
||||||
|
target: &mut Dynamic,
|
||||||
|
idx: &mut Dynamic,
|
||||||
|
new_val: &mut Dynamic,
|
||||||
|
is_ref_mut: bool,
|
||||||
|
level: usize,
|
||||||
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
|
let hash_set = crate::ast::FnCallHashes::from_native(global.hash_idx_set());
|
||||||
|
let args = &mut [target, idx, new_val];
|
||||||
|
let fn_name = crate::engine::FN_IDX_SET;
|
||||||
|
let pos = Position::NONE;
|
||||||
|
|
||||||
|
self.exec_fn_call(
|
||||||
|
None, global, state, lib, fn_name, hash_set, args, is_ref_mut, true, pos, level,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the value at the indexed position of a base type.
|
/// Get the value at the indexed position of a base type.
|
||||||
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
||||||
fn get_indexed_mut<'t>(
|
fn get_indexed_mut<'t>(
|
||||||
@ -851,7 +870,7 @@ impl Engine {
|
|||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
target: &'t mut Dynamic,
|
target: &'t mut Dynamic,
|
||||||
idx: Dynamic,
|
idx: Dynamic,
|
||||||
pos: Position,
|
idx_pos: Position,
|
||||||
add_if_not_found: bool,
|
add_if_not_found: bool,
|
||||||
use_indexers: bool,
|
use_indexers: bool,
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -868,10 +887,10 @@ impl Engine {
|
|||||||
// val_array[idx]
|
// val_array[idx]
|
||||||
let index = idx
|
let index = idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?;
|
||||||
let len = arr.len();
|
let len = arr.len();
|
||||||
let arr_idx = super::calc_index(len, index, true, || {
|
let arr_idx = super::calc_index(len, index, true, || {
|
||||||
ERR::ErrorArrayBounds(len, index, pos).into()
|
ERR::ErrorArrayBounds(len, index, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(arr.get_mut(arr_idx).map(Target::from).unwrap())
|
Ok(arr.get_mut(arr_idx).map(Target::from).unwrap())
|
||||||
@ -882,10 +901,10 @@ impl Engine {
|
|||||||
// val_blob[idx]
|
// val_blob[idx]
|
||||||
let index = idx
|
let index = idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?;
|
||||||
let len = arr.len();
|
let len = arr.len();
|
||||||
let arr_idx = super::calc_index(len, index, true, || {
|
let arr_idx = super::calc_index(len, index, true, || {
|
||||||
ERR::ErrorArrayBounds(len, index, pos).into()
|
ERR::ErrorArrayBounds(len, index, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = arr.get(arr_idx).map(|&v| (v as crate::INT).into()).unwrap();
|
let value = arr.get(arr_idx).map(|&v| (v as crate::INT).into()).unwrap();
|
||||||
@ -901,17 +920,20 @@ impl Engine {
|
|||||||
Dynamic(Union::Map(map, ..)) => {
|
Dynamic(Union::Map(map, ..)) => {
|
||||||
// val_map[idx]
|
// val_map[idx]
|
||||||
let index = idx.read_lock::<crate::ImmutableString>().ok_or_else(|| {
|
let index = idx.read_lock::<crate::ImmutableString>().ok_or_else(|| {
|
||||||
self.make_type_mismatch_err::<crate::ImmutableString>(idx.type_name(), pos)
|
self.make_type_mismatch_err::<crate::ImmutableString>(idx.type_name(), idx_pos)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if _add_if_not_found && !map.contains_key(index.as_str()) {
|
if _add_if_not_found && !map.contains_key(index.as_str()) {
|
||||||
map.insert(index.clone().into(), Dynamic::UNIT);
|
map.insert(index.clone().into(), Dynamic::UNIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(map
|
if let Some(value) = map.get_mut(index.as_str()) {
|
||||||
.get_mut(index.as_str())
|
Ok(Target::from(value))
|
||||||
.map(Target::from)
|
} else if self.fail_on_invalid_map_property() {
|
||||||
.unwrap_or_else(|| Target::from(Dynamic::UNIT)))
|
Err(ERR::ErrorPropertyNotFound(index.to_string(), idx_pos).into())
|
||||||
|
} else {
|
||||||
|
Ok(Target::from(Dynamic::UNIT))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -926,10 +948,10 @@ impl Engine {
|
|||||||
let end = range.end;
|
let end = range.end;
|
||||||
|
|
||||||
let start = super::calc_index(BITS, start, false, || {
|
let start = super::calc_index(BITS, start, false, || {
|
||||||
ERR::ErrorBitFieldBounds(BITS, start, pos).into()
|
ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
let end = super::calc_index(BITS, end, false, || {
|
let end = super::calc_index(BITS, end, false, || {
|
||||||
ERR::ErrorBitFieldBounds(BITS, end, pos).into()
|
ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if end <= start {
|
if end <= start {
|
||||||
@ -951,10 +973,10 @@ impl Engine {
|
|||||||
let end = *range.end();
|
let end = *range.end();
|
||||||
|
|
||||||
let start = super::calc_index(BITS, start, false, || {
|
let start = super::calc_index(BITS, start, false, || {
|
||||||
ERR::ErrorBitFieldBounds(BITS, start, pos).into()
|
ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
let end = super::calc_index(BITS, end, false, || {
|
let end = super::calc_index(BITS, end, false, || {
|
||||||
ERR::ErrorBitFieldBounds(BITS, end, pos).into()
|
ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if end < start {
|
if end < start {
|
||||||
@ -990,12 +1012,12 @@ impl Engine {
|
|||||||
// val_int[idx]
|
// val_int[idx]
|
||||||
let index = idx
|
let index = idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?;
|
||||||
|
|
||||||
const BITS: usize = std::mem::size_of::<crate::INT>() * 8;
|
const BITS: usize = std::mem::size_of::<crate::INT>() * 8;
|
||||||
|
|
||||||
let bit = super::calc_index(BITS, index, true, || {
|
let bit = super::calc_index(BITS, index, true, || {
|
||||||
ERR::ErrorBitFieldBounds(BITS, index, pos).into()
|
ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let bit_value = (*value & (1 << bit)) != 0;
|
let bit_value = (*value & (1 << bit)) != 0;
|
||||||
@ -1012,14 +1034,14 @@ impl Engine {
|
|||||||
// val_string[idx]
|
// val_string[idx]
|
||||||
let index = idx
|
let index = idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?;
|
||||||
|
|
||||||
let (ch, offset) = if index >= 0 {
|
let (ch, offset) = if index >= 0 {
|
||||||
let offset = index as usize;
|
let offset = index as usize;
|
||||||
(
|
(
|
||||||
s.chars().nth(offset).ok_or_else(|| {
|
s.chars().nth(offset).ok_or_else(|| {
|
||||||
let chars_len = s.chars().count();
|
let chars_len = s.chars().count();
|
||||||
ERR::ErrorStringBounds(chars_len, index, pos)
|
ERR::ErrorStringBounds(chars_len, index, idx_pos)
|
||||||
})?,
|
})?,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@ -1029,13 +1051,13 @@ impl Engine {
|
|||||||
// Count from end if negative
|
// Count from end if negative
|
||||||
s.chars().rev().nth(offset - 1).ok_or_else(|| {
|
s.chars().rev().nth(offset - 1).ok_or_else(|| {
|
||||||
let chars_len = s.chars().count();
|
let chars_len = s.chars().count();
|
||||||
ERR::ErrorStringBounds(chars_len, index, pos)
|
ERR::ErrorStringBounds(chars_len, index, idx_pos)
|
||||||
})?,
|
})?,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let chars_len = s.chars().count();
|
let chars_len = s.chars().count();
|
||||||
return Err(ERR::ErrorStringBounds(chars_len, index, pos).into());
|
return Err(ERR::ErrorStringBounds(chars_len, index, idx_pos).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Target::StringChar {
|
Ok(Target::StringChar {
|
||||||
@ -1045,17 +1067,9 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if use_indexers => {
|
_ if use_indexers => self
|
||||||
let args = &mut [target, &mut idx];
|
.call_indexer_get(global, state, lib, target, &mut idx, level)
|
||||||
let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get());
|
.map(|(v, ..)| v.into()),
|
||||||
let fn_name = crate::engine::FN_IDX_GET;
|
|
||||||
let pos = Position::NONE;
|
|
||||||
|
|
||||||
self.exec_fn_call(
|
|
||||||
None, global, state, lib, fn_name, hash_get, args, true, true, pos, level,
|
|
||||||
)
|
|
||||||
.map(|(v, ..)| v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(ERR::ErrorIndexingType(
|
_ => Err(ERR::ErrorIndexingType(
|
||||||
format!(
|
format!(
|
||||||
|
@ -327,8 +327,7 @@ impl Engine {
|
|||||||
Expr::Unit(..) => Ok(Dynamic::UNIT),
|
Expr::Unit(..) => Ok(Dynamic::UNIT),
|
||||||
|
|
||||||
// `... ${...} ...`
|
// `... ${...} ...`
|
||||||
Expr::InterpolatedString(x, pos) => {
|
Expr::InterpolatedString(x, _) => {
|
||||||
let mut pos = *pos;
|
|
||||||
let mut concat: Dynamic = self.const_empty_string().into();
|
let mut concat: Dynamic = self.const_empty_string().into();
|
||||||
let mut result = Ok(Dynamic::UNIT);
|
let mut result = Ok(Dynamic::UNIT);
|
||||||
|
|
||||||
@ -347,7 +346,7 @@ impl Engine {
|
|||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
Some(OpAssignment::new(OP_CONCAT)),
|
Some(OpAssignment::new(OP_CONCAT)),
|
||||||
pos,
|
expr.start_position(),
|
||||||
&mut (&mut concat).into(),
|
&mut (&mut concat).into(),
|
||||||
("", Position::NONE),
|
("", Position::NONE),
|
||||||
item,
|
item,
|
||||||
@ -356,8 +355,6 @@ impl Engine {
|
|||||||
result = Err(err.fill_position(expr.start_position()));
|
result = Err(err.fill_position(expr.start_position()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = expr.start_position();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.map(|_| concat)
|
result.map(|_| concat)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Module defining functions for evaluating a statement.
|
//! Module defining functions for evaluating a statement.
|
||||||
|
|
||||||
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
||||||
|
use crate::api::events::VarDefInfo;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||||
};
|
};
|
||||||
@ -818,9 +819,15 @@ impl Engine {
|
|||||||
let export = options.contains(AST_OPTION_EXPORTED);
|
let export = options.contains(AST_OPTION_EXPORTED);
|
||||||
|
|
||||||
let result = if let Some(ref filter) = self.def_var_filter {
|
let result = if let Some(ref filter) = self.def_var_filter {
|
||||||
let shadowing = scope.contains(var_name);
|
let will_shadow = scope.contains(var_name);
|
||||||
let scope_level = state.scope_level;
|
let nesting_level = state.scope_level;
|
||||||
let is_const = entry_type == AccessMode::ReadOnly;
|
let is_const = entry_type == AccessMode::ReadOnly;
|
||||||
|
let info = VarDefInfo {
|
||||||
|
name: var_name,
|
||||||
|
is_const,
|
||||||
|
nesting_level,
|
||||||
|
will_shadow,
|
||||||
|
};
|
||||||
let context = EvalContext {
|
let context = EvalContext {
|
||||||
engine: self,
|
engine: self,
|
||||||
scope,
|
scope,
|
||||||
@ -828,16 +835,16 @@ impl Engine {
|
|||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
level: level,
|
level,
|
||||||
};
|
};
|
||||||
|
|
||||||
match filter(var_name, is_const, scope_level, shadowing, &context) {
|
match filter(true, info, &context) {
|
||||||
Ok(true) => None,
|
Ok(true) => None,
|
||||||
Ok(false) => Some(Err(ERR::ErrorRuntime(
|
Ok(false) => {
|
||||||
format!("Variable cannot be defined: {}", var_name).into(),
|
Some(Err(
|
||||||
*pos,
|
ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into()
|
||||||
)
|
))
|
||||||
.into())),
|
}
|
||||||
err @ Err(_) => Some(err),
|
err @ Err(_) => Some(err),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -977,7 +984,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Share statement
|
// Share statement
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Stmt::Share(name) => {
|
Stmt::Share(name, ..) => {
|
||||||
if let Some((index, ..)) = scope.get_index(name) {
|
if let Some((index, ..)) = scope.get_index(name) {
|
||||||
let val = scope.get_mut_by_index(index);
|
let val = scope.get_mut_by_index(index);
|
||||||
|
|
||||||
@ -985,6 +992,8 @@ impl Engine {
|
|||||||
// Replace the variable with a shared value.
|
// Replace the variable with a shared value.
|
||||||
*val = std::mem::take(val).into_shared();
|
*val = std::mem::take(val).into_shared();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("variable {} not found for sharing", name);
|
||||||
}
|
}
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
}
|
}
|
||||||
|
@ -682,8 +682,30 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// blob op= int
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
{
|
||||||
|
// string op= blob
|
||||||
|
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<crate::Blob>()) {
|
||||||
|
return match op {
|
||||||
|
"+=" => Some(|_, args| {
|
||||||
|
let buf = {
|
||||||
|
let x = args[1].read_lock::<crate::Blob>().expect(BUILTIN);
|
||||||
|
if x.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
let s = args[0].read_lock::<ImmutableString>().expect(BUILTIN);
|
||||||
|
let mut buf = crate::SmartString::from(s.as_str());
|
||||||
|
buf.push_str(&String::from_utf8_lossy(&x));
|
||||||
|
buf
|
||||||
|
};
|
||||||
|
let mut s = args[0].write_lock::<ImmutableString>().expect(BUILTIN);
|
||||||
|
*s = buf.into();
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// blob op= int
|
||||||
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<INT>()) {
|
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<INT>()) {
|
||||||
use crate::Blob;
|
use crate::Blob;
|
||||||
|
|
||||||
@ -697,6 +719,42 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blob op= char
|
||||||
|
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<char>()) {
|
||||||
|
use crate::Blob;
|
||||||
|
|
||||||
|
return match op {
|
||||||
|
"+=" => Some(|_, args| {
|
||||||
|
let mut buf = [0_u8; 4];
|
||||||
|
let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf);
|
||||||
|
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||||
|
Ok(blob.extend(x.as_bytes()).into())
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// blob op= string
|
||||||
|
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<ImmutableString>()) {
|
||||||
|
use crate::Blob;
|
||||||
|
|
||||||
|
return match op {
|
||||||
|
"+=" => Some(|_, args| {
|
||||||
|
let s: crate::Blob = {
|
||||||
|
let s = args[1].read_lock::<ImmutableString>().expect(BUILTIN);
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
s.as_bytes().into()
|
||||||
|
};
|
||||||
|
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||||
|
Ok(blob.extend(s).into())
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// No built-in op-assignments for different types.
|
// No built-in op-assignments for different types.
|
||||||
if type2 != type1 {
|
if type2 != type1 {
|
||||||
return None;
|
return None;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Module defining interfaces to native-Rust functions.
|
//! Module defining interfaces to native-Rust functions.
|
||||||
|
|
||||||
use super::call::FnCallArgs;
|
use super::call::FnCallArgs;
|
||||||
|
use crate::api::events::VarDefInfo;
|
||||||
use crate::ast::FnCallHashes;
|
use crate::ast::FnCallHashes;
|
||||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||||
use crate::plugin::PluginFunction;
|
use crate::plugin::PluginFunction;
|
||||||
@ -447,8 +448,8 @@ pub type OnVarCallback =
|
|||||||
|
|
||||||
/// Callback function for variable definition.
|
/// Callback function for variable definition.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type OnDefVarCallback = dyn Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>;
|
pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool>;
|
||||||
/// Callback function for variable definition.
|
/// Callback function for variable definition.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type OnDefVarCallback =
|
pub type OnDefVarCallback =
|
||||||
dyn Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool> + Send + Sync;
|
dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + Send + Sync;
|
||||||
|
@ -147,6 +147,7 @@ type ExclusiveRange = std::ops::Range<INT>;
|
|||||||
type InclusiveRange = std::ops::RangeInclusive<INT>;
|
type InclusiveRange = std::ops::RangeInclusive<INT>;
|
||||||
|
|
||||||
pub use api::custom_syntax::Expression;
|
pub use api::custom_syntax::Expression;
|
||||||
|
pub use api::events::VarDefInfo;
|
||||||
pub use ast::{FnAccess, AST};
|
pub use ast::{FnAccess, AST};
|
||||||
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
|
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
|
||||||
pub use eval::EvalContext;
|
pub use eval::EvalContext;
|
||||||
@ -247,7 +248,7 @@ pub use tokenizer::{get_next_token, parse_string_literal};
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
pub use tokenizer::{
|
pub use tokenizer::{
|
||||||
InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
|
InputStream, MultiInputsStream, Span, Token, TokenIterator, TokenizeState, TokenizerControl,
|
||||||
TokenizerControlBlock,
|
TokenizerControlBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use crate::ast::Ident;
|
use crate::ast::Ident;
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
use crate::StaticVec;
|
use crate::{Position, StaticVec};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -12,7 +12,7 @@ use std::{
|
|||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function call.
|
/// _(internals)_ A chain of [module][crate::Module] names to namespace-qualify a variable or function call.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
@ -114,4 +114,12 @@ impl Namespace {
|
|||||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||||
self.index = index
|
self.index = index
|
||||||
}
|
}
|
||||||
|
/// Get the [position][Position] of this [`NameSpace`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the path is empty.
|
||||||
|
pub fn position(&self) -> Position {
|
||||||
|
self.path[0].pos
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT,
|
|||||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||||
use crate::func::builtin::get_builtin_binary_op_fn;
|
use crate::func::builtin::get_builtin_binary_op_fn;
|
||||||
use crate::func::hashing::get_hasher;
|
use crate::func::hashing::get_hasher;
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::{Span, Token};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope,
|
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope,
|
||||||
@ -471,7 +471,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
// -> { expr, Noop }
|
// -> { expr, Noop }
|
||||||
Stmt::Block(
|
Stmt::Block(
|
||||||
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
|
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
|
||||||
(pos, Position::NONE),
|
Span::new(pos, Position::NONE),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// -> expr
|
// -> expr
|
||||||
@ -490,7 +490,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
|
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
|
||||||
{
|
{
|
||||||
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
||||||
statements => Stmt::Block(statements.into_boxed_slice(), x.1.positions()),
|
statements => Stmt::Block(statements.into_boxed_slice(), x.1.span()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if true { if_block } else { else_block } -> if_block
|
// if true { if_block } else { else_block } -> if_block
|
||||||
@ -500,7 +500,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
|
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
|
||||||
{
|
{
|
||||||
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
||||||
statements => Stmt::Block(statements.into_boxed_slice(), x.0.positions()),
|
statements => Stmt::Block(statements.into_boxed_slice(), x.0.span()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
@ -534,7 +534,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(
|
Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
x.def_case.span_or_else(*pos, Position::NONE),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
)),
|
||||||
@ -549,8 +549,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
*stmt =
|
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span());
|
||||||
Stmt::Block(statements.into_boxed_slice(), block.statements.positions());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -590,7 +589,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(
|
Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
x.def_case.span_or_else(*pos, Position::NONE),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
)),
|
||||||
@ -601,10 +600,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
let statements = mem::take(&mut *block.statements);
|
let statements = mem::take(&mut *block.statements);
|
||||||
let statements =
|
let statements =
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
optimize_stmt_block(statements, state, true, true, false);
|
||||||
*stmt = Stmt::Block(
|
*stmt =
|
||||||
statements.into_boxed_slice(),
|
Stmt::Block(statements.into_boxed_slice(), block.statements.span());
|
||||||
block.statements.positions(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -651,7 +648,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
x.def_case.span_or_else(*pos, Position::NONE),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// switch
|
// switch
|
||||||
@ -708,8 +705,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
if preserve_result {
|
if preserve_result {
|
||||||
statements.push(Stmt::Noop(pos))
|
statements.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
*stmt =
|
*stmt = Stmt::Block(
|
||||||
Stmt::Block(statements.into_boxed_slice(), (pos, Position::NONE));
|
statements.into_boxed_slice(),
|
||||||
|
Span::new(pos, Position::NONE),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
*stmt = Stmt::Noop(pos);
|
*stmt = Stmt::Noop(pos);
|
||||||
};
|
};
|
||||||
@ -726,7 +725,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
body.positions(),
|
body.span(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// do { block } while|until expr
|
// do { block } while|until expr
|
||||||
@ -747,21 +746,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, ..) => optimize_expr(expr, state, false),
|
Stmt::Import(expr, ..) => optimize_expr(expr, state, false),
|
||||||
// { block }
|
// { block }
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, span) => {
|
||||||
let statements = mem::take(statements).into_vec().into();
|
let statements = mem::take(statements).into_vec().into();
|
||||||
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
|
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||||
|
|
||||||
match block.as_mut_slice() {
|
match block.as_mut_slice() {
|
||||||
[] => {
|
[] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Noop(pos.0);
|
*stmt = Stmt::Noop(span.start());
|
||||||
}
|
}
|
||||||
// Only one statement which is not block-dependent - promote
|
// Only one statement which is not block-dependent - promote
|
||||||
[s] if !s.is_block_dependent() => {
|
[s] if !s.is_block_dependent() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = mem::take(s);
|
*stmt = mem::take(s);
|
||||||
}
|
}
|
||||||
_ => *stmt = Stmt::Block(block.into_boxed_slice(), *pos),
|
_ => *stmt = Stmt::Block(block.into_boxed_slice(), *span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
||||||
@ -771,7 +770,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
x.try_block.positions(),
|
x.try_block.span(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// try { try_block } catch ( var ) { catch_block }
|
// try { try_block } catch ( var ) { catch_block }
|
||||||
|
@ -190,7 +190,7 @@ macro_rules! reg_functions {
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Basic arithmetic package.
|
/// Basic arithmetic package.
|
||||||
crate::ArithmeticPackage => |lib| {
|
pub ArithmeticPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "int", int_functions);
|
combine_with_exported_module!(lib, "int", int_functions);
|
||||||
|
@ -13,7 +13,7 @@ use std::{any::TypeId, cmp::Ordering, mem};
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic array utilities.
|
/// Package of basic array utilities.
|
||||||
crate::BasicArrayPackage => |lib| {
|
pub BasicArrayPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "array", array_functions);
|
combine_with_exported_module!(lib, "array", array_functions);
|
||||||
|
@ -8,7 +8,7 @@ use std::prelude::v1::*;
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic bit-field utilities.
|
/// Package of basic bit-field utilities.
|
||||||
crate::BitFieldPackage => |lib| {
|
pub BitFieldPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "bit_field", bit_field_functions);
|
combine_with_exported_module!(lib, "bit_field", bit_field_functions);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::eval::{calc_index, calc_offset_len};
|
use crate::eval::{calc_index, calc_offset_len};
|
||||||
use crate::plugin::*;
|
use crate::plugin::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
|
def_package, Array, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
|
||||||
RhaiResultOf, INT,
|
RhaiResultOf, INT,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
@ -20,7 +20,7 @@ const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic BLOB utilities.
|
/// Package of basic BLOB utilities.
|
||||||
crate::BasicBlobPackage => |lib| {
|
pub BasicBlobPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "blob", blob_functions);
|
combine_with_exported_module!(lib, "blob", blob_functions);
|
||||||
@ -94,12 +94,36 @@ pub mod blob_functions {
|
|||||||
blob.resize(len, (value & 0x000000ff) as u8);
|
blob.resize(len, (value & 0x000000ff) as u8);
|
||||||
Ok(blob)
|
Ok(blob)
|
||||||
}
|
}
|
||||||
|
/// Convert the BLOB into an array of integers.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let b = blob(5, 0x42);
|
||||||
|
///
|
||||||
|
/// let x = b.to_array();
|
||||||
|
///
|
||||||
|
/// print(x); // prints "[66, 66, 66, 66, 66]"
|
||||||
|
/// ```
|
||||||
|
#[rhai_fn(pure)]
|
||||||
|
pub fn to_array(blob: &mut Blob) -> Array {
|
||||||
|
blob.iter().map(|&ch| (ch as INT).into()).collect()
|
||||||
|
}
|
||||||
/// Return the length of the BLOB.
|
/// Return the length of the BLOB.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let b = blob(10, 0x42);
|
||||||
|
///
|
||||||
|
/// print(b); // prints "[4242424242424242 4242]"
|
||||||
|
///
|
||||||
|
/// print(b.len()); // prints 10
|
||||||
|
/// ```
|
||||||
#[rhai_fn(name = "len", get = "len", pure)]
|
#[rhai_fn(name = "len", get = "len", pure)]
|
||||||
pub fn len(blob: &mut Blob) -> INT {
|
pub fn len(blob: &mut Blob) -> INT {
|
||||||
blob.len() as INT
|
blob.len() as INT
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the byte value at the `index` position in the BLOB.
|
/// Get the byte value at the `index` position in the BLOB.
|
||||||
///
|
///
|
||||||
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element).
|
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element).
|
||||||
@ -196,15 +220,49 @@ pub mod blob_functions {
|
|||||||
///
|
///
|
||||||
/// print(b1); // prints "[4242424242111111]"
|
/// print(b1); // prints "[4242424242111111]"
|
||||||
/// ```
|
/// ```
|
||||||
pub fn append(blob: &mut Blob, y: Blob) {
|
pub fn append(blob1: &mut Blob, blob2: Blob) {
|
||||||
if !y.is_empty() {
|
if !blob2.is_empty() {
|
||||||
if blob.is_empty() {
|
if blob1.is_empty() {
|
||||||
*blob = y;
|
*blob1 = blob2;
|
||||||
} else {
|
} else {
|
||||||
blob.extend(y);
|
blob1.extend(blob2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Add a string (as UTF-8 encoded byte-stream) to the end of the BLOB
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let b = blob(5, 0x42);
|
||||||
|
///
|
||||||
|
/// b.append("hello");
|
||||||
|
///
|
||||||
|
/// print(b); // prints "[424242424268656c 6c6f]"
|
||||||
|
/// ```
|
||||||
|
#[rhai_fn(name = "+=", name = "append")]
|
||||||
|
pub fn append_str(blob: &mut Blob, string: ImmutableString) {
|
||||||
|
if !string.is_empty() {
|
||||||
|
blob.extend(string.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Add a string (as UTF-8 encoded byte-stream) to the end of the BLOB
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let b = blob(5, 0x42);
|
||||||
|
///
|
||||||
|
/// b.append('!');
|
||||||
|
///
|
||||||
|
/// print(b); // prints "[424242424221]"
|
||||||
|
/// ```
|
||||||
|
#[rhai_fn(name = "+=", name = "append")]
|
||||||
|
pub fn append_char(blob: &mut Blob, character: char) {
|
||||||
|
let mut buf = [0_u8; 4];
|
||||||
|
let x = character.encode_utf8(&mut buf);
|
||||||
|
blob.extend(x.as_bytes());
|
||||||
|
}
|
||||||
/// Add another BLOB to the end of the BLOB, returning it as a new BLOB.
|
/// Add another BLOB to the end of the BLOB, returning it as a new BLOB.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -6,19 +6,17 @@ use crate::plugin::*;
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
use crate::{Dynamic, NativeCallContext};
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
use crate::{Array, Dynamic, NativeCallContext};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::Array;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use crate::Map;
|
use crate::Map;
|
||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic debugging utilities.
|
/// Package of basic debugging utilities.
|
||||||
crate::DebuggingPackage => |lib| {
|
pub DebuggingPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "debugging", debugging_functions);
|
combine_with_exported_module!(lib, "debugging", debugging_functions);
|
||||||
|
@ -5,7 +5,7 @@ use std::prelude::v1::*;
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic function pointer utilities.
|
/// Package of basic function pointer utilities.
|
||||||
crate::BasicFnPackage => |lib| {
|
pub BasicFnPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
||||||
|
@ -299,7 +299,7 @@ macro_rules! reg_range {
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic range iterators
|
/// Package of basic range iterators
|
||||||
crate::BasicIteratorPackage => |lib| {
|
pub BasicIteratorPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
reg_range!(lib | "range" => INT);
|
reg_range!(lib | "range" => INT);
|
||||||
|
@ -7,7 +7,7 @@ use std::prelude::v1::*;
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of core language features.
|
/// Package of core language features.
|
||||||
crate::LanguageCorePackage => |lib| {
|
pub LanguageCorePackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "core", core_functions);
|
combine_with_exported_module!(lib, "core", core_functions);
|
||||||
|
@ -37,7 +37,7 @@ macro_rules! reg_functions {
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic logic operators.
|
/// Package of basic logic operators.
|
||||||
crate::LogicPackage => |lib| {
|
pub LogicPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
@ -11,7 +11,7 @@ use crate::Array;
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic object map utilities.
|
/// Package of basic object map utilities.
|
||||||
crate::BasicMapPackage => |lib| {
|
pub BasicMapPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "map", map_functions);
|
combine_with_exported_module!(lib, "map", map_functions);
|
||||||
|
@ -53,7 +53,7 @@ macro_rules! reg_functions {
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Basic mathematical package.
|
/// Basic mathematical package.
|
||||||
crate::BasicMathPackage => |lib| {
|
pub BasicMathPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
// Integer functions
|
// Integer functions
|
||||||
|
@ -67,16 +67,52 @@ pub trait Package {
|
|||||||
///
|
///
|
||||||
/// fn add(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> { Ok(x + y) }
|
/// fn add(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> { Ok(x + y) }
|
||||||
///
|
///
|
||||||
/// def_package!(rhai:MyPackage:"My super-duper package", module,
|
/// def_package! {
|
||||||
/// {
|
/// /// My super-duper package.
|
||||||
|
/// pub MyPackage(module) {
|
||||||
/// // Load a binary function with all value parameters.
|
/// // Load a binary function with all value parameters.
|
||||||
/// module.set_native_fn("my_add", add);
|
/// module.set_native_fn("my_add", add);
|
||||||
/// });
|
/// }
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! def_package {
|
macro_rules! def_package {
|
||||||
|
($($(#[$outer:meta])* $mod:vis $package:ident($lib:ident) $block:block)+) => { $(
|
||||||
|
$(#[$outer])*
|
||||||
|
$mod struct $package($crate::Shared<$crate::Module>);
|
||||||
|
|
||||||
|
impl $crate::packages::Package for $package {
|
||||||
|
fn as_shared_module(&self) -> $crate::Shared<$crate::Module> {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
fn init($lib: &mut $crate::Module) {
|
||||||
|
$block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for $package {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $package {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut module = $crate::Module::new();
|
||||||
|
<Self as $crate::packages::Package>::init(&mut module);
|
||||||
|
module.build_index();
|
||||||
|
Self(module.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)* };
|
||||||
($($(#[$outer:meta])* $root:ident :: $package:ident => | $lib:ident | $block:block)+) => { $(
|
($($(#[$outer:meta])* $root:ident :: $package:ident => | $lib:ident | $block:block)+) => { $(
|
||||||
$(#[$outer])*
|
$(#[$outer])*
|
||||||
|
/// # Deprecated
|
||||||
|
///
|
||||||
|
/// This old syntax of `def_package!` is deprecated. Use the new syntax instead.
|
||||||
|
///
|
||||||
|
/// This syntax will be removed in the next major version.
|
||||||
|
#[deprecated(since = "1.5.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")]
|
||||||
pub struct $package($root::Shared<$root::Module>);
|
pub struct $package($root::Shared<$root::Module>);
|
||||||
|
|
||||||
impl $root::packages::Package for $package {
|
impl $root::packages::Package for $package {
|
||||||
@ -104,7 +140,6 @@ macro_rules! def_package {
|
|||||||
}
|
}
|
||||||
)* };
|
)* };
|
||||||
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
|
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
|
||||||
#[deprecated(since = "1.4.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")]
|
|
||||||
#[doc=$comment]
|
#[doc=$comment]
|
||||||
///
|
///
|
||||||
/// # Deprecated
|
/// # Deprecated
|
||||||
@ -112,6 +147,7 @@ macro_rules! def_package {
|
|||||||
/// This old syntax of `def_package!` is deprecated. Use the new syntax instead.
|
/// This old syntax of `def_package!` is deprecated. Use the new syntax instead.
|
||||||
///
|
///
|
||||||
/// This syntax will be removed in the next major version.
|
/// This syntax will be removed in the next major version.
|
||||||
|
#[deprecated(since = "1.4.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")]
|
||||||
pub struct $package($root::Shared<$root::Module>);
|
pub struct $package($root::Shared<$root::Module>);
|
||||||
|
|
||||||
impl $root::packages::Package for $package {
|
impl $root::packages::Package for $package {
|
||||||
|
@ -14,7 +14,7 @@ def_package! {
|
|||||||
/// * [`BasicIteratorPackage`][super::BasicIteratorPackage]
|
/// * [`BasicIteratorPackage`][super::BasicIteratorPackage]
|
||||||
/// * [`BasicFnPackage`][super::BasicFnPackage]
|
/// * [`BasicFnPackage`][super::BasicFnPackage]
|
||||||
/// * [`DebuggingPackage`][super::DebuggingPackage]
|
/// * [`DebuggingPackage`][super::DebuggingPackage]
|
||||||
crate::CorePackage => |lib| {
|
pub CorePackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
super::LanguageCorePackage::init(lib);
|
super::LanguageCorePackage::init(lib);
|
||||||
|
@ -17,7 +17,7 @@ def_package! {
|
|||||||
/// * [`BasicMapPackage`][super::BasicMapPackage]
|
/// * [`BasicMapPackage`][super::BasicMapPackage]
|
||||||
/// * [`BasicTimePackage`][super::BasicTimePackage]
|
/// * [`BasicTimePackage`][super::BasicTimePackage]
|
||||||
/// * [`MoreStringPackage`][super::MoreStringPackage]
|
/// * [`MoreStringPackage`][super::MoreStringPackage]
|
||||||
crate::StandardPackage => |lib| {
|
pub StandardPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
super::CorePackage::init(lib);
|
super::CorePackage::init(lib);
|
||||||
|
@ -15,7 +15,7 @@ pub const FUNC_TO_DEBUG: &str = "to_debug";
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic string utilities (e.g. printing)
|
/// Package of basic string utilities (e.g. printing)
|
||||||
crate::BasicStringPackage => |lib| {
|
pub BasicStringPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
|
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
|
||||||
@ -64,22 +64,65 @@ mod print_debug_functions {
|
|||||||
pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
|
pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
|
||||||
ctx.engine().map_type_name(&format!("{:?}", item)).into()
|
ctx.engine().map_type_name(&format!("{:?}", item)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the empty string.
|
/// Return the empty string.
|
||||||
#[rhai_fn(name = "print", name = "debug")]
|
#[rhai_fn(name = "print", name = "debug")]
|
||||||
pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString {
|
pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString {
|
||||||
ctx.engine().const_empty_string()
|
ctx.engine().const_empty_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `string`.
|
/// Return the `string`.
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_string(string: ImmutableString) -> ImmutableString {
|
pub fn print_string(string: ImmutableString) -> ImmutableString {
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
|
/// Convert the string into debug format.
|
||||||
|
#[rhai_fn(name = "debug", name = "to_debug", pure)]
|
||||||
|
pub fn debug_string(string: &mut ImmutableString) -> ImmutableString {
|
||||||
|
format!("{:?}", string).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the character into a string.
|
||||||
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
|
pub fn print_char(character: char) -> ImmutableString {
|
||||||
|
character.to_string().into()
|
||||||
|
}
|
||||||
|
/// Convert the string into debug format.
|
||||||
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
|
pub fn debug_char(character: char) -> ImmutableString {
|
||||||
|
format!("{:?}", character).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert the function pointer into a string in debug format.
|
/// Convert the function pointer into a string in debug format.
|
||||||
#[rhai_fn(name = "debug", name = "to_debug", pure)]
|
#[rhai_fn(name = "debug", name = "to_debug", pure)]
|
||||||
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
|
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
|
||||||
f.to_string().into()
|
f.to_string().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the boolean value into a string.
|
||||||
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
|
pub fn print_bool(value: bool) -> ImmutableString {
|
||||||
|
format!("{}", value).into()
|
||||||
|
}
|
||||||
|
/// Convert the boolean value into a string in debug format.
|
||||||
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
|
pub fn debug_bool(value: bool) -> ImmutableString {
|
||||||
|
format!("{:?}", value).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the empty string.
|
||||||
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
|
pub fn print_unit(ctx: NativeCallContext, unit: ()) -> ImmutableString {
|
||||||
|
let _ = unit;
|
||||||
|
ctx.engine().const_empty_string()
|
||||||
|
}
|
||||||
|
/// Convert the unit into a string in debug format.
|
||||||
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
|
pub fn debug_unit(unit: ()) -> ImmutableString {
|
||||||
|
let _ = unit;
|
||||||
|
"()".into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert the value of `number` into a string.
|
/// Convert the value of `number` into a string.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
|
@ -6,9 +6,12 @@ use std::{any::TypeId, mem};
|
|||||||
|
|
||||||
use super::string_basic::{print_with_func, FUNC_TO_STRING};
|
use super::string_basic::{print_with_func, FUNC_TO_STRING};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
use crate::Blob;
|
||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
|
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
|
||||||
crate::MoreStringPackage => |lib| {
|
pub MoreStringPackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "string", string_functions);
|
combine_with_exported_module!(lib, "string", string_functions);
|
||||||
@ -19,7 +22,7 @@ def_package! {
|
|||||||
mod string_functions {
|
mod string_functions {
|
||||||
use crate::{ImmutableString, SmartString};
|
use crate::{ImmutableString, SmartString};
|
||||||
|
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "+")]
|
||||||
pub fn add_append(
|
pub fn add_append(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
string: ImmutableString,
|
string: ImmutableString,
|
||||||
@ -33,6 +36,14 @@ mod string_functions {
|
|||||||
format!("{}{}", string, s).into()
|
format!("{}{}", string, s).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+=", name = "append")]
|
||||||
|
pub fn add(ctx: NativeCallContext, string: &mut ImmutableString, mut item: Dynamic) {
|
||||||
|
let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
|
||||||
|
|
||||||
|
if !s.is_empty() {
|
||||||
|
*string = format!("{}{}", string, s).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
#[rhai_fn(name = "+", pure)]
|
#[rhai_fn(name = "+", pure)]
|
||||||
pub fn add_prepend(
|
pub fn add_prepend(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
@ -48,11 +59,13 @@ mod string_functions {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
// The following are needed in order to override the generic versions with `Dynamic` parameters.
|
||||||
|
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
||||||
string1 + string2
|
string1 + string2
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "+")]
|
||||||
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
||||||
string + character
|
string + character
|
||||||
}
|
}
|
||||||
@ -61,7 +74,7 @@ mod string_functions {
|
|||||||
format!("{}{}", character, string).into()
|
format!("{}{}", character, string).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "+")]
|
||||||
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
||||||
let _item = item;
|
let _item = item;
|
||||||
string
|
string
|
||||||
@ -71,6 +84,30 @@ mod string_functions {
|
|||||||
string
|
string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub mod blob_functions {
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn add_append_blob(string: ImmutableString, utf8: Blob) -> ImmutableString {
|
||||||
|
if utf8.is_empty() {
|
||||||
|
string
|
||||||
|
} else if string.is_empty() {
|
||||||
|
String::from_utf8_lossy(&utf8).into_owned().into()
|
||||||
|
} else {
|
||||||
|
let mut s = crate::SmartString::from(string);
|
||||||
|
s.push_str(&String::from_utf8_lossy(&utf8));
|
||||||
|
s.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[rhai_fn(name = "append")]
|
||||||
|
pub fn add_blob(string: &mut ImmutableString, utf8: Blob) {
|
||||||
|
let mut s = crate::SmartString::from(string.as_str());
|
||||||
|
if !utf8.is_empty() {
|
||||||
|
s.push_str(&String::from_utf8_lossy(&utf8));
|
||||||
|
*string = s.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the length of the string, in number of characters.
|
/// Return the length of the string, in number of characters.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -105,6 +142,25 @@ mod string_functions {
|
|||||||
string.len() as INT
|
string.len() as INT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Convert the string into an UTF-8 encoded byte-stream as a BLOB.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let text = "朝には紅顔ありて夕べには白骨となる";
|
||||||
|
///
|
||||||
|
/// let bytes = text.to_blob();
|
||||||
|
///
|
||||||
|
/// print(bytes.len()); // prints 51
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn to_blob(string: &str) -> crate::Blob {
|
||||||
|
if string.is_empty() {
|
||||||
|
crate::Blob::new()
|
||||||
|
} else {
|
||||||
|
string.as_bytes().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Remove all occurrences of a sub-string from the string.
|
/// Remove all occurrences of a sub-string from the string.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -15,7 +15,7 @@ use instant::{Duration, Instant};
|
|||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of basic timing utilities.
|
/// Package of basic timing utilities.
|
||||||
crate::BasicTimePackage => |lib| {
|
pub BasicTimePackage(lib) {
|
||||||
lib.standard = true;
|
lib.standard = true;
|
||||||
|
|
||||||
// Register date/time functions
|
// Register date/time functions
|
||||||
|
139
src/parser.rs
139
src/parser.rs
@ -1,22 +1,25 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
||||||
|
use crate::api::events::VarDefInfo;
|
||||||
use crate::api::options::LanguageOptions;
|
use crate::api::options::LanguageOptions;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
OpAssignment, ScriptFnDef, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
OpAssignment, ScriptFnDef, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||||
};
|
};
|
||||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||||
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||||
use crate::func::hashing::get_hasher;
|
use crate::func::hashing::get_hasher;
|
||||||
use crate::tokenizer::{
|
use crate::tokenizer::{
|
||||||
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
is_keyword_function, is_valid_function_name, is_valid_identifier, Span, Token, TokenStream,
|
||||||
TokenizerControl,
|
TokenizerControl,
|
||||||
};
|
};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
use crate::types::StringsInterner;
|
use crate::types::StringsInterner;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, ExclusiveRange, Identifier, ImmutableString, InclusiveRange,
|
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, Identifier,
|
||||||
LexError, OptimizationLevel, ParseError, Position, Scope, Shared, StaticVec, AST, INT, PERR,
|
ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, Scope,
|
||||||
|
Shared, StaticVec, AST, INT, PERR,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -47,7 +50,7 @@ pub struct ParseState<'e> {
|
|||||||
/// Interned strings.
|
/// Interned strings.
|
||||||
pub interned_strings: StringsInterner,
|
pub interned_strings: StringsInterner,
|
||||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||||
pub stack: StaticVec<(Identifier, AccessMode)>,
|
pub stack: Scope<'e>,
|
||||||
/// Size of the local variables stack upon entry of the current block scope.
|
/// Size of the local variables stack upon entry of the current block scope.
|
||||||
pub entry_stack_len: usize,
|
pub entry_stack_len: usize,
|
||||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||||
@ -89,7 +92,7 @@ impl<'e> ParseState<'e> {
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
allow_capture: true,
|
allow_capture: true,
|
||||||
interned_strings: StringsInterner::new(),
|
interned_strings: StringsInterner::new(),
|
||||||
stack: StaticVec::new_const(),
|
stack: Scope::new(),
|
||||||
entry_stack_len: 0,
|
entry_stack_len: 0,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
imports: StaticVec::new_const(),
|
imports: StaticVec::new_const(),
|
||||||
@ -107,18 +110,17 @@ impl<'e> ParseState<'e> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> {
|
pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> {
|
||||||
let mut barrier = false;
|
let mut hit_barrier = false;
|
||||||
let _pos = pos;
|
let _pos = pos;
|
||||||
|
|
||||||
let index = self
|
let index = self
|
||||||
.stack
|
.stack
|
||||||
.iter()
|
.iter_rev_raw()
|
||||||
.rev()
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(.., (n, ..))| {
|
.find(|&(.., (n, ..))| {
|
||||||
if n == SCOPE_SEARCH_BARRIER_MARKER {
|
if n == SCOPE_SEARCH_BARRIER_MARKER {
|
||||||
// Do not go beyond the barrier
|
// Do not go beyond the barrier
|
||||||
barrier = true;
|
hit_barrier = true;
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
n == name
|
n == name
|
||||||
@ -138,7 +140,7 @@ impl<'e> ParseState<'e> {
|
|||||||
self.allow_capture = true
|
self.allow_capture = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if barrier {
|
if hit_barrier {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
index
|
index
|
||||||
@ -1291,8 +1293,9 @@ fn parse_primary(
|
|||||||
let mut segments = StaticVec::<Expr>::new();
|
let mut segments = StaticVec::<Expr>::new();
|
||||||
|
|
||||||
match input.next().expect(NEVER_ENDS) {
|
match input.next().expect(NEVER_ENDS) {
|
||||||
|
(Token::InterpolatedString(s), ..) if s.is_empty() => (),
|
||||||
(Token::InterpolatedString(s), pos) => {
|
(Token::InterpolatedString(s), pos) => {
|
||||||
segments.push(Expr::StringConstant(s.into(), pos));
|
segments.push(Expr::StringConstant(s.into(), pos))
|
||||||
}
|
}
|
||||||
token => unreachable!("Token::InterpolatedString expected but gets {:?}", token),
|
token => unreachable!("Token::InterpolatedString expected but gets {:?}", token),
|
||||||
}
|
}
|
||||||
@ -1302,7 +1305,10 @@ fn parse_primary(
|
|||||||
block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())),
|
block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())),
|
||||||
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
|
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
|
||||||
};
|
};
|
||||||
segments.push(expr);
|
match expr {
|
||||||
|
Expr::StringConstant(s, ..) if s.is_empty() => (),
|
||||||
|
_ => segments.push(expr),
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure to parse the following as text
|
// Make sure to parse the following as text
|
||||||
let mut control = state.tokenizer_control.get();
|
let mut control = state.tokenizer_control.get();
|
||||||
@ -1332,9 +1338,13 @@ fn parse_primary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if segments.is_empty() {
|
||||||
|
Expr::StringConstant(state.get_interned_string("", ""), settings.pos)
|
||||||
|
} else {
|
||||||
segments.shrink_to_fit();
|
segments.shrink_to_fit();
|
||||||
Expr::InterpolatedString(segments.into(), settings.pos)
|
Expr::InterpolatedString(segments.into(), settings.pos)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Array literal
|
// Array literal
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -1792,7 +1802,11 @@ fn make_assignment_stmt(
|
|||||||
|| index.expect("either long or short index is `None`").get(),
|
|| index.expect("either long or short index is `None`").get(),
|
||||||
|n| n.get() as usize,
|
|n| n.get() as usize,
|
||||||
);
|
);
|
||||||
match state.stack[state.stack.len() - index].1 {
|
match state
|
||||||
|
.stack
|
||||||
|
.get_mut_by_index(state.stack.len() - index)
|
||||||
|
.access_mode()
|
||||||
|
{
|
||||||
AccessMode::ReadWrite => Ok(Stmt::Assignment(
|
AccessMode::ReadWrite => Ok(Stmt::Assignment(
|
||||||
(op_info, (lhs, rhs).into()).into(),
|
(op_info, (lhs, rhs).into()).into(),
|
||||||
op_pos,
|
op_pos,
|
||||||
@ -2185,7 +2199,7 @@ fn parse_custom_syntax(
|
|||||||
// Add a barrier variable to the stack so earlier variables will not be matched.
|
// Add a barrier variable to the stack so earlier variables will not be matched.
|
||||||
// Variable searches stop at the first barrier.
|
// Variable searches stop at the first barrier.
|
||||||
let marker = state.get_identifier("", SCOPE_SEARCH_BARRIER_MARKER);
|
let marker = state.get_identifier("", SCOPE_SEARCH_BARRIER_MARKER);
|
||||||
state.stack.push((marker, AccessMode::ReadWrite));
|
state.stack.push(marker, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
let parse_func = syntax.parse.as_ref();
|
let parse_func = syntax.parse.as_ref();
|
||||||
@ -2552,12 +2566,12 @@ fn parse_for(
|
|||||||
let counter_var = counter_name.map(|name| {
|
let counter_var = counter_name.map(|name| {
|
||||||
let name = state.get_identifier("", name);
|
let name = state.get_identifier("", name);
|
||||||
let pos = counter_pos.expect("`Some`");
|
let pos = counter_pos.expect("`Some`");
|
||||||
state.stack.push((name.clone(), AccessMode::ReadWrite));
|
state.stack.push(name.clone(), ());
|
||||||
Ident { name, pos }
|
Ident { name, pos }
|
||||||
});
|
});
|
||||||
|
|
||||||
let loop_var = state.get_identifier("", name);
|
let loop_var = state.get_identifier("", name);
|
||||||
state.stack.push((loop_var.clone(), AccessMode::ReadWrite));
|
state.stack.push(loop_var.clone(), ());
|
||||||
let loop_var = Ident {
|
let loop_var = Ident {
|
||||||
name: loop_var,
|
name: loop_var,
|
||||||
pos: name_pos,
|
pos: name_pos,
|
||||||
@ -2566,7 +2580,7 @@ fn parse_for(
|
|||||||
settings.is_breakable = true;
|
settings.is_breakable = true;
|
||||||
let body = parse_block(input, state, lib, settings.level_up())?;
|
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
state.stack.truncate(prev_stack_len);
|
state.stack.rewind(prev_stack_len);
|
||||||
|
|
||||||
Ok(Stmt::For(
|
Ok(Stmt::For(
|
||||||
expr,
|
expr,
|
||||||
@ -2600,6 +2614,36 @@ fn parse_let(
|
|||||||
return Err(PERR::VariableExists(name.to_string()).into_err(pos));
|
return Err(PERR::VariableExists(name.to_string()).into_err(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ref filter) = state.engine.def_var_filter {
|
||||||
|
let will_shadow = state.stack.iter().any(|(v, ..)| v == name.as_ref());
|
||||||
|
let level = settings.level;
|
||||||
|
let is_const = var_type == AccessMode::ReadOnly;
|
||||||
|
let info = VarDefInfo {
|
||||||
|
name: &name,
|
||||||
|
is_const,
|
||||||
|
nesting_level: level,
|
||||||
|
will_shadow,
|
||||||
|
};
|
||||||
|
let context = EvalContext {
|
||||||
|
engine: state.engine,
|
||||||
|
scope: &mut state.stack,
|
||||||
|
global: &mut GlobalRuntimeState::new(state.engine),
|
||||||
|
state: &mut EvalState::new(),
|
||||||
|
lib: &[],
|
||||||
|
this_ptr: &mut None,
|
||||||
|
level,
|
||||||
|
};
|
||||||
|
|
||||||
|
match filter(false, info, &context) {
|
||||||
|
Ok(true) => (),
|
||||||
|
Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)),
|
||||||
|
Err(err) => match *err {
|
||||||
|
EvalAltResult::ErrorParsing(perr, pos) => return Err(perr.into_err(pos)),
|
||||||
|
_ => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let name = state.get_identifier("", name);
|
let name = state.get_identifier("", name);
|
||||||
let var_def = Ident {
|
let var_def = Ident {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
@ -2614,8 +2658,6 @@ fn parse_let(
|
|||||||
Expr::Unit(Position::NONE)
|
Expr::Unit(Position::NONE)
|
||||||
};
|
};
|
||||||
|
|
||||||
state.stack.push((name, var_type));
|
|
||||||
|
|
||||||
let export = if is_export {
|
let export = if is_export {
|
||||||
AST_OPTION_EXPORTED
|
AST_OPTION_EXPORTED
|
||||||
} else {
|
} else {
|
||||||
@ -2624,14 +2666,20 @@ fn parse_let(
|
|||||||
|
|
||||||
match var_type {
|
match var_type {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
AccessMode::ReadWrite => Ok(Stmt::Var(expr, var_def.into(), export, settings.pos)),
|
AccessMode::ReadWrite => {
|
||||||
|
state.stack.push(name, ());
|
||||||
|
Ok(Stmt::Var(expr, var_def.into(), export, settings.pos))
|
||||||
|
}
|
||||||
// const name = { expr:constant }
|
// const name = { expr:constant }
|
||||||
AccessMode::ReadOnly => Ok(Stmt::Var(
|
AccessMode::ReadOnly => {
|
||||||
|
state.stack.push_constant(name, ());
|
||||||
|
Ok(Stmt::Var(
|
||||||
expr,
|
expr,
|
||||||
var_def.into(),
|
var_def.into(),
|
||||||
AST_OPTION_CONSTANT + export,
|
AST_OPTION_CONSTANT + export,
|
||||||
settings.pos,
|
settings.pos,
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2810,7 +2858,7 @@ fn parse_block(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
state.stack.truncate(state.entry_stack_len);
|
state.stack.rewind(state.entry_stack_len);
|
||||||
state.entry_stack_len = prev_entry_stack_len;
|
state.entry_stack_len = prev_entry_stack_len;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -2818,7 +2866,7 @@ fn parse_block(
|
|||||||
|
|
||||||
Ok(Stmt::Block(
|
Ok(Stmt::Block(
|
||||||
statements.into_boxed_slice(),
|
statements.into_boxed_slice(),
|
||||||
(settings.pos, end_pos),
|
Span::new(settings.pos, end_pos),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3096,7 +3144,7 @@ fn parse_try_catch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = state.get_identifier("", name);
|
let name = state.get_identifier("", name);
|
||||||
state.stack.push((name.clone(), AccessMode::ReadWrite));
|
state.stack.push(name.clone(), ());
|
||||||
Some(Ident { name, pos })
|
Some(Ident { name, pos })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -3107,7 +3155,7 @@ fn parse_try_catch(
|
|||||||
|
|
||||||
if catch_var.is_some() {
|
if catch_var.is_some() {
|
||||||
// Remove the error variable from the stack
|
// Remove the error variable from the stack
|
||||||
state.stack.pop().unwrap();
|
state.stack.rewind(state.stack.len() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Stmt::TryCatch(
|
Ok(Stmt::TryCatch(
|
||||||
@ -3166,7 +3214,7 @@ fn parse_fn(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let s = state.get_identifier("", s);
|
let s = state.get_identifier("", s);
|
||||||
state.stack.push((s.clone(), AccessMode::ReadWrite));
|
state.stack.push(s.clone(), ());
|
||||||
params.push((s, pos))
|
params.push((s, pos))
|
||||||
}
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
@ -3226,7 +3274,7 @@ fn parse_fn(
|
|||||||
fn make_curry_from_externals(
|
fn make_curry_from_externals(
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
fn_expr: Expr,
|
fn_expr: Expr,
|
||||||
externals: StaticVec<Identifier>,
|
externals: StaticVec<crate::ast::Ident>,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
// If there are no captured variables, no need to curry
|
// If there are no captured variables, no need to curry
|
||||||
@ -3239,21 +3287,26 @@ fn make_curry_from_externals(
|
|||||||
|
|
||||||
args.push(fn_expr);
|
args.push(fn_expr);
|
||||||
|
|
||||||
args.extend(externals.iter().cloned().map(|x| {
|
args.extend(
|
||||||
|
externals
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|crate::ast::Ident { name, pos }| {
|
||||||
Expr::Variable(
|
Expr::Variable(
|
||||||
None,
|
None,
|
||||||
Position::NONE,
|
pos,
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
None,
|
None,
|
||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
(),
|
(),
|
||||||
x,
|
name,
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
let expr = FnCallExpr {
|
let expr = FnCallExpr {
|
||||||
name: state.get_identifier("", crate::engine::KEYWORD_FN_PTR_CURRY),
|
name: state.get_identifier("", crate::engine::KEYWORD_FN_PTR_CURRY),
|
||||||
@ -3270,7 +3323,11 @@ fn make_curry_from_externals(
|
|||||||
// Convert the entire expression into a statement block, then insert the relevant
|
// Convert the entire expression into a statement block, then insert the relevant
|
||||||
// [`Share`][Stmt::Share] statements.
|
// [`Share`][Stmt::Share] statements.
|
||||||
let mut statements = StaticVec::with_capacity(externals.len() + 1);
|
let mut statements = StaticVec::with_capacity(externals.len() + 1);
|
||||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
statements.extend(
|
||||||
|
externals
|
||||||
|
.into_iter()
|
||||||
|
.map(|crate::ast::Ident { name, pos }| Stmt::Share(name, pos)),
|
||||||
|
);
|
||||||
statements.push(Stmt::Expr(expr));
|
statements.push(Stmt::Expr(expr));
|
||||||
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
|
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
|
||||||
}
|
}
|
||||||
@ -3300,7 +3357,7 @@ fn parse_anon_fn(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let s = state.get_identifier("", s);
|
let s = state.get_identifier("", s);
|
||||||
state.stack.push((s.clone(), AccessMode::ReadWrite));
|
state.stack.push(s.clone(), ());
|
||||||
params_list.push(s)
|
params_list.push(s)
|
||||||
}
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
@ -3336,14 +3393,14 @@ fn parse_anon_fn(
|
|||||||
// so extract them into a list.
|
// so extract them into a list.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
let (mut params, externals) = {
|
let (mut params, externals) = {
|
||||||
let externals: StaticVec<Identifier> = state
|
let externals: StaticVec<_> = state.external_vars.iter().cloned().collect();
|
||||||
.external_vars
|
|
||||||
.iter()
|
|
||||||
.map(|crate::ast::Ident { name, .. }| name.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut params = StaticVec::with_capacity(params_list.len() + externals.len());
|
let mut params = StaticVec::with_capacity(params_list.len() + externals.len());
|
||||||
params.extend(externals.iter().cloned());
|
params.extend(
|
||||||
|
externals
|
||||||
|
.iter()
|
||||||
|
.map(|crate::ast::Ident { name, .. }| name.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
(params, externals)
|
(params, externals)
|
||||||
};
|
};
|
||||||
|
16
src/reify.rs
16
src/reify.rs
@ -1,12 +1,19 @@
|
|||||||
/// Runs `$code` if `$old` is of type `$t`.
|
/// Macro to cast an identifier or expression to another type with type checks.
|
||||||
///
|
///
|
||||||
/// This macro is primarily used for type casting between known types.
|
/// Runs _code_ if _variable_ or _expression_ is of type _type_, otherwise run _fallback_.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
///
|
||||||
|
/// * `reify!(`_variable_ or _expression_`,|`_temp-variable_`: `_type_`|` _code_`,` `||` _fallback_ `)`
|
||||||
|
/// * `reify!(`_variable_ or _expression_`,|`_temp-variable_`: `_type_`|` _code_ `)`
|
||||||
|
/// * `reify!(`_variable_ or _expression_ `=>` `Option<`_type_`>` `)`
|
||||||
|
/// * `reify!(`_variable_ or _expression_ `=>` _type_ `)`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! reify {
|
macro_rules! reify {
|
||||||
($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{
|
($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{
|
||||||
if std::any::TypeId::of::<$t>() == std::any::Any::type_id(&$old) {
|
if std::any::TypeId::of::<$t>() == std::any::Any::type_id(&$old) {
|
||||||
// SAFETY: This is safe because we check to make sure the two types are
|
// SAFETY: This is safe because we already checked to make sure the two types
|
||||||
// actually the same type.
|
// are actually the same.
|
||||||
let $new: $t = unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new($old)) };
|
let $new: $t = unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new($old)) };
|
||||||
$code
|
$code
|
||||||
} else {
|
} else {
|
||||||
@ -17,6 +24,7 @@ macro_rules! reify {
|
|||||||
let old = $old;
|
let old = $old;
|
||||||
reify!(old, |$new: $t| $code, || $fallback)
|
reify!(old, |$new: $t| $code, || $fallback)
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($old:ident, |$new:ident : $t:ty| $code:expr) => {
|
($old:ident, |$new:ident : $t:ty| $code:expr) => {
|
||||||
reify!($old, |$new: $t| $code, || ())
|
reify!($old, |$new: $t| $code, || ())
|
||||||
};
|
};
|
||||||
|
125
src/tokenizer.rs
125
src/tokenizer.rs
@ -59,10 +59,10 @@ pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
|
|||||||
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
|
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
|
||||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
/// Line number - 0 = none
|
/// Line number: 0 = none
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
line: u16,
|
line: u16,
|
||||||
/// Character position - 0 = BOL
|
/// Character position: 0 = BOL
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
pos: u16,
|
pos: u16,
|
||||||
}
|
}
|
||||||
@ -226,11 +226,9 @@ impl Position {
|
|||||||
/// Print this [`Position`] for debug purposes.
|
/// Print this [`Position`] for debug purposes.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn debug_print(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
pub(crate) fn debug_print(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
if !self.is_none() {
|
if !self.is_none() {
|
||||||
write!(_f, " @ {:?}", self)?;
|
write!(_f, " @ {:?}", self)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,16 +257,19 @@ impl fmt::Display for Position {
|
|||||||
|
|
||||||
impl fmt::Debug for Position {
|
impl fmt::Debug for Position {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.is_none() {
|
||||||
|
f.write_str("none")
|
||||||
|
} else {
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
if self.is_beginning_of_line() {
|
if self.is_beginning_of_line() {
|
||||||
write!(f, "{}", self.line)?;
|
write!(f, "{}", self.line)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}:{}", self.line, self.pos)?;
|
write!(f, "{}:{}", self.line, self.pos)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_position")]
|
|
||||||
f.write_str("none")?;
|
|
||||||
|
|
||||||
Ok(())
|
#[cfg(feature = "no_position")]
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,6 +301,71 @@ impl AddAssign for Position {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct Span {
|
||||||
|
/// Starting [position][Position].
|
||||||
|
start: Position,
|
||||||
|
/// Ending [position][Position].
|
||||||
|
end: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
|
||||||
|
|
||||||
|
/// Create a new [`Span`].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(start: Position, end: Position) -> Self {
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
/// Is this [`Span`] non-existent?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_none(&self) -> bool {
|
||||||
|
self.start.is_none() && self.end.is_none()
|
||||||
|
}
|
||||||
|
/// Get the [`Span`]'s starting [position][Position].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn start(&self) -> Position {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
/// Get the [`Span`]'s ending [position][Position].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn end(&self) -> Position {
|
||||||
|
self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Span {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match (self.start().is_none(), self.end().is_none()) {
|
||||||
|
(false, false) if self.start().line() != self.end().line() => {
|
||||||
|
write!(f, "{:?}-{:?}", self.start(), self.end())
|
||||||
|
}
|
||||||
|
(false, false) => write!(
|
||||||
|
f,
|
||||||
|
"{}:{}-{}",
|
||||||
|
self.start().line().unwrap(),
|
||||||
|
self.start().position().unwrap_or(0),
|
||||||
|
self.end().position().unwrap_or(0)
|
||||||
|
),
|
||||||
|
(true, false) => write!(f, "..{:?}", self.end()),
|
||||||
|
(false, true) => write!(f, "{:?}", self.start()),
|
||||||
|
(true, true) => write!(f, "{:?}", Position::NONE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Span {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// _(internals)_ A Rhai language token.
|
/// _(internals)_ A Rhai language token.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||||
@ -1066,11 +1132,12 @@ pub fn parse_string_literal(
|
|||||||
verbatim: bool,
|
verbatim: bool,
|
||||||
allow_line_continuation: bool,
|
allow_line_continuation: bool,
|
||||||
allow_interpolation: bool,
|
allow_interpolation: bool,
|
||||||
) -> Result<(Box<str>, bool), (LexError, Position)> {
|
) -> Result<(Box<str>, bool, Position), (LexError, Position)> {
|
||||||
let mut result = String::with_capacity(12);
|
let mut result = String::with_capacity(12);
|
||||||
let mut escape = String::with_capacity(12);
|
let mut escape = String::with_capacity(12);
|
||||||
|
|
||||||
let start = *pos;
|
let start = *pos;
|
||||||
|
let mut first_char = Position::NONE;
|
||||||
let mut interpolated = false;
|
let mut interpolated = false;
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
let mut skip_whitespace_until = 0;
|
let mut skip_whitespace_until = 0;
|
||||||
@ -1123,6 +1190,21 @@ pub fn parse_string_literal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close wrapper
|
||||||
|
if termination_char == next_char && escape.is_empty() {
|
||||||
|
// Double wrapper
|
||||||
|
if stream.peek_next().map_or(false, |c| c == termination_char) {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
} else {
|
||||||
|
state.is_within_text_terminated_by = None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if first_char.is_none() {
|
||||||
|
first_char = *pos;
|
||||||
|
}
|
||||||
|
|
||||||
match next_char {
|
match next_char {
|
||||||
// \r - ignore if followed by \n
|
// \r - ignore if followed by \n
|
||||||
'\r' if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) => (),
|
'\r' if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) => (),
|
||||||
@ -1190,21 +1272,6 @@ pub fn parse_string_literal(
|
|||||||
result.push(next_char)
|
result.push(next_char)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double wrapper
|
|
||||||
_ if termination_char == next_char
|
|
||||||
&& escape.is_empty()
|
|
||||||
&& stream.peek_next().map_or(false, |c| c == termination_char) =>
|
|
||||||
{
|
|
||||||
eat_next(stream, pos);
|
|
||||||
result.push(termination_char)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close wrapper
|
|
||||||
_ if termination_char == next_char && escape.is_empty() => {
|
|
||||||
state.is_within_text_terminated_by = None;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verbatim
|
// Verbatim
|
||||||
'\n' if verbatim => {
|
'\n' if verbatim => {
|
||||||
assert_eq!(escape, "", "verbatim strings should not have any escapes");
|
assert_eq!(escape, "", "verbatim strings should not have any escapes");
|
||||||
@ -1262,7 +1329,7 @@ pub fn parse_string_literal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((result.into(), interpolated))
|
Ok((result.into(), interpolated, first_char))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume the next character.
|
/// Consume the next character.
|
||||||
@ -1397,11 +1464,9 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
// Within text?
|
// Within text?
|
||||||
if let Some(ch) = state.is_within_text_terminated_by.take() {
|
if let Some(ch) = state.is_within_text_terminated_by.take() {
|
||||||
let start_pos = *pos;
|
|
||||||
|
|
||||||
return parse_string_literal(stream, state, pos, ch, true, false, true).map_or_else(
|
return parse_string_literal(stream, state, pos, ch, true, false, true).map_or_else(
|
||||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||||
|(result, interpolated)| {
|
|(result, interpolated, start_pos)| {
|
||||||
if interpolated {
|
if interpolated {
|
||||||
Some((Token::InterpolatedString(result), start_pos))
|
Some((Token::InterpolatedString(result), start_pos))
|
||||||
} else {
|
} else {
|
||||||
@ -1612,7 +1677,7 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else(
|
return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else(
|
||||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||||
|(result, interpolated)| {
|
|(result, interpolated, ..)| {
|
||||||
if interpolated {
|
if interpolated {
|
||||||
Some((Token::InterpolatedString(result), start_pos))
|
Some((Token::InterpolatedString(result), start_pos))
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,6 +13,10 @@ use std::prelude::v1::*;
|
|||||||
///
|
///
|
||||||
/// All wrapped [`Position`] values represent the location in the script where the error occurs.
|
/// All wrapped [`Position`] values represent the location in the script where the error occurs.
|
||||||
///
|
///
|
||||||
|
/// Some errors never appear when certain features are turned on.
|
||||||
|
/// They still exist so that the application can turn features on and off without going through
|
||||||
|
/// massive code changes to remove/add back enum variants in match statements.
|
||||||
|
///
|
||||||
/// # Thread Safety
|
/// # Thread Safety
|
||||||
///
|
///
|
||||||
/// Currently, [`EvalAltResult`] is neither [`Send`] nor [`Sync`].
|
/// Currently, [`EvalAltResult`] is neither [`Send`] nor [`Sync`].
|
||||||
@ -32,8 +36,12 @@ pub enum EvalAltResult {
|
|||||||
|
|
||||||
/// Shadowing of an existing variable disallowed. Wrapped value is the variable name.
|
/// Shadowing of an existing variable disallowed. Wrapped value is the variable name.
|
||||||
ErrorVariableExists(String, Position),
|
ErrorVariableExists(String, Position),
|
||||||
/// Usage of an unknown variable. Wrapped value is the variable name.
|
/// Forbidden variable name. Wrapped value is the variable name.
|
||||||
|
ErrorForbiddenVariable(String, Position),
|
||||||
|
/// Access of an unknown variable. Wrapped value is the variable name.
|
||||||
ErrorVariableNotFound(String, Position),
|
ErrorVariableNotFound(String, Position),
|
||||||
|
/// Access of an unknown object map property. Wrapped value is the property name.
|
||||||
|
ErrorPropertyNotFound(String, Position),
|
||||||
/// Call to an unknown function. Wrapped value is the function signature.
|
/// Call to an unknown function. Wrapped value is the function signature.
|
||||||
ErrorFunctionNotFound(String, Position),
|
ErrorFunctionNotFound(String, Position),
|
||||||
/// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name.
|
/// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name.
|
||||||
@ -142,7 +150,9 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module {}: {}", s, err)?,
|
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module {}: {}", s, err)?,
|
||||||
|
|
||||||
Self::ErrorVariableExists(s, ..) => write!(f, "Variable is already defined: {}", s)?,
|
Self::ErrorVariableExists(s, ..) => write!(f, "Variable is already defined: {}", s)?,
|
||||||
|
Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", s)?,
|
||||||
Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {}", s)?,
|
Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {}", s)?,
|
||||||
|
Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {}", s)?,
|
||||||
Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {}", s)?,
|
Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {}", s)?,
|
||||||
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {}", s)?,
|
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {}", s)?,
|
||||||
Self::ErrorDataRace(s, ..) => {
|
Self::ErrorDataRace(s, ..) => {
|
||||||
@ -278,7 +288,9 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorIndexingType(..)
|
| Self::ErrorIndexingType(..)
|
||||||
| Self::ErrorFor(..)
|
| Self::ErrorFor(..)
|
||||||
| Self::ErrorVariableExists(..)
|
| Self::ErrorVariableExists(..)
|
||||||
|
| Self::ErrorForbiddenVariable(..)
|
||||||
| Self::ErrorVariableNotFound(..)
|
| Self::ErrorVariableNotFound(..)
|
||||||
|
| Self::ErrorPropertyNotFound(..)
|
||||||
| Self::ErrorModuleNotFound(..)
|
| Self::ErrorModuleNotFound(..)
|
||||||
| Self::ErrorDataRace(..)
|
| Self::ErrorDataRace(..)
|
||||||
| Self::ErrorAssignmentToConstant(..)
|
| Self::ErrorAssignmentToConstant(..)
|
||||||
@ -369,7 +381,9 @@ impl EvalAltResult {
|
|||||||
map.insert("type".into(), t.into());
|
map.insert("type".into(), t.into());
|
||||||
}
|
}
|
||||||
Self::ErrorVariableExists(v, ..)
|
Self::ErrorVariableExists(v, ..)
|
||||||
|
| Self::ErrorForbiddenVariable(v, ..)
|
||||||
| Self::ErrorVariableNotFound(v, ..)
|
| Self::ErrorVariableNotFound(v, ..)
|
||||||
|
| Self::ErrorPropertyNotFound(v, ..)
|
||||||
| Self::ErrorDataRace(v, ..)
|
| Self::ErrorDataRace(v, ..)
|
||||||
| Self::ErrorAssignmentToConstant(v, ..) => {
|
| Self::ErrorAssignmentToConstant(v, ..) => {
|
||||||
map.insert("variable".into(), v.into());
|
map.insert("variable".into(), v.into());
|
||||||
@ -431,7 +445,9 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorIndexingType(.., pos)
|
| Self::ErrorIndexingType(.., pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorVariableExists(.., pos)
|
| Self::ErrorVariableExists(.., pos)
|
||||||
|
| Self::ErrorForbiddenVariable(.., pos)
|
||||||
| Self::ErrorVariableNotFound(.., pos)
|
| Self::ErrorVariableNotFound(.., pos)
|
||||||
|
| Self::ErrorPropertyNotFound(.., pos)
|
||||||
| Self::ErrorModuleNotFound(.., pos)
|
| Self::ErrorModuleNotFound(.., pos)
|
||||||
| Self::ErrorDataRace(.., pos)
|
| Self::ErrorDataRace(.., pos)
|
||||||
| Self::ErrorAssignmentToConstant(.., pos)
|
| Self::ErrorAssignmentToConstant(.., pos)
|
||||||
@ -480,7 +496,9 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorIndexingType(.., pos)
|
| Self::ErrorIndexingType(.., pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorVariableExists(.., pos)
|
| Self::ErrorVariableExists(.., pos)
|
||||||
|
| Self::ErrorForbiddenVariable(.., pos)
|
||||||
| Self::ErrorVariableNotFound(.., pos)
|
| Self::ErrorVariableNotFound(.., pos)
|
||||||
|
| Self::ErrorPropertyNotFound(.., pos)
|
||||||
| Self::ErrorModuleNotFound(.., pos)
|
| Self::ErrorModuleNotFound(.., pos)
|
||||||
| Self::ErrorDataRace(.., pos)
|
| Self::ErrorDataRace(.., pos)
|
||||||
| Self::ErrorAssignmentToConstant(.., pos)
|
| Self::ErrorAssignmentToConstant(.., pos)
|
||||||
|
@ -10,8 +10,7 @@ use std::fmt;
|
|||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
/// _(internals)_ Error encountered when tokenizing the script text.
|
/// Error encountered when tokenizing the script text.
|
||||||
/// Exported under the `internals` feature only.
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum LexError {
|
pub enum LexError {
|
||||||
@ -89,21 +88,12 @@ pub enum ParseErrorType {
|
|||||||
MalformedCallExpr(String),
|
MalformedCallExpr(String),
|
||||||
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error
|
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error
|
||||||
/// description (if any).
|
/// description (if any).
|
||||||
///
|
|
||||||
/// Never appears under the `no_index` feature.
|
|
||||||
MalformedIndexExpr(String),
|
MalformedIndexExpr(String),
|
||||||
/// An expression in an `in` expression has syntax error. Wrapped value is the error description
|
/// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any).
|
||||||
/// (if any).
|
|
||||||
///
|
|
||||||
/// Never appears under the `no_object` and `no_index` features combination.
|
|
||||||
MalformedInExpr(String),
|
MalformedInExpr(String),
|
||||||
/// A capturing has syntax error. Wrapped value is the error description (if any).
|
/// A capturing has syntax error. Wrapped value is the error description (if any).
|
||||||
///
|
|
||||||
/// Never appears under the `no_closure` feature.
|
|
||||||
MalformedCapture(String),
|
MalformedCapture(String),
|
||||||
/// A map definition has duplicated property names. Wrapped value is the property name.
|
/// A map definition has duplicated property names. Wrapped value is the property name.
|
||||||
///
|
|
||||||
/// Never appears under the `no_object` feature.
|
|
||||||
DuplicatedProperty(String),
|
DuplicatedProperty(String),
|
||||||
/// A `switch` case is duplicated.
|
/// A `switch` case is duplicated.
|
||||||
DuplicatedSwitchCase,
|
DuplicatedSwitchCase,
|
||||||
@ -116,11 +106,11 @@ pub enum ParseErrorType {
|
|||||||
/// The case condition of a `switch` statement is not appropriate.
|
/// The case condition of a `switch` statement is not appropriate.
|
||||||
WrongSwitchCaseCondition,
|
WrongSwitchCaseCondition,
|
||||||
/// Missing a property name for custom types and maps.
|
/// Missing a property name for custom types and maps.
|
||||||
///
|
|
||||||
/// Never appears under the `no_object` feature.
|
|
||||||
PropertyExpected,
|
PropertyExpected,
|
||||||
/// Missing a variable name after the `let`, `const`, `for` or `catch` keywords.
|
/// Missing a variable name after the `let`, `const`, `for` or `catch` keywords.
|
||||||
VariableExpected,
|
VariableExpected,
|
||||||
|
/// Forbidden variable name. Wrapped value is the variable name.
|
||||||
|
ForbiddenVariable(String),
|
||||||
/// An identifier is a reserved symbol.
|
/// An identifier is a reserved symbol.
|
||||||
Reserved(String),
|
Reserved(String),
|
||||||
/// An expression is of the wrong type.
|
/// An expression is of the wrong type.
|
||||||
@ -129,38 +119,22 @@ pub enum ParseErrorType {
|
|||||||
/// Missing an expression. Wrapped value is the expression type.
|
/// Missing an expression. Wrapped value is the expression type.
|
||||||
ExprExpected(String),
|
ExprExpected(String),
|
||||||
/// Defining a doc-comment in an appropriate place (e.g. not at global level).
|
/// Defining a doc-comment in an appropriate place (e.g. not at global level).
|
||||||
///
|
|
||||||
/// Never appears under the `no_function` feature.
|
|
||||||
WrongDocComment,
|
WrongDocComment,
|
||||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||||
///
|
|
||||||
/// Never appears under the `no_function` feature.
|
|
||||||
WrongFnDefinition,
|
WrongFnDefinition,
|
||||||
/// Defining a function with a name that conflicts with an existing function.
|
/// Defining a function with a name that conflicts with an existing function.
|
||||||
/// Wrapped values are the function name and number of parameters.
|
/// Wrapped values are the function name and number of parameters.
|
||||||
///
|
|
||||||
/// Never appears under the `no_object` feature.
|
|
||||||
FnDuplicatedDefinition(String, usize),
|
FnDuplicatedDefinition(String, usize),
|
||||||
/// Missing a function name after the `fn` keyword.
|
/// Missing a function name after the `fn` keyword.
|
||||||
///
|
|
||||||
/// Never appears under the `no_function` feature.
|
|
||||||
FnMissingName,
|
FnMissingName,
|
||||||
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
||||||
///
|
|
||||||
/// Never appears under the `no_function` feature.
|
|
||||||
FnMissingParams(String),
|
FnMissingParams(String),
|
||||||
/// A function definition has duplicated parameters. Wrapped values are the function name and
|
/// A function definition has duplicated parameters. Wrapped values are the function name and
|
||||||
/// parameter name.
|
/// parameter name.
|
||||||
///
|
|
||||||
/// Never appears under the `no_function` feature.
|
|
||||||
FnDuplicatedParam(String, String),
|
FnDuplicatedParam(String, String),
|
||||||
/// A function definition is missing the body. Wrapped value is the function name.
|
/// A function definition is missing the body. Wrapped value is the function name.
|
||||||
///
|
|
||||||
/// Never appears under the `no_function` feature.
|
|
||||||
FnMissingBody(String),
|
FnMissingBody(String),
|
||||||
/// Export statement not at global level.
|
/// Export statement not at global level.
|
||||||
///
|
|
||||||
/// Never appears under the `no_module` feature.
|
|
||||||
WrongExport,
|
WrongExport,
|
||||||
/// Assignment to an a constant variable. Wrapped value is the constant variable name.
|
/// Assignment to an a constant variable. Wrapped value is the constant variable name.
|
||||||
AssignmentToConstant(String),
|
AssignmentToConstant(String),
|
||||||
@ -178,16 +152,10 @@ pub enum ParseErrorType {
|
|||||||
/// An imported module is not found.
|
/// An imported module is not found.
|
||||||
///
|
///
|
||||||
/// Only appears when strict variables mode is enabled.
|
/// Only appears when strict variables mode is enabled.
|
||||||
///
|
|
||||||
/// Never appears under the `no_module` feature.
|
|
||||||
ModuleUndefined(String),
|
ModuleUndefined(String),
|
||||||
/// Expression exceeding the maximum levels of complexity.
|
/// Expression exceeding the maximum levels of complexity.
|
||||||
///
|
|
||||||
/// Never appears under the `unchecked` feature.
|
|
||||||
ExprTooDeep,
|
ExprTooDeep,
|
||||||
/// Literal exceeding the maximum size. Wrapped values are the data type name and the maximum size.
|
/// Literal exceeding the maximum size. Wrapped values are the data type name and the maximum size.
|
||||||
///
|
|
||||||
/// Never appears under the `unchecked` feature.
|
|
||||||
LiteralTooLarge(String, usize),
|
LiteralTooLarge(String, usize),
|
||||||
/// Break statement not inside a loop.
|
/// Break statement not inside a loop.
|
||||||
LoopBreak,
|
LoopBreak,
|
||||||
@ -274,6 +242,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"),
|
Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"),
|
||||||
Self::PropertyExpected => f.write_str("Expecting name of a property"),
|
Self::PropertyExpected => f.write_str("Expecting name of a property"),
|
||||||
Self::VariableExpected => f.write_str("Expecting name of a variable"),
|
Self::VariableExpected => f.write_str("Expecting name of a variable"),
|
||||||
|
Self::ForbiddenVariable(s) => write!(f, "Forbidden variable name: {}", s),
|
||||||
Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"),
|
Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"),
|
||||||
Self::FnMissingName => f.write_str("Expecting function name in function declaration"),
|
Self::FnMissingName => f.write_str("Expecting function name in function declaration"),
|
||||||
Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"),
|
Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"),
|
||||||
|
@ -595,6 +595,16 @@ impl Scope<'_> {
|
|||||||
.zip(self.values.iter())
|
.zip(self.values.iter())
|
||||||
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
|
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
|
||||||
}
|
}
|
||||||
|
/// Get a reverse iterator to entries in the [`Scope`].
|
||||||
|
/// Shared values are not expanded.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn iter_rev_raw(&self) -> impl Iterator<Item = (&str, bool, &Dynamic)> {
|
||||||
|
self.names
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.zip(self.values.iter().rev())
|
||||||
|
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
|
||||||
|
}
|
||||||
/// Remove a range of entries within the [`Scope`].
|
/// Remove a range of entries within the [`Scope`].
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
@ -107,6 +107,23 @@ b`: 1}; y["a\nb"]
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_prop() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<()>("let x = #{a: 42}; x.b")?, ());
|
||||||
|
|
||||||
|
engine.set_fail_on_invalid_map_property(true);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(*engine.eval::<()>("let x = #{a: 42}; x.b").expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorPropertyNotFound(prop, _) if prop == "b"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_map_assign() -> Result<(), Box<EvalAltResult>> {
|
fn test_map_assign() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Position, Scope, INT};
|
use rhai::{Engine, EvalAltResult, ParseErrorType, Position, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
|
fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -125,7 +125,10 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
|
fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.on_def_var(|name, _, scope_level, _, _| match (name, scope_level) {
|
let ast = engine.compile("let x = 42;")?;
|
||||||
|
engine.run_ast(&ast)?;
|
||||||
|
|
||||||
|
engine.on_def_var(|_, info, _| match (info.name, info.nesting_level) {
|
||||||
("x", 0 | 1) => Ok(false),
|
("x", 0 | 1) => Ok(false),
|
||||||
_ => Ok(true),
|
_ => Ok(true),
|
||||||
});
|
});
|
||||||
@ -135,7 +138,14 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
|
|||||||
124
|
124
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.run("let x = 42;").is_err());
|
assert!(matches!(
|
||||||
|
*engine.compile("let x = 42;").expect_err("should error").0,
|
||||||
|
ParseErrorType::ForbiddenVariable(s) if s == "x"
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
*engine.run_ast(&ast).expect_err("should err"),
|
||||||
|
EvalAltResult::ErrorForbiddenVariable(s, _) if s == "x"
|
||||||
|
));
|
||||||
assert!(engine.run("const x = 42;").is_err());
|
assert!(engine.run("const x = 42;").is_err());
|
||||||
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
|
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
|
||||||
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
|
assert!(engine.run("let y = 42; { let x = y + 1; }").is_err());
|
||||||
|
Loading…
Reference in New Issue
Block a user