Merge pull request #557 from schungx/master

Ready version 1.7.0.
This commit is contained in:
Stephen Chung 2022-05-03 16:56:52 +08:00 committed by GitHub
commit 7d80c438e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 221 additions and 107 deletions

View File

@ -15,17 +15,33 @@ Script-breaking changes
* _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a variable/constant in the provided external `Scope`. * _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a variable/constant in the provided external `Scope`.
Changes to unstable API's
-------------------------
* The `Engine::on_var` and `Engine::on_parse_token` API's are now marked unstable/volatile.
* The closures passed to `Engine::on_var`, `Engine::on_def_var` and `Engine::register_debugger` take `EvalContext` instead of `&EvalContext` or `&mut EvalContext`.
New API
-------
* `Module::eval_ast_as_new_raw` is made public as a low-level API.
* `format_map_as_json` is provided globally, which is the same as `to_json` for object maps.
* `Engine::call_fn_raw_raw` is added to add speed to repeated function calls.
* `Engine::eval_statements_raw` is added to evaluate a sequence of statements.
New features
------------
* A custom state is provided that is persistent during the entire evaluation run. This custom state is a `Dynamic`, which can hold any data, and can be accessed by the host via `EvalContext::tag`, `EvalContext::tag_mut`, `NativeCallContext::tag` and `GlobalRuntimeState.tag`.
Enhancements Enhancements
------------ ------------
* `Module::eval_ast_as_new_raw` is made public as a low-level API.
* Improper `switch` case condition syntax is now caught at parse time. * Improper `switch` case condition syntax is now caught at parse time.
* `Engine::parse_json` now natively handles nested JSON inputs (using a token remap filter) without needing to replace `{` with `#{`. * `Engine::parse_json` now natively handles nested JSON inputs (using a token remap filter) without needing to replace `{` with `#{`.
* `to_json` is added to object maps to cheaply convert it to JSON format (`()` is mapped to `null`, all other data types must be supported by JSON) * `to_json` is added to object maps to cheaply convert it to JSON format (`()` is mapped to `null`, all other data types must be supported by JSON)
* A global function `format_map_as_json` is provided which is the same as `to_json` for object maps.
* `FileModuleResolver` now accepts a custom `Scope` to provide constants for optimization. * `FileModuleResolver` now accepts a custom `Scope` to provide constants for optimization.
* A new low-level method `Engine::call_fn_raw_raw` is added to add speed to repeated function calls. * New variants, `Start` and `End`, are added to `DebuggerEvent` triggered at the start/end of script evaluation.
* A new low-level method `Engine::eval_statements_raw` is added to evaluate a sequence of statements.
Version 1.6.1 Version 1.6.1

View File

@ -160,6 +160,8 @@ impl Engine {
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
arg_values: impl AsMut<[Dynamic]>, arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult { ) -> RhaiResult {
let mut arg_values = arg_values;
self.call_fn_internal( self.call_fn_internal(
scope, scope,
&mut GlobalRuntimeState::new(self), &mut GlobalRuntimeState::new(self),
@ -167,9 +169,9 @@ impl Engine {
ast, ast,
eval_ast, eval_ast,
rewind_scope, rewind_scope,
name, name.as_ref(),
this_ptr, this_ptr,
arg_values, arg_values.as_mut(),
) )
} }
/// _(internals)_ Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// _(internals)_ Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
@ -209,9 +211,9 @@ impl Engine {
ast: &AST, ast: &AST,
eval_ast: bool, eval_ast: bool,
rewind_scope: bool, rewind_scope: bool,
name: impl AsRef<str>, name: &str,
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
arg_values: impl AsMut<[Dynamic]>, arg_values: &mut [Dynamic],
) -> RhaiResult { ) -> RhaiResult {
self.call_fn_internal( self.call_fn_internal(
scope, scope,
@ -225,7 +227,6 @@ impl Engine {
arg_values, arg_values,
) )
} }
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
fn call_fn_internal( fn call_fn_internal(
&self, &self,
@ -235,9 +236,9 @@ impl Engine {
ast: &AST, ast: &AST,
eval_ast: bool, eval_ast: bool,
rewind_scope: bool, rewind_scope: bool,
name: impl AsRef<str>, name: &str,
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
arg_values: impl AsMut<[Dynamic]>, arg_values: &mut [Dynamic],
) -> RhaiResult { ) -> RhaiResult {
let statements = ast.statements(); let statements = ast.statements();
@ -251,31 +252,39 @@ impl Engine {
} }
} }
let name = name.as_ref();
let mut this_ptr = this_ptr; let mut this_ptr = this_ptr;
let mut arg_values = arg_values;
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
let fn_def = ast
.shared_lib()
.get_script_fn(name, args.len())
.ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?;
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::func::call::ensure_no_data_race(name, &mut args, false)?; crate::func::call::ensure_no_data_race(name, &mut args, false)?;
self.call_script_fn( let lib = &[ast.as_ref()];
let fn_def = ast
.shared_lib()
.get_script_fn(name, args.len())
.ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?;
let result = self.call_script_fn(
scope, scope,
global, global,
caches, caches,
&[ast.as_ref()], lib,
&mut this_ptr, &mut this_ptr,
fn_def, fn_def,
&mut args, &mut args,
rewind_scope, rewind_scope,
Position::NONE, Position::NONE,
0, 0,
) )?;
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.status = crate::eval::DebuggerStatus::Terminate;
let node = &crate::ast::Stmt::Noop(Position::NONE);
self.run_debugger(scope, global, lib, &mut this_ptr, node, 0)?;
}
Ok(result)
} }
} }

View File

@ -4,7 +4,7 @@ use crate::eval::{Caches, GlobalRuntimeState};
use crate::parser::ParseState; use crate::parser::ParseState;
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
Dynamic, Engine, Module, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -191,6 +191,17 @@ impl Engine {
let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?;
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.status = crate::eval::DebuggerStatus::Terminate;
let lib = &[
#[cfg(not(feature = "no_function"))]
ast.as_ref(),
];
let node = &crate::ast::Stmt::Noop(Position::NONE);
self.run_debugger(scope, global, lib, &mut None, node, 0)?;
}
let typ = self.map_type_name(result.type_name()); let typ = self.map_type_name(result.type_name());
result.try_cast::<T>().ok_or_else(|| { result.try_cast::<T>().ok_or_else(|| {
@ -222,18 +233,17 @@ impl Engine {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
let lib = [ let mut _lib = &[
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ast.as_ref(), ast.as_ref(),
]; ][..];
let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) { #[cfg(not(feature = "no_function"))]
&[] if !ast.has_functions() {
} else { _lib = &[];
&lib[..] }
};
let result = let result =
self.eval_global_statements(scope, global, &mut caches, statements, lib, level); self.eval_global_statements(scope, global, &mut caches, statements, _lib, level);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -258,7 +268,7 @@ impl Engine {
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
caches: &mut Caches, caches: &mut Caches,
statements: &[crate::ast::Stmt], statements: &[crate::ast::Stmt],
lib: &[&Module], lib: &[&crate::Module],
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
self.eval_global_statements(scope, global, caches, statements, lib, level) self.eval_global_statements(scope, global, caches, statements, lib, level)

View File

@ -21,11 +21,13 @@ pub struct VarDefInfo<'a> {
impl Engine { impl Engine {
/// Provide a callback that will be invoked before each variable access. /// Provide a callback that will be invoked before each variable access.
/// ///
/// # WARNING - Unstable API
///
/// This API is volatile and may change in the future.
///
/// # Callback Function Signature /// # Callback Function Signature
/// ///
/// The callback function signature takes the following form: /// > `Fn(name: &str, index: usize, context: EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
///
/// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
/// ///
/// where: /// where:
/// * `name`: name of the variable. /// * `name`: name of the variable.
@ -66,10 +68,11 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[deprecated = "This API is volatile and may change in the future."]
#[inline(always)] #[inline(always)]
pub fn on_var( pub fn on_var(
&mut self, &mut self,
callback: impl Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> callback: impl Fn(&str, usize, EvalContext) -> RhaiResultOf<Option<Dynamic>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {
@ -84,9 +87,7 @@ impl Engine {
/// ///
/// # Callback Function Signature /// # Callback Function Signature
/// ///
/// The callback function signature takes the following form: /// > `Fn(is_runtime: bool, info: VarInfo, context: EvalContext) -> Result<bool, Box<EvalAltResult>>`
///
/// > `Fn(is_runtime: bool, info: VarInfo, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
/// ///
/// where: /// where:
/// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation. /// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation.
@ -133,7 +134,7 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn on_def_var( pub fn on_def_var(
&mut self, &mut self,
callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + SendSync + 'static, callback: impl Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf<bool> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.def_var_filter = Some(Box::new(callback)); self.def_var_filter = Some(Box::new(callback));
self self
@ -141,9 +142,11 @@ impl Engine {
/// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens. /// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// # Callback Function Signature /// # WARNING - Unstable API
/// ///
/// The callback function signature takes the following form: /// This API is volatile and may change in the future.
///
/// # Callback Function Signature
/// ///
/// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token`
/// ///
@ -185,6 +188,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[deprecated = "This API is volatile and may change in the future."]
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
pub fn on_parse_token( pub fn on_parse_token(
@ -348,7 +352,7 @@ impl Engine {
&mut self, &mut self,
init: impl Fn() -> Dynamic + SendSync + 'static, init: impl Fn() -> Dynamic + SendSync + 'static,
callback: impl Fn( callback: impl Fn(
&mut EvalContext, EvalContext,
crate::eval::DebuggerEvent, crate::eval::DebuggerEvent,
crate::ast::ASTNode, crate::ast::ASTNode,
Option<&str>, Option<&str>,

View File

@ -64,6 +64,18 @@ impl Engine {
}; };
self.eval_global_statements(scope, global, caches, statements, lib, 0)?; self.eval_global_statements(scope, global, caches, statements, lib, 0)?;
} }
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.status = crate::eval::DebuggerStatus::Terminate;
let lib = &[
#[cfg(not(feature = "no_function"))]
ast.as_ref(),
];
let node = &crate::ast::Stmt::Noop(crate::Position::NONE);
self.run_debugger(scope, global, lib, &mut None, node, 0)?;
}
Ok(()) Ok(())
} }
} }

View File

@ -848,6 +848,7 @@ impl AsRef<crate::Shared<crate::Module>> for AST {
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Copy, Hash)] #[derive(Debug, Clone, Copy, Hash)]
#[non_exhaustive]
pub enum ASTNode<'a> { pub enum ASTNode<'a> {
/// A statement ([`Stmt`]). /// A statement ([`Stmt`]).
Stmt(&'a Stmt), Stmt(&'a Stmt),

View File

@ -348,6 +348,7 @@ impl FloatWrapper<crate::FLOAT> {
/// _(internals)_ An expression sub-tree. /// _(internals)_ An expression sub-tree.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
#[non_exhaustive]
pub enum Expr { pub enum Expr {
/// Dynamic constant. /// Dynamic constant.
/// ///

View File

@ -366,6 +366,7 @@ impl Extend<Stmt> for StmtBlock {
/// _(internals)_ A statement. /// _(internals)_ A statement.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
#[non_exhaustive]
pub enum Stmt { pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),

View File

@ -68,7 +68,10 @@ fn print_current_source(
.unwrap(); .unwrap();
let src = source.unwrap_or(""); let src = source.unwrap_or("");
if src != current_source { if src != current_source {
println!(">>> Source => {}", source.unwrap_or("main script")); println!(
"\x1b[34m>>> Source => {}\x1b[39m",
source.unwrap_or("main script")
);
*current_source = src.into(); *current_source = src.into();
} }
if !src.is_empty() { if !src.is_empty() {
@ -118,8 +121,10 @@ fn print_debug_help() {
println!("help, h => print this help"); println!("help, h => print this help");
println!("quit, q, exit, kill => quit"); println!("quit, q, exit, kill => quit");
println!("scope => print the scope"); println!("scope => print the scope");
println!("operations => print the total operations performed");
println!("source => print the current source");
println!("print, p => print all variables de-duplicated"); println!("print, p => print all variables de-duplicated");
println!("print/p this => print the 'this' pointer"); println!("print/p this => print the `this` pointer");
println!("print/p <variable> => print the current value of a variable"); println!("print/p <variable> => print the current value of a variable");
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
println!("imports => print all imported modules"); println!("imports => print all imported modules");
@ -225,7 +230,7 @@ fn load_script(engine: &Engine) -> (rhai::AST, String) {
// Main callback for debugging. // Main callback for debugging.
fn debug_callback( fn debug_callback(
context: &mut rhai::EvalContext, mut context: rhai::EvalContext,
event: DebuggerEvent, event: DebuggerEvent,
node: rhai::ASTNode, node: rhai::ASTNode,
source: Option<&str>, source: Option<&str>,
@ -234,6 +239,8 @@ fn debug_callback(
) -> Result<DebuggerCommand, Box<EvalAltResult>> { ) -> Result<DebuggerCommand, Box<EvalAltResult>> {
// Check event // Check event
match event { match event {
DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"),
DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"),
DebuggerEvent::Step => (), DebuggerEvent::Step => (),
DebuggerEvent::BreakPoint(n) => { DebuggerEvent::BreakPoint(n) => {
match context.global_runtime_state().debugger.break_points()[n] { match context.global_runtime_state().debugger.break_points()[n] {
@ -247,6 +254,7 @@ fn debug_callback(
BreakPoint::AtProperty { ref name, .. } => { BreakPoint::AtProperty { ref name, .. } => {
println!("! Property {} accessed.", name) println!("! Property {} accessed.", name)
} }
_ => unreachable!(),
} }
} }
DebuggerEvent::FunctionExitWithValue(r) => { DebuggerEvent::FunctionExitWithValue(r) => {
@ -275,10 +283,11 @@ fn debug_callback(
err err
) )
} }
_ => unreachable!(),
} }
// Print current source line // Print current source line
print_current_source(context, source, pos, lines, (0, 0)); print_current_source(&mut context, source, pos, lines, (0, 0));
// Read stdin for commands // Read stdin for commands
let mut input = String::new(); let mut input = String::new();
@ -313,14 +322,20 @@ fn debug_callback(
} }
println!(); println!();
} }
["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)), ["operations"] => {
println!("{}", context.global_runtime_state().num_operations)
}
["source"] => {
println!("{}", context.global_runtime_state().source().unwrap_or(""))
}
["list" | "l"] => print_current_source(&mut context, source, pos, &lines, (3, 6)),
["list" | "l", n] if n.parse::<usize>().is_ok() => { ["list" | "l", n] if n.parse::<usize>().is_ok() => {
let num = n.parse::<usize>().unwrap(); let num = n.parse::<usize>().unwrap();
if num <= 0 || num > lines.len() { if num <= 0 || num > lines.len() {
eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num); eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num);
} else { } else {
let pos = Position::new(num as u16, 0); let pos = Position::new(num as u16, 0);
print_current_source(context, source, pos, &lines, (3, 6)); print_current_source(&mut context, source, pos, &lines, (3, 6));
} }
} }
["continue" | "c"] => break Ok(DebuggerCommand::Continue), ["continue" | "c"] => break Ok(DebuggerCommand::Continue),
@ -333,7 +348,7 @@ fn debug_callback(
if let Some(value) = context.this_ptr() { if let Some(value) = context.this_ptr() {
println!("=> {:?}", value); println!("=> {:?}", value);
} else { } else {
println!("'this' pointer is unbound."); println!("`this` pointer is unbound.");
} }
} }
["print" | "p", var_name] => { ["print" | "p", var_name] => {
@ -632,4 +647,6 @@ fn main() {
} }
} }
} }
println!("Script terminated. Bye!");
} }

View File

@ -18,7 +18,7 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
/// Callback function for debugging. /// Callback function for debugging.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback = dyn Fn( pub type OnDebuggerCallback = dyn Fn(
&mut EvalContext, EvalContext,
DebuggerEvent, DebuggerEvent,
ASTNode, ASTNode,
Option<&str>, Option<&str>,
@ -26,18 +26,13 @@ pub type OnDebuggerCallback = dyn Fn(
) -> RhaiResultOf<DebuggerCommand>; ) -> RhaiResultOf<DebuggerCommand>;
/// Callback function for debugging. /// Callback function for debugging.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebuggerCallback = dyn Fn( pub type OnDebuggerCallback = dyn Fn(EvalContext, DebuggerEvent, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
&mut EvalContext,
DebuggerEvent,
ASTNode,
Option<&str>,
Position,
) -> RhaiResultOf<DebuggerCommand>
+ Send + Send
+ Sync; + Sync;
/// A command for the debugger on the next iteration. /// A command for the debugger on the next iteration.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum DebuggerCommand { pub enum DebuggerCommand {
// Continue normal execution. // Continue normal execution.
Continue, Continue,
@ -60,29 +55,31 @@ impl Default for DebuggerCommand {
/// The debugger status. /// The debugger status.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum DebuggerStatus { pub enum DebuggerStatus {
// Script evaluation starts.
Init,
// Stop at the next statement or expression. // Stop at the next statement or expression.
Next(bool, bool), Next(bool, bool),
// Run to the end of the current level of function call. // Run to the end of the current level of function call.
FunctionExit(usize), FunctionExit(usize),
} // Script evaluation ends.
Terminate,
impl Default for DebuggerStatus {
#[inline(always)]
fn default() -> Self {
Self::CONTINUE
}
} }
impl DebuggerStatus { impl DebuggerStatus {
pub const CONTINUE: Self = Self::Next(false, false); pub const CONTINUE: Self = Self::Next(false, false);
pub const STEP: Self = Self::Next(true, true); pub const STEP: Self = Self::Next(true, true);
pub const NEXT: Self = Self::Next(true, false); pub const NEXT: Self = Self::Next(true, false);
pub const INTO: Self = Self::Next(false, true);
} }
/// A event that triggers the debugger. /// A event that triggers the debugger.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum DebuggerEvent<'a> { pub enum DebuggerEvent<'a> {
// Script evaluation starts.
Start,
// Break on next step. // Break on next step.
Step, Step,
// Break on break-point. // Break on break-point.
@ -91,10 +88,13 @@ pub enum DebuggerEvent<'a> {
FunctionExitWithValue(&'a Dynamic), FunctionExitWithValue(&'a Dynamic),
// Return from a function with a value. // Return from a function with a value.
FunctionExitWithError(&'a EvalAltResult), FunctionExitWithError(&'a EvalAltResult),
// Script evaluation ends.
End,
} }
/// A break-point for debugging. /// A break-point for debugging.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum BreakPoint { pub enum BreakPoint {
/// Break at a particular position under a particular source. /// Break at a particular position under a particular source.
/// ///
@ -264,7 +264,7 @@ impl Debugger {
pub fn new(engine: &Engine) -> Self { pub fn new(engine: &Engine) -> Self {
Self { Self {
status: if engine.debugger.is_some() { status: if engine.debugger.is_some() {
DebuggerStatus::STEP DebuggerStatus::Init
} else { } else {
DebuggerStatus::CONTINUE DebuggerStatus::CONTINUE
}, },
@ -469,22 +469,27 @@ impl Engine {
_ => (), _ => (),
} }
let stop = match global.debugger.status { let event = match global.debugger.status {
DebuggerStatus::Next(false, false) => false, DebuggerStatus::Init => Some(DebuggerEvent::Start),
DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(..)), DebuggerStatus::CONTINUE => None,
DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(..)), DebuggerStatus::NEXT if matches!(node, ASTNode::Stmt(..)) => Some(DebuggerEvent::Step),
DebuggerStatus::Next(true, true) => true, DebuggerStatus::NEXT => None,
DebuggerStatus::FunctionExit(..) => false, DebuggerStatus::INTO if matches!(node, ASTNode::Expr(..)) => Some(DebuggerEvent::Step),
DebuggerStatus::INTO => None,
DebuggerStatus::STEP => Some(DebuggerEvent::Step),
DebuggerStatus::FunctionExit(..) => None,
DebuggerStatus::Terminate => Some(DebuggerEvent::End),
}; };
let event = if stop { let event = match event {
DebuggerEvent::Step Some(e) => e,
} else { None => {
if let Some(bp) = global.debugger.is_break_point(&global.source, node) { if let Some(bp) = global.debugger.is_break_point(&global.source, node) {
DebuggerEvent::BreakPoint(bp) DebuggerEvent::BreakPoint(bp)
} else { } else {
return Ok(None); return Ok(None);
} }
}
}; };
self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level)
@ -514,7 +519,7 @@ impl Engine {
Some(source.as_str()) Some(source.as_str())
}; };
let mut context = crate::EvalContext { let context = crate::EvalContext {
engine: self, engine: self,
scope, scope,
global, global,
@ -525,7 +530,7 @@ impl Engine {
}; };
if let Some((.., ref on_debugger)) = self.debugger { if let Some((.., ref on_debugger)) = self.debugger {
let command = on_debugger(&mut context, event, node, source, node.position())?; let command = on_debugger(context, event, node, source, node.position())?;
match command { match command {
DebuggerCommand::Continue => { DebuggerCommand::Continue => {
@ -548,12 +553,12 @@ impl Engine {
// Bump a level if it is a function call // Bump a level if it is a function call
let level = match node { let level = match node {
ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => { ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => {
context.call_level() + 1 level + 1
} }
ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => { ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => {
context.call_level() + 1 level + 1
} }
_ => context.call_level(), _ => level,
}; };
global.debugger.status = DebuggerStatus::FunctionExit(level); global.debugger.status = DebuggerStatus::FunctionExit(level);
Ok(None) Ok(None)

View File

@ -60,6 +60,18 @@ impl<'s, 'ps, 'm, 'pm, 'pt> EvalContext<'_, 's, 'ps, 'm, 'pm, '_, '_, 'pt> {
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.global.iter_imports() self.global.iter_imports()
} }
/// Custom state kept in a [`Dynamic`].
#[inline(always)]
#[must_use]
pub const fn tag(&self) -> &Dynamic {
&self.global.tag
}
/// Mutable reference to the custom state kept in a [`Dynamic`].
#[inline(always)]
#[must_use]
pub fn tag_mut(&mut self) -> &mut Dynamic {
&mut self.global.tag
}
/// _(internals)_ The current [`GlobalRuntimeState`]. /// _(internals)_ The current [`GlobalRuntimeState`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]

View File

@ -161,7 +161,7 @@ impl Engine {
level, level,
}; };
let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); let var_name = expr.get_variable_name(true).expect("`Expr::Variable`");
match resolve_var(var_name, index, &context) { match resolve_var(var_name, index, context) {
Ok(Some(mut result)) => { Ok(Some(mut result)) => {
result.set_access_mode(AccessMode::ReadOnly); result.set_access_mode(AccessMode::ReadOnly);
return Ok((result.into(), var_pos)); return Ok((result.into(), var_pos));

View File

@ -1,6 +1,6 @@
//! Global runtime state. //! Global runtime state.
use crate::{Engine, Identifier}; use crate::{Dynamic, Engine, Identifier};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt, marker::PhantomData}; use std::{fmt, marker::PhantomData};
@ -9,7 +9,7 @@ use std::{fmt, marker::PhantomData};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub type GlobalConstants = pub type GlobalConstants =
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>; crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, Dynamic>>>;
/// _(internals)_ Global runtime states. /// _(internals)_ Global runtime states.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -64,6 +64,8 @@ pub struct GlobalRuntimeState<'a> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub constants: Option<GlobalConstants>, pub constants: Option<GlobalConstants>,
/// Custom state that can be used by the external host.
pub tag: Dynamic,
/// Debugging interface. /// Debugging interface.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
pub debugger: super::Debugger, pub debugger: super::Debugger,
@ -95,6 +97,7 @@ impl GlobalRuntimeState<'_> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: None, constants: None,
tag: Dynamic::UNIT,
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
debugger: crate::eval::Debugger::new(_engine), debugger: crate::eval::Debugger::new(_engine),
dummy: PhantomData::default(), dummy: PhantomData::default(),

View File

@ -841,7 +841,7 @@ impl Engine {
level, level,
}; };
match filter(true, info, &context) { match filter(true, info, context) {
Ok(true) => None, Ok(true) => None,
Ok(false) => { Ok(false) => {
Some(Err( Some(Err(

View File

@ -10,6 +10,7 @@ use std::prelude::v1::*;
/// A type encapsulating a function callable by Rhai. /// A type encapsulating a function callable by Rhai.
#[derive(Clone)] #[derive(Clone)]
#[non_exhaustive]
pub enum CallableFunction { pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value. /// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>), Pure(Shared<FnAny>),

View File

@ -1,7 +1,6 @@
//! Module defining interfaces to native-Rust functions. //! Module defining interfaces to native-Rust functions.
use super::call::FnCallArgs; use super::call::FnCallArgs;
use crate::api::events::VarDefInfo;
use crate::ast::FnCallHashes; use crate::ast::FnCallHashes;
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
@ -9,7 +8,7 @@ use crate::tokenizer::{Token, TokenizeState};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult, calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult,
RhaiResultOf, StaticVec, ERR, RhaiResultOf, StaticVec, VarDefInfo, ERR,
}; };
use std::any::type_name; use std::any::type_name;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -218,6 +217,12 @@ impl<'a> NativeCallContext<'a> {
pub const fn source(&self) -> Option<&str> { pub const fn source(&self) -> Option<&str> {
self.source self.source
} }
/// Custom state kept in a [`Dynamic`].
#[inline(always)]
#[must_use]
pub fn tag(&self) -> Option<&Dynamic> {
self.global.as_ref().map(|g| &g.tag)
}
/// Get an iterator over the current set of modules imported via `import` statements /// Get an iterator over the current set of modules imported via `import` statements
/// in reverse order. /// in reverse order.
/// ///
@ -466,16 +471,16 @@ pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token
/// Callback function for variable access. /// Callback function for variable access.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>; pub type OnVarCallback = dyn Fn(&str, usize, EvalContext) -> RhaiResultOf<Option<Dynamic>>;
/// Callback function for variable access. /// Callback function for variable access.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnVarCallback = pub type OnVarCallback =
dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync; dyn Fn(&str, usize, EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync;
/// Callback function for variable definition. /// Callback function for variable definition.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool>; pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf<bool>;
/// Callback function for variable definition. /// Callback function for variable definition.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDefVarCallback = pub type OnDefVarCallback =
dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + Send + Sync; dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf<bool> + Send + Sync;

View File

@ -121,9 +121,11 @@ impl Engine {
ref constants, ref constants,
}) = fn_def.environ }) = fn_def.environ
{ {
for (n, m) in imports.iter().cloned() { imports
global.push_import(n, m) .iter()
} .cloned()
.for_each(|(n, m)| global.push_import(n, m));
( (
if fn_lib.is_empty() { if fn_lib.is_empty() {
lib lib
@ -167,7 +169,6 @@ impl Engine {
} else { } else {
format!("{} @ '{}' < {}", name, src, fn_def.name) format!("{} @ '{}' < {}", name, src, fn_def.name)
}; };
make_error(fn_name, fn_def, global, err, pos) make_error(fn_name, fn_def, global, err, pos)
} }
// System errors are passed straight-through // System errors are passed straight-through

View File

@ -51,6 +51,10 @@ struct OptimizerState<'a> {
propagate_constants: bool, propagate_constants: bool,
/// An [`Engine`] instance for eager function evaluation. /// An [`Engine`] instance for eager function evaluation.
engine: &'a Engine, engine: &'a Engine,
/// The global runtime state.
global: GlobalRuntimeState<'a>,
/// Function resolution caches.
caches: Caches,
/// [Module][crate::Module] containing script-defined functions. /// [Module][crate::Module] containing script-defined functions.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib: &'a [&'a crate::Module], lib: &'a [&'a crate::Module],
@ -61,7 +65,7 @@ struct OptimizerState<'a> {
impl<'a> OptimizerState<'a> { impl<'a> OptimizerState<'a> {
/// Create a new State. /// Create a new State.
#[inline(always)] #[inline(always)]
pub const fn new( pub fn new(
engine: &'a Engine, engine: &'a Engine,
#[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module], #[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
@ -71,6 +75,8 @@ impl<'a> OptimizerState<'a> {
variables: StaticVec::new_const(), variables: StaticVec::new_const(),
propagate_constants: true, propagate_constants: true,
engine, engine,
global: GlobalRuntimeState::new(engine),
caches: Caches::new(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib, lib,
optimization_level, optimization_level,
@ -127,7 +133,7 @@ impl<'a> OptimizerState<'a> {
/// Call a registered function /// Call a registered function
#[inline] #[inline]
pub fn call_fn_with_constant_arguments( pub fn call_fn_with_constant_arguments(
&self, &mut self,
fn_name: &str, fn_name: &str,
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
) -> Option<Dynamic> { ) -> Option<Dynamic> {
@ -138,8 +144,8 @@ impl<'a> OptimizerState<'a> {
self.engine self.engine
.call_native_fn( .call_native_fn(
&mut GlobalRuntimeState::new(&self.engine), &mut self.global,
&mut Caches::new(), &mut self.caches,
lib, lib,
fn_name, fn_name,
calc_fn_hash(&fn_name, arg_values.len()), calc_fn_hash(&fn_name, arg_values.len()),

View File

@ -49,6 +49,8 @@ pub struct ParseState<'e> {
interned_strings: StringsInterner, interned_strings: StringsInterner,
/// External [scope][Scope] with constants. /// External [scope][Scope] with constants.
pub scope: &'e Scope<'e>, pub scope: &'e Scope<'e>,
/// Global runtime state.
pub global: GlobalRuntimeState<'e>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
pub stack: Scope<'e>, pub stack: Scope<'e>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
@ -83,6 +85,7 @@ impl<'e> ParseState<'e> {
allow_capture: true, allow_capture: true,
interned_strings: StringsInterner::new(), interned_strings: StringsInterner::new(),
scope, scope,
global: GlobalRuntimeState::new(engine),
stack: Scope::new(), stack: Scope::new(),
block_stack_len: 0, block_stack_len: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -2715,14 +2718,14 @@ impl Engine {
let context = EvalContext { let context = EvalContext {
engine: self, engine: self,
scope: &mut state.stack, scope: &mut state.stack,
global: &mut GlobalRuntimeState::new(self), global: &mut state.global,
caches: None, caches: None,
lib: &[], lib: &[],
this_ptr: &mut None, this_ptr: &mut None,
level, level,
}; };
match filter(false, info, &context) { match filter(false, info, context) {
Ok(true) => (), Ok(true) => (),
Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)), Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)),
Err(err) => match *err { Err(err) => match *err {

View File

@ -349,6 +349,7 @@ impl fmt::Debug for Span {
/// _(internals)_ A Rhai language token. /// _(internals)_ A Rhai language token.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, PartialEq, Clone, Hash)] #[derive(Debug, PartialEq, Clone, Hash)]
#[non_exhaustive]
pub enum Token { pub enum Token {
/// An `INT` constant. /// An `INT` constant.
IntegerConstant(INT), IntegerConstant(INT),

View File

@ -55,7 +55,7 @@ fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
state.insert("foo".into(), false.into()); state.insert("foo".into(), false.into());
Dynamic::from_map(state) Dynamic::from_map(state)
}, },
|context, _, _, _, _| { |mut context, _, _, _, _| {
// Get global runtime state // Get global runtime state
let global = context.global_runtime_state_mut(); let global = context.global_runtime_state_mut();

View File

@ -202,9 +202,15 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("let x = 42;")?; let ast = engine.compile("let x = 42;")?;
engine.run_ast(&ast)?; engine.run_ast(&ast)?;
engine.on_def_var(|_, info, _| match (info.name, info.nesting_level) { engine.on_def_var(|_, info, mut ctx| {
if ctx.tag().is::<()>() {
*ctx.tag_mut() = rhai::Dynamic::ONE;
}
println!("Tag = {}", ctx.tag());
match (info.name, info.nesting_level) {
("x", 0 | 1) => Ok(false), ("x", 0 | 1) => Ok(false),
_ => Ok(true), _ => Ok(true),
}
}); });
assert_eq!( assert_eq!(