From 5848339d5a73f0162e363a08ce39012685905d2c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 11 Apr 2020 16:06:57 +0800 Subject: [PATCH] Add timestamp support. --- README.md | 31 ++++++++ scripts/primes.rhai | 3 + scripts/speed_test.rhai | 3 +- src/builtin.rs | 93 ++++++++++++++++-------- src/engine.rs | 156 +++++++++++++++++++++------------------- tests/string.rs | 1 + tests/time.rs | 39 ++++++++++ 7 files changed, 223 insertions(+), 103 deletions(-) create mode 100644 tests/time.rs diff --git a/README.md b/README.md index b39b3777..c073d244 100644 --- a/README.md +++ b/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::(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 -------------------- diff --git a/scripts/primes.rhai b/scripts/primes.rhai index d09418e8..6d5a49d5 100644 --- a/scripts/primes.rhai +++ b/scripts/primes.rhai @@ -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."); diff --git a/scripts/speed_test.rhai b/scripts/speed_test.rhai index 1aa67276..5709d6ac 100644 --- a/scripts/speed_test.rhai +++ b/scripts/speed_test.rhai @@ -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."); diff --git a/src/builtin.rs b/src/builtin.rs index e95dac04..f77b4f4a 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -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(x: T, y: T) -> bool { + x < y +} +fn lte(x: T, y: T) -> bool { + x <= y +} +fn gt(x: T, y: T) -> bool { + x > y +} +fn gte(x: T, y: T) -> bool { + x >= y +} +fn eq(x: T, y: T) -> bool { + x == y +} +fn ne(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(x: T, y: T) -> bool { - x < y - } - fn lte(x: T, y: T) -> bool { - x <= y - } - fn gt(x: T, y: T) -> bool { - x > y - } - fn gte(x: T, y: T) -> bool { - x >= y - } - fn eq(x: T, y: T) -> bool { - x == y - } - fn ne(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; + }); } } diff --git a/src/engine.rs b/src/engine.rs index 5abe17eb..3d7f3379 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -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"), (type_name::(), "string"), + (type_name::(), "timestamp"), (type_name::(), "dynamic"), (type_name::(), "variant"), ] @@ -719,23 +721,24 @@ 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 => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.name.to_string(), - idx_lhs.position(), - )); - } - ScopeEntryType::Normal => { - Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src, - index, - (val, dot_rhs.position()), - )?; - } + match src.map(|s| s.typ) { + None => (), + + Some(ScopeEntryType::Constant) => { + return Err(EvalAltResult::ErrorAssignmentToConstant( + src.unwrap().name.to_string(), + idx_lhs.position(), + )); + } + + Some(ScopeEntryType::Normal) => { + Self::update_indexed_var_in_scope( + idx_src_type, + scope, + src.unwrap(), + index, + (val, dot_rhs.position()), + )?; } } @@ -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,23 +1142,24 @@ 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 => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.name.to_string(), - lhs.position(), - )); - } - ScopeEntryType::Normal => { - Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src, - index, - (target, val_pos), - )?; - } + match src.map(|x| x.typ) { + None => (), + + Some(ScopeEntryType::Constant) => { + return Err(EvalAltResult::ErrorAssignmentToConstant( + src.unwrap().name.to_string(), + lhs.position(), + )); + } + + Some(ScopeEntryType::Normal) => { + Self::update_indexed_var_in_scope( + idx_src_type, + scope, + src.unwrap(), + index, + (target, val_pos), + )?; } } @@ -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 - { - entry - @ - ScopeSource { - typ: ScopeEntryType::Normal, - .. - } => { + 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) } - ScopeSource { - typ: ScopeEntryType::Constant, - .. - } => Err(EvalAltResult::ErrorAssignmentToConstant( + Some(( + ScopeSource { + typ: ScopeEntryType::Constant, + .. + }, + _, + )) => Err(EvalAltResult::ErrorAssignmentToConstant( name.to_string(), *op_pos, )), @@ -1294,26 +1305,25 @@ 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 => { - 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( + match src.map(|x| x.typ) { + None => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( 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()), + )?), } } diff --git a/tests/string.rs b/tests/string.rs index 5e60d87e..7454dcf2 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -24,6 +24,7 @@ fn test_string() -> Result<(), EvalAltResult> { assert_eq!(engine.eval::(r#""foo" + 123"#)?, "foo123"); #[cfg(not(feature = "no_stdlib"))] + #[cfg(not(feature = "no_object"))] assert_eq!(engine.eval::("(42).to_string()")?, "42"); #[cfg(not(feature = "no_float"))] diff --git a/tests/time.rs b/tests/time.rs new file mode 100644 index 00000000..10b232fe --- /dev/null +++ b/tests/time.rs @@ -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::("type_of(timestamp())")?, "timestamp"); + + #[cfg(not(feature = "no_float"))] + assert!( + engine.eval::( + r#" + let time = timestamp(); + let x = 10_000; + while x > 0 { x -= 1; } + elapsed(time) + "# + )? < 10.0 + ); + + #[cfg(feature = "no_float")] + assert!( + engine.eval::( + r#" + let time = timestamp(); + let x = 10_000; + while x > 0 { x -= 1; } + elapsed(time) + "# + )? < 10 + ); + + Ok(()) +}