diff --git a/README.md b/README.md index 55acc262..86a6482f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Optional features | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, [`Scope`] and `AST` are all `Send + Sync`. | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. @@ -1102,6 +1102,10 @@ last == 5; print(y.len()); // prints 3 +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 print(y.len()); // prints 10 @@ -1148,6 +1152,8 @@ The following functions (defined in the standard library but excluded if [`no_st | `clear` | empties the object map | | `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | | `+` operator | merges the first object map with the second | +| `keys` | returns an array of all the property names (in random order) | +| `values` | returns an array of all the property values (in random order) | Examples: @@ -1194,6 +1200,14 @@ y["xyz"] == (); print(y.len()); // prints 3 +for name in keys(y) { // get an array of all the property names via the 'keys' function + print(name); +} + +for val in values(y) { // get an array of all the property values via the 'values' function + print(val); +} + y.clear(); // empty the object map print(y.len()); // prints 0 @@ -1357,6 +1371,24 @@ for x in range(0, 50) { print(x); if x == 42 { break; } // break out of for loop } + + +// The 'range' function also takes a step +for x in range(0, 50, 3) { // step by 3 + if x > 10 { continue; } // skip to the next iteration + print(x); + if x == 42 { break; } // break out of for loop +} + +// Iterate through the values of an object map +let map = #{a:1, b:3, c:5, d:7, e:9}; + +// Remember that keys are returned in random order +for x in keys(map) { + if x > 10 { continue; } // skip to the next iteration + print(x); + if x == 42 { break; } // break out of for loop +} ``` `return`-ing values diff --git a/src/builtin.rs b/src/builtin.rs index db6b5345..b6baed63 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -629,11 +629,22 @@ impl Engine<'_> { self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String { format!("#{:?}", x) }); + + // Register map access functions + self.register_fn("keys", |map: Map| { + map.into_iter() + .map(|(k, _)| k.into_dynamic()) + .collect::>() + }); + + self.register_fn("values", |map: Map| { + map.into_iter().map(|(_, v)| v).collect::>() + }); } } // Register range function - fn reg_iterator(engine: &mut Engine) + fn reg_range(engine: &mut Engine) where Range: Iterator, { @@ -642,12 +653,12 @@ impl Engine<'_> { a.downcast_ref::>() .unwrap() .clone() - .map(|n| n.into_dynamic()), + .map(|x| x.into_dynamic()), ) as Box> }); } - reg_iterator::(self); + reg_range::(self); self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); #[cfg(not(feature = "only_i32"))] @@ -656,7 +667,7 @@ impl Engine<'_> { macro_rules! reg_range { ($self:expr, $x:expr, $( $y:ty ),*) => ( $( - reg_iterator::<$y>(self); + reg_range::<$y>(self); $self.register_fn($x, (|x: $y, y: $y| x..y) as fn(x: $y, y: $y)->Range<$y>); )* ) @@ -664,6 +675,67 @@ impl Engine<'_> { reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); } + + // Register range function with step + #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] + struct StepRange(T, T, T) + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd; + + impl Iterator for StepRange + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd, + { + type Item = T; + + fn next(&mut self) -> Option { + if self.0 < self.1 { + let v = self.0.clone(); + self.0 = &v + &self.2; + Some(v) + } else { + None + } + } + } + + fn reg_step(engine: &mut Engine) + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd, + StepRange: Iterator, + { + engine.register_iterator::, _>(|a: &Dynamic| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|x| x.into_dynamic()), + ) as Box> + }); + } + + reg_step::(self); + self.register_fn("range", |i1: INT, i2: INT, step: INT| { + StepRange(i1, i2, step) + }); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + macro_rules! reg_step { + ($self:expr, $x:expr, $( $y:ty ),*) => ( + $( + reg_step::<$y>(self); + $self.register_fn($x, (|x: $y, y: $y, step: $y| StepRange(x,y,step)) as fn(x: $y, y: $y, step: $y)->StepRange<$y>); + )* + ) + } + + reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + } } } diff --git a/src/engine.rs b/src/engine.rs index c60bf0d4..c6f81199 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -236,7 +236,7 @@ impl Default for Engine<'_> { (type_name::(), "dynamic"), ] .iter() - .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); // Create the new scripting Engine diff --git a/tests/for.rs b/tests/for.rs index be476328..448c7fcb 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,8 +1,8 @@ -#![cfg(not(feature = "no_index"))] use rhai::{Engine, EvalAltResult, INT}; +#[cfg(not(feature = "no_index"))] #[test] -fn test_for() -> Result<(), EvalAltResult> { +fn test_for_array() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let script = r" @@ -18,10 +18,39 @@ fn test_for() -> Result<(), EvalAltResult> { sum2 += x; } + for x in range(1, 6, 3) { + sum2 += x; + } + sum1 + sum2 "; - assert_eq!(engine.eval::(script)?, 30); + assert_eq!(engine.eval::(script)?, 35); + + Ok(()) +} + +#[cfg(not(feature = "no_object"))] +#[test] +fn test_for_object() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + let script = r#" + let sum = 0; + let keys = ""; + let map = #{a: 1, b: 2, c: 3}; + + for key in keys(map) { + keys += key; + } + for value in values(map) { + sum += value; + } + + keys.len() + sum + "#; + + assert_eq!(engine.eval::(script)?, 9); Ok(()) } diff --git a/tests/maps.rs b/tests/maps.rs index 7a17f1a9..5ed42519 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -100,3 +100,26 @@ fn test_map_return() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_map_for() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let map = #{a: 1, b: true, c: 123.456}; + let s = ""; + + for key in keys(map) { + s += key; + } + + s.len() + "# + )?, + 3 + ); + + Ok(()) +}