Add stepped range function and keys/values for maps.

This commit is contained in:
Stephen Chung 2020-04-04 12:20:24 +08:00
parent 92b549b828
commit 12a379dd57
5 changed files with 165 additions and 9 deletions

View File

@ -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_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`. | | `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. | | `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. 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. 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 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 y.pad(10, "hello"); // pad the array up to 10 elements
print(y.len()); // prints 10 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 | | `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) | | `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 | | `+` 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: Examples:
@ -1194,6 +1200,14 @@ y["xyz"] == ();
print(y.len()); // prints 3 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 y.clear(); // empty the object map
print(y.len()); // prints 0 print(y.len()); // prints 0
@ -1357,6 +1371,24 @@ for x in range(0, 50) {
print(x); print(x);
if x == 42 { break; } // break out of for loop 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 `return`-ing values

View File

@ -629,11 +629,22 @@ impl Engine<'_> {
self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String { self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String {
format!("#{:?}", x) format!("#{:?}", x)
}); });
// Register map access functions
self.register_fn("keys", |map: Map| {
map.into_iter()
.map(|(k, _)| k.into_dynamic())
.collect::<Vec<_>>()
});
self.register_fn("values", |map: Map| {
map.into_iter().map(|(_, v)| v).collect::<Vec<_>>()
});
} }
} }
// Register range function // Register range function
fn reg_iterator<T: Any + Clone>(engine: &mut Engine) fn reg_range<T: Any + Clone>(engine: &mut Engine)
where where
Range<T>: Iterator<Item = T>, Range<T>: Iterator<Item = T>,
{ {
@ -642,12 +653,12 @@ impl Engine<'_> {
a.downcast_ref::<Range<T>>() a.downcast_ref::<Range<T>>()
.unwrap() .unwrap()
.clone() .clone()
.map(|n| n.into_dynamic()), .map(|x| x.into_dynamic()),
) as Box<dyn Iterator<Item = Dynamic>> ) as Box<dyn Iterator<Item = Dynamic>>
}); });
} }
reg_iterator::<INT>(self); reg_range::<INT>(self);
self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); self.register_fn("range", |i1: INT, i2: INT| (i1..i2));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
@ -656,7 +667,7 @@ impl Engine<'_> {
macro_rules! reg_range { macro_rules! reg_range {
($self:expr, $x:expr, $( $y:ty ),*) => ( ($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>); $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); 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, T)
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd;
impl<T> Iterator for StepRange<T>
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd,
{
type Item = T;
fn next(&mut self) -> Option<T> {
if self.0 < self.1 {
let v = self.0.clone();
self.0 = &v + &self.2;
Some(v)
} else {
None
}
}
}
fn reg_step<T>(engine: &mut Engine)
where
for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>,
{
engine.register_iterator::<StepRange<T>, _>(|a: &Dynamic| {
Box::new(
a.downcast_ref::<StepRange<T>>()
.unwrap()
.clone()
.map(|x| x.into_dynamic()),
) as Box<dyn Iterator<Item = Dynamic>>
});
}
reg_step::<INT>(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);
}
} }
} }

View File

@ -236,7 +236,7 @@ impl Default for Engine<'_> {
(type_name::<Dynamic>(), "dynamic"), (type_name::<Dynamic>(), "dynamic"),
] ]
.iter() .iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string())) .map(|(k, v)| (k.to_string(), v.to_string()))
.collect(); .collect();
// Create the new scripting Engine // Create the new scripting Engine

View File

@ -1,8 +1,8 @@
#![cfg(not(feature = "no_index"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
#[cfg(not(feature = "no_index"))]
#[test] #[test]
fn test_for() -> Result<(), EvalAltResult> { fn test_for_array() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let script = r" let script = r"
@ -18,10 +18,39 @@ fn test_for() -> Result<(), EvalAltResult> {
sum2 += x; sum2 += x;
} }
for x in range(1, 6, 3) {
sum2 += x;
}
sum1 + sum2 sum1 + sum2
"; ";
assert_eq!(engine.eval::<INT>(script)?, 30); assert_eq!(engine.eval::<INT>(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::<INT>(script)?, 9);
Ok(()) Ok(())
} }

View File

@ -100,3 +100,26 @@ fn test_map_return() -> Result<(), EvalAltResult> {
Ok(()) Ok(())
} }
#[test]
fn test_map_for() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
let map = #{a: 1, b: true, c: 123.456};
let s = "";
for key in keys(map) {
s += key;
}
s.len()
"#
)?,
3
);
Ok(())
}