Support for anonymous functions.
This commit is contained in:
parent
2f33edb762
commit
cf36dc5a57
@ -4,12 +4,17 @@ Rhai Release Notes
|
|||||||
Version 0.18.0
|
Version 0.18.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
This version adds:
|
||||||
|
|
||||||
|
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
|
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
|
||||||
* 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`.
|
||||||
|
|
||||||
|
|
||||||
Version 0.17.0
|
Version 0.17.0
|
||||||
|
@ -163,4 +163,59 @@ x == 42;
|
|||||||
Beware that this only works for _method-call_ style. Normal function-call style cannot bind
|
Beware that this only works for _method-call_ style. Normal function-call style cannot bind
|
||||||
the `this` pointer (for syntactic reasons).
|
the `this` pointer (for syntactic reasons).
|
||||||
|
|
||||||
Therefore, obviously, binding the `this` pointer is unsupported under [`no_function`].
|
Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].
|
||||||
|
|
||||||
|
|
||||||
|
Anonymous Functions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Sometimes it gets tedious to define separate functions only to dispatch them via single function pointers.
|
||||||
|
This scenario is especially common when simulating object-oriented programming ([OOP]).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Define object
|
||||||
|
let obj = #{
|
||||||
|
data: 42,
|
||||||
|
increment: Fn("inc_obj"), // use function pointers to
|
||||||
|
decrement: Fn("dec_obj"), // refer to method functions
|
||||||
|
print: Fn("print_obj")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define method functions one-by-one
|
||||||
|
fn inc_obj(x) { this.data += x; }
|
||||||
|
fn dec_obj(x) { this.data -= x; }
|
||||||
|
fn print_obj() { print(this.data); }
|
||||||
|
```
|
||||||
|
|
||||||
|
The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures
|
||||||
|
(but they are **NOT** closures, merely syntactic sugar):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let obj = #{
|
||||||
|
data: 42,
|
||||||
|
increment: |x| this.data += x, // one-liner
|
||||||
|
decrement: |x| this.data -= x,
|
||||||
|
print_obj: || { print(this.data); } // full function body
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The anonymous functions will be expanded into separate functions in the global namespace:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let obj = #{
|
||||||
|
data: 42,
|
||||||
|
increment: Fn("anon_fn_1000"),
|
||||||
|
decrement: Fn("anon_fn_1001"),
|
||||||
|
print: Fn("anon_fn_1002")
|
||||||
|
};
|
||||||
|
|
||||||
|
fn anon_fn_1000(x) { this.data += x; }
|
||||||
|
fn anon_fn_1001(x) { this.data -= x; }
|
||||||
|
fn anon_fn_1002() { print this.data; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### WARNING - NOT Closures
|
||||||
|
|
||||||
|
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
|
||||||
|
**not** closures. In particular, they do not capture their running environment. They are more like
|
||||||
|
Rust's function pointers.
|
||||||
|
@ -20,17 +20,21 @@ to the [object map] before the function is called. There is no way to simulate
|
|||||||
via a normal function-call syntax because all scripted function arguments are passed by value.
|
via a normal function-call syntax because all scripted function arguments are passed by value.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn do_action(x) { print(this.data + x); } // 'this' binds to the object when called
|
fn do_action(x) { this.data += x; } // 'this' binds to the object when called
|
||||||
|
|
||||||
let obj = #{
|
let obj = #{
|
||||||
data: 40,
|
data: 40,
|
||||||
action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
|
action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.action(2); // Short-hand syntax: prints 42
|
obj.action(2); // Calls 'do_action' with `this` bound to 'obj'
|
||||||
|
|
||||||
// To achieve the above with normal function pointer calls:
|
obj.call(obj.action, 2); // The above de-sugars to this
|
||||||
fn do_action(map, x) { print(map.data + x); }
|
|
||||||
|
|
||||||
obj.action.call(obj, 2); // this call cannot mutate 'obj'
|
obj.data == 42;
|
||||||
|
|
||||||
|
// To achieve the above with normal function pointer call will fail.
|
||||||
|
fn do_action(map, x) { map.data += x; } // 'map' is a copy
|
||||||
|
|
||||||
|
obj.action.call(obj, 2); // 'obj' is passed as a copy by value
|
||||||
```
|
```
|
||||||
|
@ -17,23 +17,22 @@ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-m
|
|||||||
| [Object map] properties holding values | properties |
|
| [Object map] properties holding values | properties |
|
||||||
| [Object map] properties that hold [function pointers] | methods |
|
| [Object map] properties that hold [function pointers] | methods |
|
||||||
|
|
||||||
|
When a property of an [object map] is called like a method function, and if it happens to hold
|
||||||
|
a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be
|
||||||
|
dispatched to the actual function with `this` binding to the [object map] itself.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Define the object
|
// Define the object
|
||||||
let obj = #{
|
let obj =
|
||||||
data: 0,
|
#{
|
||||||
increment: Fn("add"), // when called, 'this' binds to 'obj'
|
data: 0,
|
||||||
update: Fn("update"), // when called, 'this' binds to 'obj'
|
increment: |x| this.data += x, // when called, 'this' binds to 'obj'
|
||||||
action: Fn("action") // when called, 'this' binds to 'obj'
|
update: |x| this.data = x, // when called, 'this' binds to 'obj'
|
||||||
};
|
action: || print(this.data) // when called, 'this' binds to 'obj'
|
||||||
|
};
|
||||||
// Define functions
|
|
||||||
fn add(x) { this.data += x; } // update using 'this'
|
|
||||||
fn update(x) { this.data = x; } // update using 'this'
|
|
||||||
fn action() { print(this.data); } // access properties of 'this'
|
|
||||||
|
|
||||||
// Use the object
|
// Use the object
|
||||||
obj.increment(1);
|
obj.increment(1);
|
||||||
|
@ -78,6 +78,8 @@
|
|||||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
[function pointers]: {{rootUrl}}/language/fn-ptr.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-ptr.md#anonymous-functions
|
||||||
|
[anonymous functions]: {{rootUrl}}/language/fn-ptr.md#anonymous-functions
|
||||||
|
|
||||||
[`Module`]: {{rootUrl}}/language/modules/index.md
|
[`Module`]: {{rootUrl}}/language/modules/index.md
|
||||||
[module]: {{rootUrl}}/language/modules/index.md
|
[module]: {{rootUrl}}/language/modules/index.md
|
||||||
|
@ -18,6 +18,7 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt,
|
fmt,
|
||||||
|
hash::Hash,
|
||||||
string::String,
|
string::String,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
@ -87,6 +87,7 @@ pub const FN_GET: &str = "get$";
|
|||||||
pub const FN_SET: &str = "set$";
|
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$";
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
pub const MARKER_EXPR: &str = "$expr$";
|
pub const MARKER_EXPR: &str = "$expr$";
|
||||||
@ -654,7 +655,7 @@ impl Engine {
|
|||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
is_ref: bool,
|
is_ref: bool,
|
||||||
is_method: bool,
|
is_method: bool,
|
||||||
def_val: Option<&Dynamic>,
|
def_val: Option<bool>,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
@ -815,7 +816,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Return default value (if any)
|
// Return default value (if any)
|
||||||
if let Some(val) = def_val {
|
if let Some(val) = def_val {
|
||||||
return Ok((val.clone(), false));
|
return Ok((val.into(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getter function not found?
|
// Getter function not found?
|
||||||
@ -976,7 +977,7 @@ impl Engine {
|
|||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
is_ref: bool,
|
is_ref: bool,
|
||||||
is_method: bool,
|
is_method: bool,
|
||||||
def_val: Option<&Dynamic>,
|
def_val: Option<bool>,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||||
@ -1081,7 +1082,6 @@ impl Engine {
|
|||||||
|
|
||||||
let is_ref = target.is_ref();
|
let is_ref = target.is_ref();
|
||||||
let is_value = target.is_value();
|
let is_value = target.is_value();
|
||||||
let def_val = def_val.as_ref();
|
|
||||||
|
|
||||||
// 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();
|
||||||
@ -1100,7 +1100,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, fn_name, *native, hash, args, false, false, def_val, level,
|
state, lib, fn_name, *native, hash, args, false, false, *def_val, level,
|
||||||
)
|
)
|
||||||
} 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
|
||||||
@ -1120,7 +1120,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
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 {
|
} else {
|
||||||
let redirected: Option<ImmutableString>;
|
let redirected: Option<ImmutableString>;
|
||||||
@ -1146,7 +1146,7 @@ impl Engine {
|
|||||||
let args = arg_values.as_mut();
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
.map_err(|err| err.new_position(*pos))?;
|
||||||
@ -1694,13 +1694,12 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Dynamic(Union::Array(mut rhs_value)) => {
|
Dynamic(Union::Array(mut rhs_value)) => {
|
||||||
let op = "==";
|
let op = "==";
|
||||||
let def_value = false.into();
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Call the `==` operator to compare each value
|
// Call the `==` operator to compare each value
|
||||||
for value in rhs_value.iter_mut() {
|
for value in rhs_value.iter_mut() {
|
||||||
|
let def_value = Some(false);
|
||||||
let args = &mut [&mut lhs_value.clone(), value];
|
let args = &mut [&mut lhs_value.clone(), value];
|
||||||
let def_value = Some(&def_value);
|
|
||||||
|
|
||||||
let hashes = (
|
let hashes = (
|
||||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||||
@ -1782,6 +1781,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::Variable(x) if (x.0).0 == KEYWORD_THIS => {
|
Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
|
||||||
if let Some(ref val) = this_ptr {
|
if let Some(ref val) = this_ptr {
|
||||||
Ok((*val).clone())
|
Ok((*val).clone())
|
||||||
@ -1941,7 +1941,6 @@ impl Engine {
|
|||||||
// Normal function call
|
// Normal function call
|
||||||
Expr::FnCall(x) if x.1.is_none() => {
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
|
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
|
||||||
let def_val = def_val.as_ref();
|
|
||||||
|
|
||||||
// Handle Fn()
|
// Handle Fn()
|
||||||
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
|
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
|
||||||
@ -2072,7 +2071,7 @@ impl Engine {
|
|||||||
|
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, name, *native, hash, args, is_ref, false, def_val, level,
|
state, lib, name, *native, hash, args, is_ref, false, *def_val, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.new_position(*pos))
|
||||||
@ -2166,7 +2165,7 @@ impl Engine {
|
|||||||
.map_err(|err| err.new_position(*pos)),
|
.map_err(|err| err.new_position(*pos)),
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => {
|
EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => {
|
||||||
Ok(def_val.clone().unwrap())
|
Ok(def_val.unwrap().into())
|
||||||
}
|
}
|
||||||
EvalAltResult::ErrorFunctionNotFound(_, _) => {
|
EvalAltResult::ErrorFunctionNotFound(_, _) => {
|
||||||
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||||
|
@ -206,6 +206,9 @@ impl fmt::Display for ParseErrorType {
|
|||||||
|
|
||||||
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
|
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
|
||||||
|
|
||||||
|
Self::FnMissingBody(s) if s.is_empty() => {
|
||||||
|
f.write_str("Expecting body statement block for anonymous function")
|
||||||
|
}
|
||||||
Self::FnMissingBody(s) => {
|
Self::FnMissingBody(s) => {
|
||||||
write!(f, "Expecting body statement block for function '{}'", s)
|
write!(f, "Expecting body statement block for function '{}'", s)
|
||||||
}
|
}
|
||||||
|
@ -1098,7 +1098,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// A `StaticVec` is used because most module-level access contains only one level,
|
/// A `StaticVec` is used because most module-level access contains only one level,
|
||||||
/// and it is wasteful to always allocate a `Vec` with one element.
|
/// and it is wasteful to always allocate a `Vec` with one element.
|
||||||
#[derive(Clone, Eq, PartialEq, Default)]
|
#[derive(Clone, Eq, PartialEq, Default, Hash)]
|
||||||
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
|
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
|
||||||
|
|
||||||
impl fmt::Debug for ModuleRef {
|
impl fmt::Debug for ModuleRef {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{
|
||||||
|
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||||
|
};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
|
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
|
||||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||||
|
use crate::token::is_valid_identifier;
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
@ -528,6 +531,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
Expr::FnCall(x)
|
Expr::FnCall(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fn("...")
|
||||||
|
Expr::FnCall(x)
|
||||||
|
if x.1.is_none()
|
||||||
|
&& (x.0).0 == KEYWORD_FN_PTR
|
||||||
|
&& x.3.len() == 1
|
||||||
|
&& matches!(x.3[0], Expr::StringConstant(_))
|
||||||
|
=> {
|
||||||
|
match &x.3[0] {
|
||||||
|
Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()),
|
||||||
|
_ => Expr::FnCall(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Eagerly call functions
|
// Eagerly call functions
|
||||||
Expr::FnCall(mut x)
|
Expr::FnCall(mut x)
|
||||||
if x.1.is_none() // Non-qualified
|
if x.1.is_none() // Non-qualified
|
||||||
@ -571,7 +587,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
Some(arg_for_type_of.to_string().into())
|
Some(arg_for_type_of.to_string().into())
|
||||||
} else {
|
} else {
|
||||||
// Otherwise use the default value, if any
|
// Otherwise use the default value, if any
|
||||||
def_value.clone()
|
def_value.map(|v| v.into())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||||
|
218
src/parser.rs
218
src/parser.rs
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
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, KEYWORD_THIS};
|
use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS};
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||||
@ -35,6 +35,12 @@ use crate::stdlib::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||||
|
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
|
use ahash::AHasher;
|
||||||
|
|
||||||
/// The system integer type.
|
/// The system integer type.
|
||||||
///
|
///
|
||||||
/// If the `only_i32` feature is enabled, this will be `i32` instead.
|
/// If the `only_i32` feature is enabled, this will be `i32` instead.
|
||||||
@ -598,21 +604,34 @@ impl Hash for CustomExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||||
|
pub struct FloatWrapper(pub FLOAT, pub Position);
|
||||||
|
|
||||||
|
impl Hash for FloatWrapper {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write(&self.0.to_le_bytes());
|
||||||
|
self.1.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An expression.
|
/// An expression.
|
||||||
///
|
///
|
||||||
/// Each variant is at most one pointer in size (for speed),
|
/// Each variant is at most one pointer in size (for speed),
|
||||||
/// with everything being allocated together in one single tuple.
|
/// with everything being allocated together in one single tuple.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
/// Integer constant.
|
/// Integer constant.
|
||||||
IntegerConstant(Box<(INT, Position)>),
|
IntegerConstant(Box<(INT, Position)>),
|
||||||
/// Floating-point constant.
|
/// Floating-point constant.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(Box<(FLOAT, Position)>),
|
FloatConstant(Box<FloatWrapper>),
|
||||||
/// Character constant.
|
/// Character constant.
|
||||||
CharConstant(Box<(char, Position)>),
|
CharConstant(Box<(char, Position)>),
|
||||||
/// String constant.
|
/// String constant.
|
||||||
StringConstant(Box<(ImmutableString, Position)>),
|
StringConstant(Box<(ImmutableString, Position)>),
|
||||||
|
/// FnPtr constant.
|
||||||
|
FnPointer(Box<(ImmutableString, Position)>),
|
||||||
/// Variable access - ((variable name, position), optional modules, hash, optional index)
|
/// Variable access - ((variable name, position), optional modules, hash, optional index)
|
||||||
Variable(
|
Variable(
|
||||||
Box<(
|
Box<(
|
||||||
@ -637,7 +656,7 @@ pub enum Expr {
|
|||||||
Option<Box<ModuleRef>>,
|
Option<Box<ModuleRef>>,
|
||||||
u64,
|
u64,
|
||||||
StaticVec<Expr>,
|
StaticVec<Expr>,
|
||||||
Option<Dynamic>,
|
Option<bool>,
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
/// expr op= expr
|
/// expr op= expr
|
||||||
@ -673,18 +692,6 @@ impl Default for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Expr {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
match self {
|
|
||||||
Self::FloatConstant(x) => {
|
|
||||||
state.write(&x.0.to_le_bytes());
|
|
||||||
x.1.hash(state);
|
|
||||||
}
|
|
||||||
_ => self.hash(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
/// Get the `Dynamic` value of a constant expression.
|
/// Get the `Dynamic` value of a constant expression.
|
||||||
///
|
///
|
||||||
@ -758,6 +765,7 @@ impl Expr {
|
|||||||
Self::IntegerConstant(x) => x.1,
|
Self::IntegerConstant(x) => x.1,
|
||||||
Self::CharConstant(x) => x.1,
|
Self::CharConstant(x) => x.1,
|
||||||
Self::StringConstant(x) => x.1,
|
Self::StringConstant(x) => x.1,
|
||||||
|
Self::FnPointer(x) => x.1,
|
||||||
Self::Array(x) => x.1,
|
Self::Array(x) => x.1,
|
||||||
Self::Map(x) => x.1,
|
Self::Map(x) => x.1,
|
||||||
Self::Property(x) => x.1,
|
Self::Property(x) => x.1,
|
||||||
@ -791,6 +799,7 @@ impl Expr {
|
|||||||
Self::IntegerConstant(x) => x.1 = new_pos,
|
Self::IntegerConstant(x) => x.1 = new_pos,
|
||||||
Self::CharConstant(x) => x.1 = new_pos,
|
Self::CharConstant(x) => x.1 = new_pos,
|
||||||
Self::StringConstant(x) => x.1 = new_pos,
|
Self::StringConstant(x) => x.1 = new_pos,
|
||||||
|
Self::FnPointer(x) => x.1 = new_pos,
|
||||||
Self::Array(x) => x.1 = new_pos,
|
Self::Array(x) => x.1 = new_pos,
|
||||||
Self::Map(x) => x.1 = new_pos,
|
Self::Map(x) => x.1 = new_pos,
|
||||||
Self::Variable(x) => (x.0).1 = new_pos,
|
Self::Variable(x) => (x.0).1 = new_pos,
|
||||||
@ -847,6 +856,7 @@ impl Expr {
|
|||||||
Self::IntegerConstant(_)
|
Self::IntegerConstant(_)
|
||||||
| Self::CharConstant(_)
|
| Self::CharConstant(_)
|
||||||
| Self::StringConstant(_)
|
| Self::StringConstant(_)
|
||||||
|
| Self::FnPointer(_)
|
||||||
| Self::True(_)
|
| Self::True(_)
|
||||||
| Self::False(_)
|
| Self::False(_)
|
||||||
| Self::Unit(_) => true,
|
| Self::Unit(_) => true,
|
||||||
@ -878,6 +888,7 @@ impl Expr {
|
|||||||
|
|
||||||
Self::IntegerConstant(_)
|
Self::IntegerConstant(_)
|
||||||
| Self::CharConstant(_)
|
| Self::CharConstant(_)
|
||||||
|
| Self::FnPointer(_)
|
||||||
| Self::In(_)
|
| Self::In(_)
|
||||||
| Self::And(_)
|
| Self::And(_)
|
||||||
| Self::Or(_)
|
| Self::Or(_)
|
||||||
@ -1476,7 +1487,7 @@ fn parse_primary(
|
|||||||
let mut root_expr = match token {
|
let mut root_expr = match token {
|
||||||
Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))),
|
Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))),
|
Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))),
|
||||||
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
|
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
|
||||||
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
|
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
|
||||||
Token::Identifier(s) => {
|
Token::Identifier(s) => {
|
||||||
@ -1616,7 +1627,10 @@ fn parse_unary(
|
|||||||
.map(|i| Expr::IntegerConstant(Box::new((i, pos))))
|
.map(|i| Expr::IntegerConstant(Box::new((i, pos))))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos))));
|
return Some(Expr::FloatConstant(Box::new(FloatWrapper(
|
||||||
|
-(x.0 as FLOAT),
|
||||||
|
pos,
|
||||||
|
))));
|
||||||
#[cfg(feature = "no_float")]
|
#[cfg(feature = "no_float")]
|
||||||
return None;
|
return None;
|
||||||
})
|
})
|
||||||
@ -1625,7 +1639,9 @@ fn parse_unary(
|
|||||||
|
|
||||||
// Negative float
|
// Negative float
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))),
|
Expr::FloatConstant(x) => {
|
||||||
|
Ok(Expr::FloatConstant(Box::new(FloatWrapper(-x.0, x.1))))
|
||||||
|
}
|
||||||
|
|
||||||
// Call negative function
|
// Call negative function
|
||||||
expr => {
|
expr => {
|
||||||
@ -1664,9 +1680,38 @@ fn parse_unary(
|
|||||||
None,
|
None,
|
||||||
hash,
|
hash,
|
||||||
args,
|
args,
|
||||||
Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
|
Some(false), // NOT operator, when operating on invalid operand, defaults to false
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
// | ...
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Token::Pipe | Token::Or => {
|
||||||
|
let mut state = ParseState::new(
|
||||||
|
state.engine,
|
||||||
|
state.max_function_expr_depth,
|
||||||
|
state.max_function_expr_depth,
|
||||||
|
);
|
||||||
|
|
||||||
|
let settings = ParseSettings {
|
||||||
|
allow_if_expr: true,
|
||||||
|
allow_stmt_expr: true,
|
||||||
|
allow_anonymous_fn: true,
|
||||||
|
is_global: false,
|
||||||
|
is_function_scope: true,
|
||||||
|
is_breakable: false,
|
||||||
|
level: 0,
|
||||||
|
pos: *token_pos,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?;
|
||||||
|
|
||||||
|
// Qualifiers (none) + function name + number of arguments.
|
||||||
|
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
|
||||||
|
|
||||||
|
lib.insert(hash, func);
|
||||||
|
|
||||||
|
Ok(expr)
|
||||||
|
}
|
||||||
// <EOF>
|
// <EOF>
|
||||||
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||||
// All other tokens
|
// All other tokens
|
||||||
@ -2018,7 +2063,7 @@ fn parse_binary_op(
|
|||||||
settings.pos = pos;
|
settings.pos = pos;
|
||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
let cmp_def = Some(false.into());
|
let cmp_def = Some(false);
|
||||||
let op = op_token.syntax();
|
let op = op_token.syntax();
|
||||||
let hash = calc_fn_hash(empty(), &op, 2, empty());
|
let hash = calc_fn_hash(empty(), &op, 2, empty());
|
||||||
let op = (op, true, pos);
|
let op = (op, true, pos);
|
||||||
@ -2041,7 +2086,7 @@ fn parse_binary_op(
|
|||||||
| Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))),
|
| Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))),
|
||||||
|
|
||||||
// '!=' defaults to true when passed invalid operands
|
// '!=' defaults to true when passed invalid operands
|
||||||
Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))),
|
Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true)))),
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
Token::EqualsTo
|
Token::EqualsTo
|
||||||
@ -2762,32 +2807,28 @@ fn parse_fn(
|
|||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if !match_token(input, Token::RightParen)? {
|
if !match_token(input, Token::RightParen)? {
|
||||||
let end_err = format!("to close the parameters list of function '{}'", name);
|
|
||||||
let sep_err = format!("to separate the parameters of function '{}'", name);
|
let sep_err = format!("to separate the parameters of function '{}'", name);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match input.peek().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::RightParen, _) => (),
|
(Token::RightParen, _) => break,
|
||||||
_ => match input.next().unwrap() {
|
(Token::Identifier(s), pos) => {
|
||||||
(Token::Identifier(s), pos) => {
|
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
||||||
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
params.push((s, pos))
|
||||||
params.push((s, pos))
|
}
|
||||||
}
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(_, pos) => {
|
||||||
(_, pos) => {
|
return Err(PERR::MissingToken(
|
||||||
return Err(
|
Token::RightParen.into(),
|
||||||
PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos)
|
format!("to close the parameters list of function '{}'", name),
|
||||||
)
|
)
|
||||||
}
|
.into_err(pos))
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::RightParen, _) => break,
|
(Token::RightParen, _) => break,
|
||||||
(Token::Comma, _) => (),
|
(Token::Comma, _) => (),
|
||||||
(Token::Identifier(_), pos) => {
|
|
||||||
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
|
|
||||||
}
|
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
|
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
|
||||||
@ -2831,6 +2872,101 @@ fn parse_fn(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an anonymous function definition.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
fn parse_anon_fn(
|
||||||
|
input: &mut TokenStream,
|
||||||
|
state: &mut ParseState,
|
||||||
|
lib: &mut FunctionsLib,
|
||||||
|
mut settings: ParseSettings,
|
||||||
|
) -> Result<(Expr, ScriptFnDef), ParseError> {
|
||||||
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
|
let mut params = Vec::new();
|
||||||
|
|
||||||
|
if input.next().unwrap().0 != Token::Or {
|
||||||
|
if !match_token(input, Token::Pipe)? {
|
||||||
|
loop {
|
||||||
|
match input.next().unwrap() {
|
||||||
|
(Token::Pipe, _) => break,
|
||||||
|
(Token::Identifier(s), pos) => {
|
||||||
|
state.stack.push((s.clone(), ScopeEntryType::Normal));
|
||||||
|
params.push((s, pos))
|
||||||
|
}
|
||||||
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::Pipe.into(),
|
||||||
|
"to close the parameters list of anonymous function".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match input.next().unwrap() {
|
||||||
|
(Token::Pipe, _) => break,
|
||||||
|
(Token::Comma, _) => (),
|
||||||
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::Comma.into(),
|
||||||
|
"to separate the parameters of anonymous function".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicating parameters
|
||||||
|
params
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.try_for_each(|(i, (p1, _))| {
|
||||||
|
params
|
||||||
|
.iter()
|
||||||
|
.skip(i + 1)
|
||||||
|
.find(|(p2, _)| p2 == p1)
|
||||||
|
.map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
|
||||||
|
})
|
||||||
|
.map_err(|(p, pos)| PERR::FnDuplicatedParam("".to_string(), p.to_string()).into_err(pos))?;
|
||||||
|
|
||||||
|
// Parse function body
|
||||||
|
settings.is_breakable = false;
|
||||||
|
let pos = input.peek().unwrap().1;
|
||||||
|
let body = parse_stmt(input, state, lib, settings.level_up())
|
||||||
|
.map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?;
|
||||||
|
|
||||||
|
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
||||||
|
|
||||||
|
// Calculate hash
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
|
let mut s: AHasher = Default::default();
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
let mut s = DefaultHasher::new();
|
||||||
|
|
||||||
|
s.write_usize(params.len());
|
||||||
|
params.iter().for_each(|a| a.hash(&mut s));
|
||||||
|
body.hash(&mut s);
|
||||||
|
let hash = s.finish();
|
||||||
|
|
||||||
|
// Create unique function name
|
||||||
|
let fn_name = format!("{}{}", FN_ANONYMOUS, hash);
|
||||||
|
|
||||||
|
let script = ScriptFnDef {
|
||||||
|
name: fn_name.clone(),
|
||||||
|
access: FnAccess::Public,
|
||||||
|
params,
|
||||||
|
body,
|
||||||
|
pos: settings.pos,
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr = Expr::FnPointer(Box::new((fn_name.into(), settings.pos)));
|
||||||
|
|
||||||
|
Ok((expr, script))
|
||||||
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub(crate) fn parse_global_expr(
|
pub(crate) fn parse_global_expr(
|
||||||
&self,
|
&self,
|
||||||
@ -2951,7 +3087,7 @@ impl Engine {
|
|||||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
|
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
|
||||||
match value.0 {
|
match value.0 {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Union::Float(value) => Some(Expr::FloatConstant(Box::new((value, pos)))),
|
Union::Float(value) => Some(Expr::FloatConstant(Box::new(FloatWrapper(value, pos)))),
|
||||||
|
|
||||||
Union::Unit(_) => Some(Expr::Unit(pos)),
|
Union::Unit(_) => Some(Expr::Unit(pos)),
|
||||||
Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),
|
Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),
|
||||||
|
Loading…
Reference in New Issue
Block a user