Merge branch 'plugins'
This commit is contained in:
commit
29044cc305
@ -22,6 +22,7 @@ num-traits = { version = "0.2.11", default-features = false }
|
|||||||
[features]
|
[features]
|
||||||
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
|
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
|
||||||
default = []
|
default = []
|
||||||
|
plugins = []
|
||||||
unchecked = [] # unchecked arithmetic
|
unchecked = [] # unchecked arithmetic
|
||||||
sync = [] # restrict to only types that implement Send + Sync
|
sync = [] # restrict to only types that implement Send + Sync
|
||||||
no_optimize = [] # no script optimizer
|
no_optimize = [] # no script optimizer
|
||||||
|
16
README.md
16
README.md
@ -1499,6 +1499,11 @@ record == "Bob X. Davis: age 42 ❤\n";
|
|||||||
"Davis" in record == true;
|
"Davis" in record == true;
|
||||||
'X' in record == true;
|
'X' in record == true;
|
||||||
'C' in record == false;
|
'C' in record == false;
|
||||||
|
|
||||||
|
// Strings can be iterated with a 'for' statement, yielding characters
|
||||||
|
for ch in record {
|
||||||
|
print(ch);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The maximum allowed length of a string can be controlled via `Engine::set_max_string_size`
|
The maximum allowed length of a string can be controlled via `Engine::set_max_string_size`
|
||||||
@ -2011,9 +2016,18 @@ loop {
|
|||||||
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
|
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let array = [1, 3, 5, 7, 9, 42];
|
// Iterate through string, yielding characters
|
||||||
|
let s = "hello, world!";
|
||||||
|
|
||||||
|
for ch in s {
|
||||||
|
if ch > 'z' { continue; } // skip to the next iteration
|
||||||
|
print(ch);
|
||||||
|
if x == '@' { break; } // break out of for loop
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate through array
|
// Iterate through array
|
||||||
|
let array = [1, 3, 5, 7, 9, 42];
|
||||||
|
|
||||||
for x in array {
|
for x in array {
|
||||||
if x > 10 { continue; } // skip to the next iteration
|
if x > 10 { continue; } // skip to the next iteration
|
||||||
print(x);
|
print(x);
|
||||||
|
@ -125,7 +125,7 @@ fn main() {
|
|||||||
|
|
||||||
match engine
|
match engine
|
||||||
.compile_with_scope(&scope, &script)
|
.compile_with_scope(&scope, &script)
|
||||||
.map_err(|err| err.into())
|
.map_err(Into::into)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
ast_u = r.clone();
|
ast_u = r.clone();
|
||||||
|
|
||||||
|
117
src/engine.rs
117
src/engine.rs
@ -230,6 +230,9 @@ pub fn get_script_function_by_signature<'a>(
|
|||||||
///
|
///
|
||||||
/// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`.
|
/// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`.
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
|
/// A unique ID identifying this scripting `Engine`.
|
||||||
|
pub id: Option<String>,
|
||||||
|
|
||||||
/// A module containing all functions directly loaded into the Engine.
|
/// A module containing all functions directly loaded into the Engine.
|
||||||
pub(crate) global_module: Module,
|
pub(crate) global_module: Module,
|
||||||
/// A collection of all library packages loaded into the Engine.
|
/// A collection of all library packages loaded into the Engine.
|
||||||
@ -274,6 +277,8 @@ impl Default for Engine {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
// Create the new scripting Engine
|
// Create the new scripting Engine
|
||||||
let mut engine = Self {
|
let mut engine = Self {
|
||||||
|
id: None,
|
||||||
|
|
||||||
packages: Default::default(),
|
packages: Default::default(),
|
||||||
global_module: Default::default(),
|
global_module: Default::default(),
|
||||||
|
|
||||||
@ -431,6 +436,8 @@ impl Engine {
|
|||||||
/// Use the `load_package` method to load additional packages of functions.
|
/// Use the `load_package` method to load additional packages of functions.
|
||||||
pub fn new_raw() -> Self {
|
pub fn new_raw() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
id: None,
|
||||||
|
|
||||||
packages: Default::default(),
|
packages: Default::default(),
|
||||||
global_module: Default::default(),
|
global_module: Default::default(),
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
@ -483,6 +490,15 @@ impl Engine {
|
|||||||
self.optimization_level = optimization_level
|
self.optimization_level = optimization_level
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current optimization level.
|
||||||
|
/// It controls whether and how the `Engine` will optimize an AST after compilation.
|
||||||
|
///
|
||||||
|
/// Not available under the `no_optimize` feature.
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub fn optimization_level(&self) -> OptimizationLevel {
|
||||||
|
self.optimization_level
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
||||||
/// infinite recursion and stack overflows.
|
/// infinite recursion and stack overflows.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -490,11 +506,27 @@ impl Engine {
|
|||||||
self.max_call_stack_depth = levels
|
self.max_call_stack_depth = levels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The maximum levels of function calls allowed for a script.
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn max_call_levels(&self) -> usize {
|
||||||
|
self.max_call_stack_depth
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the maximum number of operations allowed for a script to run to avoid
|
/// Set the maximum number of operations allowed for a script to run to avoid
|
||||||
/// consuming too much resources (0 for unlimited).
|
/// consuming too much resources (0 for unlimited).
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub fn set_max_operations(&mut self, operations: u64) {
|
pub fn set_max_operations(&mut self, operations: u64) {
|
||||||
self.max_operations = operations;
|
self.max_operations = if operations == u64::MAX {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
operations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum number of operations allowed for a script to run (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn max_operations(&self) -> u64 {
|
||||||
|
self.max_operations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum number of imported modules allowed for a script.
|
/// Set the maximum number of imported modules allowed for a script.
|
||||||
@ -503,31 +535,77 @@ impl Engine {
|
|||||||
self.max_modules = modules;
|
self.max_modules = modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the depth limits for expressions/statements (0 for unlimited).
|
/// The maximum number of imported modules allowed for a script.
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn max_modules(&self) -> usize {
|
||||||
|
self.max_modules
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the depth limits for expressions (0 for unlimited).
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) {
|
pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) {
|
||||||
self.max_expr_depth = max_expr_depth;
|
self.max_expr_depth = if max_expr_depth == usize::MAX {
|
||||||
self.max_function_expr_depth = max_function_expr_depth;
|
0
|
||||||
|
} else {
|
||||||
|
max_expr_depth
|
||||||
|
};
|
||||||
|
self.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
max_function_expr_depth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The depth limit for expressions (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn max_expr_depth(&self) -> usize {
|
||||||
|
self.max_expr_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The depth limit for expressions in functions (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn max_function_expr_depth(&self) -> usize {
|
||||||
|
self.max_function_expr_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum length of strings (0 for unlimited).
|
/// Set the maximum length of strings (0 for unlimited).
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub fn set_max_string_size(&mut self, max_size: usize) {
|
pub fn set_max_string_size(&mut self, max_size: usize) {
|
||||||
self.max_string_size = max_size;
|
self.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum length of strings (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn max_string_size(&self) -> usize {
|
||||||
|
self.max_string_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum length of arrays (0 for unlimited).
|
/// Set the maximum length of arrays (0 for unlimited).
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn set_max_array_size(&mut self, max_size: usize) {
|
pub fn set_max_array_size(&mut self, max_size: usize) {
|
||||||
self.max_array_size = max_size;
|
self.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum length of arrays (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn max_array_size(&self) -> usize {
|
||||||
|
self.max_array_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum length of object maps (0 for unlimited).
|
/// Set the maximum length of object maps (0 for unlimited).
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn set_max_map_size(&mut self, max_size: usize) {
|
pub fn set_max_map_size(&mut self, max_size: usize) {
|
||||||
self.max_map_size = max_size;
|
self.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum length of object maps (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub fn max_map_size(&self) -> usize {
|
||||||
|
self.max_map_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the module resolution service used by the `Engine`.
|
/// Set the module resolution service used by the `Engine`.
|
||||||
@ -1705,6 +1783,7 @@ impl Engine {
|
|||||||
self.call_script_fn(&mut scope, state, lib, name, fn_def, args, level)
|
self.call_script_fn(&mut scope, state, lib, name, fn_def, args, level)
|
||||||
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
||||||
}
|
}
|
||||||
|
Ok(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), *pos),
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos))
|
f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos))
|
||||||
}
|
}
|
||||||
@ -2186,14 +2265,14 @@ fn run_builtin_binary_op(
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
match op {
|
match op {
|
||||||
"+" => return add(x, y).map(Into::<Dynamic>::into).map(Some),
|
"+" => return add(x, y).map(Into::into).map(Some),
|
||||||
"-" => return sub(x, y).map(Into::<Dynamic>::into).map(Some),
|
"-" => return sub(x, y).map(Into::into).map(Some),
|
||||||
"*" => return mul(x, y).map(Into::<Dynamic>::into).map(Some),
|
"*" => return mul(x, y).map(Into::into).map(Some),
|
||||||
"/" => return div(x, y).map(Into::<Dynamic>::into).map(Some),
|
"/" => return div(x, y).map(Into::into).map(Some),
|
||||||
"%" => return modulo(x, y).map(Into::<Dynamic>::into).map(Some),
|
"%" => return modulo(x, y).map(Into::into).map(Some),
|
||||||
"~" => return pow_i_i(x, y).map(Into::<Dynamic>::into).map(Some),
|
"~" => return pow_i_i(x, y).map(Into::into).map(Some),
|
||||||
">>" => return shr(x, y).map(Into::<Dynamic>::into).map(Some),
|
">>" => return shr(x, y).map(Into::into).map(Some),
|
||||||
"<<" => return shl(x, y).map(Into::<Dynamic>::into).map(Some),
|
"<<" => return shl(x, y).map(Into::into).map(Some),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2204,9 +2283,9 @@ fn run_builtin_binary_op(
|
|||||||
"*" => return Ok(Some((x * y).into())),
|
"*" => return Ok(Some((x * y).into())),
|
||||||
"/" => return Ok(Some((x / y).into())),
|
"/" => return Ok(Some((x / y).into())),
|
||||||
"%" => return Ok(Some((x % y).into())),
|
"%" => return Ok(Some((x % y).into())),
|
||||||
"~" => return pow_i_i_u(x, y).map(Into::<Dynamic>::into).map(Some),
|
"~" => return pow_i_i_u(x, y).map(Into::into).map(Some),
|
||||||
">>" => return shr_u(x, y).map(Into::<Dynamic>::into).map(Some),
|
">>" => return shr_u(x, y).map(Into::into).map(Some),
|
||||||
"<<" => return shl_u(x, y).map(Into::<Dynamic>::into).map(Some),
|
"<<" => return shl_u(x, y).map(Into::into).map(Some),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2280,7 +2359,7 @@ fn run_builtin_binary_op(
|
|||||||
"*" => return Ok(Some((x * y).into())),
|
"*" => return Ok(Some((x * y).into())),
|
||||||
"/" => return Ok(Some((x / y).into())),
|
"/" => return Ok(Some((x / y).into())),
|
||||||
"%" => return Ok(Some((x % y).into())),
|
"%" => return Ok(Some((x % y).into())),
|
||||||
"~" => return pow_f_f(x, y).map(Into::<Dynamic>::into).map(Some),
|
"~" => return pow_f_f(x, y).map(Into::into).map(Some),
|
||||||
"==" => return Ok(Some((x == y).into())),
|
"==" => return Ok(Some((x == y).into())),
|
||||||
"!=" => return Ok(Some((x != y).into())),
|
"!=" => return Ok(Some((x != y).into())),
|
||||||
">" => return Ok(Some((x > y).into())),
|
">" => return Ok(Some((x > y).into())),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::parser::ScriptFnDef;
|
use crate::parser::ScriptFnDef;
|
||||||
|
use crate::plugin::PluginFunction;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
|
|
||||||
use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc};
|
use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc};
|
||||||
@ -59,6 +60,11 @@ pub type FnAny =
|
|||||||
|
|
||||||
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub type SharedPluginFunction = Arc<dyn PluginFunction + Send + Sync>;
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub type SharedPluginFunction = Rc<dyn PluginFunction>;
|
||||||
|
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + 'static>;
|
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + 'static>;
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
@ -74,6 +80,8 @@ pub enum CallableFunction {
|
|||||||
Method(Shared<FnAny>),
|
Method(Shared<FnAny>),
|
||||||
/// An iterator function.
|
/// An iterator function.
|
||||||
Iterator(IteratorFn),
|
Iterator(IteratorFn),
|
||||||
|
/// A plugin-defined function,
|
||||||
|
Plugin(SharedPluginFunction),
|
||||||
/// A script-defined function.
|
/// A script-defined function.
|
||||||
Script(Shared<ScriptFnDef>),
|
Script(Shared<ScriptFnDef>),
|
||||||
}
|
}
|
||||||
@ -84,6 +92,7 @@ impl fmt::Debug for CallableFunction {
|
|||||||
Self::Pure(_) => write!(f, "NativePureFunction"),
|
Self::Pure(_) => write!(f, "NativePureFunction"),
|
||||||
Self::Method(_) => write!(f, "NativeMethod"),
|
Self::Method(_) => write!(f, "NativeMethod"),
|
||||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||||
|
Self::Plugin(_) => write!(f, "PluginFunction"),
|
||||||
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
|
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,6 +104,7 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Pure(_) => true,
|
Self::Pure(_) => true,
|
||||||
Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
|
Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||||
|
Self::Plugin(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a pure native Rust method-call?
|
/// Is this a pure native Rust method-call?
|
||||||
@ -102,6 +112,7 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Method(_) => true,
|
Self::Method(_) => true,
|
||||||
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false,
|
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||||
|
Self::Plugin(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this an iterator function?
|
/// Is this an iterator function?
|
||||||
@ -109,6 +120,7 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Iterator(_) => true,
|
Self::Iterator(_) => true,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) => false,
|
Self::Pure(_) | Self::Method(_) | Self::Script(_) => false,
|
||||||
|
Self::Plugin(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a Rhai-scripted function?
|
/// Is this a Rhai-scripted function?
|
||||||
@ -116,6 +128,14 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Script(_) => true,
|
Self::Script(_) => true,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
||||||
|
Self::Plugin(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is this a plugin-defined function?
|
||||||
|
pub fn is_plugin_fn(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Plugin(_) => true,
|
||||||
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a reference to a native Rust function.
|
/// Get a reference to a native Rust function.
|
||||||
@ -127,6 +147,7 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
||||||
Self::Iterator(_) | Self::Script(_) => panic!(),
|
Self::Iterator(_) | Self::Script(_) => panic!(),
|
||||||
|
Self::Plugin(_) => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a shared reference to a script-defined function definition.
|
/// Get a shared reference to a script-defined function definition.
|
||||||
@ -136,7 +157,7 @@ impl CallableFunction {
|
|||||||
/// Panics if the `CallableFunction` is not `Script`.
|
/// Panics if the `CallableFunction` is not `Script`.
|
||||||
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(),
|
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) | Self::Iterator(_) => panic!(),
|
||||||
Self::Script(f) => f.clone(),
|
Self::Script(f) => f.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +170,7 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(),
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(),
|
||||||
Self::Script(f) => f,
|
Self::Script(f) => f,
|
||||||
|
Self::Plugin(_) => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a reference to an iterator function.
|
/// Get a reference to an iterator function.
|
||||||
@ -160,6 +182,18 @@ impl CallableFunction {
|
|||||||
match self {
|
match self {
|
||||||
Self::Iterator(f) => *f,
|
Self::Iterator(f) => *f,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) => panic!(),
|
Self::Pure(_) | Self::Method(_) | Self::Script(_) => panic!(),
|
||||||
|
Self::Plugin(_) => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Get a reference to a plugin function.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `CallableFunction` is not `Plugin`.
|
||||||
|
pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction {
|
||||||
|
match self {
|
||||||
|
Self::Plugin(f) => f.clone(),
|
||||||
|
Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Iterator(_) => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Create a new `CallableFunction::Pure`.
|
/// Create a new `CallableFunction::Pure`.
|
||||||
@ -170,6 +204,18 @@ impl CallableFunction {
|
|||||||
pub fn from_method(func: Box<FnAny>) -> Self {
|
pub fn from_method(func: Box<FnAny>) -> Self {
|
||||||
Self::Method(func.into())
|
Self::Method(func.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
/// Create a new `CallableFunction::Plugin`.
|
||||||
|
pub fn from_plugin(plugin: impl PluginFunction + 'static + Send + Sync) -> Self {
|
||||||
|
Self::Plugin(Arc::new(plugin))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
/// Create a new `CallableFunction::Plugin`.
|
||||||
|
pub fn from_plugin(plugin: impl PluginFunction + 'static) -> Self {
|
||||||
|
Self::Plugin(Rc::new(plugin))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IteratorFn> for CallableFunction {
|
impl From<IteratorFn> for CallableFunction {
|
||||||
|
@ -5,11 +5,91 @@ use crate::any::{Dynamic, Variant};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
|
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
|
||||||
use crate::parser::FnAccess;
|
use crate::parser::FnAccess;
|
||||||
|
use crate::plugin::Plugin;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::ImmutableString;
|
||||||
|
|
||||||
use crate::stdlib::{any::TypeId, boxed::Box, mem};
|
use crate::stdlib::{any::TypeId, boxed::Box, mem};
|
||||||
|
|
||||||
|
/// A trait to register custom plugins with the `Engine`.
|
||||||
|
///
|
||||||
|
/// A plugin consists of a number of functions. All functions will be registered with the engine.
|
||||||
|
pub trait RegisterPlugin<PL: crate::plugin::Plugin> {
|
||||||
|
/// Allow extensions of the engine's behavior.
|
||||||
|
///
|
||||||
|
/// This can include importing modules, registering functions to the global name space, and
|
||||||
|
/// more.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{FLOAT, INT, Module, ModuleResolver, RegisterFn, RegisterPlugin};
|
||||||
|
/// use rhai::plugin::*;
|
||||||
|
/// use rhai::module_resolvers::*;
|
||||||
|
///
|
||||||
|
/// // A function we want to expose to Rhai.
|
||||||
|
/// #[derive(Copy, Clone)]
|
||||||
|
/// struct DistanceFunction();
|
||||||
|
///
|
||||||
|
/// impl PluginFunction for DistanceFunction {
|
||||||
|
/// fn is_method_call(&self) -> bool { false }
|
||||||
|
/// fn is_varadic(&self) -> bool { false }
|
||||||
|
///
|
||||||
|
/// fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
/// let x1: &FLOAT = args[0].downcast_ref::<FLOAT>().unwrap();
|
||||||
|
/// let y1: &FLOAT = args[1].downcast_ref::<FLOAT>().unwrap();
|
||||||
|
/// let x2: &FLOAT = args[2].downcast_ref::<FLOAT>().unwrap();
|
||||||
|
/// let y2: &FLOAT = args[3].downcast_ref::<FLOAT>().unwrap();
|
||||||
|
/// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0);
|
||||||
|
/// Ok(Dynamic::from(square_sum.sqrt()))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn clone_boxed(&self) -> Box<dyn PluginFunction> {
|
||||||
|
/// Box::new(DistanceFunction())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // A simple custom plugin. This should not usually be done with hand-written code.
|
||||||
|
/// #[derive(Copy, Clone)]
|
||||||
|
/// pub struct AdvancedMathPlugin();
|
||||||
|
///
|
||||||
|
/// impl Plugin for AdvancedMathPlugin {
|
||||||
|
/// fn register_contents(self, engine: &mut Engine) {
|
||||||
|
/// // Plugins are allowed to have side-effects on the engine.
|
||||||
|
/// engine.register_fn("get_mystic_number", || { 42 as FLOAT });
|
||||||
|
///
|
||||||
|
/// // Main purpose: create a module to expose the functions to Rhai.
|
||||||
|
/// //
|
||||||
|
/// // This is currently a hack. There needs to be a better API here for "plugin"
|
||||||
|
/// // modules.
|
||||||
|
/// let mut m = Module::new();
|
||||||
|
/// m.set_fn("euclidean_distance".to_string(), FnAccess::Public,
|
||||||
|
/// &[std::any::TypeId::of::<FLOAT>(),
|
||||||
|
/// std::any::TypeId::of::<FLOAT>(),
|
||||||
|
/// std::any::TypeId::of::<FLOAT>(),
|
||||||
|
/// std::any::TypeId::of::<FLOAT>()],
|
||||||
|
/// CallableFunction::from_plugin(DistanceFunction()));
|
||||||
|
/// let mut r = StaticModuleResolver::new();
|
||||||
|
/// r.insert("Math::Advanced".to_string(), m);
|
||||||
|
/// engine.set_module_resolver(Some(r));
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// engine.register_plugin(AdvancedMathPlugin());
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval::<FLOAT>(
|
||||||
|
/// r#"import "Math::Advanced" as math;
|
||||||
|
/// let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 41.0);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn register_plugin(&mut self, plugin: PL);
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait to register custom functions with the `Engine`.
|
/// Trait to register custom functions with the `Engine`.
|
||||||
pub trait RegisterFn<FN, ARGS, RET> {
|
pub trait RegisterFn<FN, ARGS, RET> {
|
||||||
/// Register a custom function with the `Engine`.
|
/// Register a custom function with the `Engine`.
|
||||||
@ -111,6 +191,12 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<PL: Plugin> RegisterPlugin<PL> for Engine {
|
||||||
|
fn register_plugin(&mut self, plugin: PL) {
|
||||||
|
plugin.register_contents(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This macro creates a closure wrapping a registered function.
|
/// This macro creates a closure wrapping a registered function.
|
||||||
macro_rules! make_func {
|
macro_rules! make_func {
|
||||||
($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
|
($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
|
||||||
|
@ -75,10 +75,14 @@ mod engine;
|
|||||||
mod error;
|
mod error;
|
||||||
mod fn_call;
|
mod fn_call;
|
||||||
mod fn_func;
|
mod fn_func;
|
||||||
mod fn_native;
|
pub mod fn_native;
|
||||||
mod fn_register;
|
mod fn_register;
|
||||||
mod module;
|
mod module;
|
||||||
mod optimize;
|
mod optimize;
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
pub mod plugin;
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
mod plugin;
|
||||||
pub mod packages;
|
pub mod packages;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod result;
|
mod result;
|
||||||
@ -92,6 +96,7 @@ pub use any::Dynamic;
|
|||||||
pub use engine::Engine;
|
pub use engine::Engine;
|
||||||
pub use error::{ParseError, ParseErrorType};
|
pub use error::{ParseError, ParseErrorType};
|
||||||
pub use fn_register::{RegisterFn, RegisterResultFn};
|
pub use fn_register::{RegisterFn, RegisterResultFn};
|
||||||
|
pub use fn_register::RegisterPlugin;
|
||||||
pub use module::Module;
|
pub use module::Module;
|
||||||
pub use parser::{ImmutableString, AST, INT};
|
pub use parser::{ImmutableString, AST, INT};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
|
@ -15,6 +15,7 @@ use crate::stdlib::{
|
|||||||
fmt::Display,
|
fmt::Display,
|
||||||
format,
|
format,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn prepend<T: Display>(x: T, y: ImmutableString) -> FuncReturn<ImmutableString> {
|
fn prepend<T: Display>(x: T, y: ImmutableString) -> FuncReturn<ImmutableString> {
|
||||||
@ -293,4 +294,12 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
|||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Register string iterator
|
||||||
|
lib.set_iter(
|
||||||
|
TypeId::of::<ImmutableString>(),
|
||||||
|
|arr| Box::new(
|
||||||
|
arr.cast::<ImmutableString>().chars().collect::<Vec<_>>().into_iter().map(Into::into)
|
||||||
|
) as Box<dyn Iterator<Item = Dynamic>>,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
42
src/plugin.rs
Normal file
42
src/plugin.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//! Module defining plugins in Rhai. Is exported for use by plugin authors.
|
||||||
|
|
||||||
|
pub use crate::Engine;
|
||||||
|
pub use crate::any::{Dynamic, Variant};
|
||||||
|
pub use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
|
||||||
|
pub use crate::parser::{
|
||||||
|
FnAccess,
|
||||||
|
FnAccess::{Private, Public},
|
||||||
|
AST,
|
||||||
|
};
|
||||||
|
pub use crate::result::EvalAltResult;
|
||||||
|
pub use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||||
|
pub use crate::token::{Position, Token};
|
||||||
|
pub use crate::utils::StaticVec;
|
||||||
|
|
||||||
|
#[cfg(features = "sync")]
|
||||||
|
/// Represents an externally-written plugin for the Rhai interpreter.
|
||||||
|
///
|
||||||
|
/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead.
|
||||||
|
pub trait Plugin: Send {
|
||||||
|
fn register_contents(self, engine: &mut Engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(features = "sync"))]
|
||||||
|
/// Represents an externally-written plugin for the Rhai interpreter.
|
||||||
|
///
|
||||||
|
/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead.
|
||||||
|
pub trait Plugin: Send + Sync {
|
||||||
|
fn register_contents(self, engine: &mut Engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a function that is statically defined within a plugin.
|
||||||
|
///
|
||||||
|
/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead.
|
||||||
|
pub trait PluginFunction {
|
||||||
|
fn is_method_call(&self) -> bool;
|
||||||
|
fn is_varadic(&self) -> bool;
|
||||||
|
|
||||||
|
fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||||
|
|
||||||
|
fn clone_boxed(&self) -> Box<dyn PluginFunction>;
|
||||||
|
}
|
20
tests/for.rs
20
tests/for.rs
@ -30,6 +30,26 @@ fn test_for_array() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_for_string() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
let script = r#"
|
||||||
|
let s = "hello";
|
||||||
|
let sum = 0;
|
||||||
|
|
||||||
|
for ch in s {
|
||||||
|
sum += ch.to_int();
|
||||||
|
}
|
||||||
|
|
||||||
|
sum
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>(script)?, 532);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user