Skip to content

Commit 43d2e69

Browse files
authored
Merge pull request #1085 from schungx/master
EvalContext::new_frame API
2 parents cce5a1e + 5897aa2 commit 43d2e69

8 files changed

Lines changed: 364 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@ Bug fixes
88
---------
99

1010
* `Engine::compact_script` now properly compacts scripts with custom syntax that uses `$raw$` (thanks [`@yuvalrakavy`](https://github.com/yuvalrakavy) [`#1079`](https://github.com/rhaiscript/rhai/pull/1079)).
11-
* The string methods `split`, `split_rev` and their variants are now marked pure so they can be called on `const` strings ([`#1081`](https://github.com/rhaiscript/rhai/issues/1081)).
12-
* `array.index_of` now falls back to value comparison for string argument when no script function of that name is registered ([`#795`](https://github.com/rhaiscript/rhai/issues/795)).
11+
* The string methods `split`, `split_rev` and their variants are now marked `pure` so they can be called on `const` strings (thanks [`@theJC`](https://github.com/theJC) [`#1082`](https://github.com/rhaiscript/rhai/pull/1082)).
12+
* The array method `index_of` now falls back to value comparison for string argument when no script function of that name is registered (thanks [`@yinho999`](https://github.com/yinho999) [`#1086`](https://github.com/rhaiscript/rhai/pull/1086)).
1313

1414
New features
1515
------------
1616

1717
* A new advanced callback, `Engine::on_missing_function`, is added (gated under the `internals` feature) to override default handling when a called function or method is not found (thanks [`@yuvalrakavy`](https://github.com/yuvalrakavy) [`#1067`](https://github.com/rhaiscript/rhai/pull/1067)).
18+
* A new method, `EvalContext::new_frame`, is added to created an isolated frame guard that automatically restores field values upon `Drop` (thanks [`@yuvalrakavy`](https://github.com/yuvalrakavy) for the idea [`#1085`](https://github.com/rhaiscript/rhai/pull/1085)).
1819

1920
Enhancements
2021
------------
2122

2223
* Procedural macros such as `#[export_module]` and `#[derive(CustomType)]` no longer require importing standard Rhai types (thanks [`@timokoesters`](https://github.com/timokoesters) [`#1071`](https://github.com/rhaiscript/rhai/pull/1071)).
23-
* `FnPtr::call_fn_as_method` and `FnPtr::call_as_method_within_context` are added (when not under `no_object`) to accept a `this` pointer for calling the function pointer as a method call.
24-
* `NativeCallContext::call_method` and `NativeCallContext::call_native_method` are added (when not under `no_object`) to accept a `this` pointer for method calls.
24+
* `FnPtr::call_fn_as_method` and `FnPtr::call_as_method_within_context` are added (when not under `no_object`) to accept a `this` pointer for calling the function pointer as a method call (thanks [`@yunfengzh`](https://github.com/yunfengzh) for the request [`#1080`](https://github.com/rhaiscript/rhai/pull/1080)).
25+
* `NativeCallContext::call_method` and `NativeCallContext::call_native_method` are added (when not under `no_object`) to accept a `this` pointer for method calls (thanks [`@yunfengzh`](https://github.com/yunfengzh) for the idea [`#1080`](https://github.com/rhaiscript/rhai/pull/1080)).
2526

2627

2728
Version 1.24.0

src/eval/eval_context.rs

Lines changed: 256 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ use crate::ast::FnCallHashes;
55
use crate::tokenizer::{is_valid_function_name, Token};
66
use crate::types::dynamic::Variant;
77
use crate::{
8-
calc_fn_hash, expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, Position,
9-
RhaiResult, RhaiResultOf, Scope, StaticVec, ERR,
8+
calc_fn_hash, expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString,
9+
Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR,
1010
};
11-
use std::any::type_name;
1211
#[cfg(feature = "no_std")]
1312
use std::prelude::v1::*;
13+
use std::{
14+
any::type_name,
15+
mem,
16+
ops::{Deref, DerefMut},
17+
};
1418

1519
/// Context of a script evaluation process.
1620
#[allow(dead_code)]
@@ -59,6 +63,13 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
5963
pub fn source(&self) -> Option<&str> {
6064
self.global.source()
6165
}
66+
/// Get a mutable reference to the current source.
67+
#[inline(always)]
68+
#[must_use]
69+
pub fn source_mut(&mut self) -> &mut Option<ImmutableString> {
70+
&mut self.global.source
71+
}
72+
6273
/// The current [`Scope`].
6374
#[inline(always)]
6475
#[must_use]
@@ -123,6 +134,17 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
123134
pub fn namespaces(&self) -> &[crate::SharedModule] {
124135
&self.global.lib
125136
}
137+
/// _(internals)_ Mutable reference to the current set of namespaces containing definitions of all script-defined functions.
138+
/// Exported under the `internals` feature only.
139+
///
140+
/// Not available under `no_function`.
141+
#[cfg(not(feature = "no_function"))]
142+
#[cfg(feature = "internals")]
143+
#[inline(always)]
144+
#[must_use]
145+
pub fn namespaces_mut(&mut self) -> &mut [crate::SharedModule] {
146+
self.global.lib.as_mut()
147+
}
126148
/// The current bound `this` pointer, if any.
127149
#[inline(always)]
128150
#[must_use]
@@ -141,6 +163,24 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
141163
pub const fn call_level(&self) -> usize {
142164
self.global.level
143165
}
166+
/// _(internals, debugging)_ Debugging interface.
167+
/// Exported under the `debugging` and `internals` features only.
168+
#[cfg(feature = "debugging")]
169+
#[cfg(feature = "internals")]
170+
#[inline(always)]
171+
#[must_use]
172+
pub fn debugger(&self) -> Option<&crate::debugger::Debugger> {
173+
self.global.debugger.as_deref()
174+
}
175+
/// _(internals, debugging)_ Mutable reference to the debugging interface.
176+
/// Exported under the `debugging` and `internals` features only.
177+
#[cfg(feature = "debugging")]
178+
#[cfg(feature = "internals")]
179+
#[inline(always)]
180+
#[must_use]
181+
pub fn debugger_mut(&mut self) -> Option<&mut crate::debugger::Debugger> {
182+
self.global.debugger.as_deref_mut()
183+
}
144184

145185
/// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`].
146186
///
@@ -364,6 +404,69 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
364404
false,
365405
)
366406
}
407+
408+
/// Create a new isolated runtime frame guard, restoring field values upon `Drop`.
409+
///
410+
/// The [frame guard][EvalContextFrameGuard] returned derefs to [`EvalContext`].
411+
///
412+
/// # Examples
413+
///
414+
/// ```rust,ignore
415+
/// // The following pushes a new, empty, caching layer to make sure that new function resolutions
416+
/// // do not persist. This is useful if the resolution will be volatile.
417+
/// let result: i64 = context.new_frame()
418+
/// .with_new_caching_layer()
419+
/// .call_fn("foo", (0_i64,))?;
420+
///
421+
/// // In the above example, the resolution to function 'foo' will not be cached outside the frame.
422+
/// // The next call to 'foo' will be resolved again instead of using the cached resolution,
423+
/// // even if 'foo' is redefined.
424+
///
425+
/// // The following modifies the [`EvalContext`] before using, restoring its state afterwards.
426+
/// {
427+
/// // Modify the context before using...
428+
/// let context = context.new_frame()
429+
/// .rewind_scope(true) // rewinds the scope...
430+
/// .with_source("new source") // new source...
431+
/// .with_namespace(new_module) // add new namespace module...
432+
/// .up_call_level();
433+
///
434+
/// // Call a function with the modified context...
435+
/// let result: i64 = context.call_fn("foo", (0_i64,))?;
436+
///
437+
/// // ... at end of block, context automatically restored to previous state.
438+
/// }
439+
/// ```
440+
///
441+
/// ## `Drop` behavior
442+
///
443+
/// Upon `Drop`, the following fields will be automatically restored to the previous values:
444+
///
445+
/// * the stack of imported [modules][crate::Module] will be rewound to the original depth if more have been added via [`EvalContextFrameGuard::with_import`].
446+
/// * the stack of scripted function [modules][crate::Module] will be rewound to the original depth if more have been added via [`EvalContextFrameGuard::with_namespace`].
447+
/// * the original functions resolution cache will be restored if a new caching layer was created via [`EvalContextFrameGuard::with_new_caching_layer`].
448+
/// * the original [scope][EvalContext::scope] will be rewound if [`EvalContextFrameGuard::rewind_scope`] was set to `true`.
449+
/// * the [source][GlobalRuntimeState::source] will be restored if a new source was set via [`EvalContextFrameGuard::with_source`] or cleared via [`EvalContextFrameGuard::clear_source`].
450+
/// * the current [nesting level][GlobalRuntimeState::level] of function calls will be restored if modified via [`EvalContextFrameGuard::up_call_level`].
451+
/// * the current [`tag`][GlobalRuntimeState::tag] will be restored if modified via [`EvalContextFrameGuard::with_tag`] or [`EvalContextFrameGuard::clear_tag`].
452+
pub fn new_frame<'f>(&'f mut self) -> EvalContextFrameGuard<'f, 'a, 's, 'ps, 'g, 'c, 't> {
453+
EvalContextFrameGuard {
454+
#[cfg(feature = "internals")]
455+
#[cfg(not(feature = "no_module"))]
456+
imports_len: None,
457+
#[cfg(feature = "internals")]
458+
#[cfg(not(feature = "no_function"))]
459+
lib_len: None,
460+
caches_len: None,
461+
scope_len: None,
462+
source: None,
463+
#[cfg(feature = "internals")]
464+
level: None,
465+
tag: None,
466+
467+
context: self,
468+
}
469+
}
367470
}
368471

369472
/// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`].
@@ -431,3 +534,153 @@ fn _call_fn_raw(
431534
)
432535
.map(|(r, ..)| r)
433536
}
537+
538+
/// A new frame guard for [`EvalContext`], restoring field values upon `Drop`.
539+
pub struct EvalContextFrameGuard<'f, 'a, 's, 'ps, 'g, 'c, 't> {
540+
context: &'f mut EvalContext<'a, 's, 'ps, 'g, 'c, 't>,
541+
542+
caches_len: Option<usize>,
543+
scope_len: Option<usize>,
544+
#[cfg(feature = "internals")]
545+
#[cfg(not(feature = "no_module"))]
546+
imports_len: Option<usize>,
547+
#[cfg(feature = "internals")]
548+
#[cfg(not(feature = "no_function"))]
549+
lib_len: Option<usize>,
550+
source: Option<Option<ImmutableString>>,
551+
#[cfg(feature = "internals")]
552+
level: Option<usize>,
553+
tag: Option<Dynamic>,
554+
}
555+
556+
impl Drop for EvalContextFrameGuard<'_, '_, '_, '_, '_, '_, '_> {
557+
#[inline(always)]
558+
fn drop(&mut self) {
559+
if let Some(caches_len) = self.caches_len {
560+
self.context.caches.rewind_fn_resolution_caches(caches_len);
561+
}
562+
if let Some(scope_len) = self.scope_len {
563+
self.context.scope.rewind(scope_len);
564+
}
565+
#[cfg(feature = "internals")]
566+
#[cfg(not(feature = "no_module"))]
567+
if let Some(imports_len) = self.imports_len {
568+
self.context.global.truncate_imports(imports_len);
569+
}
570+
#[cfg(feature = "internals")]
571+
#[cfg(not(feature = "no_function"))]
572+
if let Some(lib_len) = self.lib_len {
573+
self.context.global.lib.truncate(lib_len);
574+
}
575+
if let Some(source) = self.source.take() {
576+
*self.context.source_mut() = source;
577+
}
578+
#[cfg(feature = "internals")]
579+
if let Some(level) = self.level {
580+
self.context.global.level = level;
581+
}
582+
if let Some(tag) = self.tag.take() {
583+
*self.context.tag_mut() = tag;
584+
}
585+
}
586+
}
587+
588+
impl<'a, 's, 'ps, 'g, 'c, 't> Deref for EvalContextFrameGuard<'_, 'a, 's, 'ps, 'g, 'c, 't> {
589+
type Target = EvalContext<'a, 's, 'ps, 'g, 'c, 't>;
590+
591+
#[inline(always)]
592+
fn deref(&self) -> &Self::Target {
593+
self.context
594+
}
595+
}
596+
597+
impl<'a, 's, 'ps, 'g, 'c, 't> DerefMut for EvalContextFrameGuard<'_, 'a, 's, 'ps, 'g, 'c, 't> {
598+
#[inline(always)]
599+
fn deref_mut(&mut self) -> &mut Self::Target {
600+
self.context
601+
}
602+
}
603+
604+
impl<'t> EvalContextFrameGuard<'_, '_, '_, '_, '_, '_, 't> {
605+
/// Push a new caching layer for function resolution results.
606+
pub fn with_new_caching_layer(mut self) -> Self {
607+
self.caches_len = Some(self.context.caches.fn_resolution_caches_len());
608+
self.context.caches.push_fn_resolution_cache();
609+
self
610+
}
611+
/// Rewind the [scope][EvalContext::scope].
612+
#[inline(always)]
613+
pub fn rewind_scope(mut self, enable: bool) -> Self {
614+
self.scope_len = if enable {
615+
Some(self.context.scope().len())
616+
} else {
617+
None
618+
};
619+
self
620+
}
621+
/// Modify the current source.
622+
#[inline(always)]
623+
pub fn with_source(mut self, source: impl Into<ImmutableString>) -> Self {
624+
self.source = Some(Some(source.into()));
625+
mem::swap(self.context.source_mut(), self.source.as_mut().unwrap());
626+
self
627+
}
628+
/// Clear the current source.
629+
#[inline(always)]
630+
pub fn clear_source(mut self) -> Self {
631+
self.source = Some(None);
632+
mem::swap(self.context.source_mut(), self.source.as_mut().unwrap());
633+
self
634+
}
635+
/// Modify the custom state.
636+
#[inline(always)]
637+
pub fn with_tag(mut self, tag: Dynamic) -> Self {
638+
self.tag = Some(tag);
639+
mem::swap(self.context.tag_mut(), self.tag.as_mut().unwrap());
640+
self
641+
}
642+
/// Modify the custom state to [`Dynamic::UNIT`].
643+
#[inline(always)]
644+
pub fn clear_tag(mut self) -> Self {
645+
self.tag = Some(Dynamic::UNIT);
646+
mem::swap(self.context.tag_mut(), self.tag.as_mut().unwrap());
647+
self
648+
}
649+
/// _(internals)_ Add a new imported [module][crate::Module].
650+
/// Exported under the `internals` feature only.
651+
///
652+
/// Not available under `no_module`.
653+
#[cfg(feature = "internals")]
654+
#[cfg(not(feature = "no_module"))]
655+
pub fn with_import(
656+
mut self,
657+
name: impl Into<ImmutableString>,
658+
module: impl Into<crate::SharedModule>,
659+
) -> Self {
660+
self.imports_len = Some(self.context.global.num_imports());
661+
self.context.global.push_import(name.into(), module.into());
662+
self
663+
}
664+
/// _(internals)_ Add a new [module][crate::Module] containing script-defined functions.
665+
/// Exported under the `internals` feature only.
666+
///
667+
/// Not available under `no_function`.
668+
#[cfg(feature = "internals")]
669+
#[cfg(not(feature = "no_function"))]
670+
#[inline(always)]
671+
#[must_use]
672+
pub fn with_namespace(mut self, module: impl Into<crate::SharedModule>) -> Self {
673+
self.lib_len = Some(self.context.global.lib.len());
674+
self.context.global.lib.push(module.into());
675+
self
676+
}
677+
/// _(internals)_ Increment the current nesting level of function calls.
678+
/// Exported under the `internals` feature only.
679+
#[cfg(feature = "internals")]
680+
#[inline(always)]
681+
pub fn up_call_level(mut self) -> Self {
682+
self.level = Some(self.context.global.level);
683+
self.context.global.level += 1;
684+
self
685+
}
686+
}

src/eval/global_state.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ impl GlobalRuntimeState {
152152
module: impl Into<crate::SharedModule>,
153153
) {
154154
self.imports.push(name.into());
155-
156155
self.modules.push(module.into());
157156
}
158157
/// Truncate the stack of globally-imported [modules][crate::Module] to a particular length.
@@ -164,7 +163,8 @@ impl GlobalRuntimeState {
164163
self.imports.truncate(size);
165164
self.modules.truncate(size);
166165
}
167-
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
166+
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order
167+
/// (i.e. modules imported last come first).
168168
///
169169
/// Not available under `no_module`.
170170
#[cfg(not(feature = "no_module"))]
@@ -176,7 +176,8 @@ impl GlobalRuntimeState {
176176
.zip(self.modules.iter().rev())
177177
.map(|(name, module)| (name.as_str(), &**module))
178178
}
179-
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
179+
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order
180+
/// (i.e. modules imported last come first).
180181
///
181182
/// Not available under `no_module`.
182183
#[cfg(not(feature = "no_module"))]

src/eval/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub use debugger::{
2525
BreakPoint, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus, OnDebuggerCallback,
2626
OnDebuggingInit,
2727
};
28-
pub use eval_context::EvalContext;
28+
pub use eval_context::{EvalContext, EvalContextFrameGuard};
2929

3030
pub use global_state::GlobalRuntimeState;
3131
#[cfg(not(feature = "no_module"))]

0 commit comments

Comments
 (0)