Merge pull request #259 from schungx/master

Refactor and minor enhancements.
This commit is contained in:
Stephen Chung 2020-10-16 17:40:58 +08:00 committed by GitHub
commit ae6dbd6c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 721 additions and 458 deletions

View File

@ -6,7 +6,7 @@ members = [
[package]
name = "rhai"
version = "0.20.0"
version = "0.19.1"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust"

View File

@ -1,10 +1,11 @@
Rhai Release Notes
==================
Version 0.20.0
Version 0.19.1
==============
This version adds a variable resolver with the ability to short-circuit variable access.
This version adds a variable resolver with the ability to short-circuit variable access,
plus a whole bunch of array methods.
Breaking changes
----------------
@ -12,6 +13,7 @@ Breaking changes
* `AST::iter_functions` now returns an iterator instead of taking a closure.
* `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `&<Shared<ScriptFnDef>>`.
* `Module::num_fn`, `Module::num_var` and `Module::num_iter` are removed and merged into `Module::count`.
* `Module::set_iter` is renamed to `Module::set_iter_raw`.
* The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`.
* `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant.
* The following `EvalAltResult` variants are removed and merged into `EvalAltResult::ErrorMismatchDataType`: `ErrorCharMismatch`, `ErrorNumericIndexExpr`, `ErrorStringIndexExpr`, `ErrorImportExpr`, `ErrorLogicGuard`, `ErrorBooleanArgMismatch`
@ -20,7 +22,7 @@ Breaking changes
* New reserved symbols: `++`, `--`, `..`, `...`.
* Callback signature for custom syntax implementation function is changed to allow for more flexibility.
* Default call stack depth for `debug` builds is reduced to 12 (from 16).
* Precedence for `~` and `%` is raised.
* Precedence for `~` is raised, while `in` is moved below logic comparison operators.
New features
------------
@ -31,7 +33,8 @@ New features
* `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined.
* `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value.
* `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one.
* `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `splice` and `sort` functions for arrays.
* `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `extract`, `splice`, `chop` and `sort` functions for arrays.
* New `Module::set_iterable` and `Module::set_iterator` to define type iterators more easily. `Engine::register_iterator` is changed to use the simpler version.
Enhancements
------------

View File

@ -16,7 +16,6 @@ use quote::{quote, quote_spanned};
use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned};
use crate::attrs::{ExportInfo, ExportScope, ExportedParams};
use crate::rhai_module::flatten_type_groups;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Index {
@ -48,10 +47,10 @@ impl FnSpecialAccess {
match self {
FnSpecialAccess::None => None,
FnSpecialAccess::Property(Property::Get(ref g)) => {
Some((format!("get${}", g.to_string()), g.to_string(), g.span()))
Some((format!("{}{}", FN_GET, g), g.to_string(), g.span()))
}
FnSpecialAccess::Property(Property::Set(ref s)) => {
Some((format!("set${}", s.to_string()), s.to_string(), s.span()))
Some((format!("{}{}", FN_SET, s), s.to_string(), s.span()))
}
FnSpecialAccess::Index(Index::Get) => Some((
FN_IDX_GET.to_string(),
@ -67,6 +66,14 @@ impl FnSpecialAccess {
}
}
pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type {
match ty {
syn::Type::Group(syn::TypeGroup { ref elem, .. })
| syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()),
_ => ty,
}
}
#[derive(Debug, Default)]
pub(crate) struct ExportedFnParams {
pub name: Option<Vec<String>>,
@ -76,6 +83,8 @@ pub(crate) struct ExportedFnParams {
pub special: FnSpecialAccess,
}
pub const FN_GET: &str = "get$";
pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$";
@ -130,36 +139,24 @@ impl ExportedParams for ExportedFnParams {
"use attribute 'index_set' instead",
))
}
("name", Some(s)) if s.value().starts_with("get$") => {
("name", Some(s)) if s.value().starts_with(FN_GET) => {
return Err(syn::Error::new(
item_span,
format!(
"use attribute 'getter = \"{}\"' instead",
&s.value()["get$".len()..]
&s.value()[FN_GET.len()..]
),
))
}
("name", Some(s)) if s.value().starts_with("set$") => {
("name", Some(s)) if s.value().starts_with(FN_SET) => {
return Err(syn::Error::new(
item_span,
format!(
"use attribute 'setter = \"{}\"' instead",
&s.value()["set$".len()..]
&s.value()[FN_SET.len()..]
),
))
}
("name", Some(s)) if s.value().contains('$') => {
return Err(syn::Error::new(
s.span(),
"Rhai function names may not contain dollar sign",
))
}
("name", Some(s)) if s.value().contains('.') => {
return Err(syn::Error::new(
s.span(),
"Rhai function names may not contain dot",
))
}
("name", Some(s)) => name.push(s.value()),
("set", Some(s)) => {
special = match special {
@ -225,6 +222,7 @@ pub(crate) struct ExportedFn {
entire_span: proc_macro2::Span,
signature: syn::Signature,
is_public: bool,
return_dynamic: bool,
mut_receiver: bool,
params: ExportedFnParams,
}
@ -235,6 +233,10 @@ impl Parse for ExportedFn {
let entire_span = fn_all.span();
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let dynamic_type_path1 = syn::parse2::<syn::Path>(quote! { Dynamic }).unwrap();
let dynamic_type_path2 = syn::parse2::<syn::Path>(quote! { rhai::Dynamic }).unwrap();
let mut return_dynamic = false;
// #[cfg] attributes are not allowed on functions due to what is generated for them
crate::attrs::deny_cfg_attr(&fn_all.attrs)?;
@ -250,16 +252,16 @@ impl Parse for ExportedFn {
}) => true,
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
match flatten_type_groups(ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference {
syn::Type::Reference(syn::TypeReference {
mutability: Some(_),
..
}) => true,
&syn::Type::Reference(syn::TypeReference {
syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => match flatten_type_groups(elem.as_ref()) {
&syn::Type::Path(ref p) if p.path == str_type_path => false,
syn::Type::Path(ref p) if p.path == str_type_path => false,
_ => {
return Err(syn::Error::new(
ty.span(),
@ -285,18 +287,18 @@ impl Parse for ExportedFn {
_ => panic!("internal error: receiver argument outside of first position!?"),
};
let is_ok = match flatten_type_groups(ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference {
syn::Type::Reference(syn::TypeReference {
mutability: Some(_),
..
}) => false,
&syn::Type::Reference(syn::TypeReference {
syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => {
matches!(flatten_type_groups(elem.as_ref()), &syn::Type::Path(ref p) if p.path == str_type_path)
matches!(flatten_type_groups(elem.as_ref()), syn::Type::Path(ref p) if p.path == str_type_path)
}
&syn::Type::Verbatim(_) => false,
syn::Type::Verbatim(_) => false,
_ => true,
};
if !is_ok {
@ -308,21 +310,26 @@ impl Parse for ExportedFn {
}
}
// No returning references or pointers.
// Check return type.
if let syn::ReturnType::Type(_, ref rtype) = fn_all.sig.output {
match rtype.as_ref() {
&syn::Type::Ptr(_) => {
match flatten_type_groups(rtype.as_ref()) {
syn::Type::Ptr(_) => {
return Err(syn::Error::new(
fn_all.sig.output.span(),
"cannot return a pointer to Rhai",
"Rhai functions cannot return pointers",
))
}
&syn::Type::Reference(_) => {
syn::Type::Reference(_) => {
return Err(syn::Error::new(
fn_all.sig.output.span(),
"cannot return a reference to Rhai",
"Rhai functions cannot return references",
))
}
syn::Type::Path(p)
if p.path == dynamic_type_path1 || p.path == dynamic_type_path2 =>
{
return_dynamic = true
}
_ => {}
}
}
@ -330,6 +337,7 @@ impl Parse for ExportedFn {
entire_span,
signature: fn_all.sig,
is_public,
return_dynamic,
mut_receiver,
params: ExportedFnParams::default(),
})
@ -419,7 +427,7 @@ impl ExportedFn {
pub(crate) fn return_type(&self) -> Option<&syn::Type> {
if let syn::ReturnType::Type(_, ref rtype) = self.signature.output {
Some(rtype)
Some(flatten_type_groups(rtype))
} else {
None
}
@ -437,7 +445,7 @@ impl ExportedFn {
{
return Err(syn::Error::new(
self.signature.span(),
"return_raw functions must return Result<T>",
"return_raw functions must return Result<T, Box<EvalAltResult>>",
));
}
@ -467,7 +475,7 @@ impl ExportedFn {
FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => {
return Err(syn::Error::new(
self.signature.span(),
"property setter must return no value",
"property setter cannot return any value",
))
}
// 4a. Index getters must take the subject and the accessed "index" as arguments.
@ -495,7 +503,7 @@ impl ExportedFn {
FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => {
return Err(syn::Error::new(
self.signature.span(),
"index setter must return no value",
"index setter cannot return a value",
))
}
_ => {}
@ -532,7 +540,7 @@ impl ExportedFn {
dynamic_signature.ident =
syn::Ident::new("dynamic_result_fn", proc_macro2::Span::call_site());
dynamic_signature.output = syn::parse2::<syn::ReturnType>(quote! {
-> Result<Dynamic, EvalBox>
-> Result<Dynamic, Box<EvalAltResult>>
})
.unwrap();
let arguments: Vec<syn::Ident> = dynamic_signature
@ -555,18 +563,22 @@ impl ExportedFn {
.return_type()
.map(|r| r.span())
.unwrap_or_else(|| proc_macro2::Span::call_site());
if !self.params.return_raw {
if self.params.return_raw {
quote_spanned! { return_span=>
type EvalBox = Box<EvalAltResult>;
pub #dynamic_signature {
Ok(Dynamic::from(super::#name(#(#arguments),*)))
super::#name(#(#arguments),*)
}
}
} else if self.return_dynamic {
quote_spanned! { return_span=>
pub #dynamic_signature {
Ok(super::#name(#(#arguments),*))
}
}
} else {
quote_spanned! { return_span=>
type EvalBox = Box<EvalAltResult>;
pub #dynamic_signature {
super::#name(#(#arguments),*)
Ok(Dynamic::from(super::#name(#(#arguments),*)))
}
}
}
@ -620,8 +632,8 @@ impl ExportedFn {
let var = syn::Ident::new("arg0", proc_macro2::Span::call_site());
match first_arg {
syn::FnArg::Typed(pattern) => {
let arg_type: &syn::Type = match flatten_type_groups(pattern.ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(),
let arg_type = match flatten_type_groups(pattern.ty.as_ref()) {
syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(),
p => p,
};
let downcast_span = quote_spanned!(
@ -658,14 +670,14 @@ impl ExportedFn {
let is_ref;
match arg {
syn::FnArg::Typed(pattern) => {
let arg_type: &syn::Type = pattern.ty.as_ref();
let arg_type = pattern.ty.as_ref();
let downcast_span = match flatten_type_groups(pattern.ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference {
syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => match flatten_type_groups(elem.as_ref()) {
&syn::Type::Path(ref p) if p.path == str_type_path => {
syn::Type::Path(ref p) if p.path == str_type_path => {
is_string = true;
is_ref = true;
quote_spanned!(arg_type.span()=>
@ -673,7 +685,7 @@ impl ExportedFn {
}
_ => panic!("internal error: why wasn't this found earlier!?"),
},
&syn::Type::Path(ref p) if p.path == string_type_path => {
syn::Type::Path(ref p) if p.path == string_type_path => {
is_string = true;
is_ref = false;
quote_spanned!(arg_type.span()=>
@ -734,9 +746,15 @@ impl ExportedFn {
.map(|r| r.span())
.unwrap_or_else(|| proc_macro2::Span::call_site());
let return_expr = if !self.params.return_raw {
if self.return_dynamic {
quote_spanned! { return_span=>
Ok(#sig_name(#(#unpack_exprs),*))
}
} else {
quote_spanned! { return_span=>
Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*)))
}
}
} else {
quote_spanned! { return_span=>
#sig_name(#(#unpack_exprs),*)

View File

@ -3,6 +3,7 @@ use std::collections::HashMap;
use quote::{quote, ToTokens};
use crate::attrs::ExportScope;
use crate::function::flatten_type_groups;
use crate::function::{ExportedFn, FnSpecialAccess};
use crate::module::Module;
@ -37,7 +38,7 @@ pub(crate) fn generate_body(
if itemmod.skipped() {
continue;
}
let module_name: &syn::Ident = itemmod.module_name().unwrap();
let module_name = itemmod.module_name().unwrap();
let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() {
syn::LitStr::new(&name, proc_macro2::Span::call_site())
} else {
@ -174,14 +175,6 @@ pub(crate) fn generate_body(
}
}
pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type {
match ty {
syn::Type::Group(syn::TypeGroup { ref elem, .. })
| syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()),
_ => ty,
}
}
pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> {
fn make_key(name: impl ToString, itemfn: &ExportedFn) -> String {
itemfn

View File

@ -88,7 +88,10 @@ mod function_tests {
};
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!(format!("{}", err), "cannot return a reference to Rhai");
assert_eq!(
format!("{}", err),
"Rhai functions cannot return references"
);
}
#[test]
@ -98,7 +101,7 @@ mod function_tests {
};
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!(format!("{}", err), "cannot return a pointer to Rhai");
assert_eq!(format!("{}", err), "Rhai functions cannot return pointers");
}
#[test]
@ -295,8 +298,7 @@ mod generate_tests {
pub fn token_input_types() -> Box<[TypeId]> {
Token().input_types()
}
type EvalBox = Box<EvalAltResult>;
pub fn dynamic_result_fn() -> Result<Dynamic, EvalBox> {
pub fn dynamic_result_fn() -> Result<Dynamic, Box<EvalAltResult> > {
Ok(Dynamic::from(super::do_nothing()))
}
}
@ -340,8 +342,7 @@ mod generate_tests {
pub fn token_input_types() -> Box<[TypeId]> {
Token().input_types()
}
type EvalBox = Box<EvalAltResult>;
pub fn dynamic_result_fn(x: usize) -> Result<Dynamic, EvalBox> {
pub fn dynamic_result_fn(x: usize) -> Result<Dynamic, Box<EvalAltResult> > {
Ok(Dynamic::from(super::do_something(x)))
}
}
@ -351,6 +352,51 @@ mod generate_tests {
assert_streams_eq(item_fn.generate(), expected_tokens);
}
#[test]
fn return_dynamic() {
let input_tokens: TokenStream = quote! {
pub fn return_dynamic() -> (((rhai::Dynamic))) {
().into()
}
};
let expected_tokens = quote! {
#[allow(unused)]
pub mod rhai_fn_return_dynamic {
use super::*;
struct Token();
impl PluginFunction for Token {
fn call(&self,
args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 0usize,
"wrong arg count: {} != {}", args.len(), 0usize);
Ok(return_dynamic())
}
fn is_method_call(&self) -> bool { false }
fn is_variadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![].into_boxed_slice()
}
}
pub fn token_callable() -> CallableFunction {
Token().into()
}
pub fn token_input_types() -> Box<[TypeId]> {
Token().input_types()
}
pub fn dynamic_result_fn() -> Result<Dynamic, Box<EvalAltResult> > {
Ok(super::return_dynamic())
}
}
};
let item_fn = syn::parse2::<ExportedFn>(input_tokens).unwrap();
assert_streams_eq(item_fn.generate(), expected_tokens);
}
#[test]
fn one_arg_usize_fn_impl() {
let input_tokens: TokenStream = quote! {
@ -417,8 +463,7 @@ mod generate_tests {
pub fn token_input_types() -> Box<[TypeId]> {
Token().input_types()
}
type EvalBox = Box<EvalAltResult>;
pub fn dynamic_result_fn(x: usize, y: usize) -> Result<Dynamic, EvalBox> {
pub fn dynamic_result_fn(x: usize, y: usize) -> Result<Dynamic, Box<EvalAltResult> > {
Ok(Dynamic::from(super::add_together(x, y)))
}
}
@ -464,8 +509,7 @@ mod generate_tests {
pub fn token_input_types() -> Box<[TypeId]> {
Token().input_types()
}
type EvalBox = Box<EvalAltResult>;
pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result<Dynamic, EvalBox> {
pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result<Dynamic, Box<EvalAltResult> > {
Ok(Dynamic::from(super::increment(x, y)))
}
}
@ -510,8 +554,7 @@ mod generate_tests {
pub fn token_input_types() -> Box<[TypeId]> {
Token().input_types()
}
type EvalBox = Box<EvalAltResult>;
pub fn dynamic_result_fn(message: &str) -> Result<Dynamic, EvalBox> {
pub fn dynamic_result_fn(message: &str) -> Result<Dynamic, Box<EvalAltResult> > {
Ok(Dynamic::from(super::special_print(message)))
}
}

View File

@ -1,4 +1,4 @@
error: return_raw functions must return Result<T>
error: return_raw functions must return Result<T, Box<EvalAltResult>>
--> $DIR/export_fn_raw_noreturn.rs:10:5
|
10 | pub fn test_fn(input: &mut Point) {

View File

@ -1,4 +1,4 @@
error: return_raw functions must return Result<T>
error: return_raw functions must return Result<T, Box<EvalAltResult>>
--> $DIR/export_mod_raw_noreturn.rs:12:5
|
12 | pub fn test_fn(input: &mut Point) {

View File

@ -1,4 +1,4 @@
error: cannot return a reference to Rhai
error: Rhai functions cannot return references
--> $DIR/return_mut_ref.rs:12:38
|
12 | pub fn test_fn(input: &mut Clonable) -> &mut bool {

View File

@ -1,4 +1,4 @@
error: cannot return a pointer to Rhai
error: Rhai functions cannot return pointers
--> $DIR/return_pointer.rs:12:33
|
12 | pub fn test_fn(input: Clonable) -> *const str {

View File

@ -1,4 +1,4 @@
error: cannot return a reference to Rhai
error: Rhai functions cannot return references
--> $DIR/return_shared_ref.rs:12:33
|
12 | pub fn test_fn(input: Clonable) -> &'static str {

View File

@ -1,28 +0,0 @@
use rhai::plugin::*;
#[derive(Clone)]
pub struct Point {
x: f32,
y: f32,
}
#[export_module]
pub mod test_module {
pub use super::Point;
#[rhai_fn(name = "big$caching")]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
}
fn main() {
let n = Point {
x: 0.0,
y: 10.0,
};
if test_module::test_fn(n) {
println!("yes");
} else {
println!("no");
}
}

View File

@ -1,11 +0,0 @@
error: Rhai function names may not contain dollar sign
--> $DIR/rhai_fn_rename_dollar_sign.rs:12:22
|
12 | #[rhai_fn(name = "big$caching")]
| ^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> $DIR/rhai_fn_rename_dollar_sign.rs:23:8
|
23 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared crate or module `test_module`

View File

@ -1,28 +0,0 @@
use rhai::plugin::*;
#[derive(Clone)]
pub struct Point {
x: f32,
y: f32,
}
#[export_module]
pub mod test_module {
pub use super::Point;
#[rhai_fn(name = "foo.bar")]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
}
fn main() {
let n = Point {
x: 0.0,
y: 10.0,
};
if test_module::test_fn(n) {
println!("yes");
} else {
println!("no");
}
}

View File

@ -1,11 +0,0 @@
error: Rhai function names may not contain dot
--> $DIR/rhai_fn_rename_dot.rs:12:22
|
12 | #[rhai_fn(name = "foo.bar")]
| ^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> $DIR/rhai_fn_rename_dot.rs:23:8
|
23 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared crate or module `test_module`

View File

@ -1,4 +1,4 @@
error: property setter must return no value
error: property setter cannot return any value
--> $DIR/rhai_fn_setter_return.rs:13:9
|
13 | pub fn test_fn(input: &mut Point, value: f32) -> bool {

View File

@ -60,6 +60,7 @@ Symbols and Patterns
| `--` | decrement | _reserved_ |
| `..` | range | _reserved_ |
| `...` | range | _reserved_ |
| `**` | exponentiation | _reserved_ |
| `#` | hash | _reserved_ |
| `@` | at | _reserved_ |
| `$` | dollar | _reserved_ |

View File

@ -1,5 +1,5 @@
{
"version": "0.20.0",
"version": "0.19.1",
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
"rootUrl": "",

View File

@ -84,7 +84,11 @@ Operator Precedence
All operators in Rhai has a _precedence_ indicating how tightly they bind.
The following _precedence table_ show the built-in precedence of standard Rhai operators:
A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc.
When registering a custom operator, the operator's precedence must also be provided.
The following _precedence table_ shows the built-in precedence of standard Rhai operators:
| Category | Operators | Precedence (0-255) |
| ------------------- | :-------------------------------------------------------------------------------------: | :----------------: |
@ -92,15 +96,11 @@ The following _precedence table_ show the built-in precedence of standard Rhai o
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | 30 |
| Logic and bit masks | `&`, `&&` | 60 |
| Comparisons | `==`, `!=` | 90 |
| Comparisons | `>`, `>=`, `<`, `<=` | 110 |
| | `in` | 130 |
| | `in` | 110 |
| Comparisons | `>`, `>=`, `<`, `<=` | 130 |
| Arithmetic | `+`, `-` | 150 |
| Arithmetic | `*`, `/` | 180 |
| Arithmetic | `~`, `%` | 190 |
| Arithmetic | `*`, `/`, `%` | 180 |
| Arithmetic | `~` | 190 |
| Bit-shifts | `<<`, `>>` | 210 |
| Object | `.` _(binds to right)_ | 240 |
| _Others_ | | 0 |
A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc.
When registering a custom operator, the operator's precedence must also be provided.
| Unary operators | unary `+`, `-`, `!` _(binds to right)_ | 255 |

View File

@ -114,7 +114,8 @@ Any custom syntax must include an _implementation_ of it.
The function signature of an implementation is:
> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>`
> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression])`
> `-> Result<Dynamic, Box<EvalAltResult>>`
where:

View File

@ -62,7 +62,8 @@ Function Signature
The function signature passed to `Engine::on_var` takes the following form:
> `Fn(name: &str, index: usize, scope: &Scope, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
> `Fn(name: &str, index: usize, scope: &Scope, context: &EvalContext)`
> `-> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
where:

View File

@ -31,29 +31,32 @@ Built-in Functions
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
| Function | Parameter(s) | Description |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `append` | array to append | concatenates the second array to the end of the first |
| `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
| `+=` operator | 1) array<br/>2) array to append | concatenates the second array to the end of the first |
| `+` operator | 1) first array<br/>2) second array | concatenates the first array with the second |
| `insert` | 1) element to insert<br/>2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index |
| `insert` | 1) element to insert<br/>2) position, beginning if < 0, end if > length | inserts an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `extract` | 1) start position, beginning if < 0, end if > length,<br/>2) _(optional)_ number of items to extract, none if < 0 | extracts a portion of the array into a new array |
| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) |
| `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements |
| `pad` | 1) target length<br/>2) element to pad | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
| `splice` | 1) start position (beginning if <= 0, end if >= length),<br/>2) number of items to remove (none if <= 0),<br/>3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
| `filter` | [function pointer] to predicate (can be a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
| `map` | [function pointer] to conversion function (can be a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
| `reduce` | [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items accumulated by the accumulator function:<br/>1st parameter: accumulated value ([`()`] initially),<br/>2nd parameter: array item,<br/>3rd parameter: offset index (optional) |
| `reduce_rev` | [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items (in reverse order) accumulated by the accumulator function:<br/>1st parameter: accumulated value ([`()`] initially),<br/>2nd parameter: array item,<br/>3rd parameter: offset index (optional) |
| `some` | [function pointer] to predicate (can be a [closure]) | returns `true` if any item returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
| `all` | [function pointer] to predicate (can be a [closure]) | returns `true` if all item returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
| `sort` | [function pointer] to a comparison function (can be a [closure]) | sorts the array with a comparison function:<br/>1st parameter: first item,<br/>2nd parameter: second item |
| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length |
| `splice` | 1) start position, beginning if < 0, end if > length,<br/>2) number of items to remove, none if < 0,<br/>3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: _(optional)_ offset index |
| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:<br/>1st parameter: array item,<br/>2nd parameter: _(optional)_ offset index |
| `reduce` | 1) [function pointer] to accumulator function (usually a [closure]),<br/>2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array into a single value via the accumulator function:<br/>1st parameter: accumulated value ([`()`] initially),<br/>2nd parameter: array item,<br/>3rd parameter: _(optional)_ offset index |
| `reduce_rev` | 1) [function pointer] to accumulator function (usually a [closure]),<br/>2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array (in reverse order) into a single value via the accumulator function:<br/>1st parameter: accumulated value ([`()`] initially),<br/>2nd parameter: array item,<br/>3rd parameter: _(optional)_ offset index |
| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any item returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: _(optional)_ offset index |
| `none` | [function pointer] to predicate (usually a [closure]) | returns `true` if no item returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: _(optional)_ offset index |
| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all items return `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: _(optional)_ offset index |
| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function:<br/>1st parameter: first item,<br/>2nd parameter: second item,<br/>return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second |
Use Custom Types With Arrays
@ -71,12 +74,13 @@ Examples
--------
```rust
let y = [2, 3]; // array literal with 2 elements
let y = [2, 3]; // y == [2, 3]
let y = [2, 3,]; // trailing comma is OK
let y = [2, 3,]; // y == [2, 3]
y.insert(0, 1); // insert element at the beginning
y.insert(999, 4); // insert element at the end
y.insert(0, 1); // y == [1, 2, 3]
y.insert(999, 4); // y == [1, 2, 3, 4]
y.len == 4;
@ -89,103 +93,111 @@ y[3] == 4;
(42 in y) == false; // 'in' uses the '==' operator (which users can override)
// to check if the target item exists in the array
y[1] = 42; // array elements can be reassigned
y[1] = 42; // y == [1, 42, 3, 4]
(42 in y) == true;
y.remove(2) == 3; // remove element
y.remove(2) == 3; // y == [1, 42, 4]
y.len == 3;
y[2] == 4; // elements after the removed element are shifted
ts.list = y; // arrays can be assigned completely (by value copy)
let foo = ts.list[1];
foo == 42;
let foo = [1, 2, 3][0];
foo == 1;
ts.list[1] == 42;
[1, 2, 3][0] == 1; // indexing on array literal
fn abc() {
[42, 43, 44] // a function returning an array
}
let foo = abc()[0];
foo == 42;
abc()[0] == 42;
let foo = y[0];
foo == 1;
y.push(4); // y == [1, 42, 4, 4]
y.push(4); // 4 elements
y += 5; // 5 elements
y += 5; // y == [1, 42, 4, 4, 5]
y.len == 5;
let first = y.shift(); // remove the first element, 4 elements remaining
first == 1;
y.shift() == 1; // y == [42, 4, 4, 5]
let last = y.pop(); // remove the last element, 3 elements remaining
last == 5;
y.chop(3); // y == [4, 4, 5]
y.len == 3;
y.pop() == 5; // y == [4, 4]
y.len == 2;
for item in y { // arrays can be iterated with a 'for' statement
print(item);
}
y.pad(10, "hello"); // pad the array up to 10 elements
y.pad(6, "hello"); // y == [4, 4, "hello", "hello", "hello", "hello"]
y.len == 10;
y.len == 6;
y.truncate(5); // truncate the array to 5 elements
y.truncate(4); // y == [4, 4, "hello", "hello"]
y.len == 5;
y.len == 4;
y.clear(); // empty the array
y.clear(); // y == []
y.len == 0;
let a = [42, 123, 99];
a.map(|v| v + 1); // [43, 124, 100]
a.map(|v| v + 1); // returns [43, 124, 100]
a.map(|v, i| v + i); // [42, 124, 101]
a.map(|v, i| v + i); // returns [42, 124, 101]
a.filter(|v| v > 50); // [123, 99]
a.filter(|v| v > 50); // returns [123, 99]
a.filter(|v, i| i == 1); // [123]
a.filter(|v, i| i == 1); // returns [123]
a.reduce(|sum, v| {
// Detect the initial value of '()'
if sum.type_of() == "()" { v } else { sum + v }
// Use a closure to provide the initial value
a.reduce(|sum, v| sum + v, || 0) == 264;
// Detect the initial value of '()'
a.reduce(
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
) == 264;
a.reduce(|sum, v, i| {
// Detect the initial value via index
a.reduce(|sum, v, i|
if i == 0 { v } else { sum + v }
) == 264;
a.reduce_rev(|sum, v| {
// Detect the initial value of '()'
if sum.type_of() == "()" { v } else { sum + v }
// Use a closure to provide the initial value
a.reduce_rev(|sum, v| sum + v, || 0) == 264;
// Detect the initial value of '()'
a.reduce_rev(
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
) == 264;
a.reduce_rev(|sum, v, i| {
if i == 0 { v } else { sum + v }
// Detect the initial value via index
a.reduce_rev(|sum, v, i|
if i == 2 { v } else { sum + v }
) == 264;
a.some(|v| v > 50) == true;
a.some(|v| v > 50); // returns true
a.some(|v, i| v < i) == false;
a.some(|v, i| v < i); // returns false
a.all(|v| v > 50) == false;
a.none(|v| v != 0); // returns false
a.all(|v, i| v > i) == true;
a.none(|v, i| v == i); // returns true
a.splice(1, 1, [1, 3, 2]);
a.all(|v| v > 50); // returns false
a == [42, 1, 3, 2, 99];
a.all(|v, i| v > i); // returns true
a.sort(|x, y| x - y);
a.splice(1, 1, [1, 3, 2]); // a == [42, 1, 3, 2, 99]
a == [1, 2, 3, 42, 99];
a.extract(1, 3); // returns [1, 3, 2]
a.sort(|x, y| x - y); // a == [1, 2, 3, 42, 99]
```

View File

@ -13,7 +13,7 @@ Built-in methods
----------------
The following standard methods (mostly defined in the [`BasicFnPackage`][packages] but excluded if
using a [raw `Engine`]) operate on [strings]:
using a [raw `Engine`]) operate on function pointers:
| Function | Parameter(s) | Description |
| -------------------------- | ------------ | ---------------------------------------------------------------------------- |
@ -64,7 +64,7 @@ Global Namespace Only
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
They can only refer to functions within the global [namespace][function namespace].
See [function namespaces] for more details.
See _[Function Namespaces]_ for more details.
```rust
import "foo" as f; // assume there is 'f::do_work()'
@ -135,13 +135,13 @@ map[func].call(42);
```
Binding the `this` Pointer
-------------------------
Bind the `this` Pointer
----------------------
When `call` is called as a _method_ but not on a `FnPtr` value, it is possible to dynamically dispatch
When `call` is called as a _method_ but not on a function pointer, it is possible to dynamically dispatch
to a function call while binding the object in the method call to the `this` pointer of the function.
To achieve this, pass the `FnPtr` value as the _first_ argument to `call`:
To achieve this, pass the function pointer as the _first_ argument to `call`:
```rust
fn add(x) { // define function which uses 'this'
@ -167,3 +167,52 @@ Beware that this only works for _method-call_ style. Normal function-call style
the `this` pointer (for syntactic reasons).
Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].
Call a Function Pointer in Rust
------------------------------
It is completely normal to register a Rust function with an [`Engine`] that takes parameters
whose types are function pointers. The Rust type in question is `rhai::FnPtr`.
A function pointer in Rhai is essentially syntactic sugar wrapping the _name_ of a function
to call in script. Therefore, the script's [`AST`] is required to call a function pointer,
as well as the entire _execution context_ that the script is running in.
For a rust function taking a function pointer as parameter, the [Low-Level API](../rust/register-raw.md)
must be used to register the function.
Essentially, use the low-level `Engine::register_raw_fn` method to register the function.
`FnPtr::call_dynamic` is used to actually call the function pointer, passing to it the
current scripting [`Engine`], collection of script-defined functions, the `this` pointer,
and other necessary arguments.
```rust
use rhai::{Engine, Module, Dynamic, FnPtr};
let mut engine = Engine::new();
// Define Rust function in required low-level API signature
fn call_fn_ptr_with_value(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic])
-> Result<Dynamic, Box<EvalAltResult>>
{
// 'args' is guaranteed to contain enough arguments of the correct types
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found.
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
}
// Register a Rust function using the low-level API
engine.register_raw_fn("super_call",
&[ // parameter types
std::any::TypeId::of::<i64>(),
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>()
],
call_fn_ptr_with_value
);
```

View File

@ -79,7 +79,7 @@ for x in range(0, 50, 3) { // step by 3
Iterate Through Object Maps
--------------------------
Two functions, `keys` and `values`, return [arrays] containing cloned _copies_
Two methods, `keys` and `values`, return [arrays] containing cloned _copies_
of all property names and values of an [object map], respectively.
These [arrays] can be iterated.
@ -88,7 +88,7 @@ These [arrays] can be iterated.
let map = #{a:1, b:3, c:5, d:7, e:9};
// Property names are returned in unsorted, random order
for x in keys(map) {
for x in map.keys() {
if x > 10 { continue; } // skip to the next iteration
print(x);
@ -97,7 +97,7 @@ for x in keys(map) {
}
// Property values are returned in unsorted, random order
for val in values(map) {
for val in map.values() {
print(val);
}
```

View File

@ -126,11 +126,11 @@ y.remove("a") == 1; // remove property
y.len() == 2;
y.has("a") == false;
for name in keys(y) { // get an array of all the property names via the 'keys' function
for name in y.keys() { // get an array of all the property names via 'keys'
print(name);
}
for val in values(y) { // get an array of all the property values via the 'values' function
for val in y.values() { // get an array of all the property values via 'values'
print(val);
}
@ -138,3 +138,30 @@ y.clear(); // empty the object map
y.len() == 0;
```
No Support for Property Getters
------------------------------
In order not to affect the speed of accessing properties in an object map, new property
[getters][getters/setters] cannot be registered because they conflict with the syntax of
property access.
A property [getter][getters/setters] function registered via `Engine::register_get`, for example,
for a `Map` will never be found - instead, the property will be looked up in the object map.
Therefore, _method-call_ notation must be used for built-in properties:
```rust
map.len // access property 'len', returns '()' if not found
map.len() // returns the number of properties
map.keys // access property 'keys', returns '()' if not found
map.keys() // returns array of all property names
map.values // access property 'values', returns '()' if not found
map.values() // returns array of all property values
```

View File

@ -7,17 +7,17 @@ The following standard methods (mostly defined in the [`MoreStringPackage`][pack
using a [raw `Engine`]) operate on [strings]:
| Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| ------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | 1) character to pad<br/>2) target length | pads the string with an character to at least a specified length |
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `index_of` | 1) character/sub-string to search for<br/>2) start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | 1) start index<br/>2) length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `index_of` | 1) character/sub-string to search for<br/>2) _(optional)_ start index | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | 1) start index<br/>2) _(optional)_ number of characters to extract, none if < 0 | extracts a sub-string (to the end of the string if length is not specified) |
| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] |
| `crop` | 1) start index<br/>2) length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `crop` | 1) start index<br/>2) _(optional)_ number of characters to retain, none if < 0 | retains only a portion of the string |
| `replace` | 1) target character/sub-string<br/>2) replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |

View File

@ -59,7 +59,8 @@ Function Signature
The function signature passed to `Engine::register_raw_fn` takes the following form:
> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic])`
> `-> Result<T, Box<EvalAltResult>> + 'static`
where:
@ -117,7 +118,7 @@ engine.register_raw_fn(
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
// 'args' is guaranteed to contain enough arguments of the correct types
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer

View File

@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, EvalContext, Imports, State};
use crate::error::ParseError;
use crate::fn_native::{IteratorFn, SendSync};
use crate::fn_native::SendSync;
use crate::module::{FuncReturn, Module};
use crate::optimize::OptimizationLevel;
use crate::parser::AST;
@ -174,11 +174,15 @@ impl Engine {
self
}
/// Register an iterator adapter for a type with the `Engine`.
/// Register an iterator adapter for an iterable type with the `Engine`.
/// This is an advanced feature.
#[inline(always)]
pub fn register_iterator<T: Variant + Clone>(&mut self, f: IteratorFn) -> &mut Self {
self.global_module.set_iter(TypeId::of::<T>(), f);
pub fn register_iterator<T>(&mut self) -> &mut Self
where
T: Variant + Clone + Iterator,
<T as Iterator>::Item: Variant + Clone,
{
self.global_module.set_iterable::<T>();
self
}

View File

@ -129,6 +129,54 @@ pub enum ChainType {
Dot,
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[derive(Debug, Clone)]
pub enum IndexChainValue {
None,
FnCallArgs(StaticVec<Dynamic>),
Value(Dynamic),
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl IndexChainValue {
/// Return the `Dynamic` value.
///
/// # Panics
///
/// Panics if not `IndexChainValue::Value`.
pub fn as_value(self) -> Dynamic {
match self {
Self::None | Self::FnCallArgs(_) => panic!("expecting IndexChainValue::Value"),
Self::Value(value) => value,
}
}
/// Return the `StaticVec<Dynamic>` value.
///
/// # Panics
///
/// Panics if not `IndexChainValue::FnCallArgs`.
pub fn as_fn_call_args(self) -> StaticVec<Dynamic> {
match self {
Self::None | Self::Value(_) => panic!("expecting IndexChainValue::FnCallArgs"),
Self::FnCallArgs(value) => value,
}
}
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl From<StaticVec<Dynamic>> for IndexChainValue {
fn from(value: StaticVec<Dynamic>) -> Self {
Self::FnCallArgs(value)
}
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl From<Dynamic> for IndexChainValue {
fn from(value: Dynamic) -> Self {
Self::Value(value)
}
}
/// A type that encapsulates a mutation target for an expression with side effects.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[derive(Debug)]
@ -790,7 +838,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
target: &mut Target,
rhs: &Expr,
idx_values: &mut StaticVec<Dynamic>,
mut idx_values: StaticVec<IndexChainValue>,
chain_type: ChainType,
level: usize,
new_val: Option<(Dynamic, Position)>,
@ -820,6 +868,7 @@ impl Engine {
Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref();
let idx_pos = idx.position();
let idx_val = idx_val.as_value();
let obj_ptr = &mut self.get_indexed_mut(
state, lib, target, idx_val, idx_pos, false, true, level,
)?;
@ -832,6 +881,7 @@ impl Engine {
}
// xxx[rhs] = new_val
_ if new_val.is_some() => {
let idx_val = idx_val.as_value();
let mut idx_val2 = idx_val.clone();
// `call_setter` is introduced to bypass double mutable borrowing of target
@ -876,9 +926,11 @@ impl Engine {
Ok(Default::default())
}
// xxx[rhs]
_ => self
.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
.map(|v| (v.take_or_clone(), false)),
_ => {
let idx_val = idx_val.as_value();
self.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
.map(|v| (v.take_or_clone(), false))
}
}
}
@ -889,9 +941,9 @@ impl Engine {
Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
let args = idx_val.as_fn_call_args();
self.make_method_call(
state, lib, name, *hash, target, idx_val, &def_val, *native, false,
level,
state, lib, name, *hash, target, args, &def_val, *native, false, level,
)
.map_err(|err| err.fill_position(*pos))
}
@ -956,10 +1008,11 @@ impl Engine {
Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
let args = idx_val.as_fn_call_args();
let (val, _) = self
.make_method_call(
state, lib, name, *hash, target, idx_val, &def_val,
*native, false, level,
state, lib, name, *hash, target, args, &def_val, *native,
false, level,
)
.map_err(|err| err.fill_position(*pos))?;
val.into()
@ -1034,10 +1087,11 @@ impl Engine {
Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
let args = idx_val.as_fn_call_args();
let (mut val, _) = self
.make_method_call(
state, lib, name, *hash, target, idx_val, &def_val,
*native, false, level,
state, lib, name, *hash, target, args, &def_val, *native,
false, level,
)
.map_err(|err| err.fill_position(*pos))?;
let val = &mut val;
@ -1083,10 +1137,19 @@ impl Engine {
_ => unreachable!(),
};
let idx_values = &mut StaticVec::new();
let mut idx_values = StaticVec::new();
self.eval_indexed_chain(
scope, mods, state, lib, this_ptr, dot_rhs, chain_type, idx_values, 0, level,
scope,
mods,
state,
lib,
this_ptr,
dot_rhs,
chain_type,
&mut idx_values,
0,
level,
)?;
match dot_lhs {
@ -1133,11 +1196,8 @@ impl Engine {
}
}
/// Evaluate a chain of indexes and store the results in a list.
/// The first few results are stored in the array `list` which is of fixed length.
/// Any spill-overs are stored in `more`, which is dynamic.
/// The fixed length array is used to avoid an allocation in the overwhelming cases of just a few levels of indexing.
/// The total number of values is returned.
/// Evaluate a chain of indexes and store the results in a StaticVec.
/// StaticVec is used to avoid an allocation in the overwhelming cases of just a few levels of indexing.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn eval_indexed_chain(
&self,
@ -1148,7 +1208,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr,
chain_type: ChainType,
idx_values: &mut StaticVec<Dynamic>,
idx_values: &mut StaticVec<IndexChainValue>,
size: usize,
level: usize,
) -> Result<(), Box<EvalAltResult>> {
@ -1162,31 +1222,30 @@ impl Engine {
.map(|arg_expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
})
.collect::<Result<StaticVec<Dynamic>, _>>()?;
.collect::<Result<StaticVec<_>, _>>()?;
idx_values.push(Dynamic::from(arg_values));
idx_values.push(arg_values.into());
}
Expr::FnCall(_) => unreachable!(),
Expr::Property(_) => idx_values.push(().into()), // Store a placeholder - no need to copy the property name
Expr::Property(_) => idx_values.push(IndexChainValue::None),
Expr::Index(x) | Expr::Dot(x) => {
let (lhs, rhs, _) = x.as_ref();
// Evaluate in left-to-right order
let lhs_val = match lhs {
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
Expr::Property(_) => IndexChainValue::None,
Expr::FnCall(x) if chain_type == ChainType::Dot && x.1.is_none() => {
let arg_values = x
.3
.iter()
x.3.iter()
.map(|arg_expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
})
.collect::<Result<StaticVec<Dynamic>, _>>()?;
Dynamic::from(arg_values)
.collect::<Result<StaticVec<Dynamic>, _>>()?
.into()
}
Expr::FnCall(_) => unreachable!(),
_ => self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?,
_ => self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
.into(),
};
// Push in reverse order
@ -1201,7 +1260,10 @@ impl Engine {
idx_values.push(lhs_val);
}
_ => idx_values.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?),
_ => idx_values.push(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.into(),
),
}
Ok(())
@ -1782,21 +1844,22 @@ impl Engine {
// For loop
Stmt::For(x) => {
let (name, expr, stmt, _) = x.as_ref();
let iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let tid = iter_type.type_id();
let iter_obj = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let iter_type = iter_obj.type_id();
if let Some(func) = self
let func = self
.global_module
.get_iter(tid)
.or_else(|| self.packages.get_iter(tid))
{
.get_iter(iter_type)
.or_else(|| self.packages.get_iter(iter_type));
if let Some(func) = func {
// Add the loop variable
let var_name = unsafe_cast_var_name_to_lifetime(name, &state);
scope.push(var_name, ());
let index = scope.len() - 1;
state.scope_level += 1;
for iter_value in func(iter_type) {
for iter_value in func(iter_obj) {
let (loop_var, _) = scope.get_mut(index);
let value = iter_value.flatten();

View File

@ -193,10 +193,8 @@ impl Engine {
// Search for the native function
// First search registered functions (can override packages)
// Then search packages
let func = self
.global_module
.get_fn(hash_fn, pub_only)
.or_else(|| lib.get_fn(hash_fn, pub_only))
let func = //lib.get_fn(hash_fn, pub_only)
self.global_module.get_fn(hash_fn, pub_only)
.or_else(|| self.packages.get_fn(hash_fn, pub_only));
if let Some(func) = func {
@ -462,9 +460,9 @@ impl Engine {
// First check script-defined functions
lib.contains_fn(hash_script, pub_only)
|| lib.contains_fn(hash_fn, pub_only)
//|| lib.contains_fn(hash_fn, pub_only)
// Then check registered functions
|| self.global_module.contains_fn(hash_script, pub_only)
//|| self.global_module.contains_fn(hash_script, pub_only)
|| self.global_module.contains_fn(hash_fn, pub_only)
// Then check packages
|| self.packages.contains_fn(hash_script, pub_only)
@ -547,15 +545,14 @@ impl Engine {
// Script-like function found
#[cfg(not(feature = "no_function"))]
_ if self.global_module.contains_fn(hash_script, pub_only)
|| lib.contains_fn(hash_script, pub_only)
_ if lib.contains_fn(hash_script, pub_only)
//|| self.global_module.contains_fn(hash_script, pub_only)
|| self.packages.contains_fn(hash_script, pub_only) =>
{
// Get function
let func = self
.global_module
let func = lib
.get_fn(hash_script, pub_only)
.or_else(|| lib.get_fn(hash_script, pub_only))
//.or_else(|| self.global_module.get_fn(hash_script, pub_only))
.or_else(|| self.packages.get_fn(hash_script, pub_only))
.unwrap();
@ -698,7 +695,7 @@ impl Engine {
name: &str,
hash_script: u64,
target: &mut Target,
idx_val: Dynamic,
mut call_args: StaticVec<Dynamic>,
def_val: &Option<Dynamic>,
native: bool,
pub_only: bool,
@ -708,7 +705,6 @@ impl Engine {
// Get a reference to the mutation target Dynamic
let obj = target.as_mut();
let mut idx = idx_val.cast::<StaticVec<Dynamic>>();
let mut _fn_name = name;
let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
@ -721,12 +717,12 @@ impl Engine {
let hash = if native {
0
} else {
calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty())
calc_fn_hash(empty(), fn_name, curry.len() + call_args.len(), empty())
};
// Arguments are passed as-is, adding the curried arguments
let mut arg_values = curry
.iter_mut()
.chain(idx.iter_mut())
.chain(call_args.iter_mut())
.collect::<StaticVec<_>>();
let args = arg_values.as_mut();
@ -734,9 +730,12 @@ impl Engine {
self.exec_fn_call(
state, lib, fn_name, hash, args, false, false, pub_only, None, 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
&& call_args.len() > 0
&& call_args[0].is::<FnPtr>()
{
// FnPtr call on object
let fn_ptr = idx.remove(0).cast::<FnPtr>();
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name
let fn_name = fn_ptr.get_fn_name().clone();
@ -744,12 +743,12 @@ impl Engine {
let hash = if native {
0
} else {
calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty())
calc_fn_hash(empty(), &fn_name, curry.len() + call_args.len(), empty())
};
// Replace the first argument with the object pointer, adding the curried arguments
let mut arg_values = once(obj)
.chain(curry.iter_mut())
.chain(idx.iter_mut())
.chain(call_args.iter_mut())
.collect::<StaticVec<_>>();
let args = arg_values.as_mut();
@ -767,7 +766,7 @@ impl Engine {
.curry()
.iter()
.cloned()
.chain(idx.into_iter())
.chain(call_args.into_iter())
.collect(),
)
.into(),
@ -776,7 +775,7 @@ impl Engine {
} else if {
#[cfg(not(feature = "no_closure"))]
{
_fn_name == KEYWORD_IS_SHARED && idx.is_empty()
_fn_name == KEYWORD_IS_SHARED && call_args.is_empty()
}
#[cfg(feature = "no_closure")]
false
@ -802,13 +801,13 @@ impl Engine {
.iter()
.cloned()
.enumerate()
.for_each(|(i, v)| idx.insert(i, v));
.for_each(|(i, v)| call_args.insert(i, v));
}
// Recalculate the hash based on the new function name and new arguments
hash = if native {
0
} else {
calc_fn_hash(empty(), _fn_name, idx.len(), empty())
calc_fn_hash(empty(), _fn_name, call_args.len(), empty())
};
}
}
@ -819,7 +818,9 @@ impl Engine {
}
// Attached object pointer in front of the arguments
let mut arg_values = once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
let mut arg_values = once(obj)
.chain(call_args.iter_mut())
.collect::<StaticVec<_>>();
let args = arg_values.as_mut();
self.exec_fn_call(
@ -876,28 +877,27 @@ impl Engine {
// Handle curry()
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !arg_value.is::<FnPtr>() {
if !fn_ptr.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(arg_value.type_name()),
self.map_type_name(fn_ptr.type_name()),
expr.position(),
));
}
let (fn_name, fn_curry) = arg_value.cast::<FnPtr>().take_data();
let (fn_name, mut fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
let curry: StaticVec<_> = args_expr
// Append the new curried arguments to the existing list.
args_expr
.iter()
.skip(1)
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
.try_for_each(|expr| -> Result<(), Box<EvalAltResult>> {
fn_curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?);
Ok(())
})?;
return Ok(FnPtr::new_unchecked(
fn_name,
fn_curry.into_iter().chain(curry.into_iter()).collect(),
)
.into());
return Ok(FnPtr::new_unchecked(fn_name, fn_curry).into());
}
// Handle is_shared()
@ -920,24 +920,27 @@ impl Engine {
&& !self.has_override(lib, 0, hash_script, pub_only)
{
let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if arg_value.is::<FnPtr>() {
let fn_ptr = arg_value.cast::<FnPtr>();
curry = fn_ptr.curry().iter().cloned().collect();
// Redirect function name
redirected = fn_ptr.take_data().0;
name = &redirected;
// Skip the first argument
args_expr = &args_expr.as_ref()[1..];
// Recalculate hash
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
} else {
if !fn_ptr.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(arg_value.type_name()),
self.map_type_name(fn_ptr.type_name()),
expr.position(),
));
}
let fn_ptr = fn_ptr.cast::<FnPtr>();
curry = fn_ptr.curry().iter().cloned().collect();
// Redirect function name
redirected = fn_ptr.take_data().0;
name = &redirected;
// Skip the first argument
args_expr = &args_expr.as_ref()[1..];
// Recalculate hash
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
}
// Handle is_def_var()
@ -946,8 +949,8 @@ impl Engine {
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let var_name = arg_value.as_str().map_err(|err| {
let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let var_name = var_name.as_str().map_err(|err| {
self.make_type_mismatch_err::<ImmutableString>(err, expr.position())
})?;
if var_name.is_empty() {
@ -970,20 +973,18 @@ impl Engine {
);
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
let fn_name_expr = args_expr.get(0).unwrap();
let num_params_expr = args_expr.get(1).unwrap();
let expr0 = args_expr.get(0).unwrap();
let expr1 = args_expr.get(1).unwrap();
let arg0_value =
self.eval_expr(scope, mods, state, lib, this_ptr, fn_name_expr, level)?;
let arg1_value =
self.eval_expr(scope, mods, state, lib, this_ptr, num_params_expr, level)?;
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr0, level)?;
let num_params = self.eval_expr(scope, mods, state, lib, this_ptr, expr1, level)?;
let fn_name = arg0_value.as_str().map_err(|err| {
self.make_type_mismatch_err::<ImmutableString>(err, fn_name_expr.position())
})?;
let num_params = arg1_value.as_int().map_err(|err| {
self.make_type_mismatch_err::<INT>(err, num_params_expr.position())
let fn_name = fn_name.as_str().map_err(|err| {
self.make_type_mismatch_err::<ImmutableString>(err, expr0.position())
})?;
let num_params = num_params
.as_int()
.map_err(|err| self.make_type_mismatch_err::<INT>(err, expr1.position()))?;
if fn_name.is_empty() || num_params < 0 {
return Ok(false.into());
@ -1002,8 +1003,8 @@ impl Engine {
// eval - only in function call style
let prev_len = scope.len();
let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let script = arg_value.as_str().map_err(|typ| {
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let script = script.as_str().map_err(|typ| {
self.make_type_mismatch_err::<ImmutableString>(typ, expr.position())
})?;
let result = if !script.is_empty() {

View File

@ -85,7 +85,7 @@ mod utils;
pub use any::Dynamic;
pub use engine::{Engine, EvalContext};
pub use error::{ParseError, ParseErrorType};
pub use fn_native::{FnPtr, IteratorFn};
pub use fn_native::FnPtr;
pub use fn_register::{RegisterFn, RegisterResultFn};
pub use module::Module;
pub use parser::{ImmutableString, AST, INT};

View File

@ -549,36 +549,6 @@ impl Module {
)
}
/// Set a raw function but with a signature that is a scripted function (meaning that the types
/// are not determined), but the implementation is in Rust.
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
#[inline]
#[allow(dead_code)]
pub(crate) fn set_raw_fn_as_scripted(
&mut self,
name: impl Into<String>,
num_params: usize,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<Dynamic> + SendSync + 'static,
) -> u64 {
// None + function name + number of arguments.
let name = name.into();
let hash_script = calc_fn_hash(empty(), &name, num_params, empty());
let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| func(engine, lib, args);
self.functions.insert(
hash_script,
(
name,
FnAccess::Public,
num_params,
None,
CallableFunction::from_pure(Box::new(f)),
),
);
self.indexed = false;
hash_script
}
/// Set a Rust function taking no parameters into the module, returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
@ -1498,6 +1468,28 @@ impl Module {
self
}
/// Set a type iterator into the module.
pub fn set_iterable<T>(&mut self) -> &mut Self
where
T: Variant + Clone + IntoIterator,
<T as IntoIterator>::Item: Variant + Clone,
{
self.set_iter(TypeId::of::<T>(), |obj: Dynamic| {
Box::new(obj.cast::<T>().into_iter().map(Dynamic::from))
})
}
/// Set an iterator type into the module as a type iterator.
pub fn set_iterator<T>(&mut self) -> &mut Self
where
T: Variant + Clone + Iterator,
<T as Iterator>::Item: Variant + Clone,
{
self.set_iter(TypeId::of::<T>(), |obj: Dynamic| {
Box::new(obj.cast::<T>().map(Dynamic::from))
})
}
/// Get the specified type iterator.
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.type_iterators.get(&id).cloned()

View File

@ -74,19 +74,19 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
lib.set_raw_fn("map", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], map);
lib.set_raw_fn("filter", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], filter);
lib.set_raw_fn("reduce", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], reduce);
lib.set_raw_fn("reduce", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>(), TypeId::of::<FnPtr>()], reduce_with_initial);
lib.set_raw_fn("reduce_rev", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], reduce_rev);
lib.set_raw_fn("reduce_rev", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>(), TypeId::of::<FnPtr>()], reduce_rev_with_initial);
lib.set_raw_fn("some", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], some);
lib.set_raw_fn("all", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], all);
lib.set_raw_fn("none", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], none);
lib.set_raw_fn("sort", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], sort);
// Merge in the module at the end to override `+=` for arrays
combine_with_exported_module!(lib, "array", array_functions);
// Register array iterator
lib.set_iter(
TypeId::of::<Array>(),
|arr| Box::new(arr.cast::<Array>().into_iter()) as Box<dyn Iterator<Item = Dynamic>>,
);
lib.set_iterable::<Array>();
});
#[export_module]
@ -131,6 +131,14 @@ mod array_functions {
list.clear();
}
}
pub fn chop(list: &mut Array, len: INT) {
if len as usize >= list.len() {
} else if len >= 0 {
list.drain(0..list.len() - len as usize);
} else {
list.clear();
}
}
pub fn reverse(list: &mut Array) {
list.reverse();
}
@ -153,6 +161,37 @@ mod array_functions {
list.splice(start..start + len, replace.into_iter());
}
pub fn extract(list: &mut Array, start: INT, len: INT) -> Array {
let start = if start < 0 {
0
} else if start as usize >= list.len() {
list.len() - 1
} else {
start as usize
};
let len = if len < 0 {
0
} else if len as usize > list.len() - start {
list.len() - start
} else {
len as usize
};
list[start..start + len].iter().cloned().collect()
}
#[rhai_fn(name = "extract")]
pub fn extract_tail(list: &mut Array, start: INT) -> Array {
let start = if start < 0 {
0
} else if start as usize >= list.len() {
list.len() - 1
} else {
start as usize
};
list[start..].iter().cloned().collect()
}
}
fn pad<T: Variant + Clone>(
@ -325,6 +364,40 @@ fn all(
Ok(true.into())
}
fn none(
engine: &Engine,
lib: &Module,
args: &mut [&mut Dynamic],
) -> Result<bool, Box<EvalAltResult>> {
let list = args[0].read_lock::<Array>().unwrap();
let filter = args[1].read_lock::<FnPtr>().unwrap();
for (i, item) in list.iter().enumerate() {
if filter
.call_dynamic(engine, lib, None, [item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => {
filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()])
}
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"filter".to_string(),
err,
Position::none(),
))
})?
.as_bool()
.unwrap_or(false)
{
return Ok(false.into());
}
}
Ok(true.into())
}
fn reduce(
engine: &Engine,
lib: &Module,
@ -359,6 +432,47 @@ fn reduce(
Ok(result)
}
fn reduce_with_initial(
engine: &Engine,
lib: &Module,
args: &mut [&mut Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
let list = args[0].read_lock::<Array>().unwrap();
let reducer = args[1].read_lock::<FnPtr>().unwrap();
let initial = args[2].read_lock::<FnPtr>().unwrap();
let mut result = initial.call_dynamic(engine, lib, None, []).map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
err,
Position::none(),
))
})?;
for (i, item) in list.iter().enumerate() {
result = reducer
.call_dynamic(engine, lib, None, [result.clone(), item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic(
engine,
lib,
None,
[result, item.clone(), (i as INT).into()],
),
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
err,
Position::none(),
))
})?;
}
Ok(result)
}
fn reduce_rev(
engine: &Engine,
lib: &Module,
@ -393,6 +507,47 @@ fn reduce_rev(
Ok(result)
}
fn reduce_rev_with_initial(
engine: &Engine,
lib: &Module,
args: &mut [&mut Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
let list = args[0].read_lock::<Array>().unwrap();
let reducer = args[1].read_lock::<FnPtr>().unwrap();
let initial = args[2].read_lock::<FnPtr>().unwrap();
let mut result = initial.call_dynamic(engine, lib, None, []).map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
err,
Position::none(),
))
})?;
for (i, item) in list.iter().enumerate().rev() {
result = reducer
.call_dynamic(engine, lib, None, [result.clone(), item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic(
engine,
lib,
None,
[result, item.clone(), (i as INT).into()],
),
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"reduce".to_string(),
err,
Position::none(),
))
})?;
}
Ok(result)
}
fn sort(
engine: &Engine,
lib: &Module,

View File

@ -1,24 +1,9 @@
use crate::any::{Dynamic, Variant};
use crate::any::Variant;
use crate::def_package;
use crate::module::{FuncReturn, Module};
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::{
any::TypeId,
boxed::Box,
ops::{Add, Range},
};
// Register range function
fn reg_range<T: Variant + Clone>(lib: &mut Module)
where
Range<T>: Iterator<Item = T>,
{
lib.set_iter(TypeId::of::<Range<T>>(), |source| {
Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
});
}
use crate::stdlib::ops::{Add, Range};
fn get_range<T: Variant + Clone>(from: T, to: T) -> FuncReturn<Range<T>> {
Ok(from..to)
@ -49,18 +34,6 @@ where
}
}
fn reg_step<T>(lib: &mut Module)
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Variant + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>,
{
lib.set_iter(TypeId::of::<StepRange<T>>(), |source| {
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
});
}
fn get_step_range<T>(from: T, to: T, step: T) -> FuncReturn<StepRange<T>>
where
for<'a> &'a T: Add<&'a T, Output = T>,
@ -70,14 +43,15 @@ where
}
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
reg_range::<INT>(lib);
lib.set_iterator::<Range<INT>>();
lib.set_fn_2("range", get_range::<INT>);
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
macro_rules! reg_range {
($lib:expr, $x:expr, $( $y:ty ),*) => (
$(
reg_range::<$y>($lib);
$lib.set_iterator::<Range<$y>>();
$lib.set_fn_2($x, get_range::<$y>);
)*
)
@ -90,14 +64,14 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
}
}
reg_step::<INT>(lib);
lib.set_iterator::<StepRange<INT>>();
lib.set_fn_3("range", get_step_range::<INT>);
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
macro_rules! reg_step {
($lib:expr, $x:expr, $( $y:ty ),*) => (
$(
reg_step::<$y>($lib);
$lib.set_iterator::<StepRange<$y>>();
$lib.set_fn_3($x, get_step_range::<$y>);
)*
)

View File

@ -6,7 +6,8 @@ use crate::engine::Map;
use crate::parser::{ImmutableString, INT};
use crate::plugin::*;
use crate::stdlib::vec::Vec;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
combine_with_exported_module!(lib, "map", map_functions);
@ -47,10 +48,10 @@ mod map_functions {
#[cfg(not(feature = "no_index"))]
pub mod indexing {
pub fn keys(map: &mut Map) -> Vec<Dynamic> {
pub fn keys(map: &mut Map) -> Array {
map.iter().map(|(k, _)| k.clone().into()).collect()
}
pub fn values(map: &mut Map) -> Vec<Dynamic> {
pub fn values(map: &mut Map) -> Array {
map.iter().map(|(_, v)| v.clone()).collect()
}
}

View File

@ -108,9 +108,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
// Register string iterator
lib.set_iter(
TypeId::of::<ImmutableString>(),
|arr| Box::new(
arr.cast::<ImmutableString>().chars().collect::<Vec<_>>().into_iter().map(Into::into)
) as Box<dyn Iterator<Item = Dynamic>>,
|s: Dynamic| Box::new(s.cast::<ImmutableString>().chars().collect::<Vec<_>>().into_iter().map(Into::into))
);
});

View File

@ -16,7 +16,7 @@ mod inner {
pub use core_error as error;
pub mod collections {
pub use hashbrown::{HashMap, HashSet};
pub use hashbrown::{hash_map, hash_set, HashMap, HashSet};
}
}

View File

@ -617,15 +617,15 @@ impl Token {
EqualsTo | NotEqualsTo => 90,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 110,
In => 110,
In => 130,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
Plus | Minus => 150,
Divide | Multiply => 180,
Divide | Multiply | Modulo => 180,
PowerOf | Modulo => 190,
PowerOf => 190,
LeftShift | RightShift => 210,
@ -1231,6 +1231,10 @@ fn get_next_token_inner(
('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)),
('-', _) => return Some((Token::Minus, start_pos)),
('*', '*') => {
eat_next(stream, pos);
return Some((Token::Reserved("**".into()), start_pos));
}
('*', ')') => {
eat_next(stream, pos);
return Some((Token::Reserved("*)".into()), start_pos));

View File

@ -61,10 +61,10 @@ fn test_for_object() -> Result<(), Box<EvalAltResult>> {
let keys = "";
let map = #{a: 1, b: 2, c: 3};
for key in keys(map) {
for key in map.keys() {
keys += key;
}
for value in values(map) {
for value in map.values() {
sum += value;
}