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"
repository = "https://github.com/jonathandturner/rhai"
readme = "README.md"
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
include = [
"**/*.rs",
"scripts/*.rhai",
"Cargo.toml"
]
keywords = [ "scripting" ]
categories = [ "no-std", "embedded", "parser-implementations" ]
[dependencies]
num-traits = "0.2.11"

View File

@ -1,6 +1,13 @@
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'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
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.
This may cause the script to behave differently from the intended semantics because essentially the result of each function call will
always be the same value.
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because
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.

View File

@ -51,7 +51,7 @@ impl<'e> Engine<'e> {
///
/// impl TestStruct {
/// 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> {
@ -64,11 +64,11 @@ impl<'e> Engine<'e> {
///
/// 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);
///
/// 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
/// );
/// # Ok(())
@ -138,6 +138,8 @@ impl<'e> Engine<'e> {
/// 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
///
/// ```
@ -148,6 +150,8 @@ impl<'e> Engine<'e> {
///
/// impl TestStruct {
/// 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 }
/// }
///
@ -222,6 +226,8 @@ impl<'e> Engine<'e> {
/// Shorthand for registering both getter and setter functions
/// of a registered type with the `Engine`.
///
/// All function signatures must start with `&mut self` and not `&self`.
///
/// # Example
///
/// ```
@ -233,6 +239,7 @@ impl<'e> Engine<'e> {
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// 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; }
/// }
///

View File

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

View File

@ -1,16 +1,13 @@
//! Main module defining the script evaluation `Engine`.
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::scope::{Scope, ScopeSource, VariableType};
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::OptimizationLevel;
#[cfg(not(feature = "no_index"))]
use crate::INT;
use crate::stdlib::{
any::{type_name, TypeId},
borrow::Cow,
@ -1156,7 +1153,7 @@ impl Engine<'_> {
}
// If-else statement
Stmt::IfElse(guard, if_body, else_body) => self
Stmt::IfThenElse(guard, if_body, else_body) => self
.eval_expr(scope, guard)?
.downcast::<bool>()
.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 {
match stmt {
// 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();
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 }
Stmt::IfElse(expr, if_block, None) => match *expr {
Stmt::IfThenElse(expr, if_block, None) => match *expr {
// if false { if_block } -> Noop
Expr::False(pos) => {
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
Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block }
expr => Stmt::IfElse(
expr => Stmt::IfThenElse(
Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*if_block, state, true)),
None,
),
},
// 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
Expr::False(_) => optimize_stmt(*else_block, state, true),
// if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block } else { else_block }
expr => Stmt::IfElse(
expr => Stmt::IfThenElse(
Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*if_block, state, true)),
match optimize_stmt(*else_block, state, true) {
@ -584,8 +584,9 @@ pub fn optimize_into_ast(
AST(
match engine.optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple => optimize(statements, engine, &scope),
OptimizationLevel::Full => optimize(statements, engine, &scope),
OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope)
}
},
functions
.into_iter()

View File

@ -204,7 +204,7 @@ pub enum Stmt {
/// No-op.
Noop(Position),
/// 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(Box<Expr>, Box<Stmt>),
/// loop { stmt }
@ -235,7 +235,7 @@ impl Stmt {
| Stmt::Block(_, pos)
| Stmt::Break(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(),
}
}
@ -243,7 +243,7 @@ impl Stmt {
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool {
match self {
Stmt::IfElse(_, _, _)
Stmt::IfThenElse(_, _, _)
| Stmt::While(_, _)
| Stmt::Loop(_)
| Stmt::For(_, _, _)
@ -265,10 +265,10 @@ impl Stmt {
match self {
Stmt::Noop(_) => true,
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()
}
Stmt::IfElse(guard, block, None) | Stmt::While(guard, block) => {
Stmt::IfThenElse(guard, block, None) | Stmt::While(guard, block) => {
guard.is_pure() && block.is_pure()
}
Stmt::Loop(block) => block.is_pure(),
@ -2020,7 +2020,11 @@ fn parse_if<'a>(
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.

View File

@ -1,16 +1,41 @@
use rhai::{Engine, EvalAltResult, RegisterFn, Scope};
use std::cell::Cell;
///! This test simulates an external command object that is driven by a script.
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::cell::RefCell;
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 {
value: Rc<Cell<i64>>,
command: Rc<RefCell<Command>>,
}
impl CommandWrapper {
pub fn set_value(&mut self, x: i64) {
let val = self.value.get();
self.value.set(val + x);
/// Delegate command action.
pub fn do_action(&mut self, x: i64) {
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 scope = Scope::new();
let payload = Rc::new(Cell::new(12));
assert_eq!(payload.get(), 12);
// Create the command object with initial state, handled by an `Rc`.
let command = Rc::new(RefCell::new(Command { state: 12 }));
assert_eq!(command.borrow().get(), 12);
let command = CommandWrapper {
value: payload.clone(),
// Create the wrapper.
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_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(())
}