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.
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
@ -15,11 +21,14 @@ Bug fixes
|
||||
* Globally-defined constants are now encapsulated correctly inside a loaded module and no longer spill across call boundaries.
|
||||
* Type names display is fixed.
|
||||
* Exceptions thrown inside function calls now unwrap correctly when `catch`-ed.
|
||||
* Error messages for certain invalid property accesses are fixed.
|
||||
|
||||
Script-breaking changes
|
||||
-----------------------
|
||||
|
||||
* 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
|
||||
------------
|
||||
@ -27,8 +36,10 @@ New features
|
||||
* A debugging interface is added.
|
||||
* 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.
|
||||
* `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::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
|
||||
------------
|
||||
@ -42,6 +53,9 @@ Enhancements
|
||||
* `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.
|
||||
* `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
|
||||
-----------------
|
||||
|
@ -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 }
|
||||
# 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
|
||||
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]
|
||||
serde_bytes = "0.11"
|
||||
|
@ -1,12 +1,40 @@
|
||||
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
|
||||
cargo run --example sample_app_to_run
|
||||
Because of its popularity, included are sample implementations for the pattern
|
||||
[_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};
|
||||
|
||||
#[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_object"))]
|
||||
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();
|
||||
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.register_fn("new_ts", TestStruct::new)
|
||||
.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 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};
|
||||
|
||||
#[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"))]
|
||||
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();
|
||||
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.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();
|
||||
x.x = 42;
|
||||
x.update();
|
||||
x
|
||||
x.calc(x.x)
|
||||
",
|
||||
)?;
|
||||
|
||||
println!("result: {}", result.x); // prints 1001
|
||||
println!("result: {}", result); // prints 1085764
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! A simple example that evaluates an expression and prints the result.
|
||||
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -7,7 +9,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let result = engine.eval::<i64>("40 + 2")?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
println!("The Answer: {}", result); // prints 42
|
||||
|
||||
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};
|
||||
|
||||
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"))]
|
||||
fn main() {
|
||||
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};
|
||||
|
||||
fn add(x: i64, y: i64) -> i64 {
|
||||
|
@ -1,5 +1,6 @@
|
||||
///! This example registers a variety of functions that operate on strings.
|
||||
///! Remember to use `ImmutableString` or `&str` instead of `String` as parameters.
|
||||
//! An example that registers a variety of functions that operate on strings.
|
||||
//! Remember to use `ImmutableString` or `&str` instead of `String` as parameters.
|
||||
|
||||
use rhai::{Engine, EvalAltResult, ImmutableString, Scope};
|
||||
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
|
||||
/// 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 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;
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
|
@ -5,6 +5,19 @@ use crate::{Dynamic, Engine, EvalContext, Position, RhaiResultOf};
|
||||
#[cfg(feature = "no_std")]
|
||||
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 {
|
||||
/// 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 .
|
||||
///
|
||||
/// # WARNING - Unstable API
|
||||
///
|
||||
/// This API is volatile and may change in the future.
|
||||
///
|
||||
/// # Callback Function Signature
|
||||
///
|
||||
/// 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:
|
||||
/// * `name`: name of the variable to be defined.
|
||||
/// * `is_const`: `true` if the statement is `const`, otherwise it is `let`.
|
||||
/// * `block_level`: the current nesting level of statement blocks, with zero being the global level
|
||||
/// * `will_shadow`: will the variable _shadow_ an existing variable?
|
||||
/// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation.
|
||||
/// * `info`: information on the variable.
|
||||
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// * `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
|
||||
///
|
||||
@ -96,9 +111,9 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register a variable definition filter.
|
||||
/// engine.on_def_var(|name, is_const, _, _, _| {
|
||||
/// engine.on_def_var(|_, info, _| {
|
||||
/// // Disallow defining MYSTIC_NUMBER as a constant
|
||||
/// if name == "MYSTIC_NUMBER" && is_const {
|
||||
/// if info.name == "MYSTIC_NUMBER" && info.is_const {
|
||||
/// Ok(false)
|
||||
/// } else {
|
||||
/// Ok(true)
|
||||
@ -114,12 +129,11 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[deprecated = "This API is volatile and may change in the future."]
|
||||
#[inline(always)]
|
||||
pub fn on_def_var(
|
||||
&mut self,
|
||||
callback: impl Fn(&str, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
self.def_var_filter = Some(Box::new(callback));
|
||||
self
|
||||
@ -321,8 +335,13 @@ impl Engine {
|
||||
self.debug = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// _(debugging)_ Register callbacks for debugging.
|
||||
/// _(debugging)_ Register a callback for debugging.
|
||||
/// 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")]
|
||||
#[inline(always)]
|
||||
pub fn register_debugger(
|
||||
|
@ -18,10 +18,14 @@ pub struct LanguageOptions {
|
||||
pub allow_anonymous_fn: bool,
|
||||
/// Is looping allowed?
|
||||
pub allow_looping: bool,
|
||||
/// Strict variables mode?
|
||||
pub strict_var: bool,
|
||||
/// Is variables shadowing allowed?
|
||||
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 {
|
||||
@ -37,6 +41,8 @@ impl LanguageOptions {
|
||||
allow_looping: true,
|
||||
strict_var: false,
|
||||
allow_shadowing: true,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fail_on_invalid_map_property: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,6 +55,7 @@ impl Default for LanguageOptions {
|
||||
|
||||
impl Engine {
|
||||
/// Is `if`-expression allowed?
|
||||
/// Default is `true`.
|
||||
#[inline(always)]
|
||||
pub fn allow_if_expression(&self) -> bool {
|
||||
self.options.allow_if_expr
|
||||
@ -59,6 +66,7 @@ impl Engine {
|
||||
self.options.allow_if_expr = enable;
|
||||
}
|
||||
/// Is `switch` expression allowed?
|
||||
/// Default is `true`.
|
||||
#[inline(always)]
|
||||
pub fn allow_switch_expression(&self) -> bool {
|
||||
self.options.allow_switch_expr
|
||||
@ -69,6 +77,7 @@ impl Engine {
|
||||
self.options.allow_switch_expr = enable;
|
||||
}
|
||||
/// Is statement-expression allowed?
|
||||
/// Default is `true`.
|
||||
#[inline(always)]
|
||||
pub fn allow_statement_expression(&self) -> bool {
|
||||
self.options.allow_stmt_expr
|
||||
@ -79,6 +88,7 @@ impl Engine {
|
||||
self.options.allow_stmt_expr = enable;
|
||||
}
|
||||
/// Is anonymous function allowed?
|
||||
/// Default is `true`.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -95,6 +105,7 @@ impl Engine {
|
||||
self.options.allow_anonymous_fn = enable;
|
||||
}
|
||||
/// Is looping allowed?
|
||||
/// Default is `true`.
|
||||
#[inline(always)]
|
||||
pub fn allow_looping(&self) -> bool {
|
||||
self.options.allow_looping
|
||||
@ -104,17 +115,8 @@ impl Engine {
|
||||
pub fn set_allow_looping(&mut self, enable: bool) {
|
||||
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?
|
||||
/// Default is `true`.
|
||||
#[inline(always)]
|
||||
pub fn allow_shadowing(&self) -> bool {
|
||||
self.options.allow_shadowing
|
||||
@ -124,4 +126,32 @@ impl Engine {
|
||||
pub fn set_allow_shadowing(&mut self, enable: bool) {
|
||||
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<'_> {
|
||||
/// Get the [`Position`] of this [`ASTNode`].
|
||||
pub const fn position(&self) -> Position {
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
ASTNode::Stmt(stmt) => stmt.position(),
|
||||
ASTNode::Expr(expr) => expr.position(),
|
||||
|
@ -440,7 +440,7 @@ impl Default for Expr {
|
||||
|
||||
impl fmt::Debug for Expr {
|
||||
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 {
|
||||
Self::DynamicConstant(value, ..) => write!(f, "{:?}", value),
|
||||
@ -470,24 +470,34 @@ impl fmt::Debug for Expr {
|
||||
f.write_str("Variable(")?;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some((.., ref namespace)) = x.1 {
|
||||
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?
|
||||
if let Some((ref namespace, ..)) = x.1 {
|
||||
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?;
|
||||
let pos = namespace.position();
|
||||
if !pos.is_none() {
|
||||
display_pos = format!(" @ {:?}", pos);
|
||||
}
|
||||
}
|
||||
f.write_str(&x.2)?;
|
||||
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(")")
|
||||
}
|
||||
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) => {
|
||||
let pos = x.span();
|
||||
if !pos.is_none() {
|
||||
display_pos = format!(" @ {:?}", pos);
|
||||
}
|
||||
f.write_str("ExprStmtBlock")?;
|
||||
f.debug_list().entries(x.iter()).finish()
|
||||
}
|
||||
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
||||
Self::Index(x, term, pos) => {
|
||||
display_pos = *pos;
|
||||
if !pos.is_none() {
|
||||
display_pos = format!(" @ {:?}", pos);
|
||||
}
|
||||
|
||||
f.debug_struct("Index")
|
||||
.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)
|
||||
.field("lhs", &x.lhs)
|
||||
@ -516,7 +528,7 @@ impl fmt::Debug for Expr {
|
||||
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.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn start_position(&self) -> Position {
|
||||
pub fn start_position(&self) -> Position {
|
||||
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, ..) => {
|
||||
x.lhs.start_position()
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
|
||||
use crate::engine::KEYWORD_EVAL;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::tokenizer::{Span, Token};
|
||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -134,7 +134,7 @@ pub struct TryCatchBlock {
|
||||
/// _(internals)_ A scoped block of statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub struct StmtBlock(StaticVec<Stmt>, (Position, Position));
|
||||
pub struct StmtBlock(StaticVec<Stmt>, Span);
|
||||
|
||||
impl StmtBlock {
|
||||
/// A [`StmtBlock`] that does not exist.
|
||||
@ -149,13 +149,13 @@ impl StmtBlock {
|
||||
) -> Self {
|
||||
let mut statements: StaticVec<_> = statements.into_iter().collect();
|
||||
statements.shrink_to_fit();
|
||||
Self(statements, (start_pos, end_pos))
|
||||
Self(statements, Span::new(start_pos, end_pos))
|
||||
}
|
||||
/// Create an empty [`StmtBlock`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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?
|
||||
#[inline(always)]
|
||||
@ -191,38 +191,34 @@ impl StmtBlock {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn position(&self) -> Position {
|
||||
(self.1).0
|
||||
(self.1).start()
|
||||
}
|
||||
/// Get the end position (location of the ending `}`) of this statements block.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn positions(&self) -> (Position, Position) {
|
||||
pub const fn span(&self) -> Span {
|
||||
self.1
|
||||
}
|
||||
/// Get the positions (locations of the beginning `{` and ending `}`) of this statements block
|
||||
/// or a default.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn positions_or_else(
|
||||
&self,
|
||||
def_start_pos: Position,
|
||||
def_end_pos: Position,
|
||||
) -> (Position, Position) {
|
||||
(
|
||||
(self.1).0.or_else(def_start_pos),
|
||||
(self.1).1.or_else(def_end_pos),
|
||||
pub const fn span_or_else(&self, def_start_pos: Position, def_end_pos: Position) -> Span {
|
||||
Span::new(
|
||||
(self.1).start().or_else(def_start_pos),
|
||||
(self.1).end().or_else(def_end_pos),
|
||||
)
|
||||
}
|
||||
/// Set the positions of this statements block.
|
||||
#[inline(always)]
|
||||
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 {
|
||||
f.write_str("Block")?;
|
||||
fmt::Debug::fmt(&self.0, f)?;
|
||||
(self.1).0.debug_print(f)?;
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
if !(self.1).1.is_none() {
|
||||
write!(f, "-{:?}", (self.1).1)?;
|
||||
if !self.1.is_none() {
|
||||
write!(f, " @ {:?}", self.1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -273,11 +267,11 @@ impl From<Stmt> for StmtBlock {
|
||||
#[inline]
|
||||
fn from(stmt: Stmt) -> Self {
|
||||
match stmt {
|
||||
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
||||
Stmt::Noop(pos) => Self(StaticVec::new_const(), (pos, pos)),
|
||||
Stmt::Block(mut block, span) => Self(block.iter_mut().map(mem::take).collect(), span),
|
||||
Stmt::Noop(pos) => Self(StaticVec::new_const(), Span::new(pos, pos)),
|
||||
_ => {
|
||||
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.
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// `{` stmt`;` ... `}`
|
||||
Block(Box<[Stmt]>, (Position, Position)),
|
||||
Block(Box<[Stmt]>, Span),
|
||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||
TryCatch(Box<TryCatchBlock>, Position),
|
||||
/// [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
|
||||
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Share(crate::Identifier),
|
||||
Share(crate::Identifier, Position),
|
||||
}
|
||||
|
||||
impl Default for Stmt {
|
||||
@ -408,11 +402,10 @@ impl Stmt {
|
||||
}
|
||||
/// Get the [position][Position] of this statement.
|
||||
#[must_use]
|
||||
pub const fn position(&self) -> Position {
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
| Self::BreakLoop(.., pos)
|
||||
| Self::Block(.., (pos, ..))
|
||||
| Self::Assignment(.., pos)
|
||||
| Self::FnCall(.., pos)
|
||||
| Self::If(.., pos)
|
||||
@ -424,6 +417,8 @@ impl Stmt {
|
||||
| Self::Var(.., pos)
|
||||
| Self::TryCatch(.., pos) => *pos,
|
||||
|
||||
Self::Block(.., span) => span.start(),
|
||||
|
||||
Self::Expr(x) => x.start_position(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -432,7 +427,7 @@ impl Stmt {
|
||||
Self::Export(.., pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Self::Share(..) => Position::NONE,
|
||||
Self::Share(.., pos) => *pos,
|
||||
}
|
||||
}
|
||||
/// Override the [position][Position] of this statement.
|
||||
@ -440,7 +435,6 @@ impl Stmt {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
| Self::BreakLoop(.., pos)
|
||||
| Self::Block(.., (pos, ..))
|
||||
| Self::Assignment(.., pos)
|
||||
| Self::FnCall(.., pos)
|
||||
| Self::If(.., pos)
|
||||
@ -452,6 +446,8 @@ impl Stmt {
|
||||
| Self::Var(.., pos)
|
||||
| Self::TryCatch(.., pos) => *pos = new_pos,
|
||||
|
||||
Self::Block(.., span) => *span = Span::new(new_pos, span.end()),
|
||||
|
||||
Self::Expr(x) => {
|
||||
x.set_position(new_pos);
|
||||
}
|
||||
@ -462,7 +458,7 @@ impl Stmt {
|
||||
Self::Export(.., pos) => *pos = new_pos,
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Self::Share(..) => (),
|
||||
Self::Share(.., pos) => *pos = new_pos,
|
||||
}
|
||||
|
||||
self
|
||||
|
@ -10,28 +10,45 @@ use std::{
|
||||
};
|
||||
|
||||
/// Pretty-print source line.
|
||||
fn print_source(lines: &[String], pos: Position, offset: 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
|
||||
fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, usize)) {
|
||||
if pos.is_none() {
|
||||
// No position
|
||||
println!();
|
||||
} else {
|
||||
// Specific position - print line text
|
||||
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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,
|
||||
source: Option<&str>,
|
||||
pos: Position,
|
||||
lines: &Vec<String>,
|
||||
lines: &[String],
|
||||
window: (usize, usize),
|
||||
) {
|
||||
let current_source = &mut *context
|
||||
.global_runtime_state_mut()
|
||||
@ -58,7 +76,7 @@ fn print_current_source(
|
||||
println!("{} @ {:?}", src, pos);
|
||||
} else {
|
||||
// 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!("scope => print the scope");
|
||||
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");
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
println!("imports => print all imported modules");
|
||||
println!("node => print the current AST node");
|
||||
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!("info break, i b => print all break-points");
|
||||
println!("enable/en <bp#> => enable a break-point");
|
||||
@ -122,17 +142,19 @@ fn print_debug_help() {
|
||||
println!(
|
||||
"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!("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!("finish, f => continue until the end of the current function call");
|
||||
println!("continue, c => continue normal execution");
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Display the scope.
|
||||
/// Display the current scope.
|
||||
fn print_scope(scope: &Scope, dedup: bool) {
|
||||
let flattened_clone;
|
||||
let scope = if dedup {
|
||||
@ -167,27 +189,19 @@ fn print_scope(scope: &Scope, dedup: bool) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
let mut script = String::new();
|
||||
let main_ast;
|
||||
|
||||
{
|
||||
// Load init scripts
|
||||
// Load script to debug.
|
||||
fn load_script(engine: &Engine) -> (rhai::AST, String) {
|
||||
if let Some(filename) = env::args().skip(1).next() {
|
||||
let mut contents = String::new();
|
||||
|
||||
let filename = match Path::new(&filename).canonicalize() {
|
||||
Err(err) => {
|
||||
eprintln!("Error script file path: {}\n{}", filename, err);
|
||||
eprintln!(
|
||||
"\x1b[31mError script file path: {}\n{}\x1b[39m",
|
||||
filename, err
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
Ok(f) => {
|
||||
@ -201,7 +215,7 @@ fn main() {
|
||||
let mut f = match File::open(&filename) {
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Error reading script file: {}\n{}",
|
||||
"\x1b[31mError reading script file: {}\n{}\x1b[39m",
|
||||
filename.to_string_lossy(),
|
||||
err
|
||||
);
|
||||
@ -210,7 +224,7 @@ fn main() {
|
||||
Ok(f) => f,
|
||||
};
|
||||
|
||||
if let Err(err) = f.read_to_string(&mut script) {
|
||||
if let Err(err) = f.read_to_string(&mut contents) {
|
||||
println!(
|
||||
"Error reading script file: {}\n{}",
|
||||
filename.to_string_lossy(),
|
||||
@ -219,40 +233,43 @@ fn main() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let script = if script.starts_with("#!") {
|
||||
let script = if contents.starts_with("#!") {
|
||||
// Skip shebang
|
||||
&script[script.find('\n').unwrap_or(0)..]
|
||||
&contents[contents.find('\n').unwrap_or(0)..]
|
||||
} else {
|
||||
&script[..]
|
||||
&contents[..]
|
||||
};
|
||||
|
||||
main_ast = match engine
|
||||
.compile(&script)
|
||||
let ast = match engine
|
||||
.compile(script)
|
||||
.map_err(Into::<Box<EvalAltResult>>::into)
|
||||
{
|
||||
Err(err) => {
|
||||
print_error(&script, *err);
|
||||
print_error(script, *err);
|
||||
exit(1);
|
||||
}
|
||||
Ok(ast) => ast,
|
||||
};
|
||||
|
||||
println!("Script '{}' loaded.", filename.to_string_lossy());
|
||||
println!();
|
||||
|
||||
(ast, contents)
|
||||
} else {
|
||||
eprintln!("No script file specified.");
|
||||
eprintln!("\x1b[31mNo script file specified.\x1b[39m");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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| {
|
||||
// Main callback for debugging.
|
||||
fn debug_callback(
|
||||
context: &mut rhai::EvalContext,
|
||||
event: DebuggerEvent,
|
||||
node: rhai::ASTNode,
|
||||
source: Option<&str>,
|
||||
pos: Position,
|
||||
lines: &[String],
|
||||
) -> Result<DebuggerCommand, Box<EvalAltResult>> {
|
||||
// Check event
|
||||
match event {
|
||||
DebuggerEvent::Step => (),
|
||||
DebuggerEvent::BreakPoint(n) => {
|
||||
@ -298,13 +315,14 @@ fn main() {
|
||||
}
|
||||
|
||||
// Print current source line
|
||||
print_current_source(context, source, pos, &lines);
|
||||
print_current_source(context, source, pos, lines, (0, 0));
|
||||
|
||||
// Read stdin for commands
|
||||
let mut input = String::new();
|
||||
|
||||
loop {
|
||||
print!("rhai-dbg> ");
|
||||
|
||||
stdout().flush().expect("couldn't flush stdout");
|
||||
|
||||
input.clear();
|
||||
@ -317,36 +335,59 @@ fn main() {
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice()
|
||||
{
|
||||
["help" | "h", ..] => print_debug_help(),
|
||||
["help" | "h"] => print_debug_help(),
|
||||
["exit" | "quit" | "q" | "kill", ..] => {
|
||||
println!("Script terminated. Bye!");
|
||||
exit(0);
|
||||
}
|
||||
["node", ..] => {
|
||||
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
||||
["node"] => {
|
||||
if pos.is_none() {
|
||||
println!("{:?}", node);
|
||||
} else if let Some(source) = source {
|
||||
println!("{:?} {} @ {:?}", node, source, pos);
|
||||
} else {
|
||||
println!("{:?} @ {:?}", node, pos);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
["list" | "l", ..] => print_current_source(context, source, pos, &lines),
|
||||
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
|
||||
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
|
||||
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
|
||||
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
||||
["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!("=> ()");
|
||||
["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)),
|
||||
["list" | "l", n] if n.parse::<usize>().is_ok() => {
|
||||
let num = n.parse::<usize>().unwrap();
|
||||
if num <= 0 || num > lines.len() {
|
||||
eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num);
|
||||
} 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 {
|
||||
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"))]
|
||||
["imports", ..] => {
|
||||
["imports"] => {
|
||||
for (i, (name, module)) in context
|
||||
.global_runtime_state()
|
||||
.scan_imports_raw()
|
||||
@ -363,7 +404,7 @@ fn main() {
|
||||
println!();
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
["backtrace" | "bt", ..] => {
|
||||
["backtrace" | "bt"] => {
|
||||
for frame in context
|
||||
.global_runtime_state()
|
||||
.debugger
|
||||
@ -374,7 +415,7 @@ fn main() {
|
||||
println!("{}", frame)
|
||||
}
|
||||
}
|
||||
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
|
||||
["info" | "i", "break" | "b"] => Iterator::for_each(
|
||||
context
|
||||
.global_runtime_state()
|
||||
.debugger
|
||||
@ -386,12 +427,12 @@ fn main() {
|
||||
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
|
||||
let line_num = format!("[{}] line ", i + 1);
|
||||
print!("{}", line_num);
|
||||
print_source(&lines, *pos, line_num.len());
|
||||
print_source(&lines, *pos, line_num.len(), (0, 0));
|
||||
}
|
||||
_ => println!("[{}] {}", i + 1, bp),
|
||||
},
|
||||
),
|
||||
["enable" | "en", n, ..] => {
|
||||
["enable" | "en", n] => {
|
||||
if let Ok(n) = n.parse::<usize>() {
|
||||
let range = 1..=context
|
||||
.global_runtime_state_mut()
|
||||
@ -408,13 +449,13 @@ fn main() {
|
||||
.enable(true);
|
||||
println!("Break-point #{} enabled.", n)
|
||||
} else {
|
||||
eprintln!("Invalid break-point: {}", n);
|
||||
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||
}
|
||||
} 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>() {
|
||||
let range = 1..=context
|
||||
.global_runtime_state_mut()
|
||||
@ -431,13 +472,13 @@ fn main() {
|
||||
.enable(false);
|
||||
println!("Break-point #{} disabled.", n)
|
||||
} else {
|
||||
eprintln!("Invalid break-point: {}", n);
|
||||
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||
}
|
||||
} 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>() {
|
||||
let range = 1..=context
|
||||
.global_runtime_state_mut()
|
||||
@ -452,13 +493,13 @@ fn main() {
|
||||
.remove(n - 1);
|
||||
println!("Break-point #{} deleted.", n)
|
||||
} else {
|
||||
eprintln!("Invalid break-point: {}", n);
|
||||
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid break-point: '{}'", n);
|
||||
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||
}
|
||||
}
|
||||
["delete" | "d", ..] => {
|
||||
["delete" | "d"] => {
|
||||
context
|
||||
.global_runtime_state_mut()
|
||||
.debugger
|
||||
@ -466,7 +507,7 @@ fn main() {
|
||||
.clear();
|
||||
println!("All break-points deleted.");
|
||||
}
|
||||
["break" | "b", fn_name, args, ..] => {
|
||||
["break" | "b", fn_name, args] => {
|
||||
if let Ok(args) = args.parse::<usize>() {
|
||||
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
||||
name: fn_name.trim().into(),
|
||||
@ -480,7 +521,7 @@ fn main() {
|
||||
.break_points_mut()
|
||||
.push(bp);
|
||||
} else {
|
||||
eprintln!("Invalid number of arguments: '{}'", args);
|
||||
eprintln!("\x1b[31mInvalid number of arguments: '{}'\x1b[39m", args);
|
||||
}
|
||||
}
|
||||
// Property name
|
||||
@ -520,7 +561,7 @@ fn main() {
|
||||
.break_points_mut()
|
||||
.push(bp);
|
||||
} else {
|
||||
eprintln!("Invalid line number: {}", n);
|
||||
eprintln!("\x1b[31mInvalid line number: '{}'\x1b[39m", n);
|
||||
}
|
||||
}
|
||||
// Function name parameter
|
||||
@ -550,9 +591,7 @@ fn main() {
|
||||
.break_points_mut()
|
||||
.push(bp);
|
||||
}
|
||||
["throw"] => {
|
||||
break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
|
||||
}
|
||||
["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()),
|
||||
["throw", num] if num.trim().parse::<INT>().is_ok() => {
|
||||
let value = num.trim().parse::<INT>().unwrap().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("");
|
||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||
}
|
||||
["run" | "r", ..] => {
|
||||
["run" | "r"] => {
|
||||
println!("Restarting script...");
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
print_debug_help();
|
||||
println!("Type 'help' for commands list.");
|
||||
println!();
|
||||
|
||||
// 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 {
|
||||
// Loop back to restart
|
||||
EvalAltResult::ErrorTerminated(..) => (),
|
||||
|
@ -10,7 +10,7 @@ const HISTORY_FILE: &str = ".rhai-repl-history";
|
||||
|
||||
/// Pretty-print error.
|
||||
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 line_no = if lines.len() > 1 {
|
||||
@ -60,6 +60,7 @@ fn print_help() {
|
||||
#[cfg(feature = "metadata")]
|
||||
println!("json => output all functions in JSON format");
|
||||
println!("ast => print the last AST (optimized)");
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
println!("astu => print the last raw, un-optimized AST");
|
||||
println!();
|
||||
println!("press Ctrl-Enter or end a line with `\\`");
|
||||
@ -84,6 +85,7 @@ fn print_keys() {
|
||||
println!("Ctrl-R => reverse search history");
|
||||
println!(" (Ctrl-S forward, Ctrl-G cancel)");
|
||||
println!("Ctrl-L => clear screen");
|
||||
#[cfg(target_family = "windows")]
|
||||
println!("Escape => clear all input");
|
||||
println!("Ctrl-C => exit");
|
||||
println!("Ctrl-D => EOF (when line empty)");
|
||||
@ -94,7 +96,10 @@ fn print_keys() {
|
||||
println!("Ctrl-T => transpose characters");
|
||||
println!("Ctrl-V => insert special character");
|
||||
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!("Enter => run code");
|
||||
println!("Shift-Ctrl-Enter => continue to next line");
|
||||
@ -311,6 +316,7 @@ fn main() {
|
||||
let mut history_offset = 1;
|
||||
|
||||
let mut main_ast = AST::empty();
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
let mut ast_u = AST::empty();
|
||||
let mut ast = AST::empty();
|
||||
|
||||
@ -344,13 +350,13 @@ fn main() {
|
||||
// Line continuation
|
||||
Ok(mut line) if line.ends_with("\\") => {
|
||||
line.pop();
|
||||
input += line.trim_end();
|
||||
input += &line;
|
||||
input.push('\n');
|
||||
}
|
||||
Ok(line) => {
|
||||
input += line.trim_end();
|
||||
if !input.is_empty() && !input.starts_with('!') && input.trim() != "history"
|
||||
{
|
||||
input += &line;
|
||||
let cmd = input.trim();
|
||||
if !cmd.is_empty() && !cmd.starts_with('!') && cmd.trim() != "history" {
|
||||
if rl.add_history_entry(input.clone()) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Implement standard commands
|
||||
match script {
|
||||
match cmd {
|
||||
"help" => {
|
||||
print_help();
|
||||
continue;
|
||||
@ -429,6 +435,7 @@ fn main() {
|
||||
print_scope(&scope);
|
||||
continue;
|
||||
}
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
"astu" => {
|
||||
// print the last un-optimized AST
|
||||
println!("{:#?}\n", ast_u);
|
||||
@ -473,8 +480,8 @@ fn main() {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ if script.starts_with("!?") => {
|
||||
let text = script[2..].trim();
|
||||
_ if cmd.starts_with("!?") => {
|
||||
let text = cmd[2..].trim();
|
||||
if let Some((n, line)) = rl
|
||||
.history()
|
||||
.iter()
|
||||
@ -489,8 +496,8 @@ fn main() {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ if script.starts_with('!') => {
|
||||
if let Ok(num) = script[1..].parse::<usize>() {
|
||||
_ if cmd.starts_with('!') => {
|
||||
if let Ok(num) = cmd[1..].parse::<usize>() {
|
||||
if num >= history_offset {
|
||||
if let Some(line) = rl.history().get(num - history_offset) {
|
||||
replacement = Some(line.clone());
|
||||
@ -499,7 +506,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let prefix = script[1..].trim();
|
||||
let prefix = cmd[1..].trim();
|
||||
if let Some((n, line)) = rl
|
||||
.history()
|
||||
.iter()
|
||||
@ -512,20 +519,20 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
eprintln!("History line not found: {}", &script[1..]);
|
||||
eprintln!("History line not found: {}", &cmd[1..]);
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match engine
|
||||
.compile_with_scope(&scope, &script)
|
||||
.compile_with_scope(&scope, &input)
|
||||
.map_err(Into::into)
|
||||
.and_then(|r| {
|
||||
ast_u = r.clone();
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
{
|
||||
ast_u = r.clone();
|
||||
|
||||
ast = engine.optimize_ast(&scope, r, optimize_level);
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,7 @@
|
||||
use super::{EvalState, GlobalRuntimeState, Target};
|
||||
use crate::ast::{Expr, OpAssignment};
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{
|
||||
Dynamic, Engine, Module, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR,
|
||||
};
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
|
||||
use std::hash::Hash;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -149,7 +147,6 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ChainType::Indexing => {
|
||||
let pos = rhs.start_position();
|
||||
let root_pos = idx_val.position();
|
||||
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
||||
|
||||
match rhs {
|
||||
@ -185,20 +182,15 @@ impl Engine {
|
||||
|
||||
if let Some(mut new_val) = try_setter {
|
||||
// Try to call index setter if value is changed
|
||||
let hash_set =
|
||||
crate::ast::FnCallHashes::from_native(global.hash_idx_set());
|
||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
||||
let fn_name = crate::engine::FN_IDX_SET;
|
||||
|
||||
if let Err(err) = self.exec_fn_call(
|
||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||
true, root_pos, level,
|
||||
) {
|
||||
// Just ignore if there is no index setter
|
||||
if !matches!(*err, ERR::ErrorFunctionNotFound(..)) {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
let idx = &mut idx_val_for_setter;
|
||||
let new_val = &mut new_val;
|
||||
self.call_indexer_set(
|
||||
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||
)
|
||||
.or_else(|idx_err| match *idx_err {
|
||||
ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)),
|
||||
_ => Err(idx_err),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
@ -234,14 +226,10 @@ impl Engine {
|
||||
|
||||
if let Some(mut new_val) = try_setter {
|
||||
// Try to call index setter
|
||||
let hash_set =
|
||||
crate::ast::FnCallHashes::from_native(global.hash_idx_set());
|
||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
||||
let fn_name = crate::engine::FN_IDX_SET;
|
||||
|
||||
self.exec_fn_call(
|
||||
None, global, state, lib, fn_name, hash_set, args, is_ref_mut,
|
||||
true, root_pos, level,
|
||||
let idx = &mut idx_val_for_setter;
|
||||
let new_val = &mut new_val;
|
||||
self.call_indexer_set(
|
||||
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -341,12 +329,10 @@ impl Engine {
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
ERR::ErrorDotExpr(..) => {
|
||||
let prop = name.into();
|
||||
self.get_indexed_mut(
|
||||
global, state, lib, target, prop, *pos, false, true,
|
||||
level,
|
||||
let mut prop = name.into();
|
||||
self.call_indexer_get(
|
||||
global, state, lib, target, &mut prop, level,
|
||||
)
|
||||
.map(|v| (v.take_or_clone(), false))
|
||||
.map_err(
|
||||
|idx_err| match *idx_err {
|
||||
ERR::ErrorIndexingType(..) => err,
|
||||
@ -382,15 +368,10 @@ impl Engine {
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
ERR::ErrorDotExpr(..) => {
|
||||
let args = &mut [target, &mut name.into(), &mut new_val];
|
||||
let fn_name = crate::engine::FN_IDX_SET;
|
||||
let hash_set =
|
||||
crate::ast::FnCallHashes::from_native(global.hash_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,
|
||||
let idx = &mut name.into();
|
||||
let new_val = &mut new_val;
|
||||
self.call_indexer_set(
|
||||
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||
)
|
||||
.map_err(
|
||||
|idx_err| match *idx_err {
|
||||
@ -418,11 +399,10 @@ impl Engine {
|
||||
|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
ERR::ErrorDotExpr(..) => {
|
||||
let prop = name.into();
|
||||
self.get_indexed_mut(
|
||||
global, state, lib, target, prop, *pos, false, true, level,
|
||||
let mut prop = name.into();
|
||||
self.call_indexer_get(
|
||||
global, state, lib, target, &mut prop, level,
|
||||
)
|
||||
.map(|v| (v.take_or_clone(), false))
|
||||
.map_err(|idx_err| {
|
||||
match *idx_err {
|
||||
ERR::ErrorIndexingType(..) => err,
|
||||
@ -517,12 +497,10 @@ impl Engine {
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
ERR::ErrorDotExpr(..) => {
|
||||
let prop = name.into();
|
||||
self.get_indexed_mut(
|
||||
global, state, lib, target, prop, pos, false, true,
|
||||
level,
|
||||
let mut prop = name.into();
|
||||
self.call_indexer_get(
|
||||
global, state, lib, target, &mut prop, level,
|
||||
)
|
||||
.map(|v| (v.take_or_clone(), false))
|
||||
.map_err(
|
||||
|idx_err| match *idx_err {
|
||||
ERR::ErrorIndexingType(..) => err,
|
||||
@ -566,21 +544,16 @@ impl Engine {
|
||||
|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
ERR::ErrorDotExpr(..) => {
|
||||
let args =
|
||||
&mut [target.as_mut(), &mut name.into(), val];
|
||||
let fn_name = crate::engine::FN_IDX_SET;
|
||||
let hash_set =
|
||||
crate::ast::FnCallHashes::from_native(
|
||||
global.hash_idx_set(),
|
||||
);
|
||||
self.exec_fn_call(
|
||||
None, global, state, lib, fn_name, hash_set,
|
||||
args, is_ref_mut, true, pos, level,
|
||||
let idx = &mut name.into();
|
||||
let new_val = val;
|
||||
self.call_indexer_set(
|
||||
global, state, lib, target, idx, new_val,
|
||||
is_ref_mut, level,
|
||||
)
|
||||
.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(..) => {
|
||||
// If there is no setter, no need to feed it back because
|
||||
// the property is read-only
|
||||
Ok((Dynamic::UNIT, false))
|
||||
}
|
||||
_ => Err(idx_err),
|
||||
@ -743,7 +716,7 @@ impl Engine {
|
||||
pos = arg_pos;
|
||||
}
|
||||
values.push(value.flatten());
|
||||
Ok::<_, RhaiError>((values, pos))
|
||||
Ok::<_, crate::RhaiError>((values, pos))
|
||||
},
|
||||
)?;
|
||||
|
||||
@ -789,7 +762,7 @@ impl Engine {
|
||||
pos = arg_pos
|
||||
}
|
||||
values.push(value.flatten());
|
||||
Ok::<_, RhaiError>((values, pos))
|
||||
Ok::<_, crate::RhaiError>((values, pos))
|
||||
},
|
||||
)?;
|
||||
super::ChainArgument::from_fn_call_args(values, pos)
|
||||
@ -842,6 +815,52 @@ impl Engine {
|
||||
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.
|
||||
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
||||
fn get_indexed_mut<'t>(
|
||||
@ -851,7 +870,7 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
target: &'t mut Dynamic,
|
||||
idx: Dynamic,
|
||||
pos: Position,
|
||||
idx_pos: Position,
|
||||
add_if_not_found: bool,
|
||||
use_indexers: bool,
|
||||
level: usize,
|
||||
@ -868,10 +887,10 @@ impl Engine {
|
||||
// val_array[idx]
|
||||
let index = idx
|
||||
.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 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())
|
||||
@ -882,10 +901,10 @@ impl Engine {
|
||||
// val_blob[idx]
|
||||
let index = idx
|
||||
.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 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();
|
||||
@ -901,17 +920,20 @@ impl Engine {
|
||||
Dynamic(Union::Map(map, ..)) => {
|
||||
// val_map[idx]
|
||||
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()) {
|
||||
map.insert(index.clone().into(), Dynamic::UNIT);
|
||||
}
|
||||
|
||||
Ok(map
|
||||
.get_mut(index.as_str())
|
||||
.map(Target::from)
|
||||
.unwrap_or_else(|| Target::from(Dynamic::UNIT)))
|
||||
if let Some(value) = map.get_mut(index.as_str()) {
|
||||
Ok(Target::from(value))
|
||||
} else if self.fail_on_invalid_map_property() {
|
||||
Err(ERR::ErrorPropertyNotFound(index.to_string(), idx_pos).into())
|
||||
} else {
|
||||
Ok(Target::from(Dynamic::UNIT))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -926,10 +948,10 @@ impl Engine {
|
||||
let end = range.end;
|
||||
|
||||
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, || {
|
||||
ERR::ErrorBitFieldBounds(BITS, end, pos).into()
|
||||
ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()
|
||||
})?;
|
||||
|
||||
if end <= start {
|
||||
@ -951,10 +973,10 @@ impl Engine {
|
||||
let end = *range.end();
|
||||
|
||||
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, || {
|
||||
ERR::ErrorBitFieldBounds(BITS, end, pos).into()
|
||||
ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()
|
||||
})?;
|
||||
|
||||
if end < start {
|
||||
@ -990,12 +1012,12 @@ impl Engine {
|
||||
// val_int[idx]
|
||||
let index = idx
|
||||
.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;
|
||||
|
||||
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;
|
||||
@ -1012,14 +1034,14 @@ impl Engine {
|
||||
// val_string[idx]
|
||||
let index = idx
|
||||
.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 offset = index as usize;
|
||||
(
|
||||
s.chars().nth(offset).ok_or_else(|| {
|
||||
let chars_len = s.chars().count();
|
||||
ERR::ErrorStringBounds(chars_len, index, pos)
|
||||
ERR::ErrorStringBounds(chars_len, index, idx_pos)
|
||||
})?,
|
||||
offset,
|
||||
)
|
||||
@ -1029,13 +1051,13 @@ impl Engine {
|
||||
// Count from end if negative
|
||||
s.chars().rev().nth(offset - 1).ok_or_else(|| {
|
||||
let chars_len = s.chars().count();
|
||||
ERR::ErrorStringBounds(chars_len, index, pos)
|
||||
ERR::ErrorStringBounds(chars_len, index, idx_pos)
|
||||
})?,
|
||||
offset,
|
||||
)
|
||||
} else {
|
||||
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 {
|
||||
@ -1045,17 +1067,9 @@ impl Engine {
|
||||
})
|
||||
}
|
||||
|
||||
_ if use_indexers => {
|
||||
let args = &mut [target, &mut 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,
|
||||
)
|
||||
.map(|(v, ..)| v.into())
|
||||
}
|
||||
_ if use_indexers => self
|
||||
.call_indexer_get(global, state, lib, target, &mut idx, level)
|
||||
.map(|(v, ..)| v.into()),
|
||||
|
||||
_ => Err(ERR::ErrorIndexingType(
|
||||
format!(
|
||||
|
@ -327,8 +327,7 @@ impl Engine {
|
||||
Expr::Unit(..) => Ok(Dynamic::UNIT),
|
||||
|
||||
// `... ${...} ...`
|
||||
Expr::InterpolatedString(x, pos) => {
|
||||
let mut pos = *pos;
|
||||
Expr::InterpolatedString(x, _) => {
|
||||
let mut concat: Dynamic = self.const_empty_string().into();
|
||||
let mut result = Ok(Dynamic::UNIT);
|
||||
|
||||
@ -347,7 +346,7 @@ impl Engine {
|
||||
state,
|
||||
lib,
|
||||
Some(OpAssignment::new(OP_CONCAT)),
|
||||
pos,
|
||||
expr.start_position(),
|
||||
&mut (&mut concat).into(),
|
||||
("", Position::NONE),
|
||||
item,
|
||||
@ -356,8 +355,6 @@ impl Engine {
|
||||
result = Err(err.fill_position(expr.start_position()));
|
||||
break;
|
||||
}
|
||||
|
||||
pos = expr.start_position();
|
||||
}
|
||||
|
||||
result.map(|_| concat)
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Module defining functions for evaluating a statement.
|
||||
|
||||
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::ast::{
|
||||
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
@ -818,9 +819,15 @@ impl Engine {
|
||||
let export = options.contains(AST_OPTION_EXPORTED);
|
||||
|
||||
let result = if let Some(ref filter) = self.def_var_filter {
|
||||
let shadowing = scope.contains(var_name);
|
||||
let scope_level = state.scope_level;
|
||||
let will_shadow = scope.contains(var_name);
|
||||
let nesting_level = state.scope_level;
|
||||
let is_const = entry_type == AccessMode::ReadOnly;
|
||||
let info = VarDefInfo {
|
||||
name: var_name,
|
||||
is_const,
|
||||
nesting_level,
|
||||
will_shadow,
|
||||
};
|
||||
let context = EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
@ -828,16 +835,16 @@ impl Engine {
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
level: level,
|
||||
level,
|
||||
};
|
||||
|
||||
match filter(var_name, is_const, scope_level, shadowing, &context) {
|
||||
match filter(true, info, &context) {
|
||||
Ok(true) => None,
|
||||
Ok(false) => Some(Err(ERR::ErrorRuntime(
|
||||
format!("Variable cannot be defined: {}", var_name).into(),
|
||||
*pos,
|
||||
)
|
||||
.into())),
|
||||
Ok(false) => {
|
||||
Some(Err(
|
||||
ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into()
|
||||
))
|
||||
}
|
||||
err @ Err(_) => Some(err),
|
||||
}
|
||||
} else {
|
||||
@ -977,7 +984,7 @@ impl Engine {
|
||||
|
||||
// Share statement
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(name) => {
|
||||
Stmt::Share(name, ..) => {
|
||||
if let Some((index, ..)) = scope.get_index(name) {
|
||||
let val = scope.get_mut_by_index(index);
|
||||
|
||||
@ -985,6 +992,8 @@ impl Engine {
|
||||
// Replace the variable with a shared value.
|
||||
*val = std::mem::take(val).into_shared();
|
||||
}
|
||||
} else {
|
||||
unreachable!("variable {} not found for sharing", name);
|
||||
}
|
||||
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"))]
|
||||
{
|
||||
// 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>()) {
|
||||
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.
|
||||
if type2 != type1 {
|
||||
return None;
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Module defining interfaces to native-Rust functions.
|
||||
|
||||
use super::call::FnCallArgs;
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::ast::FnCallHashes;
|
||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||
use crate::plugin::PluginFunction;
|
||||
@ -447,8 +448,8 @@ pub type OnVarCallback =
|
||||
|
||||
/// Callback function for variable definition.
|
||||
#[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.
|
||||
#[cfg(feature = "sync")]
|
||||
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>;
|
||||
|
||||
pub use api::custom_syntax::Expression;
|
||||
pub use api::events::VarDefInfo;
|
||||
pub use ast::{FnAccess, AST};
|
||||
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
|
||||
pub use eval::EvalContext;
|
||||
@ -247,7 +248,7 @@ pub use tokenizer::{get_next_token, parse_string_literal};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
pub use tokenizer::{
|
||||
InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
|
||||
InputStream, MultiInputsStream, Span, Token, TokenIterator, TokenizeState, TokenizerControl,
|
||||
TokenizerControlBlock,
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
use crate::ast::Ident;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::StaticVec;
|
||||
use crate::{Position, StaticVec};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
@ -12,7 +12,7 @@ use std::{
|
||||
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.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
@ -114,4 +114,12 @@ impl Namespace {
|
||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||
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::func::builtin::get_builtin_binary_op_fn;
|
||||
use crate::func::hashing::get_hasher;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::tokenizer::{Span, Token};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{
|
||||
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 }
|
||||
Stmt::Block(
|
||||
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
|
||||
(pos, Position::NONE),
|
||||
Span::new(pos, Position::NONE),
|
||||
)
|
||||
} else {
|
||||
// -> 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)
|
||||
{
|
||||
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
|
||||
@ -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)
|
||||
{
|
||||
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 }
|
||||
@ -534,7 +534,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
mem::take(&mut block.statements),
|
||||
Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||
x.def_case.span_or_else(*pos, Position::NONE),
|
||||
)
|
||||
.into(),
|
||||
)),
|
||||
@ -549,8 +549,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
true,
|
||||
false,
|
||||
);
|
||||
*stmt =
|
||||
Stmt::Block(statements.into_boxed_slice(), block.statements.positions());
|
||||
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span());
|
||||
}
|
||||
|
||||
state.set_dirty();
|
||||
@ -590,7 +589,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
mem::take(&mut block.statements),
|
||||
Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||
x.def_case.span_or_else(*pos, Position::NONE),
|
||||
)
|
||||
.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 =
|
||||
optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt = Stmt::Block(
|
||||
statements.into_boxed_slice(),
|
||||
block.statements.positions(),
|
||||
);
|
||||
*stmt =
|
||||
Stmt::Block(statements.into_boxed_slice(), block.statements.span());
|
||||
}
|
||||
|
||||
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);
|
||||
*stmt = Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.def_case.positions_or_else(*pos, Position::NONE),
|
||||
x.def_case.span_or_else(*pos, Position::NONE),
|
||||
);
|
||||
}
|
||||
// switch
|
||||
@ -708,8 +705,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
if preserve_result {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
*stmt =
|
||||
Stmt::Block(statements.into_boxed_slice(), (pos, Position::NONE));
|
||||
*stmt = Stmt::Block(
|
||||
statements.into_boxed_slice(),
|
||||
Span::new(pos, Position::NONE),
|
||||
);
|
||||
} else {
|
||||
*stmt = Stmt::Noop(pos);
|
||||
};
|
||||
@ -726,7 +725,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
||||
.into_boxed_slice(),
|
||||
body.positions(),
|
||||
body.span(),
|
||||
);
|
||||
}
|
||||
// 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"))]
|
||||
Stmt::Import(expr, ..) => optimize_expr(expr, state, false),
|
||||
// { block }
|
||||
Stmt::Block(statements, pos) => {
|
||||
Stmt::Block(statements, span) => {
|
||||
let statements = mem::take(statements).into_vec().into();
|
||||
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
match block.as_mut_slice() {
|
||||
[] => {
|
||||
state.set_dirty();
|
||||
*stmt = Stmt::Noop(pos.0);
|
||||
*stmt = Stmt::Noop(span.start());
|
||||
}
|
||||
// Only one statement which is not block-dependent - promote
|
||||
[s] if !s.is_block_dependent() => {
|
||||
state.set_dirty();
|
||||
*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
|
||||
@ -771,7 +770,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
||||
.into_boxed_slice(),
|
||||
x.try_block.positions(),
|
||||
x.try_block.span(),
|
||||
);
|
||||
}
|
||||
// try { try_block } catch ( var ) { catch_block }
|
||||
|
@ -190,7 +190,7 @@ macro_rules! reg_functions {
|
||||
|
||||
def_package! {
|
||||
/// Basic arithmetic package.
|
||||
crate::ArithmeticPackage => |lib| {
|
||||
pub ArithmeticPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "int", int_functions);
|
||||
|
@ -13,7 +13,7 @@ use std::{any::TypeId, cmp::Ordering, mem};
|
||||
|
||||
def_package! {
|
||||
/// Package of basic array utilities.
|
||||
crate::BasicArrayPackage => |lib| {
|
||||
pub BasicArrayPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "array", array_functions);
|
||||
|
@ -8,7 +8,7 @@ use std::prelude::v1::*;
|
||||
|
||||
def_package! {
|
||||
/// Package of basic bit-field utilities.
|
||||
crate::BitFieldPackage => |lib| {
|
||||
pub BitFieldPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "bit_field", bit_field_functions);
|
||||
|
@ -3,7 +3,7 @@
|
||||
use crate::eval::{calc_index, calc_offset_len};
|
||||
use crate::plugin::*;
|
||||
use crate::{
|
||||
def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
|
||||
def_package, Array, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
|
||||
RhaiResultOf, INT,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -20,7 +20,7 @@ const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
|
||||
|
||||
def_package! {
|
||||
/// Package of basic BLOB utilities.
|
||||
crate::BasicBlobPackage => |lib| {
|
||||
pub BasicBlobPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "blob", blob_functions);
|
||||
@ -94,12 +94,36 @@ pub mod blob_functions {
|
||||
blob.resize(len, (value & 0x000000ff) as u8);
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rhai
|
||||
/// let b = blob(10, 0x42);
|
||||
///
|
||||
/// print(b); // prints "[4242424242424242 4242]"
|
||||
///
|
||||
/// print(b.len()); // prints 10
|
||||
/// ```
|
||||
#[rhai_fn(name = "len", get = "len", pure)]
|
||||
pub fn len(blob: &mut Blob) -> INT {
|
||||
blob.len() as INT
|
||||
}
|
||||
|
||||
/// 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).
|
||||
@ -196,15 +220,49 @@ pub mod blob_functions {
|
||||
///
|
||||
/// print(b1); // prints "[4242424242111111]"
|
||||
/// ```
|
||||
pub fn append(blob: &mut Blob, y: Blob) {
|
||||
if !y.is_empty() {
|
||||
if blob.is_empty() {
|
||||
*blob = y;
|
||||
pub fn append(blob1: &mut Blob, blob2: Blob) {
|
||||
if !blob2.is_empty() {
|
||||
if blob1.is_empty() {
|
||||
*blob1 = blob2;
|
||||
} 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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -6,19 +6,17 @@ use crate::plugin::*;
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[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_index"))]
|
||||
use crate::Array;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::Map;
|
||||
|
||||
def_package! {
|
||||
/// Package of basic debugging utilities.
|
||||
crate::DebuggingPackage => |lib| {
|
||||
pub DebuggingPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "debugging", debugging_functions);
|
||||
|
@ -5,7 +5,7 @@ use std::prelude::v1::*;
|
||||
|
||||
def_package! {
|
||||
/// Package of basic function pointer utilities.
|
||||
crate::BasicFnPackage => |lib| {
|
||||
pub BasicFnPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
||||
|
@ -299,7 +299,7 @@ macro_rules! reg_range {
|
||||
|
||||
def_package! {
|
||||
/// Package of basic range iterators
|
||||
crate::BasicIteratorPackage => |lib| {
|
||||
pub BasicIteratorPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
reg_range!(lib | "range" => INT);
|
||||
|
@ -7,7 +7,7 @@ use std::prelude::v1::*;
|
||||
|
||||
def_package! {
|
||||
/// Package of core language features.
|
||||
crate::LanguageCorePackage => |lib| {
|
||||
pub LanguageCorePackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "core", core_functions);
|
||||
|
@ -37,7 +37,7 @@ macro_rules! reg_functions {
|
||||
|
||||
def_package! {
|
||||
/// Package of basic logic operators.
|
||||
crate::LogicPackage => |lib| {
|
||||
pub LogicPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
|
@ -11,7 +11,7 @@ use crate::Array;
|
||||
|
||||
def_package! {
|
||||
/// Package of basic object map utilities.
|
||||
crate::BasicMapPackage => |lib| {
|
||||
pub BasicMapPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "map", map_functions);
|
||||
|
@ -53,7 +53,7 @@ macro_rules! reg_functions {
|
||||
|
||||
def_package! {
|
||||
/// Basic mathematical package.
|
||||
crate::BasicMathPackage => |lib| {
|
||||
pub BasicMathPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
// Integer functions
|
||||
|
@ -67,16 +67,52 @@ pub trait Package {
|
||||
///
|
||||
/// 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.
|
||||
/// module.set_native_fn("my_add", add);
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
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])*
|
||||
/// # 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>);
|
||||
|
||||
impl $root::packages::Package for $package {
|
||||
@ -104,7 +140,6 @@ macro_rules! def_package {
|
||||
}
|
||||
)* };
|
||||
($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]
|
||||
///
|
||||
/// # Deprecated
|
||||
@ -112,6 +147,7 @@ macro_rules! def_package {
|
||||
/// 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.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>);
|
||||
|
||||
impl $root::packages::Package for $package {
|
||||
|
@ -14,7 +14,7 @@ def_package! {
|
||||
/// * [`BasicIteratorPackage`][super::BasicIteratorPackage]
|
||||
/// * [`BasicFnPackage`][super::BasicFnPackage]
|
||||
/// * [`DebuggingPackage`][super::DebuggingPackage]
|
||||
crate::CorePackage => |lib| {
|
||||
pub CorePackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
super::LanguageCorePackage::init(lib);
|
||||
|
@ -17,7 +17,7 @@ def_package! {
|
||||
/// * [`BasicMapPackage`][super::BasicMapPackage]
|
||||
/// * [`BasicTimePackage`][super::BasicTimePackage]
|
||||
/// * [`MoreStringPackage`][super::MoreStringPackage]
|
||||
crate::StandardPackage => |lib| {
|
||||
pub StandardPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
super::CorePackage::init(lib);
|
||||
|
@ -15,7 +15,7 @@ pub const FUNC_TO_DEBUG: &str = "to_debug";
|
||||
|
||||
def_package! {
|
||||
/// Package of basic string utilities (e.g. printing)
|
||||
crate::BasicStringPackage => |lib| {
|
||||
pub BasicStringPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
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 {
|
||||
ctx.engine().map_type_name(&format!("{:?}", item)).into()
|
||||
}
|
||||
|
||||
/// Return the empty string.
|
||||
#[rhai_fn(name = "print", name = "debug")]
|
||||
pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString {
|
||||
ctx.engine().const_empty_string()
|
||||
}
|
||||
|
||||
/// Return the `string`.
|
||||
#[rhai_fn(name = "print", name = "to_string")]
|
||||
pub fn print_string(string: ImmutableString) -> ImmutableString {
|
||||
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.
|
||||
#[rhai_fn(name = "debug", name = "to_debug", pure)]
|
||||
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
|
||||
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.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[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};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::Blob;
|
||||
|
||||
def_package! {
|
||||
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
|
||||
crate::MoreStringPackage => |lib| {
|
||||
pub MoreStringPackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "string", string_functions);
|
||||
@ -19,7 +22,7 @@ def_package! {
|
||||
mod string_functions {
|
||||
use crate::{ImmutableString, SmartString};
|
||||
|
||||
#[rhai_fn(name = "+", name = "append")]
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_append(
|
||||
ctx: NativeCallContext,
|
||||
string: ImmutableString,
|
||||
@ -33,6 +36,14 @@ mod string_functions {
|
||||
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)]
|
||||
pub fn add_prepend(
|
||||
ctx: NativeCallContext,
|
||||
@ -48,11 +59,13 @@ mod string_functions {
|
||||
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 {
|
||||
string1 + string2
|
||||
}
|
||||
#[rhai_fn(name = "+", name = "append")]
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
||||
string + character
|
||||
}
|
||||
@ -61,7 +74,7 @@ mod string_functions {
|
||||
format!("{}{}", character, string).into()
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "+", name = "append")]
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
||||
let _item = item;
|
||||
string
|
||||
@ -71,6 +84,30 @@ mod string_functions {
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
@ -105,6 +142,25 @@ mod string_functions {
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -15,7 +15,7 @@ use instant::{Duration, Instant};
|
||||
|
||||
def_package! {
|
||||
/// Package of basic timing utilities.
|
||||
crate::BasicTimePackage => |lib| {
|
||||
pub BasicTimePackage(lib) {
|
||||
lib.standard = true;
|
||||
|
||||
// Register date/time functions
|
||||
|
139
src/parser.rs
139
src/parser.rs
@ -1,22 +1,25 @@
|
||||
//! Main module defining the lexer and parser.
|
||||
|
||||
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::api::options::LanguageOptions;
|
||||
use crate::ast::{
|
||||
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, ScriptFnDef, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||
use crate::func::hashing::get_hasher;
|
||||
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,
|
||||
};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, ExclusiveRange, Identifier, ImmutableString, InclusiveRange,
|
||||
LexError, OptimizationLevel, ParseError, Position, Scope, Shared, StaticVec, AST, INT, PERR,
|
||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, Identifier,
|
||||
ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, Scope,
|
||||
Shared, StaticVec, AST, INT, PERR,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -47,7 +50,7 @@ pub struct ParseState<'e> {
|
||||
/// Interned strings.
|
||||
pub interned_strings: StringsInterner,
|
||||
/// 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.
|
||||
pub entry_stack_len: usize,
|
||||
/// 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"))]
|
||||
allow_capture: true,
|
||||
interned_strings: StringsInterner::new(),
|
||||
stack: StaticVec::new_const(),
|
||||
stack: Scope::new(),
|
||||
entry_stack_len: 0,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
imports: StaticVec::new_const(),
|
||||
@ -107,18 +110,17 @@ impl<'e> ParseState<'e> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
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 index = self
|
||||
.stack
|
||||
.iter()
|
||||
.rev()
|
||||
.iter_rev_raw()
|
||||
.enumerate()
|
||||
.find(|(.., (n, ..))| {
|
||||
.find(|&(.., (n, ..))| {
|
||||
if n == SCOPE_SEARCH_BARRIER_MARKER {
|
||||
// Do not go beyond the barrier
|
||||
barrier = true;
|
||||
hit_barrier = true;
|
||||
false
|
||||
} else {
|
||||
n == name
|
||||
@ -138,7 +140,7 @@ impl<'e> ParseState<'e> {
|
||||
self.allow_capture = true
|
||||
}
|
||||
|
||||
if barrier {
|
||||
if hit_barrier {
|
||||
None
|
||||
} else {
|
||||
index
|
||||
@ -1291,8 +1293,9 @@ fn parse_primary(
|
||||
let mut segments = StaticVec::<Expr>::new();
|
||||
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
(Token::InterpolatedString(s), ..) if s.is_empty() => (),
|
||||
(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),
|
||||
}
|
||||
@ -1302,7 +1305,10 @@ fn parse_primary(
|
||||
block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())),
|
||||
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
|
||||
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();
|
||||
Expr::InterpolatedString(segments.into(), settings.pos)
|
||||
}
|
||||
}
|
||||
|
||||
// Array literal
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -1792,7 +1802,11 @@ fn make_assignment_stmt(
|
||||
|| index.expect("either long or short index is `None`").get(),
|
||||
|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(
|
||||
(op_info, (lhs, rhs).into()).into(),
|
||||
op_pos,
|
||||
@ -2185,7 +2199,7 @@ fn parse_custom_syntax(
|
||||
// Add a barrier variable to the stack so earlier variables will not be matched.
|
||||
// Variable searches stop at the first barrier.
|
||||
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();
|
||||
@ -2552,12 +2566,12 @@ fn parse_for(
|
||||
let counter_var = counter_name.map(|name| {
|
||||
let name = state.get_identifier("", name);
|
||||
let pos = counter_pos.expect("`Some`");
|
||||
state.stack.push((name.clone(), AccessMode::ReadWrite));
|
||||
state.stack.push(name.clone(), ());
|
||||
Ident { name, pos }
|
||||
});
|
||||
|
||||
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 {
|
||||
name: loop_var,
|
||||
pos: name_pos,
|
||||
@ -2566,7 +2580,7 @@ fn parse_for(
|
||||
settings.is_breakable = true;
|
||||
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(
|
||||
expr,
|
||||
@ -2600,6 +2614,36 @@ fn parse_let(
|
||||
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 var_def = Ident {
|
||||
name: name.clone(),
|
||||
@ -2614,8 +2658,6 @@ fn parse_let(
|
||||
Expr::Unit(Position::NONE)
|
||||
};
|
||||
|
||||
state.stack.push((name, var_type));
|
||||
|
||||
let export = if is_export {
|
||||
AST_OPTION_EXPORTED
|
||||
} else {
|
||||
@ -2624,14 +2666,20 @@ fn parse_let(
|
||||
|
||||
match var_type {
|
||||
// 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 }
|
||||
AccessMode::ReadOnly => Ok(Stmt::Var(
|
||||
AccessMode::ReadOnly => {
|
||||
state.stack.push_constant(name, ());
|
||||
Ok(Stmt::Var(
|
||||
expr,
|
||||
var_def.into(),
|
||||
AST_OPTION_CONSTANT + export,
|
||||
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;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -2818,7 +2866,7 @@ fn parse_block(
|
||||
|
||||
Ok(Stmt::Block(
|
||||
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);
|
||||
state.stack.push((name.clone(), AccessMode::ReadWrite));
|
||||
state.stack.push(name.clone(), ());
|
||||
Some(Ident { name, pos })
|
||||
} else {
|
||||
None
|
||||
@ -3107,7 +3155,7 @@ fn parse_try_catch(
|
||||
|
||||
if catch_var.is_some() {
|
||||
// Remove the error variable from the stack
|
||||
state.stack.pop().unwrap();
|
||||
state.stack.rewind(state.stack.len() - 1);
|
||||
}
|
||||
|
||||
Ok(Stmt::TryCatch(
|
||||
@ -3166,7 +3214,7 @@ fn parse_fn(
|
||||
);
|
||||
}
|
||||
let s = state.get_identifier("", s);
|
||||
state.stack.push((s.clone(), AccessMode::ReadWrite));
|
||||
state.stack.push(s.clone(), ());
|
||||
params.push((s, pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
@ -3226,7 +3274,7 @@ fn parse_fn(
|
||||
fn make_curry_from_externals(
|
||||
state: &mut ParseState,
|
||||
fn_expr: Expr,
|
||||
externals: StaticVec<Identifier>,
|
||||
externals: StaticVec<crate::ast::Ident>,
|
||||
pos: Position,
|
||||
) -> Expr {
|
||||
// If there are no captured variables, no need to curry
|
||||
@ -3239,21 +3287,26 @@ fn make_curry_from_externals(
|
||||
|
||||
args.push(fn_expr);
|
||||
|
||||
args.extend(externals.iter().cloned().map(|x| {
|
||||
args.extend(
|
||||
externals
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|crate::ast::Ident { name, pos }| {
|
||||
Expr::Variable(
|
||||
None,
|
||||
Position::NONE,
|
||||
pos,
|
||||
(
|
||||
None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
None,
|
||||
#[cfg(feature = "no_module")]
|
||||
(),
|
||||
x,
|
||||
name,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
let expr = FnCallExpr {
|
||||
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
|
||||
// [`Share`][Stmt::Share] statements.
|
||||
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));
|
||||
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);
|
||||
state.stack.push((s.clone(), AccessMode::ReadWrite));
|
||||
state.stack.push(s.clone(), ());
|
||||
params_list.push(s)
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
@ -3336,14 +3393,14 @@ fn parse_anon_fn(
|
||||
// so extract them into a list.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let (mut params, externals) = {
|
||||
let externals: StaticVec<Identifier> = state
|
||||
.external_vars
|
||||
.iter()
|
||||
.map(|crate::ast::Ident { name, .. }| name.clone())
|
||||
.collect();
|
||||
let externals: StaticVec<_> = state.external_vars.iter().cloned().collect();
|
||||
|
||||
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)
|
||||
};
|
||||
|
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_rules! reify {
|
||||
($old:ident, |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{
|
||||
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
|
||||
// actually the same type.
|
||||
// SAFETY: This is safe because we already checked to make sure the two types
|
||||
// are actually the same.
|
||||
let $new: $t = unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new($old)) };
|
||||
$code
|
||||
} else {
|
||||
@ -17,6 +24,7 @@ macro_rules! reify {
|
||||
let old = $old;
|
||||
reify!(old, |$new: $t| $code, || $fallback)
|
||||
}};
|
||||
|
||||
($old:ident, |$new:ident : $t:ty| $code:expr) => {
|
||||
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.
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
pub struct Position {
|
||||
/// Line number - 0 = none
|
||||
/// Line number: 0 = none
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
line: u16,
|
||||
/// Character position - 0 = BOL
|
||||
/// Character position: 0 = BOL
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
pos: u16,
|
||||
}
|
||||
@ -226,11 +226,9 @@ impl Position {
|
||||
/// Print this [`Position`] for debug purposes.
|
||||
#[inline]
|
||||
pub(crate) fn debug_print(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
if !self.is_none() {
|
||||
write!(_f, " @ {:?}", self)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -259,16 +257,19 @@ impl fmt::Display for Position {
|
||||
|
||||
impl fmt::Debug for Position {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_none() {
|
||||
f.write_str("none")
|
||||
} else {
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
if self.is_beginning_of_line() {
|
||||
write!(f, "{}", self.line)?;
|
||||
write!(f, "{}", self.line)
|
||||
} 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.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
@ -1066,11 +1132,12 @@ pub fn parse_string_literal(
|
||||
verbatim: bool,
|
||||
allow_line_continuation: 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 escape = String::with_capacity(12);
|
||||
|
||||
let start = *pos;
|
||||
let mut first_char = Position::NONE;
|
||||
let mut interpolated = false;
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
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 {
|
||||
// \r - ignore if followed by \n
|
||||
'\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)
|
||||
}
|
||||
|
||||
// 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
|
||||
'\n' if verbatim => {
|
||||
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.
|
||||
@ -1397,11 +1464,9 @@ fn get_next_token_inner(
|
||||
|
||||
// Within text?
|
||||
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(
|
||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||
|(result, interpolated)| {
|
||||
|(result, interpolated, start_pos)| {
|
||||
if interpolated {
|
||||
Some((Token::InterpolatedString(result), start_pos))
|
||||
} else {
|
||||
@ -1612,7 +1677,7 @@ fn get_next_token_inner(
|
||||
|
||||
return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else(
|
||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||
|(result, interpolated)| {
|
||||
|(result, interpolated, ..)| {
|
||||
if interpolated {
|
||||
Some((Token::InterpolatedString(result), start_pos))
|
||||
} else {
|
||||
|
@ -13,6 +13,10 @@ use std::prelude::v1::*;
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// 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.
|
||||
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),
|
||||
/// 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.
|
||||
ErrorFunctionNotFound(String, Position),
|
||||
/// 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::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::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {}", s)?,
|
||||
Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {}", s)?,
|
||||
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {}", s)?,
|
||||
Self::ErrorDataRace(s, ..) => {
|
||||
@ -278,7 +288,9 @@ impl EvalAltResult {
|
||||
| Self::ErrorIndexingType(..)
|
||||
| Self::ErrorFor(..)
|
||||
| Self::ErrorVariableExists(..)
|
||||
| Self::ErrorForbiddenVariable(..)
|
||||
| Self::ErrorVariableNotFound(..)
|
||||
| Self::ErrorPropertyNotFound(..)
|
||||
| Self::ErrorModuleNotFound(..)
|
||||
| Self::ErrorDataRace(..)
|
||||
| Self::ErrorAssignmentToConstant(..)
|
||||
@ -369,7 +381,9 @@ impl EvalAltResult {
|
||||
map.insert("type".into(), t.into());
|
||||
}
|
||||
Self::ErrorVariableExists(v, ..)
|
||||
| Self::ErrorForbiddenVariable(v, ..)
|
||||
| Self::ErrorVariableNotFound(v, ..)
|
||||
| Self::ErrorPropertyNotFound(v, ..)
|
||||
| Self::ErrorDataRace(v, ..)
|
||||
| Self::ErrorAssignmentToConstant(v, ..) => {
|
||||
map.insert("variable".into(), v.into());
|
||||
@ -431,7 +445,9 @@ impl EvalAltResult {
|
||||
| Self::ErrorIndexingType(.., pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableExists(.., pos)
|
||||
| Self::ErrorForbiddenVariable(.., pos)
|
||||
| Self::ErrorVariableNotFound(.., pos)
|
||||
| Self::ErrorPropertyNotFound(.., pos)
|
||||
| Self::ErrorModuleNotFound(.., pos)
|
||||
| Self::ErrorDataRace(.., pos)
|
||||
| Self::ErrorAssignmentToConstant(.., pos)
|
||||
@ -480,7 +496,9 @@ impl EvalAltResult {
|
||||
| Self::ErrorIndexingType(.., pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableExists(.., pos)
|
||||
| Self::ErrorForbiddenVariable(.., pos)
|
||||
| Self::ErrorVariableNotFound(.., pos)
|
||||
| Self::ErrorPropertyNotFound(.., pos)
|
||||
| Self::ErrorModuleNotFound(.., pos)
|
||||
| Self::ErrorDataRace(.., pos)
|
||||
| Self::ErrorAssignmentToConstant(.., pos)
|
||||
|
@ -10,8 +10,7 @@ use std::fmt;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
/// _(internals)_ Error encountered when tokenizing the script text.
|
||||
/// Exported under the `internals` feature only.
|
||||
/// Error encountered when tokenizing the script text.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum LexError {
|
||||
@ -89,21 +88,12 @@ pub enum ParseErrorType {
|
||||
MalformedCallExpr(String),
|
||||
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error
|
||||
/// description (if any).
|
||||
///
|
||||
/// Never appears under the `no_index` feature.
|
||||
MalformedIndexExpr(String),
|
||||
/// An expression in an `in` expression has syntax error. Wrapped value is the error description
|
||||
/// (if any).
|
||||
///
|
||||
/// Never appears under the `no_object` and `no_index` features combination.
|
||||
/// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any).
|
||||
MalformedInExpr(String),
|
||||
/// A capturing has syntax error. Wrapped value is the error description (if any).
|
||||
///
|
||||
/// Never appears under the `no_closure` feature.
|
||||
MalformedCapture(String),
|
||||
/// A map definition has duplicated property names. Wrapped value is the property name.
|
||||
///
|
||||
/// Never appears under the `no_object` feature.
|
||||
DuplicatedProperty(String),
|
||||
/// A `switch` case is duplicated.
|
||||
DuplicatedSwitchCase,
|
||||
@ -116,11 +106,11 @@ pub enum ParseErrorType {
|
||||
/// The case condition of a `switch` statement is not appropriate.
|
||||
WrongSwitchCaseCondition,
|
||||
/// Missing a property name for custom types and maps.
|
||||
///
|
||||
/// Never appears under the `no_object` feature.
|
||||
PropertyExpected,
|
||||
/// Missing a variable name after the `let`, `const`, `for` or `catch` keywords.
|
||||
VariableExpected,
|
||||
/// Forbidden variable name. Wrapped value is the variable name.
|
||||
ForbiddenVariable(String),
|
||||
/// An identifier is a reserved symbol.
|
||||
Reserved(String),
|
||||
/// An expression is of the wrong type.
|
||||
@ -129,38 +119,22 @@ pub enum ParseErrorType {
|
||||
/// Missing an expression. Wrapped value is the expression type.
|
||||
ExprExpected(String),
|
||||
/// Defining a doc-comment in an appropriate place (e.g. not at global level).
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
WrongDocComment,
|
||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
WrongFnDefinition,
|
||||
/// Defining a function with a name that conflicts with an existing function.
|
||||
/// Wrapped values are the function name and number of parameters.
|
||||
///
|
||||
/// Never appears under the `no_object` feature.
|
||||
FnDuplicatedDefinition(String, usize),
|
||||
/// Missing a function name after the `fn` keyword.
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingName,
|
||||
/// A function definition is missing the parameters list. Wrapped value is the function name.
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingParams(String),
|
||||
/// A function definition has duplicated parameters. Wrapped values are the function name and
|
||||
/// parameter name.
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnDuplicatedParam(String, String),
|
||||
/// A function definition is missing the body. Wrapped value is the function name.
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingBody(String),
|
||||
/// Export statement not at global level.
|
||||
///
|
||||
/// Never appears under the `no_module` feature.
|
||||
WrongExport,
|
||||
/// Assignment to an a constant variable. Wrapped value is the constant variable name.
|
||||
AssignmentToConstant(String),
|
||||
@ -178,16 +152,10 @@ pub enum ParseErrorType {
|
||||
/// An imported module is not found.
|
||||
///
|
||||
/// Only appears when strict variables mode is enabled.
|
||||
///
|
||||
/// Never appears under the `no_module` feature.
|
||||
ModuleUndefined(String),
|
||||
/// Expression exceeding the maximum levels of complexity.
|
||||
///
|
||||
/// Never appears under the `unchecked` feature.
|
||||
ExprTooDeep,
|
||||
/// 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),
|
||||
/// Break statement not inside a loop.
|
||||
LoopBreak,
|
||||
@ -274,6 +242,7 @@ impl fmt::Display for ParseErrorType {
|
||||
Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"),
|
||||
Self::PropertyExpected => f.write_str("Expecting name of a property"),
|
||||
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::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"),
|
||||
|
@ -595,6 +595,16 @@ impl Scope<'_> {
|
||||
.zip(self.values.iter())
|
||||
.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`].
|
||||
///
|
||||
/// # Panics
|
||||
|
@ -107,6 +107,23 @@ b`: 1}; y["a\nb"]
|
||||
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]
|
||||
fn test_map_assign() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, Position, Scope, INT};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, Position, Scope, INT};
|
||||
|
||||
#[test]
|
||||
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>> {
|
||||
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),
|
||||
_ => Ok(true),
|
||||
});
|
||||
@ -135,7 +138,14 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
|
||||
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("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