Support for anonymous functions.

This commit is contained in:
Stephen Chung 2020-07-19 17:14:55 +08:00
parent 2f33edb762
commit cf36dc5a57
11 changed files with 294 additions and 74 deletions

View File

@ -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

View File

@ -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.

View File

@ -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
``` ```

View File

@ -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);

View File

@ -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

View File

@ -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,
}; };

View File

@ -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(

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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))

View File

@ -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)))),