Add custom state.

This commit is contained in:
Stephen Chung
2022-05-02 00:03:45 +08:00
parent 98e0042214
commit c69f98c2c4
13 changed files with 91 additions and 46 deletions

View File

@@ -21,11 +21,13 @@ pub struct VarDefInfo<'a> {
impl Engine {
/// 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
///
/// 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:
/// * `name`: name of the variable.
@@ -66,10 +68,11 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[deprecated = "This API is volatile and may change in the future."]
#[inline(always)]
pub fn on_var(
&mut self,
callback: impl Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>
callback: impl Fn(&str, usize, EvalContext) -> RhaiResultOf<Option<Dynamic>>
+ SendSync
+ 'static,
) -> &mut Self {
@@ -84,9 +87,7 @@ impl Engine {
///
/// # 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:
/// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation.
@@ -133,7 +134,7 @@ impl Engine {
#[inline(always)]
pub fn on_def_var(
&mut self,
callback: impl Fn(bool, VarDefInfo, &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
@@ -141,9 +142,11 @@ impl Engine {
/// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens.
/// 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`
///
@@ -185,6 +188,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[deprecated = "This API is volatile and may change in the future."]
#[cfg(feature = "internals")]
#[inline(always)]
pub fn on_parse_token(
@@ -348,7 +352,7 @@ impl Engine {
&mut self,
init: impl Fn() -> Dynamic + SendSync + 'static,
callback: impl Fn(
&mut EvalContext,
EvalContext,
crate::eval::DebuggerEvent,
crate::ast::ASTNode,
Option<&str>,

View File

@@ -230,7 +230,7 @@ fn load_script(engine: &Engine) -> (rhai::AST, String) {
// Main callback for debugging.
fn debug_callback(
context: &mut rhai::EvalContext,
mut context: rhai::EvalContext,
event: DebuggerEvent,
node: rhai::ASTNode,
source: Option<&str>,
@@ -287,7 +287,7 @@ fn debug_callback(
}
// 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
let mut input = String::new();
@@ -328,14 +328,14 @@ fn debug_callback(
["source"] => {
println!("{}", context.global_runtime_state().source().unwrap_or(""))
}
["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)),
["list" | "l"] => print_current_source(&mut 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 {
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),

View File

@@ -18,7 +18,7 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
/// Callback function for debugging.
#[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback = dyn Fn(
&mut EvalContext,
EvalContext,
DebuggerEvent,
ASTNode,
Option<&str>,
@@ -26,13 +26,7 @@ pub type OnDebuggerCallback = dyn Fn(
) -> RhaiResultOf<DebuggerCommand>;
/// Callback function for debugging.
#[cfg(feature = "sync")]
pub type OnDebuggerCallback = dyn Fn(
&mut EvalContext,
DebuggerEvent,
ASTNode,
Option<&str>,
Position,
) -> RhaiResultOf<DebuggerCommand>
pub type OnDebuggerCallback = dyn Fn(EvalContext, DebuggerEvent, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ Send
+ Sync;
@@ -525,7 +519,7 @@ impl Engine {
Some(source.as_str())
};
let mut context = crate::EvalContext {
let context = crate::EvalContext {
engine: self,
scope,
global,
@@ -536,7 +530,7 @@ impl Engine {
};
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 {
DebuggerCommand::Continue => {
@@ -559,12 +553,12 @@ impl Engine {
// Bump a level if it is a function call
let level = match node {
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(..)) => {
context.call_level() + 1
level + 1
}
_ => context.call_level(),
_ => level,
};
global.debugger.status = DebuggerStatus::FunctionExit(level);
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)> {
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`].
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")]

View File

@@ -161,7 +161,7 @@ impl Engine {
level,
};
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)) => {
result.set_access_mode(AccessMode::ReadOnly);
return Ok((result.into(), var_pos));

View File

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

View File

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

View File

@@ -217,6 +217,12 @@ impl<'a> NativeCallContext<'a> {
pub const fn source(&self) -> Option<&str> {
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
/// in reverse order.
///
@@ -465,16 +471,16 @@ pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token
/// Callback function for variable access.
#[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.
#[cfg(feature = "sync")]
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.
#[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.
#[cfg(feature = "sync")]
pub type OnDefVarCallback =
dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + Send + Sync;
dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf<bool> + Send + Sync;

View File

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

View File

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