Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-07-23 16:12:09 +08:00
commit a2ddd2175e
21 changed files with 379 additions and 208 deletions

View File

@ -7,6 +7,7 @@ Version 0.18.0
This version adds: This version adds:
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions. * Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
* Currying of function pointers.
New features New features
------------ ------------
@ -15,6 +16,8 @@ New features
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. * 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`. * `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`. * 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 Breaking changes
---------------- ----------------
@ -48,7 +51,7 @@ Breaking changes
New features 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. 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::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator. * `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) 3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md) 4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md) 5. [Anonymous Functions](language/fn-anon.md)
6. [Currying](language/fn-curry.md)
15. [Print and Debug](language/print-debug.md) 15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md) 16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.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` (lower-case `f`) | Function definition | [`no_function`] |
| `Fn` (capital `F`) | Function to create a [function pointer] | | | `Fn` (capital `F`) | Function to create a [function pointer] | |
| `call` | Call a [function pointer] | | | `call` | Call a [function pointer] | |
| `curry` | Curry a [function pointer] | |
| `this` | Reference to base object for method call | [`no_function`] | | `this` | Reference to base object for method call | [`no_function`] |
| `type_of` | Get type name of value | | | `type_of` | Get type name of value | |
| `print` | Print 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. * Where you just want to confuse your user and make their lives miserable, because you can.
Step One - Start With `internals` Step One - Design The Syntax
--------------------------------
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
--------------------------- ---------------------------
A custom syntax is simply a list of symbols. 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. 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)?; 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 ### Declare Variables
New variables maybe declared (usually with a variable name that is passed in via `$ident$). 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 var_name = inputs[0].get_variable_name().unwrap().to_string();
let expr = inputs.get(1).unwrap(); 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. 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. 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. of what you're doing.
Step Six - Document Step Five - Document
------------------- --------------------
For custom syntax, documentation is crucial. For custom syntax, documentation is crucial.
Make sure there are _lots_ of examples for users to follow. 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. 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. 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 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. 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 For example, a DSL may disable the `while` loop while keeping all other statement types intact.
types intact.
It is possible, in Rhai, to surgically [disable keywords and operators]. 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] - For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] -
essentially custom statement types. 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 ```rust
let table = [..., ..., ..., ...]; 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; 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: // 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$ // Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$
let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50; 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 After registering this custom syntax with Rhai, it can be used anywhere inside a script as
a normal expression. 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 * `inputs[0] = "sum"` - math operator
`exprs[1] = "price"` - field name * `inputs[1] = "price"` - field name
`exprs[2] = Expression(table)` - data source * `inputs[2] = Expression(table)` - data source
`exprs[3] = "row"` - loop variable name * `inputs[3] = "row"` - loop variable name
`exprs[4] = Expression(row.wright > 50)` - expression * `inputs[4] = Expression(row.wright > 50)` - filter predicate
The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`,
parsed in the order defined within the custom syntax. 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]). 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 Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy.
because using `String` is very inefficient (the `String` must always be cloned).
### **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`. 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. 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 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. | | **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | | **[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)"` | | **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **[`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. | | **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 [functions]: {{rootUrl}}/language/functions.md
[function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointer]: {{rootUrl}}/language/fn-ptr.md
[function pointers]: {{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 namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.md [anonymous function]: {{rootUrl}}/language/fn-anon.md

View File

@ -4,19 +4,32 @@
{{#include ../links.md}} {{#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` `&str` Maps to `ImmutableString`
------------------------------- -------------------------------
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to 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. [`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. Using `ImmutableString` or `&str` is much more efficient.
A common mistake made by novice Rhai users is to register functions with `String` parameters. A common mistake made by novice Rhai users is to register functions with `String` parameters.
```rust ```rust
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai finds this function, but very inefficient fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Very inefficient!!!
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine 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 fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
engine engine
@ -24,9 +37,9 @@ engine
.register_fn("len2", get_len2) .register_fn("len2", get_len2)
.register_fn("len3", get_len3); .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.len1()")?; // 'x' is cloned, very inefficient!
let len = engine.eval::<i64>("x.len2()")?; // works fine let len = engine.eval::<i64>("x.len2()")?; // 'x' is shared
let len = engine.eval::<i64>("x.len3()")?; // works fine 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 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. 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 [`ImmutableString`][string] is an exception to this rule.
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. 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 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, 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>), Array(Box<Array>),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Map(Box<Map>), Map(Box<Map>),
FnPtr(FnPtr), FnPtr(Box<FnPtr>),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>),
} }
@ -274,7 +274,7 @@ impl fmt::Debug for Dynamic {
f.write_str("#")?; f.write_str("#")?;
fmt::Debug::fmt(value, f) 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"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
@ -481,7 +481,7 @@ impl Dynamic {
} }
if type_id == TypeId::of::<FnPtr>() { if type_id == TypeId::of::<FnPtr>() {
return match self.0 { return match self.0 {
Union::FnPtr(value) => unsafe_try_cast(value), Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
@ -582,7 +582,7 @@ impl Dynamic {
} }
if type_id == TypeId::of::<FnPtr>() { if type_id == TypeId::of::<FnPtr>() {
return match &self.0 { 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, _ => None,
}; };
} }
@ -656,7 +656,7 @@ impl Dynamic {
} }
if type_id == TypeId::of::<FnPtr>() { if type_id == TypeId::of::<FnPtr>() {
return match &mut self.0 { 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, _ => None,
}; };
} }
@ -735,7 +735,7 @@ impl Dynamic {
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> { pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), 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()), _ => 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 { impl From<FnPtr> for Dynamic {
fn from(value: FnPtr) -> Self { 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)) Self(Union::FnPtr(value))
} }
} }

View File

@ -11,15 +11,13 @@ use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, St
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::syntax::{CustomSyntax, EvalContext, Expression};
use crate::token::Position; use crate::token::Position;
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(feature = "internals")]
use crate::syntax::{CustomSyntax, EvalContext};
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
@ -81,6 +79,7 @@ pub const KEYWORD_TYPE_OF: &str = "type_of";
pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_EVAL: &str = "eval";
pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
pub const KEYWORD_THIS: &str = "this"; pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string"; pub const FN_TO_STRING: &str = "to_string";
pub const FN_GET: &str = "get$"; pub const FN_GET: &str = "get$";
@ -88,45 +87,10 @@ pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
pub const FN_ANONYMOUS: &str = "anon$"; pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(feature = "internals")]
pub const MARKER_EXPR: &str = "$expr$"; pub const MARKER_EXPR: &str = "$expr$";
#[cfg(feature = "internals")]
pub const MARKER_BLOCK: &str = "$block$"; pub const MARKER_BLOCK: &str = "$block$";
#[cfg(feature = "internals")]
pub const MARKER_IDENT: &str = "$ident$"; pub const MARKER_IDENT: &str = "$ident$";
#[cfg(feature = "internals")]
#[derive(Debug, Clone, Hash)]
pub struct Expression<'a>(&'a Expr);
#[cfg(feature = "internals")]
impl<'a> From<&'a Expr> for Expression<'a> {
fn from(expr: &'a Expr) -> Self {
Self(expr)
}
}
#[cfg(feature = "internals")]
impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise `None`.
#[cfg(feature = "internals")]
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()
}
}
/// A type specifying the method of chaining. /// A type specifying the method of chaining.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum ChainType { enum ChainType {
@ -203,7 +167,7 @@ impl Target<'_> {
.as_char() .as_char()
.map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?; .map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?;
let mut chars: StaticVec<char> = s.chars().collect(); let mut chars = s.chars().collect::<StaticVec<_>>();
let ch = chars[*index]; let ch = chars[*index];
// See if changed - if so, update the String // See if changed - if so, update the String
@ -316,7 +280,6 @@ pub struct Engine {
/// A hashset containing custom keywords and precedence to recognize. /// A hashset containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: Option<HashMap<String, u8>>, pub(crate) custom_keywords: Option<HashMap<String, u8>>,
/// Custom syntax. /// Custom syntax.
#[cfg(feature = "internals")]
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>, pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
@ -376,8 +339,6 @@ impl Default for Engine {
type_names: None, type_names: None,
disabled_symbols: None, disabled_symbols: None,
custom_keywords: None, custom_keywords: None,
#[cfg(feature = "internals")]
custom_syntax: None, custom_syntax: None,
// default print/debug implementations // default print/debug implementations
@ -605,8 +566,6 @@ impl Engine {
type_names: None, type_names: None,
disabled_symbols: None, disabled_symbols: None,
custom_keywords: None, custom_keywords: None,
#[cfg(feature = "internals")]
custom_syntax: None, custom_syntax: None,
print: Box::new(|_| {}), print: Box::new(|_| {}),
@ -1066,7 +1025,7 @@ impl Engine {
lib: &Module, lib: &Module,
target: &mut Target, target: &mut Target,
expr: &Expr, expr: &Expr,
mut idx_val: Dynamic, idx_val: Dynamic,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let ((name, native, pos), _, hash, _, def_val) = match expr { let ((name, native, pos), _, hash, _, def_val) = match expr {
@ -1079,17 +1038,22 @@ impl Engine {
// Get a reference to the mutation target Dynamic // Get a reference to the mutation target Dynamic
let obj = target.as_mut(); let obj = target.as_mut();
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap(); let mut idx = idx_val.cast::<StaticVec<Dynamic>>();
let mut fn_name = name.as_ref(); let mut fn_name = name.as_ref();
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() { let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// FnPtr call // FnPtr call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name // Redirect function name
let fn_name = obj.as_str().unwrap(); let fn_name = fn_ptr.fn_name();
// Recalculate hash // Recalculate hash
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty());
// Arguments are passed as-is // Arguments are passed as-is, adding the curried arguments
let mut arg_values = idx.iter_mut().collect::<StaticVec<_>>(); let mut arg_values = curry
.iter_mut()
.chain(idx.iter_mut())
.collect::<StaticVec<_>>();
let args = arg_values.as_mut(); let args = arg_values.as_mut();
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
@ -1098,17 +1062,16 @@ impl Engine {
) )
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() { } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object // FnPtr call on object
let fn_ptr = idx.remove(0).cast::<FnPtr>();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name // Redirect function name
let fn_name = idx[0] let fn_name = fn_ptr.get_fn_name().clone();
.downcast_ref::<FnPtr>()
.unwrap()
.get_fn_name()
.clone();
// Recalculate hash // Recalculate hash
let hash = calc_fn_hash(empty(), &fn_name, idx.len() - 1, empty()); let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty());
// Replace the first argument with the object pointer // Replace the first argument with the object pointer, adding the curried arguments
let mut arg_values = once(obj) let mut arg_values = once(obj)
.chain(idx.iter_mut().skip(1)) .chain(curry.iter_mut())
.chain(idx.iter_mut())
.collect::<StaticVec<_>>(); .collect::<StaticVec<_>>();
let args = arg_values.as_mut(); let args = arg_values.as_mut();
@ -1116,8 +1079,24 @@ impl Engine {
self.exec_fn_call( self.exec_fn_call(
state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
) )
} else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
Ok((
FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(),
fn_ptr
.curry()
.iter()
.cloned()
.chain(idx.into_iter())
.collect(),
)
.into(),
false,
))
} else { } else {
let redirected: Option<ImmutableString>; let redirected;
let mut hash = *hash; let mut hash = *hash;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
@ -1126,9 +1105,8 @@ impl Engine {
if let Some(val) = map.get(fn_name) { if let Some(val) = map.get(fn_name) {
if let Some(f) = val.downcast_ref::<FnPtr>() { if let Some(f) = val.downcast_ref::<FnPtr>() {
// Remap the function name // Remap the function name
redirected = Some(f.get_fn_name().clone()); redirected = f.get_fn_name().clone();
fn_name = redirected.as_ref().unwrap(); fn_name = &redirected;
// Recalculate the hash based on the new function name // Recalculate the hash based on the new function name
hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
} }
@ -1734,8 +1712,6 @@ impl Engine {
/// ## WARNING - Low Level API /// ## WARNING - Low Level API
/// ///
/// This function is very low level. It evaluates an expression from an AST. /// This function is very low level. It evaluates an expression from an AST.
#[cfg(feature = "internals")]
#[deprecated(note = "this method is volatile and may change")]
pub fn eval_expression_tree( pub fn eval_expression_tree(
&self, &self,
context: &mut EvalContext, context: &mut EvalContext,
@ -1775,7 +1751,7 @@ impl Engine {
Expr::FloatConstant(x) => Ok(x.0.into()), Expr::FloatConstant(x) => Ok(x.0.into()),
Expr::StringConstant(x) => Ok(x.0.to_string().into()), Expr::StringConstant(x) => Ok(x.0.to_string().into()),
Expr::CharConstant(x) => Ok(x.0.into()), Expr::CharConstant(x) => Ok(x.0.into()),
Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()), Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone(), Default::default()).into()),
Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
Ok(val.clone()) Ok(val.clone())
@ -1963,6 +1939,34 @@ impl Engine {
} }
} }
// Handle curry()
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
let expr = args_expr.get(0);
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !fn_ptr.is::<FnPtr>() {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(),
self.map_type_name(fn_ptr.type_name()).into(),
expr.position(),
)));
}
let (fn_name, fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
let curry: StaticVec<_> = args_expr
.iter()
.skip(1)
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
return Ok(FnPtr::new_unchecked(
fn_name,
fn_curry.into_iter().chain(curry.into_iter()).collect(),
)
.into());
}
// Handle eval() // Handle eval()
if name == KEYWORD_EVAL && args_expr.len() == 1 { if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn = let hash_fn =
@ -1992,6 +1996,7 @@ impl Engine {
let redirected; let redirected;
let mut name = name.as_ref(); let mut name = name.as_ref();
let mut args_expr = args_expr.as_ref(); let mut args_expr = args_expr.as_ref();
let mut curry: StaticVec<_> = Default::default();
let mut hash = *hash; let mut hash = *hash;
if name == KEYWORD_FN_PTR_CALL if name == KEYWORD_FN_PTR_CALL
@ -1999,20 +2004,22 @@ impl Engine {
&& !self.has_override(lib, 0, hash) && !self.has_override(lib, 0, hash)
{ {
let expr = args_expr.get(0).unwrap(); let expr = args_expr.get(0).unwrap();
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if fn_ptr.is::<FnPtr>() { if fn_name.is::<FnPtr>() {
let fn_ptr = fn_name.cast::<FnPtr>();
curry = fn_ptr.curry().iter().cloned().collect();
// Redirect function name // Redirect function name
redirected = Some(fn_ptr.cast::<FnPtr>().take_fn_name()); redirected = fn_ptr.take_data().0;
name = redirected.as_ref().unwrap(); name = &redirected;
// Skip the first argument // Skip the first argument
args_expr = &args_expr.as_ref()[1..]; args_expr = &args_expr.as_ref()[1..];
// Recalculate hash // Recalculate hash
hash = calc_fn_hash(empty(), name, args_expr.len(), empty()); hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
} else { } else {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(), self.map_type_name(type_name::<FnPtr>()).into(),
fn_ptr.type_name().into(), fn_name.type_name().into(),
expr.position(), expr.position(),
))); )));
} }
@ -2023,7 +2030,7 @@ impl Engine {
let mut args: StaticVec<_>; let mut args: StaticVec<_>;
let mut is_ref = false; let mut is_ref = false;
if args_expr.is_empty() { if args_expr.is_empty() && curry.is_empty() {
// No arguments // No arguments
args = Default::default(); args = Default::default();
} else { } else {
@ -2046,7 +2053,10 @@ impl Engine {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.new_position(pos))?; .map_err(|err| err.new_position(pos))?;
args = once(target).chain(arg_values.iter_mut()).collect(); args = once(target)
.chain(curry.iter_mut())
.chain(arg_values.iter_mut())
.collect();
is_ref = true; is_ref = true;
} }
@ -2059,7 +2069,7 @@ impl Engine {
}) })
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
args = arg_values.iter_mut().collect(); args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
} }
} }
} }
@ -2216,10 +2226,9 @@ impl Engine {
Expr::False(_) => Ok(false.into()), Expr::False(_) => Ok(false.into()),
Expr::Unit(_) => Ok(().into()), Expr::Unit(_) => Ok(().into()),
#[cfg(feature = "internals")]
Expr::Custom(x) => { Expr::Custom(x) => {
let func = (x.0).1.as_ref(); let func = (x.0).1.as_ref();
let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect(); let ep = (x.0).0.iter().map(|e| e.into()).collect::<StaticVec<_>>();
let mut context = EvalContext { let mut context = EvalContext {
mods, mods,
state, state,

View File

@ -1,14 +1,15 @@
//! Module containing interfaces with native-Rust functions. //! Module containing interfaces with native-Rust functions.
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::engine::Engine; use crate::engine::Engine;
use crate::module::Module; use crate::module::{FuncReturn, Module};
use crate::parser::ScriptFnDef; use crate::parser::ScriptFnDef;
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position}; 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. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -50,14 +51,15 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
pub type FnCallArgs<'a> = [&'a mut Dynamic]; pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer. /// A general function pointer, which may carry additional (i.e. curried) argument values
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] /// to be passed onto a function during a call.
pub struct FnPtr(ImmutableString); #[derive(Debug, Clone, Default)]
pub struct FnPtr(ImmutableString, Vec<Dynamic>);
impl FnPtr { impl FnPtr {
/// Create a new function pointer. /// Create a new function pointer.
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S) -> Self { pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S, curry: Vec<Dynamic>) -> Self {
Self(name.into()) Self(name.into(), curry)
} }
/// Get the name of the function. /// Get the name of the function.
pub fn fn_name(&self) -> &str { pub fn fn_name(&self) -> &str {
@ -67,9 +69,38 @@ impl FnPtr {
pub(crate) fn get_fn_name(&self) -> &ImmutableString { pub(crate) fn get_fn_name(&self) -> &ImmutableString {
&self.0 &self.0
} }
/// Get the name of the function. /// Get the underlying data of the function pointer.
pub(crate) fn take_fn_name(self) -> ImmutableString { pub(crate) fn take_data(self) -> (ImmutableString, Vec<Dynamic>) {
self.0 (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)
} }
} }
@ -84,7 +115,7 @@ impl TryFrom<ImmutableString> for FnPtr {
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> { fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
if is_valid_identifier(value.chars()) { if is_valid_identifier(value.chars()) {
Ok(Self(value)) Ok(Self(value, Default::default()))
} else { } else {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound( Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
value.into(), value.into(),

View File

@ -95,7 +95,6 @@ mod scope;
mod serde; mod serde;
mod settings; mod settings;
mod stdlib; mod stdlib;
#[cfg(feature = "internals")]
mod syntax; mod syntax;
mod token; mod token;
mod r#unsafe; mod r#unsafe;
@ -110,6 +109,7 @@ pub use module::Module;
pub use parser::{ImmutableString, AST, INT}; pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
pub use syntax::{EvalContext, Expression};
pub use token::Position; pub use token::Position;
pub use utils::calc_fn_spec as calc_fn_hash; pub use utils::calc_fn_spec as calc_fn_hash;
@ -173,11 +173,7 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")] #[deprecated(note = "this type is volatile and may change")]
pub use engine::{Expression, Imports, State as EvalState}; pub use engine::{Imports, State as EvalState};
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
pub use syntax::EvalContext;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")] #[deprecated(note = "this type is volatile and may change")]

View File

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

View File

@ -1,14 +1,13 @@
//! Module containing implementation for custom syntax. //! Module containing implementation for custom syntax.
#![cfg(feature = "internals")]
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::engine::{Engine, Expression, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::error::LexError; use crate::error::{LexError, ParseError};
use crate::fn_native::{SendSync, Shared}; use crate::fn_native::{SendSync, Shared};
use crate::module::Module; use crate::module::Module;
use crate::parser::Expr;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; 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::utils::StaticVec;
use crate::stdlib::{ use crate::stdlib::{
@ -18,7 +17,7 @@ use crate::stdlib::{
sync::Arc, sync::Arc,
}; };
/// A general function trail object. /// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxEval = dyn Fn( pub type FnCustomSyntaxEval = dyn Fn(
&Engine, &Engine,
@ -26,12 +25,40 @@ pub type FnCustomSyntaxEval = dyn Fn(
&mut Scope, &mut Scope,
&[Expression], &[Expression],
) -> Result<Dynamic, Box<EvalAltResult>>; ) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general function trail object. /// A general expression evaluation trait object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ Send + Send
+ Sync; + 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)] #[derive(Clone)]
pub struct CustomSyntax { pub struct CustomSyntax {
pub segments: StaticVec<String>, pub segments: StaticVec<String>,
@ -45,6 +72,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 struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
pub(crate) mods: &'a mut Imports<'b>, pub(crate) mods: &'a mut Imports<'b>,
pub(crate) state: &'s mut State, pub(crate) state: &'s mut State,
@ -56,7 +85,7 @@ pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
impl Engine { impl Engine {
pub fn register_custom_syntax<S: AsRef<str> + ToString>( pub fn register_custom_syntax<S: AsRef<str> + ToString>(
&mut self, &mut self,
value: &[S], keywords: &[S],
scope_delta: isize, scope_delta: isize,
func: impl Fn( func: impl Fn(
&Engine, &Engine,
@ -66,15 +95,18 @@ impl Engine {
) -> Result<Dynamic, Box<EvalAltResult>> ) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> Result<&mut Self, Box<LexError>> { ) -> Result<&mut Self, Box<ParseError>> {
if value.is_empty() {
return Err(Box::new(LexError::ImproperSymbol("".to_string())));
}
let mut segments: StaticVec<_> = Default::default(); let mut segments: StaticVec<_> = Default::default();
for s in value { for s in keywords {
let seg = match s.as_ref() { let s = s.as_ref().trim();
// skip empty keywords
if s.is_empty() {
continue;
}
let seg = match s {
// Markers not in first position // Markers not in first position
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(), MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
// Standard symbols not in first position // Standard symbols not in first position
@ -115,12 +147,25 @@ impl Engine {
s.into() s.into()
} }
// Anything else is an error // 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); 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 key = segments.remove(0);
let syntax = CustomSyntax { let syntax = CustomSyntax {

View File

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

View File

@ -387,7 +387,7 @@ impl<T> StaticVec<T> {
let value = self.extract_from_list(index); let value = self.extract_from_list(index);
// Move all items one slot to the left // 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); let orig_value = self.extract_from_list(x);
self.set_into_list(x - 1, orig_value, false); self.set_into_list(x - 1, orig_value, false);
} }

View File

@ -2,6 +2,7 @@
use rhai::{ use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT, Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
}; };
use std::any::TypeId;
#[test] #[test]
fn test_fn() -> Result<(), Box<EvalAltResult>> { fn test_fn() -> Result<(), Box<EvalAltResult>> {
@ -121,9 +122,9 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
engine.register_raw_fn( engine.register_raw_fn(
"bar", "bar",
&[ &[
std::any::TypeId::of::<INT>(), TypeId::of::<INT>(),
std::any::TypeId::of::<FnPtr>(), TypeId::of::<FnPtr>(),
std::any::TypeId::of::<INT>(), TypeId::of::<INT>(),
], ],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>(); let fp = std::mem::take(args[1]).cast::<FnPtr>();
@ -157,3 +158,35 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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(()) 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::{
use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT}; Engine, EvalAltResult, EvalContext, Expression, ParseError, ParseErrorType, Scope, INT,
};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { 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 // The first symbol must be an identifier
assert!(matches!( assert!(matches!(
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"), *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(()) Ok(())