Merge pull request #189 from schungx/master

Function pointers currying.
This commit is contained in:
Stephen Chung 2020-07-24 16:56:45 +08:00 committed by GitHub
commit f7378f7feb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1501 additions and 1251 deletions

View File

@ -7,6 +7,7 @@ Version 0.18.0
This version adds:
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
* Currying of function pointers.
New features
------------
@ -15,6 +16,8 @@ New features
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`.
* Custom syntax now works even without the `internals` feature.
* Currying of function pointers is supported via the `curry` keyword.
Breaking changes
----------------
@ -48,7 +51,7 @@ Breaking changes
New features
------------
* New `serde` feature to allow serializating/deserializating to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
* New `serde` feature to allow serializing/deserializing to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde).
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back.
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator.

View File

@ -76,6 +76,7 @@ The Rhai Scripting Language
3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md)
6. [Currying](language/fn-curry.md)
15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)

View File

@ -26,6 +26,7 @@ Keywords List
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
| `Fn` (capital `F`) | Function to create a [function pointer] | |
| `call` | Call a [function pointer] | |
| `curry` | Curry a [function pointer] | |
| `this` | Reference to base object for method call | [`no_function`] |
| `type_of` | Get type name of value | |
| `print` | Print value | |

View File

@ -33,18 +33,7 @@ Where This Might Be Useful
* Where you just want to confuse your user and make their lives miserable, because you can.
Step One - Start With `internals`
--------------------------------
Since a custom syntax taps deeply into the `AST` and evaluation process of the `Engine`,
the [`internals`] feature must be on in order to expose these necessary internal data structures.
Beware that Rhai internal data structures are _volatile_ and may change without warning.
Caveat emptor.
Step Two - Design The Syntax
Step One - Design The Syntax
---------------------------
A custom syntax is simply a list of symbols.
@ -116,8 +105,8 @@ print(hello); // variable declared by a custom syntax persists!
```
Step Three - Implementation
--------------------------
Step Two - Implementation
-------------------------
Any custom syntax must include an _implementation_ of it.
@ -164,8 +153,6 @@ let expr = inputs.get(0).unwrap();
let result = engine.eval_expression_tree(context, scope, expr)?;
```
As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`.
### Declare Variables
New variables maybe declared (usually with a variable name that is passed in via `$ident$).
@ -179,14 +166,14 @@ In other words, any `scope.push(...)` calls must come _before_ any `engine::eval
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let expr = inputs.get(1).unwrap();
scope.push(var_name, 0 as INT); // do this BEFORE engine.eval_expression_tree!
scope.push(var_name, 0 as INT); // do this BEFORE 'engine.eval_expression_tree'!
let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?;
let result = engine.eval_expression_tree(context, scope, expr)?;
```
Step Four - Register the Custom Syntax
-------------------------------------
Step Three - Register the Custom Syntax
--------------------------------------
Use `Engine::register_custom_syntax` to register a custom syntax.
@ -236,7 +223,7 @@ engine.register_custom_syntax(
```
Step Five - Disable Unneeded Statement Types
Step Four - Disable Unneeded Statement Types
-------------------------------------------
When a DSL needs a custom syntax, most likely than not it is extremely specialized.
@ -254,13 +241,13 @@ custom syntax (plus possibly expressions). But again, Don't Do It™ - unless y
of what you're doing.
Step Six - Document
-------------------
Step Five - Document
--------------------
For custom syntax, documentation is crucial.
Make sure there are _lots_ of examples for users to follow.
Step Seven - Profit!
--------------------
Step Six - Profit!
------------------

View File

@ -11,7 +11,7 @@ Expressions Only
In many DSL scenarios, only evaluation of expressions is needed.
The `Engine::eval_expression_XXX`[`eval_expression`] API can be used to restrict
The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict
a script to expressions only.
@ -21,8 +21,7 @@ Disable Keywords and/or Operators
In some DSL scenarios, it is necessary to further restrict the language to exclude certain
language features that are not necessary or dangerous to the application.
For example, a DSL may disable the `while` loop altogether while keeping all other statement
types intact.
For example, a DSL may disable the `while` loop while keeping all other statement types intact.
It is possible, in Rhai, to surgically [disable keywords and operators].
@ -54,31 +53,29 @@ Custom Syntax
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] -
essentially custom statement types.
The [`internals`] feature is needed to be able to define [custom syntax] in Rhai.
For example, the following is a SQL like syntax for some obscure DSL operation:
For example, the following is a SQL-like syntax for some obscure DSL operation:
```rust
let table = [..., ..., ..., ...];
// Syntax = "calculate" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$
// Syntax = calculate $ident$ $ident$ from $expr$ -> $ident$ : $expr$
let total = calculate sum price from table -> row : row.weight > 50;
// Note: There is nothing special about the use of symbols; to make it look exactly like SQL:
// Syntax = "SELECT" $ident$ "(" $ident$ ")" "FROM" $expr$ "AS" $ident$ "WHERE" $expr$
// Note: There is nothing special about those symbols; to make it look exactly like SQL:
// Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$
let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50;
```
After registering this custom syntax with Rhai, it can be used anywhere inside a script as
a normal expression.
For its evaluation, the callback function will receive the following list of parameters:
For its evaluation, the callback function will receive the following list of inputs:
`exprs[0] = "sum"` - math operator
`exprs[1] = "price"` - field name
`exprs[2] = Expression(table)` - data source
`exprs[3] = "row"` - loop variable name
`exprs[4] = Expression(row.wright > 50)` - expression
* `inputs[0] = "sum"` - math operator
* `inputs[1] = "price"` - field name
* `inputs[2] = Expression(table)` - data source
* `inputs[3] = "row"` - loop variable name
* `inputs[4] = Expression(row.wright > 50)` - filter predicate
The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are
parsed in the order defined within the custom syntax.
Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`,
are parsed in the order defined within the custom syntax.

View File

@ -0,0 +1,30 @@
Function Pointer Currying
========================
{{#include ../links.md}}
It is possible to _curry_ a [function pointer] by providing partial (or all) arguments.
Currying is done via the `curry` keyword and produces a new [function pointer] which carries
the curried arguments.
When the curried [function pointer] is called, the curried arguments are inserted starting from the left.
The actual call arguments should be reduced by the number of curried arguments.
```rust
fn mul(x, y) { // function with two parameters
x * y
}
let func = Fn("mul");
func.call(21, 2) == 42; // two arguments are required for 'mul'
let curried = func.curry(21); // currying produces a new function pointer which
// carries 21 as the first argument
let curried = curry(func, 21); // function-call style also works
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
// only one argument is now required
```

View File

@ -20,9 +20,14 @@ The `ImmutableString` Type
-------------------------
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
An `ImmutableString` does not change and can be shared.
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions
because using `String` is very inefficient (the `String` must always be cloned).
Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy.
### **IMPORTANT** - Avoid `String` Parameters
`ImmutableString` should be used in place of `String` for function parameters because using
`String` is very inefficient (the `String` argument is cloned during every call).
A alternative is to use `&str` which maps straight to `ImmutableString`.
@ -67,14 +72,6 @@ Individual characters within a Rhai string can also be replaced just as if the s
In Rhai, there are also no separate concepts of `String` and `&str` as in Rust.
Immutable Strings
----------------
Rhai use _immutable_ strings (type `ImmutableString`) and can be shared.
Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy.
Examples
--------

View File

@ -14,7 +14,7 @@ The following primitive types are supported natively:
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if not [WASM] build) | `"timestamp"` | _not supported_ |
| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ |
| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |

View File

@ -76,6 +76,7 @@
[functions]: {{rootUrl}}/language/functions.md
[function pointer]: {{rootUrl}}/language/fn-ptr.md
[function pointers]: {{rootUrl}}/language/fn-ptr.md
[currying]: {{rootUrl}}/language/fn-curry.md
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.md

View File

@ -4,19 +4,32 @@
{{#include ../links.md}}
Avoid `String`
--------------
As must as possible, avoid using `String` parameters in functions.
Each `String` argument is cloned during every single call to that function - and the copy
immediately thrown away right after the call.
Needless to say, it is _extremely_ inefficient to use `String` parameters.
`&str` Maps to `ImmutableString`
-------------------------------
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to
[`ImmutableString`][string] which is the type that Rhai uses to represent [strings] internally.
The parameter type `String` is discouraged because it involves converting an [`ImmutableString`] into a `String`.
The parameter type `String` involves always converting an [`ImmutableString`][string] into a `String`
which mandates cloning it.
Using `ImmutableString` or `&str` is much more efficient.
A common mistake made by novice Rhai users is to register functions with `String` parameters.
```rust
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai finds this function, but very inefficient
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Very inefficient!!!
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- This is better
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
engine
@ -24,9 +37,9 @@ engine
.register_fn("len2", get_len2)
.register_fn("len3", get_len3);
let len = engine.eval::<i64>("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found
let len = engine.eval::<i64>("x.len2()")?; // works fine
let len = engine.eval::<i64>("x.len3()")?; // works fine
let len = engine.eval::<i64>("x.len1()")?; // 'x' is cloned, very inefficient!
let len = engine.eval::<i64>("x.len2()")?; // 'x' is shared
let len = engine.eval::<i64>("x.len3()")?; // 'x' is shared
```
@ -37,9 +50,11 @@ Rhai functions can take a first `&mut` parameter. Usually this is a good idea b
cloning of the argument (except for primary types where cloning is cheap), so its use is encouraged
even though there is no intention to ever mutate that argument.
`ImmutableString` is an exception to this rule. While `ImmutableString` is cheap to clone (only
incrementing a reference count), taking a mutable reference to it involves making a private clone
of the underlying string because Rhai has no way to find out whether that parameter will be mutated.
[`ImmutableString`][string] is an exception to this rule.
While `ImmutableString` is cheap to clone (only incrementing a reference count), taking a mutable
reference to it involves making a private clone of the underlying string because Rhai has no way
to find out whether that parameter will be mutated.
If the `ImmutableString` is not shared by any other variables, then Rhai just returns a mutable
reference to it since nobody else is watching! Otherwise a private copy is made first,

View File

@ -137,7 +137,7 @@ pub enum Union {
Array(Box<Array>),
#[cfg(not(feature = "no_object"))]
Map(Box<Map>),
FnPtr(FnPtr),
FnPtr(Box<FnPtr>),
Variant(Box<Box<dyn Variant>>),
}
@ -274,7 +274,7 @@ impl fmt::Debug for Dynamic {
f.write_str("#")?;
fmt::Debug::fmt(value, f)
}
Union::FnPtr(value) => fmt::Display::fmt(value, f),
Union::FnPtr(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
@ -481,7 +481,7 @@ impl Dynamic {
}
if type_id == TypeId::of::<FnPtr>() {
return match self.0 {
Union::FnPtr(value) => unsafe_try_cast(value),
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None,
};
}
@ -582,7 +582,7 @@ impl Dynamic {
}
if type_id == TypeId::of::<FnPtr>() {
return match &self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
@ -656,7 +656,7 @@ impl Dynamic {
}
if type_id == TypeId::of::<FnPtr>() {
return match &mut self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None,
};
}
@ -735,7 +735,7 @@ impl Dynamic {
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 {
Union::Str(s) => Ok(s),
Union::FnPtr(f) => Ok(f.take_fn_name()),
Union::FnPtr(f) => Ok(f.take_data().0),
_ => Err(self.type_name()),
}
}
@ -801,6 +801,11 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
}
impl From<FnPtr> for Dynamic {
fn from(value: FnPtr) -> Self {
Box::new(value).into()
}
}
impl From<Box<FnPtr>> for Dynamic {
fn from(value: Box<FnPtr>) -> Self {
Self(Union::FnPtr(value))
}
}

View File

@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET};
use crate::error::ParseError;
use crate::fn_call::FuncArgs;
use crate::fn_args::FuncArgs;
use crate::fn_native::{IteratorFn, SendSync};
use crate::fn_register::RegisterFn;
use crate::module::{FuncReturn, Module};

File diff suppressed because it is too large Load Diff

45
src/fn_args.rs Normal file
View File

@ -0,0 +1,45 @@
//! Helper module which defines `FuncArgs` to make function calling easier.
#![allow(non_snake_case)]
use crate::any::{Dynamic, Variant};
use crate::utils::StaticVec;
/// Trait that represents arguments to a function call.
/// Any data type that can be converted into a `Vec<Dynamic>` can be used
/// as arguments to a function call.
pub trait FuncArgs {
/// Convert to a `StaticVec<Dynamic>` of the function call arguments.
fn into_vec(self) -> StaticVec<Dynamic>;
}
/// Macro to implement `FuncArgs` for tuples of standard types (each can be
/// converted into `Dynamic`).
macro_rules! impl_args {
($($p:ident),*) => {
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
{
fn into_vec(self) -> StaticVec<Dynamic> {
let ($($p,)*) = self;
#[allow(unused_mut)]
let mut v = StaticVec::new();
$(v.push($p.into_dynamic());)*
v
}
}
impl_args!(@pop $($p),*);
};
(@pop) => {
};
(@pop $head:ident) => {
impl_args!();
};
(@pop $head:ident $(, $tail:ident)+) => {
impl_args!($($tail),*);
};
}
impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
//! Module which defines the function registration mechanism.
#![cfg(not(feature = "no_function"))]
#![allow(non_snake_case)]

View File

@ -1,13 +1,15 @@
//! Module containing interfaces with native-Rust functions.
//! Module defining interfaces to native-Rust functions.
use crate::any::Dynamic;
use crate::engine::Engine;
use crate::module::Module;
use crate::module::{FuncReturn, Module};
use crate::parser::ScriptFnDef;
use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString;
use crate::utils::{ImmutableString, StaticVec};
use crate::Scope;
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, string::String, sync::Arc};
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc};
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")]
@ -49,14 +51,15 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct FnPtr(ImmutableString);
/// A general function pointer, which may carry additional (i.e. curried) argument values
/// to be passed onto a function during a call.
#[derive(Debug, Clone, Default)]
pub struct FnPtr(ImmutableString, Vec<Dynamic>);
impl FnPtr {
/// Create a new function pointer.
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S) -> Self {
Self(name.into())
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S, curry: Vec<Dynamic>) -> Self {
Self(name.into(), curry)
}
/// Get the name of the function.
pub fn fn_name(&self) -> &str {
@ -66,9 +69,38 @@ impl FnPtr {
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
&self.0
}
/// Get the name of the function.
pub(crate) fn take_fn_name(self) -> ImmutableString {
self.0
/// Get the underlying data of the function pointer.
pub(crate) fn take_data(self) -> (ImmutableString, Vec<Dynamic>) {
(self.0, self.1)
}
/// Get the curried arguments.
pub fn curry(&self) -> &[Dynamic] {
&self.1
}
/// Call the function pointer with curried arguments (if any).
///
/// ## WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
pub fn call_dynamic(
&self,
engine: &Engine,
lib: impl AsRef<Module>,
this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> FuncReturn<Dynamic> {
let args = self
.1
.iter()
.cloned()
.chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v)))
.collect::<StaticVec<_>>();
engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args)
}
}
@ -83,7 +115,7 @@ impl TryFrom<ImmutableString> for FnPtr {
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
if is_valid_identifier(value.chars()) {
Ok(Self(value))
Ok(Self(value, Default::default()))
} else {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
value.into(),

View File

@ -1,4 +1,5 @@
//! Module which defines the function registration mechanism.
#![allow(non_snake_case)]
use crate::any::{Dynamic, Variant};

View File

@ -77,6 +77,7 @@ mod any;
mod api;
mod engine;
mod error;
mod fn_args;
mod fn_call;
mod fn_func;
mod fn_native;
@ -91,7 +92,6 @@ mod scope;
mod serde;
mod settings;
mod stdlib;
#[cfg(feature = "internals")]
mod syntax;
mod token;
mod r#unsafe;
@ -106,6 +106,7 @@ pub use module::Module;
pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult;
pub use scope::Scope;
pub use syntax::{EvalContext, Expression};
pub use token::Position;
pub use utils::calc_fn_spec as calc_fn_hash;
@ -169,11 +170,7 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt};
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
pub use engine::{Expression, Imports, State as EvalState};
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
pub use syntax::EvalContext;
pub use engine::{Imports, State as EvalState};
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]

View File

@ -1,3 +1,5 @@
//! Module implementing the AST optimizer.
use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::{

View File

@ -2,23 +2,19 @@
use crate::any::{Dynamic, Union};
use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS};
use crate::engine::{
make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR,
MARKER_IDENT,
};
use crate::error::{LexError, ParseError, ParseErrorType};
use crate::fn_native::Shared;
use crate::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::syntax::FnCustomSyntaxEval;
use crate::token::{is_valid_identifier, Position, Token, TokenStream};
use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(feature = "internals")]
use crate::engine::{MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
#[cfg(feature = "internals")]
use crate::fn_native::Shared;
#[cfg(feature = "internals")]
use crate::syntax::FnCustomSyntaxEval;
use crate::stdlib::{
borrow::Cow,
boxed::Box,
@ -587,17 +583,14 @@ impl Stmt {
}
#[derive(Clone)]
#[cfg(feature = "internals")]
pub struct CustomExpr(pub StaticVec<Expr>, pub Shared<FnCustomSyntaxEval>);
#[cfg(feature = "internals")]
impl fmt::Debug for CustomExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
#[cfg(feature = "internals")]
impl Hash for CustomExpr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
@ -683,7 +676,6 @@ pub enum Expr {
/// ()
Unit(Position),
/// Custom syntax
#[cfg(feature = "internals")]
Custom(Box<(CustomExpr, Position)>),
}
@ -781,7 +773,6 @@ impl Expr {
Self::Dot(x) | Self::Index(x) => x.0.position(),
#[cfg(feature = "internals")]
Self::Custom(x) => x.1,
}
}
@ -816,8 +807,6 @@ impl Expr {
Self::Assignment(x) => x.3 = new_pos,
Self::Dot(x) => x.2 = new_pos,
Self::Index(x) => x.2 = new_pos,
#[cfg(feature = "internals")]
Self::Custom(x) => x.1 = new_pos,
}
@ -925,7 +914,6 @@ impl Expr {
_ => false,
},
#[cfg(feature = "internals")]
Self::Custom(_) => false,
}
}
@ -2148,7 +2136,6 @@ fn parse_expr(
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// Check if it is a custom syntax.
#[cfg(feature = "internals")]
if let Some(ref custom) = state.engine.custom_syntax {
let (token, pos) = input.peek().unwrap();
let token_pos = *pos;

View File

@ -1,3 +1,5 @@
//! Configuration settings for `Engine`.
use crate::engine::Engine;
use crate::module::ModuleResolver;
use crate::optimize::OptimizationLevel;

View File

@ -1,14 +1,14 @@
//! Module containing implementation for custom syntax.
#![cfg(feature = "internals")]
//! Module implementing custom syntax for `Engine`.
use crate::any::Dynamic;
use crate::engine::{Engine, Expression, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::error::LexError;
use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::error::{LexError, ParseError};
use crate::fn_native::{SendSync, Shared};
use crate::module::Module;
use crate::parser::Expr;
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{is_valid_identifier, Token};
use crate::token::{is_valid_identifier, Position, Token};
use crate::utils::StaticVec;
use crate::stdlib::{
@ -18,7 +18,7 @@ use crate::stdlib::{
sync::Arc,
};
/// A general function trail object.
/// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxEval = dyn Fn(
&Engine,
@ -26,12 +26,40 @@ pub type FnCustomSyntaxEval = dyn Fn(
&mut Scope,
&[Expression],
) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general function trail object.
/// A general expression evaluation trait object.
#[cfg(feature = "sync")]
pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync;
/// An expression sub-tree in an AST.
#[derive(Debug, Clone, Hash)]
pub struct Expression<'a>(&'a Expr);
impl<'a> From<&'a Expr> for Expression<'a> {
fn from(expr: &'a Expr) -> Self {
Self(expr)
}
}
impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise `None`.
pub fn get_variable_name(&self) -> Option<&str> {
match self.0 {
Expr::Variable(x) => Some((x.0).0.as_str()),
_ => None,
}
}
/// Get the expression.
pub(crate) fn expr(&self) -> &Expr {
&self.0
}
/// Get the position of this expression.
pub fn position(&self) -> Position {
self.0.position()
}
}
#[derive(Clone)]
pub struct CustomSyntax {
pub segments: StaticVec<String>,
@ -45,6 +73,8 @@ impl fmt::Debug for CustomSyntax {
}
}
/// Context of a script evaluation process.
#[derive(Debug)]
pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
pub(crate) mods: &'a mut Imports<'b>,
pub(crate) state: &'s mut State,
@ -56,7 +86,7 @@ pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
impl Engine {
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
&mut self,
value: &[S],
keywords: &[S],
scope_delta: isize,
func: impl Fn(
&Engine,
@ -66,15 +96,18 @@ impl Engine {
) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync
+ 'static,
) -> Result<&mut Self, Box<LexError>> {
if value.is_empty() {
return Err(Box::new(LexError::ImproperSymbol("".to_string())));
}
) -> Result<&mut Self, Box<ParseError>> {
let mut segments: StaticVec<_> = Default::default();
for s in value {
let seg = match s.as_ref() {
for s in keywords {
let s = s.as_ref().trim();
// skip empty keywords
if s.is_empty() {
continue;
}
let seg = match s {
// Markers not in first position
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
// Standard symbols not in first position
@ -115,12 +148,25 @@ impl Engine {
s.into()
}
// Anything else is an error
_ => return Err(Box::new(LexError::ImproperSymbol(s.to_string()))),
_ => {
return Err(LexError::ImproperSymbol(format!(
"Improper symbol for custom syntax: '{}'",
s
))
.into_err(Position::none())
.into());
}
};
segments.push(seg);
}
// If the syntax has no keywords, just ignore the registration
if segments.is_empty() {
return Ok(self);
}
// Remove the first keyword as the discriminator
let key = segments.remove(0);
let syntax = CustomSyntax {
@ -143,4 +189,26 @@ impl Engine {
Ok(self)
}
/// Evaluate an expression tree.
///
/// ## WARNING - Low Level API
///
/// This function is very low level. It evaluates an expression from an AST.
pub fn eval_expression_tree(
&self,
context: &mut EvalContext,
scope: &mut Scope,
expr: &Expression,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.eval_expr(
scope,
context.mods,
context.state,
context.lib,
context.this_ptr,
expr.expr(),
context.level,
)
}
}

View File

@ -1,8 +1,8 @@
//! Main module defining the lexer and parser.
use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT,
KEYWORD_THIS, KEYWORD_TYPE_OF,
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
use crate::error::LexError;
@ -404,7 +404,7 @@ impl Token {
Reserved(syntax.into())
}
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()),
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),
_ => return None,
})

View File

@ -1,4 +1,4 @@
//! A module containing all unsafe code.
//! A helper module containing unsafe utility functions.
use crate::any::Variant;
use crate::engine::State;

View File

@ -15,7 +15,7 @@ use crate::stdlib::{
iter::FromIterator,
mem,
mem::MaybeUninit,
ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut},
str::FromStr,
string::{String, ToString},
vec::Vec,
@ -387,7 +387,7 @@ impl<T> StaticVec<T> {
let value = self.extract_from_list(index);
// Move all items one slot to the left
for x in index + 1..self.len - 1 {
for x in index + 1..self.len {
let orig_value = self.extract_from_list(x);
self.set_into_list(x - 1, orig_value, false);
}
@ -572,6 +572,19 @@ impl<T> AsMut<[T]> for StaticVec<T> {
}
}
impl<T> Deref for StaticVec<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<T> DerefMut for StaticVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}
impl<T> Index<usize> for StaticVec<T> {
type Output = T;

View File

@ -2,6 +2,7 @@
use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
};
use std::any::TypeId;
#[test]
fn test_fn() -> Result<(), Box<EvalAltResult>> {
@ -121,9 +122,9 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
engine.register_raw_fn(
"bar",
&[
std::any::TypeId::of::<INT>(),
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<INT>(),
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
TypeId::of::<INT>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>();
@ -157,3 +158,35 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
let mut module = Module::new();
module.set_raw_fn(
"call_with_arg",
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|engine: &Engine, module: &Module, args: &mut [&mut Dynamic]| {
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
fn_ptr.call_dynamic(engine, module, None, [std::mem::take(args[1])])
},
);
let mut engine = Engine::new();
engine.load_package(module.into());
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
r#"
let addition = |x, y| { x + y };
let curried = addition.curry(2);
call_with_arg(curried, 40)
"#
)?,
42
);
Ok(())
}

View File

@ -78,3 +78,35 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_fn_ptr_curry() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_fn("foo", |x: &mut INT, y: INT| *x + y);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
r#"
let f = Fn("foo");
let f2 = f.curry(40);
f2.call(2)
"#
)?,
42
);
assert_eq!(
engine.eval::<INT>(
r#"
let f = Fn("foo");
let f2 = curry(f, 40);
call(f2, 2)
"#
)?,
42
);
Ok(())
}

View File

@ -1,5 +1,6 @@
#![cfg(feature = "internals")]
use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT};
use rhai::{
Engine, EvalAltResult, EvalContext, Expression, ParseError, ParseErrorType, Scope, INT,
};
#[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -72,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
// The first symbol must be an identifier
assert!(matches!(
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"),
LexError::ImproperSymbol(s) if s == "!"
ParseError(err, _) if *err == ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
));
Ok(())