diff --git a/RELEASES.md b/RELEASES.md index c3762002..73fd61d2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,6 +20,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. New features ------------ @@ -30,7 +31,7 @@ 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` and `reduce` functions for arrays. +* `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `splice` and `sort` functions for arrays. Enhancements ------------ diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 5fc3c4fc..437364d6 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -30,25 +30,30 @@ 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
2) element to insert (not another array) | inserts an element at the end | -| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | -| `+` operator | 1) first array
2) second array | concatenates the first array with the second | -| `insert` | 1) element to insert
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) | -| `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
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) | -| `filter` | 1) array
2) [function pointer] to predicate (can be a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: offset index (optional) | -| `map` | 1) array
2) [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:
1st parameter: array item,
2nd parameter: offset index (optional) | -| `reduce` | 1) array
2) [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items accumulated by the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: offset index (optional) | +| 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
2) element to insert (not another array) | inserts an element at the end | +| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | +| `+` operator | 1) first array
2) second array | concatenates the first array with the second | +| `insert` | 1) element to insert
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) | +| `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
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),
2) number of items to remove (none if <= 0),
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:
1st parameter: array item,
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:
1st parameter: array item,
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:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
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:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
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:
1st parameter: array item,
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:
1st parameter: array item,
2nd parameter: offset index (optional) | +| `sort` | [function pointer] to a comparison function (can be a [closure]) | sorts the array with a comparison function:
1st parameter: first item,
2nd parameter: second item | Use Custom Types With Arrays @@ -158,4 +163,29 @@ a.reduce(|sum, v| { 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 } +) == 264; + +a.reduce_rev(|sum, v, i| { + if i == 0 { v } else { sum + v } +) == 264; + +a.some(|v| v > 50) == true; + +a.some(|v, i| v < i) == false; + +a.all(|v| v > 50) == false; + +a.all(|v, i| v > i) == true; + +a.splice(1, 1, [1, 3, 2]); + +a == [42, 1, 3, 2, 99]; + +a.sort(|x, y| x - y); + +a == [1, 2, 3, 42, 99]; ``` diff --git a/src/module/mod.rs b/src/module/mod.rs index f98d1f89..afa82cfe 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -554,6 +554,7 @@ impl Module { #[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, diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index bb2b2aa4..107ed85e 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -13,7 +13,7 @@ use crate::token::Position; #[cfg(not(feature = "no_object"))] use crate::engine::Map; -use crate::stdlib::{any::TypeId, boxed::Box, string::ToString}; +use crate::stdlib::{any::TypeId, boxed::Box, cmp::Ordering, string::ToString}; pub type Unit = (); @@ -74,6 +74,10 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { lib.set_raw_fn("map", &[TypeId::of::(), TypeId::of::()], map); lib.set_raw_fn("filter", &[TypeId::of::(), TypeId::of::()], filter); lib.set_raw_fn("reduce", &[TypeId::of::(), TypeId::of::()], reduce); + lib.set_raw_fn("reduce_rev", &[TypeId::of::(), TypeId::of::()], reduce_rev); + lib.set_raw_fn("some", &[TypeId::of::(), TypeId::of::()], some); + lib.set_raw_fn("all", &[TypeId::of::(), TypeId::of::()], all); + lib.set_raw_fn("sort", &[TypeId::of::(), TypeId::of::()], sort); // Merge in the module at the end to override `+=` for arrays combine_with_exported_module!(lib, "array", array_functions); @@ -130,6 +134,25 @@ mod array_functions { pub fn reverse(list: &mut Array) { list.reverse(); } + pub fn splice(list: &mut Array, start: INT, len: INT, replace: 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.splice(start..start + len, replace.into_iter()); + } } fn pad( @@ -234,6 +257,74 @@ fn filter( Ok(array) } +fn some( + engine: &Engine, + lib: &Module, + args: &mut [&mut Dynamic], +) -> Result> { + let list = args[0].read_lock::().unwrap(); + let filter = args[1].read_lock::().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(true.into()); + } + } + + Ok(false.into()) +} + +fn all( + engine: &Engine, + lib: &Module, + args: &mut [&mut Dynamic], +) -> Result> { + let list = args[0].read_lock::().unwrap(); + let filter = args[1].read_lock::().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, @@ -268,6 +359,79 @@ fn reduce( Ok(result) } +fn reduce_rev( + engine: &Engine, + lib: &Module, + args: &mut [&mut Dynamic], +) -> Result> { + let list = args[0].read_lock::().unwrap(); + let reducer = args[1].read_lock::().unwrap(); + + let mut result: Dynamic = ().into(); + + 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, + args: &mut [&mut Dynamic], +) -> Result> { + let comparer = args[1].read_lock::().unwrap().clone(); + let mut list = args[0].write_lock::().unwrap(); + + list.sort_by(|x, y| { + comparer + .call_dynamic(engine, lib, None, [x.clone(), y.clone()]) + .ok() + .and_then(|v| v.as_int().ok()) + .map(|v| { + if v > 0 { + Ordering::Greater + } else if v < 0 { + Ordering::Less + } else { + Ordering::Equal + } + }) + .unwrap_or_else(|| { + let x_type_id = x.type_id(); + let y_type_id = y.type_id(); + + if x_type_id > y_type_id { + Ordering::Greater + } else if x_type_id < y_type_id { + Ordering::Less + } else { + Ordering::Equal + } + }) + }); + + Ok(().into()) +} + gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit); #[cfg(not(feature = "only_i32"))] diff --git a/tests/arrays.rs b/tests/arrays.rs index a0619aad..cbd4daf5 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -168,7 +168,7 @@ fn test_arrays_map_reduce() -> Result<(), Box> { engine.eval::( r#" let x = [1, 2, 3]; - x.reduce(|sum, v| if sum.type_of() == "()" { v } else { sum + v * v }) + x.reduce(|sum, v| if sum.type_of() == "()" { v * v } else { sum + v * v }) "# )?, 14 @@ -178,10 +178,59 @@ fn test_arrays_map_reduce() -> Result<(), Box> { engine.eval::( r#" let x = [1, 2, 3]; - x.reduce(|sum, v, i| { if i==0 { sum = 10 } sum + v * v }) + x.reduce(|sum, v, i| { if i == 0 { sum = 10 } sum + v * v }) "# )?, 24 ); + + assert_eq!( + engine.eval::( + r#" + let x = [1, 2, 3]; + x.reduce_rev(|sum, v| if sum.type_of() == "()" { v * v } else { sum + v * v }) + "# + )?, + 14 + ); + + assert_eq!( + engine.eval::( + r#" + let x = [1, 2, 3]; + x.reduce_rev(|sum, v, i| { if i == 2 { sum = 10 } sum + v * v }) + "# + )?, + 24 + ); + + assert!(engine.eval::( + r#" + let x = [1, 2, 3]; + x.some(|v| v > 1) + "# + )?); + + assert!(engine.eval::( + r#" + let x = [1, 2, 3]; + x.some(|v, i| v * i == 0) + "# + )?); + + assert!(!engine.eval::( + r#" + let x = [1, 2, 3]; + x.all(|v| v > 1) + "# + )?); + + assert!(engine.eval::( + r#" + let x = [1, 2, 3]; + x.all(|v, i| v > i) + "# + )?); + Ok(()) }