Minor fine tuning.

This commit is contained in:
Stephen Chung 2020-03-22 10:18:16 +08:00
parent f51864e190
commit b6320c0eef
8 changed files with 103 additions and 47 deletions

View File

@ -7,13 +7,14 @@ description = "Embedded scripting for Rust"
homepage = "https://github.com/jonathandturner/rhai" homepage = "https://github.com/jonathandturner/rhai"
repository = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai"
readme = "README.md" readme = "README.md"
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
include = [ include = [
"**/*.rs", "**/*.rs",
"scripts/*.rhai", "scripts/*.rhai",
"Cargo.toml" "Cargo.toml"
] ]
keywords = [ "scripting" ] keywords = [ "scripting" ]
categories = [ "no-std", "embedded", "parser-implementations" ]
[dependencies] [dependencies]
num-traits = "0.2.11" num-traits = "0.2.11"

View File

@ -1,6 +1,13 @@
Rhai - Embedded Scripting for Rust Rhai - Embedded Scripting for Rust
================================= =================================
![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai)
[![Travis (.org)](https://img.shields.io/travis/jonathandturner/rhai)](http://travis-ci.org/jonathandturner/rhai)
[![license](https://img.shields.io/github/license/jonathandturner/rhai)](https://github.com/license/jonathandturner/rhai)
[![crates.io](https://img.shields.io/crates/v/rhai.svg)](https::/crates.io/crates/rhai/)
![crates.io](https://img.shields.io/crates/d/rhai)
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/)
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application.
Rhai's current features set: Rhai's current features set:
@ -1464,9 +1471,9 @@ Function volatility considerations
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments. The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
This may cause the script to behave differently from the intended semantics because essentially the result of each function call will it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because
always be the same value. essentially the result of the function call will always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.

View File

@ -50,8 +50,8 @@ impl<'e> Engine<'e> {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { TestStruct { field: 1 } }
/// fn update(&mut self) { self.field += 41; } /// fn update(&mut self, offset: i64) { self.field += offset; }
/// } /// }
/// ///
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
@ -64,11 +64,11 @@ impl<'e> Engine<'e> {
/// ///
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
/// ///
/// // Register method on the type. /// // Use `register_fn` to register methods on the type.
/// engine.register_fn("update", TestStruct::update); /// engine.register_fn("update", TestStruct::update);
/// ///
/// assert_eq!( /// assert_eq!(
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?.field, /// engine.eval::<TestStruct>("let x = new_ts(); x.update(41); x")?.field,
/// 42 /// 42
/// ); /// );
/// # Ok(()) /// # Ok(())
@ -138,6 +138,8 @@ impl<'e> Engine<'e> {
/// Register a getter function for a member of a registered type with the `Engine`. /// Register a getter function for a member of a registered type with the `Engine`.
/// ///
/// The function signature must start with `&mut self` and not `&self`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -148,6 +150,8 @@ impl<'e> Engine<'e> {
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { TestStruct { field: 1 } }
///
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field } /// fn get_field(&mut self) -> i64 { self.field }
/// } /// }
/// ///
@ -222,6 +226,8 @@ impl<'e> Engine<'e> {
/// Shorthand for registering both getter and setter functions /// Shorthand for registering both getter and setter functions
/// of a registered type with the `Engine`. /// of a registered type with the `Engine`.
/// ///
/// All function signatures must start with `&mut self` and not `&self`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -231,8 +237,9 @@ impl<'e> Engine<'e> {
/// } /// }
/// ///
/// impl TestStruct { /// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } } /// fn new() -> Self { TestStruct { field: 1 } }
/// fn get_field(&mut self) -> i64 { self.field } /// fn get_field(&mut self) -> i64 { self.field }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// } /// }
/// ///

View File

@ -3,6 +3,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::parser::INT;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
@ -36,11 +37,8 @@ impl_std_args!(Array);
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64); impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64);
#[cfg(feature = "only_i32")] #[cfg(any(feature = "only_i32", feature = "only_i64"))]
impl_std_args!(i32); impl_std_args!(INT);
#[cfg(feature = "only_i64")]
impl_std_args!(i64);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl_std_args!(f32, f64); impl_std_args!(f32, f64);

View File

@ -1,16 +1,13 @@
//! Main module defining the script evaluation `Engine`. //! Main module defining the script evaluation `Engine`.
use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Scope, ScopeSource, VariableType}; use crate::scope::{Scope, ScopeSource, VariableType};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
#[cfg(not(feature = "no_index"))]
use crate::INT;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
@ -1156,7 +1153,7 @@ impl Engine<'_> {
} }
// If-else statement // If-else statement
Stmt::IfElse(guard, if_body, else_body) => self Stmt::IfThenElse(guard, if_body, else_body) => self
.eval_expr(scope, guard)? .eval_expr(scope, guard)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))

View File

@ -79,7 +79,7 @@ impl State<'_> {
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
match stmt { match stmt {
// if expr { Noop } // if expr { Noop }
Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => { Stmt::IfThenElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
state.set_dirty(); state.set_dirty();
let pos = expr.position(); let pos = expr.position();
@ -94,7 +94,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
} }
} }
// if expr { if_block } // if expr { if_block }
Stmt::IfElse(expr, if_block, None) => match *expr { Stmt::IfThenElse(expr, if_block, None) => match *expr {
// if false { if_block } -> Noop // if false { if_block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
@ -103,20 +103,20 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
// if true { if_block } -> if_block // if true { if_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true), Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block } // if expr { if_block }
expr => Stmt::IfElse( expr => Stmt::IfThenElse(
Box::new(optimize_expr(expr, state)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*if_block, state, true)), Box::new(optimize_stmt(*if_block, state, true)),
None, None,
), ),
}, },
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr { Stmt::IfThenElse(expr, if_block, Some(else_block)) => match *expr {
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Expr::False(_) => optimize_stmt(*else_block, state, true), Expr::False(_) => optimize_stmt(*else_block, state, true),
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true), Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
expr => Stmt::IfElse( expr => Stmt::IfThenElse(
Box::new(optimize_expr(expr, state)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*if_block, state, true)), Box::new(optimize_stmt(*if_block, state, true)),
match optimize_stmt(*else_block, state, true) { match optimize_stmt(*else_block, state, true) {
@ -584,8 +584,9 @@ pub fn optimize_into_ast(
AST( AST(
match engine.optimization_level { match engine.optimization_level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,
OptimizationLevel::Simple => optimize(statements, engine, &scope), OptimizationLevel::Simple | OptimizationLevel::Full => {
OptimizationLevel::Full => optimize(statements, engine, &scope), optimize(statements, engine, &scope)
}
}, },
functions functions
.into_iter() .into_iter()

View File

@ -204,7 +204,7 @@ pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),
/// if expr { stmt } else { stmt } /// if expr { stmt } else { stmt }
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>), IfThenElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
/// while expr { stmt } /// while expr { stmt }
While(Box<Expr>, Box<Stmt>), While(Box<Expr>, Box<Stmt>),
/// loop { stmt } /// loop { stmt }
@ -235,7 +235,7 @@ impl Stmt {
| Stmt::Block(_, pos) | Stmt::Block(_, pos)
| Stmt::Break(pos) | Stmt::Break(pos)
| Stmt::ReturnWithVal(_, _, pos) => *pos, | Stmt::ReturnWithVal(_, _, pos) => *pos,
Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(),
} }
} }
@ -243,7 +243,7 @@ impl Stmt {
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)? /// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool { pub fn is_self_terminated(&self) -> bool {
match self { match self {
Stmt::IfElse(_, _, _) Stmt::IfThenElse(_, _, _)
| Stmt::While(_, _) | Stmt::While(_, _)
| Stmt::Loop(_) | Stmt::Loop(_)
| Stmt::For(_, _, _) | Stmt::For(_, _, _)
@ -265,10 +265,10 @@ impl Stmt {
match self { match self {
Stmt::Noop(_) => true, Stmt::Noop(_) => true,
Stmt::Expr(expr) => expr.is_pure(), Stmt::Expr(expr) => expr.is_pure(),
Stmt::IfElse(guard, if_block, Some(else_block)) => { Stmt::IfThenElse(guard, if_block, Some(else_block)) => {
guard.is_pure() && if_block.is_pure() && else_block.is_pure() guard.is_pure() && if_block.is_pure() && else_block.is_pure()
} }
Stmt::IfElse(guard, block, None) | Stmt::While(guard, block) => { Stmt::IfThenElse(guard, block, None) | Stmt::While(guard, block) => {
guard.is_pure() && block.is_pure() guard.is_pure() && block.is_pure()
} }
Stmt::Loop(block) => block.is_pure(), Stmt::Loop(block) => block.is_pure(),
@ -2020,7 +2020,11 @@ fn parse_if<'a>(
None None
}; };
Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body)) Ok(Stmt::IfThenElse(
Box::new(guard),
Box::new(if_body),
else_body,
))
} }
/// Parse a while loop. /// Parse a while loop.

View File

@ -1,16 +1,41 @@
use rhai::{Engine, EvalAltResult, RegisterFn, Scope}; ///! This test simulates an external command object that is driven by a script.
use std::cell::Cell; use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug, Clone)] /// External command.
struct Command {
state: i64,
}
impl Command {
/// Do some action.
pub fn action(&mut self, val: i64) {
self.state = val;
}
/// Get current value.
pub fn get(&self) -> i64 {
self.state
}
}
/// Wrapper object to wrap a command object.
#[derive(Clone)]
struct CommandWrapper { struct CommandWrapper {
value: Rc<Cell<i64>>, command: Rc<RefCell<Command>>,
} }
impl CommandWrapper { impl CommandWrapper {
pub fn set_value(&mut self, x: i64) { /// Delegate command action.
let val = self.value.get(); pub fn do_action(&mut self, x: i64) {
self.value.set(val + x); let mut command = self.command.borrow_mut();
let val = command.get();
command.action(val + x);
}
/// Delegate get value action.
pub fn get_value(&mut self) -> i64 {
let command = self.command.borrow();
command.get()
} }
} }
@ -19,21 +44,37 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
let payload = Rc::new(Cell::new(12)); // Create the command object with initial state, handled by an `Rc`.
assert_eq!(payload.get(), 12); let command = Rc::new(RefCell::new(Command { state: 12 }));
assert_eq!(command.borrow().get(), 12);
let command = CommandWrapper { // Create the wrapper.
value: payload.clone(), let wrapper = CommandWrapper {
command: command.clone(), // Notice this clones the `Rc` only
}; };
scope.push_constant("Command", command); // Make the wrapper a singleton in the script environment.
scope.push_constant("Command", wrapper);
// Register type.
engine.register_type_with_name::<CommandWrapper>("CommandType"); engine.register_type_with_name::<CommandWrapper>("CommandType");
engine.register_fn("action", CommandWrapper::set_value); engine.register_fn("action", CommandWrapper::do_action);
engine.register_get("value", CommandWrapper::get_value);
engine.eval_with_scope::<()>(&mut scope, "Command.action(30)")?; assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
// Drive the command object via the wrapper
Command.action(30);
Command.value
"
)?,
42
);
assert_eq!(payload.get(), 42); // Make sure the actions are properly performed
assert_eq!(command.borrow().get(), 42);
Ok(()) Ok(())
} }