Add timestamp support.

This commit is contained in:
Stephen Chung 2020-04-11 16:06:57 +08:00
parent d73cfb6da5
commit 5848339d5a
7 changed files with 223 additions and 103 deletions

View File

@ -362,6 +362,7 @@ The following primitive types are supported natively:
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | | **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` |
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **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_ | | **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 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. | | **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 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 Comparison operators
-------------------- --------------------

View File

@ -1,5 +1,7 @@
// This script uses the Sieve of Eratosthenes to calculate prime numbers. // This script uses the Sieve of Eratosthenes to calculate prime numbers.
let now = timestamp();
const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000 const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000
let prime_mask = []; let prime_mask = [];
@ -24,3 +26,4 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
} }
print("Total " + total_primes_found + " primes."); print("Total " + total_primes_found + " primes.");
print("Run time = " + now.elapsed() + " seconds.");

View File

@ -1,6 +1,7 @@
// This script runs 1 million iterations // This script runs 1 million iterations
// to test the speed of the scripting engine. // to test the speed of the scripting engine.
let now = timestamp();
let x = 1_000_000; let x = 1_000_000;
print("Ready... Go!"); print("Ready... Go!");
@ -9,4 +10,4 @@ while x > 0 {
x = x - 1; x = x - 1;
} }
print("Finished."); print("Finished. Run time = " + now.elapsed() + " seconds.");

View File

@ -27,6 +27,7 @@ use crate::stdlib::{
format, format,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
string::{String, ToString}, string::{String, ToString},
time::Instant,
vec::Vec, vec::Vec,
{i32, i64, u32}, {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<'_> { impl Engine<'_> {
/// Register the core built-in library. /// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) { 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 // Logic operators
fn and(x: bool, y: bool) -> bool { fn and(x: bool, y: bool) -> bool {
x && y 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, "<", lt, INT, String, char);
reg_cmp!(self, "<=", lte, INT, String, char); reg_cmp!(self, "<=", lte, INT, String, char);
reg_cmp!(self, ">", gt, INT, String, char); reg_cmp!(self, ">", gt, INT, String, char);
@ -433,7 +434,7 @@ impl Engine<'_> {
} }
// `&&` and `||` are treated specially as they short-circuit. // `&&` 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, "||", or, bool);
//reg_op!(self, "&&", and, bool); //reg_op!(self, "&&", and, bool);
@ -1031,5 +1032,39 @@ impl Engine<'_> {
*s = trimmed.to_string(); *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;
});
} }
} }

View File

@ -19,6 +19,7 @@ use crate::stdlib::{
rc::Rc, rc::Rc,
string::{String, ToString}, string::{String, ToString},
sync::Arc, sync::Arc,
time::Instant,
vec, vec,
vec::Vec, vec::Vec,
}; };
@ -362,6 +363,7 @@ impl Engine<'_> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
(type_name::<Map>(), "map"), (type_name::<Map>(), "map"),
(type_name::<String>(), "string"), (type_name::<String>(), "string"),
(type_name::<Instant>(), "timestamp"),
(type_name::<Dynamic>(), "dynamic"), (type_name::<Dynamic>(), "dynamic"),
(type_name::<Variant>(), "variant"), (type_name::<Variant>(), "variant"),
] ]
@ -719,23 +721,24 @@ impl Engine<'_> {
let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level); 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. // 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.map(|s| s.typ) {
match src.typ { None => (),
ScopeEntryType::Constant => {
return Err(EvalAltResult::ErrorAssignmentToConstant( Some(ScopeEntryType::Constant) => {
src.name.to_string(), return Err(EvalAltResult::ErrorAssignmentToConstant(
idx_lhs.position(), src.unwrap().name.to_string(),
)); idx_lhs.position(),
} ));
ScopeEntryType::Normal => { }
Self::update_indexed_var_in_scope(
idx_src_type, Some(ScopeEntryType::Normal) => {
scope, Self::update_indexed_var_in_scope(
src, idx_src_type,
index, scope,
(val, dot_rhs.position()), src.unwrap(),
)?; index,
} (val, dot_rhs.position()),
)?;
} }
} }
@ -1106,16 +1109,16 @@ impl Engine<'_> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
Expr::Variable(id, pos) => { 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( ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
id.to_string(), id.to_string(),
op_pos, op_pos,
)), )),
_ => { _ => {
// Avoid referencing scope which is used below as mut // 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 this_ptr = target.as_mut();
let value = self let value = self
.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); .set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level);
@ -1139,23 +1142,24 @@ impl Engine<'_> {
self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); 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. // 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.map(|x| x.typ) {
match src.typ { None => (),
ScopeEntryType::Constant => {
return Err(EvalAltResult::ErrorAssignmentToConstant( Some(ScopeEntryType::Constant) => {
src.name.to_string(), return Err(EvalAltResult::ErrorAssignmentToConstant(
lhs.position(), src.unwrap().name.to_string(),
)); lhs.position(),
} ));
ScopeEntryType::Normal => { }
Self::update_indexed_var_in_scope(
idx_src_type, Some(ScopeEntryType::Normal) => {
scope, Self::update_indexed_var_in_scope(
src, idx_src_type,
index, scope,
(target, val_pos), src.unwrap(),
)?; index,
} (target, val_pos),
)?;
} }
} }
@ -1260,29 +1264,36 @@ impl Engine<'_> {
match lhs.as_ref() { match lhs.as_ref() {
// name = rhs // name = rhs
Expr::Variable(name, pos) => match scope Expr::Variable(name, pos) => match scope.get(name) {
.get(name) None => {
.ok_or_else(|| { return Err(EvalAltResult::ErrorVariableNotFound(
EvalAltResult::ErrorVariableNotFound(name.clone().into_owned(), *pos) name.clone().into_owned(),
})? *pos,
.0 ))
{ }
entry
@ Some((
ScopeSource { entry
typ: ScopeEntryType::Normal, @
.. ScopeSource {
} => { typ: ScopeEntryType::Normal,
..
},
_,
)) => {
// Avoid referencing scope which is used below as mut // Avoid referencing scope which is used below as mut
let entry = ScopeSource { name, ..entry }; let entry = ScopeSource { name, ..entry };
*scope.get_mut(entry) = rhs_val.clone(); *scope.get_mut(entry) = rhs_val.clone();
Ok(rhs_val) Ok(rhs_val)
} }
ScopeSource { Some((
typ: ScopeEntryType::Constant, ScopeSource {
.. typ: ScopeEntryType::Constant,
} => Err(EvalAltResult::ErrorAssignmentToConstant( ..
},
_,
)) => Err(EvalAltResult::ErrorAssignmentToConstant(
name.to_string(), name.to_string(),
*op_pos, *op_pos,
)), )),
@ -1294,26 +1305,25 @@ impl Engine<'_> {
let (idx_src_type, src, index, _) = let (idx_src_type, src, index, _) =
self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?;
if let Some(src) = src { match src.map(|x| x.typ) {
match src.typ { None => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
ScopeEntryType::Constant => {
Err(EvalAltResult::ErrorAssignmentToConstant(
src.name.to_string(),
idx_lhs.position(),
))
}
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
idx_src_type,
scope,
src,
index,
(rhs_val, rhs.position()),
)?),
}
} else {
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
idx_lhs.position(), idx_lhs.position(),
)) )),
Some(ScopeEntryType::Constant) => {
Err(EvalAltResult::ErrorAssignmentToConstant(
src.unwrap().name.to_string(),
idx_lhs.position(),
))
}
Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope(
idx_src_type,
scope,
src.unwrap(),
index,
(rhs_val, rhs.position()),
)?),
} }
} }

View File

@ -24,6 +24,7 @@ fn test_string() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123"); assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42"); assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

39
tests/time.rs Normal file
View 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(())
}