Add timestamp support.
This commit is contained in:
parent
d73cfb6da5
commit
5848339d5a
31
README.md
31
README.md
@ -362,6 +362,7 @@ The following primitive types are supported natively:
|
||||
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. |
|
||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
|
||||
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
|
||||
| **Timestamp** (implemented in standard library) | `std::time::Instant` | `"timestamp"` | _not supported_ |
|
||||
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
|
||||
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
||||
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
|
||||
@ -1448,6 +1449,36 @@ let result = engine.eval_with_scope::<INT>(r#"map["^^^!!!"].len()"#)?;
|
||||
result == 3; // the object map is successfully used in the script
|
||||
```
|
||||
|
||||
`timestamp`'s
|
||||
-------------
|
||||
[`timestamp`]: #timestamp-s
|
||||
|
||||
Timestamps are provided by the standard library (excluded if using a [raw `Engine`]) via the `timestamp`
|
||||
function.
|
||||
|
||||
The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp returns `"timestamp"`.
|
||||
|
||||
### Built-in functions
|
||||
|
||||
The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------ | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `elapsed` | _none_ | returns the number of seconds since the timestamp |
|
||||
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
||||
|
||||
### Examples
|
||||
|
||||
```rust
|
||||
let now = timestamp();
|
||||
|
||||
// Do some lengthy operation...
|
||||
|
||||
if now.elapsed() > 30.0 {
|
||||
print("takes too long (over 30 seconds)!")
|
||||
}
|
||||
```
|
||||
|
||||
Comparison operators
|
||||
--------------------
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
// This script uses the Sieve of Eratosthenes to calculate prime numbers.
|
||||
|
||||
let now = timestamp();
|
||||
|
||||
const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000
|
||||
|
||||
let prime_mask = [];
|
||||
@ -24,3 +26,4 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
|
||||
}
|
||||
|
||||
print("Total " + total_primes_found + " primes.");
|
||||
print("Run time = " + now.elapsed() + " seconds.");
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This script runs 1 million iterations
|
||||
// to test the speed of the scripting engine.
|
||||
|
||||
let now = timestamp();
|
||||
let x = 1_000_000;
|
||||
|
||||
print("Ready... Go!");
|
||||
@ -9,4 +10,4 @@ while x > 0 {
|
||||
x = x - 1;
|
||||
}
|
||||
|
||||
print("Finished.");
|
||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
||||
|
@ -27,6 +27,7 @@ use crate::stdlib::{
|
||||
format,
|
||||
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
|
||||
string::{String, ToString},
|
||||
time::Instant,
|
||||
vec::Vec,
|
||||
{i32, i64, u32},
|
||||
};
|
||||
@ -57,6 +58,34 @@ macro_rules! reg_op_result1 {
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! reg_cmp {
|
||||
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
||||
$(
|
||||
$self.register_fn($x, $op as fn(x: $y, y: $y)->bool);
|
||||
)*
|
||||
)
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x < y
|
||||
}
|
||||
fn lte<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x <= y
|
||||
}
|
||||
fn gt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x > y
|
||||
}
|
||||
fn gte<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x >= y
|
||||
}
|
||||
fn eq<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x == y
|
||||
}
|
||||
fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x != y
|
||||
}
|
||||
|
||||
impl Engine<'_> {
|
||||
/// Register the core built-in library.
|
||||
pub(crate) fn register_core_lib(&mut self) {
|
||||
@ -176,26 +205,6 @@ impl Engine<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x < y
|
||||
}
|
||||
fn lte<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x <= y
|
||||
}
|
||||
fn gt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x > y
|
||||
}
|
||||
fn gte<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x >= y
|
||||
}
|
||||
fn eq<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x == y
|
||||
}
|
||||
fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x != y
|
||||
}
|
||||
|
||||
// Logic operators
|
||||
fn and(x: bool, y: bool) -> bool {
|
||||
x && y
|
||||
@ -395,14 +404,6 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
{
|
||||
macro_rules! reg_cmp {
|
||||
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
||||
$(
|
||||
$self.register_fn($x, $op as fn(x: $y, y: $y)->bool);
|
||||
)*
|
||||
)
|
||||
}
|
||||
|
||||
reg_cmp!(self, "<", lt, INT, String, char);
|
||||
reg_cmp!(self, "<=", lte, INT, String, char);
|
||||
reg_cmp!(self, ">", gt, INT, String, char);
|
||||
@ -433,7 +434,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// `&&` and `||` are treated specially as they short-circuit.
|
||||
// They are implemented as special `Expr` instances, not function calls.
|
||||
// They are implemented as special `Expr` Instants, not function calls.
|
||||
//reg_op!(self, "||", or, bool);
|
||||
//reg_op!(self, "&&", and, bool);
|
||||
|
||||
@ -1031,5 +1032,39 @@ impl Engine<'_> {
|
||||
*s = trimmed.to_string();
|
||||
}
|
||||
});
|
||||
|
||||
// Register date/time functions
|
||||
self.register_fn("timestamp", || Instant::now());
|
||||
|
||||
self.register_fn("-", |ts1: Instant, ts2: Instant| {
|
||||
if ts2 > ts1 {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return -(ts2 - ts1).as_secs_f64();
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return -((ts2 - ts1).as_secs() as INT);
|
||||
} else {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return (ts1 - ts2).as_secs_f64();
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return (ts1 - ts2).as_secs() as INT;
|
||||
}
|
||||
});
|
||||
|
||||
reg_cmp!(self, "<", lt, Instant);
|
||||
reg_cmp!(self, "<=", lte, Instant);
|
||||
reg_cmp!(self, ">", gt, Instant);
|
||||
reg_cmp!(self, ">=", gte, Instant);
|
||||
reg_cmp!(self, "==", eq, Instant);
|
||||
reg_cmp!(self, "!=", ne, Instant);
|
||||
|
||||
self.register_fn("elapsed", |timestamp: Instant| {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return timestamp.elapsed().as_secs_f64();
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return timestamp.elapsed().as_secs() as INT;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ use crate::stdlib::{
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
@ -362,6 +363,7 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(type_name::<Map>(), "map"),
|
||||
(type_name::<String>(), "string"),
|
||||
(type_name::<Instant>(), "timestamp"),
|
||||
(type_name::<Dynamic>(), "dynamic"),
|
||||
(type_name::<Variant>(), "variant"),
|
||||
]
|
||||
@ -719,25 +721,26 @@ impl Engine<'_> {
|
||||
let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level);
|
||||
|
||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||
if let Some(src) = src {
|
||||
match src.typ {
|
||||
ScopeEntryType::Constant => {
|
||||
match src.map(|s| s.typ) {
|
||||
None => (),
|
||||
|
||||
Some(ScopeEntryType::Constant) => {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
src.name.to_string(),
|
||||
src.unwrap().name.to_string(),
|
||||
idx_lhs.position(),
|
||||
));
|
||||
}
|
||||
ScopeEntryType::Normal => {
|
||||
|
||||
Some(ScopeEntryType::Normal) => {
|
||||
Self::update_indexed_var_in_scope(
|
||||
idx_src_type,
|
||||
scope,
|
||||
src,
|
||||
src.unwrap(),
|
||||
index,
|
||||
(val, dot_rhs.position()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
@ -1106,16 +1109,16 @@ impl Engine<'_> {
|
||||
match dot_lhs {
|
||||
// id.???
|
||||
Expr::Variable(id, pos) => {
|
||||
let (entry, mut target) = Self::search_scope(scope, id, *pos)?;
|
||||
let (src, mut target) = Self::search_scope(scope, id, *pos)?;
|
||||
|
||||
match entry.typ {
|
||||
match src.typ {
|
||||
ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
id.to_string(),
|
||||
op_pos,
|
||||
)),
|
||||
_ => {
|
||||
// Avoid referencing scope which is used below as mut
|
||||
let entry = ScopeSource { name: id, ..entry };
|
||||
let entry = ScopeSource { name: id, ..src };
|
||||
let this_ptr = target.as_mut();
|
||||
let value = self
|
||||
.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level);
|
||||
@ -1139,25 +1142,26 @@ impl Engine<'_> {
|
||||
self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level);
|
||||
|
||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||
if let Some(src) = src {
|
||||
match src.typ {
|
||||
ScopeEntryType::Constant => {
|
||||
match src.map(|x| x.typ) {
|
||||
None => (),
|
||||
|
||||
Some(ScopeEntryType::Constant) => {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
src.name.to_string(),
|
||||
src.unwrap().name.to_string(),
|
||||
lhs.position(),
|
||||
));
|
||||
}
|
||||
ScopeEntryType::Normal => {
|
||||
|
||||
Some(ScopeEntryType::Normal) => {
|
||||
Self::update_indexed_var_in_scope(
|
||||
idx_src_type,
|
||||
scope,
|
||||
src,
|
||||
src.unwrap(),
|
||||
index,
|
||||
(target, val_pos),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
@ -1260,29 +1264,36 @@ impl Engine<'_> {
|
||||
|
||||
match lhs.as_ref() {
|
||||
// name = rhs
|
||||
Expr::Variable(name, pos) => match scope
|
||||
.get(name)
|
||||
.ok_or_else(|| {
|
||||
EvalAltResult::ErrorVariableNotFound(name.clone().into_owned(), *pos)
|
||||
})?
|
||||
.0
|
||||
{
|
||||
Expr::Variable(name, pos) => match scope.get(name) {
|
||||
None => {
|
||||
return Err(EvalAltResult::ErrorVariableNotFound(
|
||||
name.clone().into_owned(),
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
|
||||
Some((
|
||||
entry
|
||||
@
|
||||
ScopeSource {
|
||||
typ: ScopeEntryType::Normal,
|
||||
..
|
||||
} => {
|
||||
},
|
||||
_,
|
||||
)) => {
|
||||
// Avoid referencing scope which is used below as mut
|
||||
let entry = ScopeSource { name, ..entry };
|
||||
*scope.get_mut(entry) = rhs_val.clone();
|
||||
Ok(rhs_val)
|
||||
}
|
||||
|
||||
Some((
|
||||
ScopeSource {
|
||||
typ: ScopeEntryType::Constant,
|
||||
..
|
||||
} => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
},
|
||||
_,
|
||||
)) => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
name.to_string(),
|
||||
*op_pos,
|
||||
)),
|
||||
@ -1294,27 +1305,26 @@ impl Engine<'_> {
|
||||
let (idx_src_type, src, index, _) =
|
||||
self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?;
|
||||
|
||||
if let Some(src) = src {
|
||||
match src.typ {
|
||||
ScopeEntryType::Constant => {
|
||||
match src.map(|x| x.typ) {
|
||||
None => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||
idx_lhs.position(),
|
||||
)),
|
||||
|
||||
Some(ScopeEntryType::Constant) => {
|
||||
Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
src.name.to_string(),
|
||||
src.unwrap().name.to_string(),
|
||||
idx_lhs.position(),
|
||||
))
|
||||
}
|
||||
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
|
||||
|
||||
Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope(
|
||||
idx_src_type,
|
||||
scope,
|
||||
src,
|
||||
src.unwrap(),
|
||||
index,
|
||||
(rhs_val, rhs.position()),
|
||||
)?),
|
||||
}
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||
idx_lhs.position(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// dot_lhs.dot_rhs = rhs
|
||||
|
@ -24,6 +24,7 @@ fn test_string() -> Result<(), EvalAltResult> {
|
||||
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
|
39
tests/time.rs
Normal file
39
tests/time.rs
Normal file
@ -0,0 +1,39 @@
|
||||
#![cfg(not(feature = "no_stdlib"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use rhai::FLOAT;
|
||||
|
||||
#[test]
|
||||
fn test_timestamp() -> Result<(), EvalAltResult> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<String>("type_of(timestamp())")?, "timestamp");
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
assert!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"
|
||||
let time = timestamp();
|
||||
let x = 10_000;
|
||||
while x > 0 { x -= 1; }
|
||||
elapsed(time)
|
||||
"#
|
||||
)? < 10.0
|
||||
);
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
assert!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let time = timestamp();
|
||||
let x = 10_000;
|
||||
while x > 0 { x -= 1; }
|
||||
elapsed(time)
|
||||
"#
|
||||
)? < 10
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user