Better API for Scope.
This commit is contained in:
parent
fa13588f69
commit
a1591ae45b
24
README.md
24
README.md
@ -391,11 +391,11 @@ a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed.
|
|||||||
a.x == 500;
|
a.x == 500;
|
||||||
```
|
```
|
||||||
|
|
||||||
# Maintaining state
|
# Initializing and maintaining state
|
||||||
|
|
||||||
By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
|
By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
|
||||||
|
|
||||||
In this example, we thread the same state through multiple invocations:
|
In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate rhai;
|
extern crate rhai;
|
||||||
@ -403,13 +403,29 @@ use rhai::{Engine, Scope};
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// First create the state
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
if let Ok(_) = engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5") { } else { assert!(false); }
|
// Then push some initialized variables into the state
|
||||||
|
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
||||||
|
// Better stick to them or it gets hard to work with other variables in the script.
|
||||||
|
scope.push("y".into(), 42_i64);
|
||||||
|
scope.push("z".into(), 999_i64);
|
||||||
|
|
||||||
|
// First invocation
|
||||||
|
engine.eval_with_scope::<()>(&mut scope, r"
|
||||||
|
let x = 4 + 5 - y + z;
|
||||||
|
y = 1;
|
||||||
|
").expect("y and z not found?");
|
||||||
|
|
||||||
|
// Second invocation using the same state
|
||||||
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
|
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
|
||||||
println!("result: {}", result);
|
println!("result: {}", result); // should print 966
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Variable y is changed in the script
|
||||||
|
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope};
|
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec};
|
||||||
use crate::parser::{lex, parse, ParseError, Position, AST};
|
use crate::parser::{lex, parse, ParseError, Position, AST};
|
||||||
|
use crate::scope::Scope;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
@ -174,11 +174,6 @@ impl Engine {
|
|||||||
self.register_fn("+", concat);
|
self.register_fn("+", concat);
|
||||||
self.register_fn("==", unit_eq);
|
self.register_fn("==", unit_eq);
|
||||||
|
|
||||||
// self.register_fn("[]", idx);
|
|
||||||
// FIXME? Registering array lookups are a special case because we want to return boxes
|
|
||||||
// directly let ent = self.fns.entry("[]".to_string()).or_insert_with(Vec::new);
|
|
||||||
// (*ent).push(FnType::ExternalFn2(Box::new(idx)));
|
|
||||||
|
|
||||||
// Register conversion functions
|
// Register conversion functions
|
||||||
self.register_fn("to_float", |x: i8| x as f64);
|
self.register_fn("to_float", |x: i8| x as f64);
|
||||||
self.register_fn("to_float", |x: u8| x as f64);
|
self.register_fn("to_float", |x: u8| x as f64);
|
||||||
|
@ -8,6 +8,7 @@ use crate::any::{Any, AnyExt, Dynamic, Variant};
|
|||||||
use crate::call::FunArgs;
|
use crate::call::FunArgs;
|
||||||
use crate::fn_register::RegisterFn;
|
use crate::fn_register::RegisterFn;
|
||||||
use crate::parser::{Expr, FnDef, ParseError, Position, Stmt};
|
use crate::parser::{Expr, FnDef, ParseError, Position, Stmt};
|
||||||
|
use crate::scope::Scope;
|
||||||
|
|
||||||
pub type Array = Vec<Dynamic>;
|
pub type Array = Vec<Dynamic>;
|
||||||
pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
|
pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
|
||||||
@ -162,22 +163,6 @@ pub enum FnIntExt {
|
|||||||
|
|
||||||
pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
|
pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
|
||||||
|
|
||||||
/// A type containing information about current scope.
|
|
||||||
/// Useful for keeping state between `Engine` runs
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rhai::{Engine, Scope};
|
|
||||||
///
|
|
||||||
/// let mut engine = Engine::new();
|
|
||||||
/// let mut my_scope = Scope::new();
|
|
||||||
///
|
|
||||||
/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok());
|
|
||||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1").unwrap(), 6);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Between runs, `Engine` only remembers functions when not using own `Scope`.
|
|
||||||
pub type Scope = Vec<(String, Dynamic)>;
|
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result<T, EvalAltResult>
|
pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result<T, EvalAltResult>
|
||||||
where
|
where
|
||||||
@ -185,7 +170,7 @@ impl Engine {
|
|||||||
A: FunArgs<'a>,
|
A: FunArgs<'a>,
|
||||||
T: Any + Clone,
|
T: Any + Clone,
|
||||||
{
|
{
|
||||||
let pos = Position { line: 0, pos: 0 };
|
let pos = Position::new();
|
||||||
|
|
||||||
self.call_fn_raw(ident.into(), args.into_vec(), None, pos)
|
self.call_fn_raw(ident.into(), args.into_vec(), None, pos)
|
||||||
.and_then(|b| {
|
.and_then(|b| {
|
||||||
@ -438,19 +423,16 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_scope<'a, T>(
|
fn search_scope<T>(
|
||||||
scope: &'a mut Scope,
|
scope: &Scope,
|
||||||
id: &str,
|
id: &str,
|
||||||
map: impl FnOnce(&'a mut Variant) -> Result<T, EvalAltResult>,
|
map: impl FnOnce(&Variant) -> Result<T, EvalAltResult>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<(usize, T), EvalAltResult> {
|
) -> Result<(usize, T), EvalAltResult> {
|
||||||
scope
|
scope
|
||||||
.iter_mut()
|
.get(id)
|
||||||
.enumerate()
|
|
||||||
.rev()
|
|
||||||
.find(|&(_, &mut (ref name, _))| id == name)
|
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
||||||
.and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val)))
|
.and_then(move |(idx, _, val)| map(val.as_ref()).map(|v| (idx, v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn indexed_value(
|
fn indexed_value(
|
||||||
@ -471,7 +453,7 @@ impl Engine {
|
|||||||
scope,
|
scope,
|
||||||
id,
|
id,
|
||||||
|val| {
|
|val| {
|
||||||
if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> {
|
if let Some(arr) = (*val).downcast_ref() as Option<&Array> {
|
||||||
is_array = true;
|
is_array = true;
|
||||||
|
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
@ -481,7 +463,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin))
|
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin))
|
||||||
}
|
}
|
||||||
} else if let Some(s) = (*val).downcast_mut() as Option<&mut String> {
|
} else if let Some(s) = (*val).downcast_ref() as Option<&String> {
|
||||||
is_array = false;
|
is_array = false;
|
||||||
|
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
@ -537,7 +519,7 @@ impl Engine {
|
|||||||
|
|
||||||
// In case the expression mutated `target`, we need to reassign it because
|
// In case the expression mutated `target`, we need to reassign it because
|
||||||
// of the above `clone`.
|
// of the above `clone`.
|
||||||
scope[sc_idx].1 = target;
|
*scope.get_mut(id, sc_idx) = target;
|
||||||
|
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
@ -551,10 +533,10 @@ impl Engine {
|
|||||||
// of the above `clone`.
|
// of the above `clone`.
|
||||||
|
|
||||||
if is_array {
|
if is_array {
|
||||||
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
|
scope.get_mut(id, sc_idx).downcast_mut::<Array>().unwrap()[idx] = target;
|
||||||
} else {
|
} else {
|
||||||
Self::str_replace_char(
|
Self::str_replace_char(
|
||||||
scope[sc_idx].1.downcast_mut::<String>().unwrap(), // Root is a string
|
scope.get_mut(id, sc_idx).downcast_mut::<String>().unwrap(), // Root is a string
|
||||||
idx,
|
idx,
|
||||||
*target.downcast::<char>().unwrap(), // Target should be a char
|
*target.downcast::<char>().unwrap(), // Target should be a char
|
||||||
);
|
);
|
||||||
@ -617,7 +599,7 @@ impl Engine {
|
|||||||
|
|
||||||
// In case the expression mutated `target`, we need to reassign it because
|
// In case the expression mutated `target`, we need to reassign it because
|
||||||
// of the above `clone`.
|
// of the above `clone`.
|
||||||
scope[sc_idx].1 = target;
|
*scope.get_mut(id, sc_idx) = target;
|
||||||
|
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
@ -630,10 +612,10 @@ impl Engine {
|
|||||||
// In case the expression mutated `target`, we need to reassign it because
|
// In case the expression mutated `target`, we need to reassign it because
|
||||||
// of the above `clone`.
|
// of the above `clone`.
|
||||||
if is_array {
|
if is_array {
|
||||||
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
|
scope.get_mut(id, sc_idx).downcast_mut::<Array>().unwrap()[idx] = target;
|
||||||
} else {
|
} else {
|
||||||
Self::str_replace_char(
|
Self::str_replace_char(
|
||||||
scope[sc_idx].1.downcast_mut::<String>().unwrap(), // Root is a string
|
scope.get_mut(id, sc_idx).downcast_mut::<String>().unwrap(), // Root is a string
|
||||||
idx,
|
idx,
|
||||||
*target.downcast::<char>().unwrap(), // Target should be a char
|
*target.downcast::<char>().unwrap(), // Target should be a char
|
||||||
);
|
);
|
||||||
@ -871,11 +853,11 @@ impl Engine {
|
|||||||
let tid = Any::type_id(&*arr);
|
let tid = Any::type_id(&*arr);
|
||||||
|
|
||||||
if let Some(iter_fn) = self.type_iterators.get(&tid) {
|
if let Some(iter_fn) = self.type_iterators.get(&tid) {
|
||||||
scope.push((name.clone(), Box::new(())));
|
scope.push(name.clone(), ());
|
||||||
let idx = scope.len() - 1;
|
let idx = scope.len() - 1;
|
||||||
|
|
||||||
for a in iter_fn(&arr) {
|
for a in iter_fn(&arr) {
|
||||||
scope[idx].1 = a;
|
*scope.get_mut(name, idx) = a;
|
||||||
|
|
||||||
match self.eval_stmt(scope, body) {
|
match self.eval_stmt(scope, body) {
|
||||||
Err(EvalAltResult::LoopBreak) => break,
|
Err(EvalAltResult::LoopBreak) => break,
|
||||||
@ -883,7 +865,7 @@ impl Engine {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope.remove(idx);
|
scope.pop();
|
||||||
Ok(Box::new(()))
|
Ok(Box::new(()))
|
||||||
} else {
|
} else {
|
||||||
return Err(EvalAltResult::ErrorFor(expr.position()));
|
return Err(EvalAltResult::ErrorFor(expr.position()));
|
||||||
@ -901,10 +883,10 @@ impl Engine {
|
|||||||
|
|
||||||
Stmt::Let(name, init, _) => {
|
Stmt::Let(name, init, _) => {
|
||||||
if let Some(v) = init {
|
if let Some(v) = init {
|
||||||
let i = self.eval_expr(scope, v)?;
|
let val = self.eval_expr(scope, v)?;
|
||||||
scope.push((name.clone(), i));
|
scope.push_dynamic(name.clone(), val);
|
||||||
} else {
|
} else {
|
||||||
scope.push((name.clone(), Box::new(())));
|
scope.push(name.clone(), ());
|
||||||
}
|
}
|
||||||
Ok(Box::new(()))
|
Ok(Box::new(()))
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,10 @@ mod call;
|
|||||||
mod engine;
|
mod engine;
|
||||||
mod fn_register;
|
mod fn_register;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
mod scope;
|
||||||
|
|
||||||
pub use any::Dynamic;
|
pub use any::Dynamic;
|
||||||
pub use engine::{Array, Engine, EvalAltResult, Scope};
|
pub use engine::{Array, Engine, EvalAltResult};
|
||||||
|
pub use scope::Scope;
|
||||||
pub use fn_register::{RegisterDynamicFn, RegisterFn};
|
pub use fn_register::{RegisterDynamicFn, RegisterFn};
|
||||||
pub use parser::{ParseError, ParseErrorType, AST};
|
pub use parser::{ParseError, ParseErrorType, AST};
|
||||||
|
103
src/scope.rs
Normal file
103
src/scope.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use crate::any::{Any, Dynamic};
|
||||||
|
|
||||||
|
/// A type containing information about current scope.
|
||||||
|
/// Useful for keeping state between `Engine` runs
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rhai::{Engine, Scope};
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok());
|
||||||
|
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1").unwrap(), 6);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Between runs, `Engine` only remembers functions when not using own `Scope`.
|
||||||
|
|
||||||
|
pub struct Scope(Vec<(String, Dynamic)>);
|
||||||
|
|
||||||
|
impl Scope {
|
||||||
|
/// Create a new Scope.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Empty the Scope.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of variables inside the Scope.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add (push) a new variable to the Scope.
|
||||||
|
pub fn push<T: Any>(&mut self, key: String, value: T) {
|
||||||
|
self.0.push((key, Box::new(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add (push) a new variable to the Scope.
|
||||||
|
pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) {
|
||||||
|
self.0.push((key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove (pop) the last variable from the Scope.
|
||||||
|
pub fn pop(&mut self) -> Option<(String, Dynamic)> {
|
||||||
|
self.0.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Truncate (rewind) the Scope to a previous size.
|
||||||
|
pub fn rewind(&mut self, size: usize) {
|
||||||
|
self.0.truncate(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a variable in the Scope, starting from the last.
|
||||||
|
pub fn get(&self, key: &str) -> Option<(usize, String, Dynamic)> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.find(|(_, (n, _))| n == key)
|
||||||
|
.map(|(i, (n, v))| (i, n.clone(), v.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of a variable in the Scope, starting from the last.
|
||||||
|
pub fn get_value<T: Any + Clone>(&self, key: &str) -> Option<T> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.find(|(_, (n, _))| n == key)
|
||||||
|
.map(|(_, (_, v))| v.downcast_ref() as Option<&T>)
|
||||||
|
.map(|v| v.unwrap().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to a variable in the Scope.
|
||||||
|
pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic {
|
||||||
|
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
||||||
|
|
||||||
|
if entry.0 != key {
|
||||||
|
panic!("incorrect key at Scope entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
&mut entry.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator to variables in the Scope.
|
||||||
|
pub fn iter(&self) -> std::slice::Iter<(String, Dynamic)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable iterator to variables in the Scope.
|
||||||
|
pub(crate) fn iter_mut(&mut self) -> std::slice::IterMut<(String, Dynamic)> {
|
||||||
|
self.0.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::iter::Extend<(String, Dynamic)> for Scope {
|
||||||
|
fn extend<T: IntoIterator<Item = (String, Dynamic)>>(&mut self, iter: T) {
|
||||||
|
self.0.extend(iter);
|
||||||
|
}
|
||||||
|
}
|
@ -14,3 +14,38 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_eval() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// First create the state
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
// Then push some initialized variables into the state
|
||||||
|
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
||||||
|
// Better stick to them or it gets hard to work with other variables in the script.
|
||||||
|
scope.push("y".into(), 42_i64);
|
||||||
|
scope.push("z".into(), 999_i64);
|
||||||
|
|
||||||
|
// First invocation
|
||||||
|
engine
|
||||||
|
.eval_with_scope::<()>(
|
||||||
|
&mut scope,
|
||||||
|
r"
|
||||||
|
let x = 4 + 5 - y + z;
|
||||||
|
y = 1;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.expect("y and z not found?");
|
||||||
|
|
||||||
|
// Second invocation using the same state
|
||||||
|
if let Ok(result) = engine.eval_with_scope::<i64>(&mut scope, "x") {
|
||||||
|
println!("result: {}", result); // should print 966
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable y is changed in the script
|
||||||
|
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user