Merge pull request #525 from schungx/master

Release 1.5.0.
This commit is contained in:
Stephen Chung 2022-02-15 11:29:07 +08:00 committed by GitHub
commit 40004ec361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1555 additions and 856 deletions

View File

@ -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
-----------------

View File

@ -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"

View File

@ -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}
```

View File

@ -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
View 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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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>> {

View File

@ -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.");

View File

@ -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 {

View File

@ -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`.

View File

@ -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")]

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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(),

View File

@ -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()
}

View File

@ -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

View File

@ -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(..) => (),

View File

@ -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);
}

View File

@ -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!(

View File

@ -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)

View File

@ -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)
}

View File

@ -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;

View File

@ -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;

View File

@ -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,
};

View File

@ -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
}
}

View File

@ -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 }

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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"))]

View File

@ -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);

View File

@ -53,7 +53,7 @@ macro_rules! reg_functions {
def_package! {
/// Basic mathematical package.
crate::BasicMathPackage => |lib| {
pub BasicMathPackage(lib) {
lib.standard = true;
// Integer functions

View File

@ -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 {

View File

@ -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);

View File

@ -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);

View File

@ -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")]

View File

@ -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

View File

@ -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

View File

@ -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)
};

View File

@ -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, || ())
};

View File

@ -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 {

View File

@ -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)

View File

@ -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"),

View File

@ -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

View File

@ -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();

View File

@ -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());