Minor fine tuning.
This commit is contained in:
parent
f51864e190
commit
b6320c0eef
@ -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"
|
||||||
|
13
README.md
13
README.md
@ -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.
|
||||||
|
|
||||||
|
17
src/api.rs
17
src/api.rs
@ -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; }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
@ -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);
|
||||||
|
@ -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()))
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user