From ef14079c619e243bc35a00cb1b2847e5af236576 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 15 Dec 2021 12:06:17 +0800 Subject: [PATCH] Add ranges. --- CHANGELOG.md | 11 ++ README.md | 2 +- benches/eval_array.rs | 2 +- benches/eval_expression.rs | 10 +- benches/iterations.rs | 4 +- benches/parsing.rs | 2 +- benches/primes.rs | 2 +- scripts/fibonacci.rhai | 2 +- scripts/for2.rhai | 2 +- scripts/mat_mul.rhai | 16 +-- scripts/primes.rhai | 6 +- src/api/events.rs | 8 +- src/ast.rs | 50 ++++++- src/engine.rs | 258 +++++++++++++++++++++++++++--------- src/func/builtin.rs | 10 ++ src/lib.rs | 7 +- src/optimizer.rs | 162 +++++++++++++++------- src/packages/arithmetic.rs | 9 ++ src/packages/array_basic.rs | 53 +++++++- src/packages/blob_basic.rs | 157 +++++++++++++++++++++- src/packages/iter_basic.rs | 88 +++++++++++- src/packages/logic.rs | 37 +++++- src/packages/string_more.rs | 34 ++++- src/parser.rs | 64 +++++---- src/tokenizer.rs | 44 ++++-- src/types/dynamic.rs | 165 +++++++++++++++++------ src/types/parse_error.rs | 7 +- tests/bit_shift.rs | 17 +++ tests/blobs.rs | 75 ++++++++--- tests/for.rs | 59 +++++++-- tests/modules.rs | 6 +- tests/operations.rs | 12 +- tests/serde.rs | 2 +- tests/switch.rs | 90 +++++++++++++ tests/time.rs | 2 +- 35 files changed, 1206 insertions(+), 269 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e07fe18..64a35df2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Rhai Release Notes ================== +Version 1.4.0 +============= + +This version adds support for integer _ranges_ via the `..` and `..=` operators. + +New features +------------ + +* Added support for integer _ranges_ via the `..` and `..=` operators. + + Version 1.3.0 ============= diff --git a/README.md b/README.md index 07a221a4..92248da0 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ print("Ready... Go!"); let result; let now = timestamp(); -for n in range(0, REPEAT) { +for n in 0..REPEAT { result = fib(TARGET); } diff --git a/benches/eval_array.rs b/benches/eval_array.rs index 9dcf2129..b89cc9d6 100644 --- a/benches/eval_array.rs +++ b/benches/eval_array.rs @@ -67,7 +67,7 @@ fn bench_eval_array_loop(bench: &mut Bencher) { let script = r#" let list = []; - for i in range(0, 10_000) { + for i in 0..10_000 { list.push(i); } diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs index 07050ebf..73a2afb9 100644 --- a/benches/eval_expression.rs +++ b/benches/eval_expression.rs @@ -110,7 +110,7 @@ fn bench_eval_call(bench: &mut Bencher) { fn bench_eval_loop_number(bench: &mut Bencher) { let script = r#" let s = 0; - for x in range(0, 10000) { + for x in 0..10000 { s += 1; } "#; @@ -127,7 +127,7 @@ fn bench_eval_loop_number(bench: &mut Bencher) { fn bench_eval_loop_strings_build(bench: &mut Bencher) { let script = r#" let s; - for x in range(0, 10000) { + for x in 0..10000 { s = "hello, world!" + "hello, world!"; } "#; @@ -144,7 +144,7 @@ fn bench_eval_loop_strings_build(bench: &mut Bencher) { fn bench_eval_loop_strings_no_build(bench: &mut Bencher) { let script = r#" let s; - for x in range(0, 10000) { + for x in 0..10000 { s = "hello" + ""; } "#; @@ -163,7 +163,7 @@ fn bench_eval_switch(bench: &mut Bencher) { let sum = 0; let rem = 0; - for x in range(0, 10) { + for x in 0..10 { rem = x % 10; sum += switch rem { @@ -195,7 +195,7 @@ fn bench_eval_nested_if(bench: &mut Bencher) { let sum = 0; let rem = 0; - for x in range(0, 10) { + for x in 0..10 { rem = x % 10; sum += if rem == 0 { 10 } diff --git a/benches/iterations.rs b/benches/iterations.rs index e414a69e..b322a95d 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -51,7 +51,7 @@ fn bench_iterations_array(bench: &mut Bencher) { let script = r#" let x = []; x.pad(1000, 0); - for i in range(0, 1000) { x[i] = i % 256; } + for i in 0..1000 { x[i] = i % 256; } "#; let mut engine = Engine::new(); @@ -66,7 +66,7 @@ fn bench_iterations_array(bench: &mut Bencher) { fn bench_iterations_blob(bench: &mut Bencher) { let script = r#" let x = blob(1000, 0); - for i in range(0, 1000) { x[i] = i % 256; } + for i in 0..1000 { x[i] = i % 256; } "#; let mut engine = Engine::new(); diff --git a/benches/parsing.rs b/benches/parsing.rs index d0f7aced..907a7c71 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -79,7 +79,7 @@ fn bench_parse_primes(bench: &mut Bencher) { let total_primes_found = 0; - for p in range(2, MAX_NUMBER_TO_CHECK) { + for p in 2..=MAX_NUMBER_TO_CHECK { if prime_mask[p] { print(p); diff --git a/benches/primes.rs b/benches/primes.rs index e6a059fc..8110c4f7 100644 --- a/benches/primes.rs +++ b/benches/primes.rs @@ -19,7 +19,7 @@ prime_mask[1] = false; let total_primes_found = 0; -for p in range(2, MAX_NUMBER_TO_CHECK) { +for p in 2..=MAX_NUMBER_TO_CHECK { if prime_mask[p] { total_primes_found += 1; let i = 2 * p; diff --git a/scripts/fibonacci.rhai b/scripts/fibonacci.rhai index 8451c477..97c93d08 100644 --- a/scripts/fibonacci.rhai +++ b/scripts/fibonacci.rhai @@ -19,7 +19,7 @@ print("Ready... Go!"); let result; let now = timestamp(); -for n in range(0, REPEAT) { +for n in 0..REPEAT { result = fib(TARGET); } diff --git a/scripts/for2.rhai b/scripts/for2.rhai index 0f4cdce7..5ded413a 100644 --- a/scripts/for2.rhai +++ b/scripts/for2.rhai @@ -8,7 +8,7 @@ let now = timestamp(); let list = []; -for i in range(0, MAX) { +for i in 0..MAX { list.push(i); } diff --git a/scripts/mat_mul.rhai b/scripts/mat_mul.rhai index cb0ced95..35b1df70 100644 --- a/scripts/mat_mul.rhai +++ b/scripts/mat_mul.rhai @@ -15,8 +15,8 @@ fn mat_gen() { const tmp = 1.0 / n / n; let m = new_mat(n, n); - for i in range(0, n) { - for j in range(0, n) { + for i in 0..n { + for j in 0..n { m[i][j] = tmp * (i - j) * (i + j); } } @@ -27,19 +27,19 @@ fn mat_gen() { fn mat_mul(a, b) { let b2 = new_mat(a[0].len, b[0].len); - for i in range(0, a[0].len) { - for j in range(0, b[0].len) { + for i in 0..a[0].len { + for j in 0..b[0].len { b2[j][i] = b[i][j]; } } let c = new_mat(a.len, b[0].len); - for i in range(0, c.len) { - for j in range(0, c[i].len) { + for i in 0..c.len { + for j in 0..c[i].len { c[i][j] = 0.0; - for z in range(0, a[i].len) { + for z in 0..a[i].len { c[i][j] += a[i][z] * b2[j][z]; } } @@ -55,7 +55,7 @@ const b = mat_gen(); const c = mat_mul(a, b); /* -for i in range(0, SIZE) { +for i in 0..SIZE) { print(c[i]); } */ diff --git a/scripts/primes.rhai b/scripts/primes.rhai index 085228ee..a827de5f 100644 --- a/scripts/primes.rhai +++ b/scripts/primes.rhai @@ -5,21 +5,21 @@ let now = timestamp(); const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000 let prime_mask = []; -prime_mask.pad(MAX_NUMBER_TO_CHECK, true); +prime_mask.pad(MAX_NUMBER_TO_CHECK + 1, true); prime_mask[0] = false; prime_mask[1] = false; let total_primes_found = 0; -for p in range(2, MAX_NUMBER_TO_CHECK) { +for p in 2..=MAX_NUMBER_TO_CHECK { if !prime_mask[p] { continue; } //print(p); total_primes_found += 1; - for i in range(2 * p, MAX_NUMBER_TO_CHECK, p) { + for i in range(2 * p, MAX_NUMBER_TO_CHECK + 1, p) { prime_mask[i] = false; } } diff --git a/src/api/events.rs b/src/api/events.rs index 766d260b..a180e054 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -156,9 +156,9 @@ impl Engine { /// let mut engine = Engine::new(); /// /// engine.on_progress(move |ops| { - /// if ops > 10000 { - /// Some("Over 10,000 operations!".into()) - /// } else if ops % 800 == 0 { + /// if ops > 1000 { + /// Some("Over 1,000 operations!".into()) + /// } else if ops % 100 == 0 { /// *logger.write().unwrap() = ops; /// None /// } else { @@ -166,7 +166,7 @@ impl Engine { /// } /// }); /// - /// engine.run("for x in range(0, 50000) {}") + /// engine.run("for x in 0..5000 { print(x); }") /// .expect_err("should error"); /// /// assert_eq!(*result.read().unwrap(), 9600); diff --git a/src/ast.rs b/src/ast.rs index 15b9ba10..dc87ec79 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,6 +1,7 @@ //! Module defining the AST (abstract syntax tree). use crate::calc_fn_hash; +use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; use crate::func::hashing::ALT_ZERO_HASH; use crate::module::NamespaceRef; use crate::tokenizer::Token; @@ -1262,7 +1263,11 @@ pub enum Stmt { /// `switch` expr `if` condition `{` literal or _ `=>` stmt `,` ... `}` Switch( Expr, - Box<(BTreeMap, StmtBlock)>>, StmtBlock)>, + Box<( + BTreeMap, StmtBlock)>>, + StmtBlock, + StaticVec<(INT, INT, bool, Option, StmtBlock)>, + )>, Position, ), /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` @@ -1501,6 +1506,10 @@ impl Stmt { block.0.as_ref().map(Expr::is_pure).unwrap_or(true) && (block.1).0.iter().all(Stmt::is_pure) }) + && (x.2).iter().all(|(_, _, _, condition, stmt)| { + condition.as_ref().map(Expr::is_pure).unwrap_or(true) + && stmt.0.iter().all(Stmt::is_pure) + }) && (x.1).0.iter().all(Stmt::is_pure) } @@ -1617,6 +1626,16 @@ impl Stmt { } } } + for (_, _, _, c, stmt) in &x.2 { + if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + return false; + } + for s in &stmt.0 { + if !s.walk(path, on_node) { + return false; + } + } + } for s in &(x.1).0 { if !s.walk(path, on_node) { return false; @@ -2235,6 +2254,35 @@ impl Expr { })) } + // Binary operators + Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() { + // x..y + OP_EXCLUSIVE_RANGE => { + if let Expr::IntegerConstant(ref start, _) = x.args[0] { + if let Expr::IntegerConstant(ref end, _) = x.args[1] { + (*start..*end).into() + } else { + return None; + } + } else { + return None; + } + } + // x..=y + OP_INCLUSIVE_RANGE => { + if let Expr::IntegerConstant(ref start, _) = x.args[0] { + if let Expr::IntegerConstant(ref end, _) = x.args[1] { + (*start..=*end).into() + } else { + return None; + } + } else { + return None; + } + } + _ => return None, + }, + _ => return None, }) } diff --git a/src/engine.rs b/src/engine.rs index b77050f5..b5be3a7e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -13,8 +13,8 @@ use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::tokenizer::Token; use crate::types::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::{ - Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope, - Shared, StaticVec, INT, + calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ExclusiveRange, Identifier, + ImmutableString, InclusiveRange, Module, Position, RhaiResult, Scope, Shared, StaticVec, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -367,6 +367,12 @@ pub const OP_EQUALS: &str = "=="; /// The `in` operator is implemented as a call to this method. pub const OP_CONTAINS: &str = "contains"; +/// Standard exclusive range operator. +pub const OP_EXCLUSIVE_RANGE: &str = ".."; + +/// Standard inclusive range operator. +pub const OP_INCLUSIVE_RANGE: &str = "..="; + /// Standard concatenation operator token. pub const TOKEN_OP_CONCAT: Token = Token::PlusAssign; @@ -484,7 +490,11 @@ pub enum Target<'a> { /// The target is a bit inside an [`INT`][crate::INT]. /// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible. #[cfg(not(feature = "no_index"))] - BitField(&'a mut Dynamic, usize, Dynamic), + Bit(&'a mut Dynamic, Dynamic, u8), + /// The target is a range of bits inside an [`INT`][crate::INT]. + /// This is necessary because directly pointing to a range of bits inside an [`INT`][crate::INT] is impossible. + #[cfg(not(feature = "no_index"))] + BitField(&'a mut Dynamic, INT, Dynamic, u8), /// The target is a byte inside a Blob. /// This is necessary because directly pointing to a byte (in [`Dynamic`] form) inside a blob is impossible. #[cfg(not(feature = "no_index"))] @@ -507,7 +517,8 @@ impl<'a> Target<'a> { Self::LockGuard(_) => true, Self::TempValue(_) => false, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, + Self::Bit(_, _, _) | Self::BitField(_, _, _, _) => false, + Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a temp value? @@ -520,7 +531,8 @@ impl<'a> Target<'a> { Self::LockGuard(_) => false, Self::TempValue(_) => true, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, + Self::Bit(_, _, _) | Self::BitField(_, _, _, _) => false, + Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a shared value? @@ -534,7 +546,8 @@ impl<'a> Target<'a> { Self::LockGuard(_) => true, Self::TempValue(r) => r.is_shared(), #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, + Self::Bit(_, _, _) | Self::BitField(_, _, _, _) => false, + Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a specific type? @@ -548,7 +561,9 @@ impl<'a> Target<'a> { Self::LockGuard((r, _)) => r.is::(), Self::TempValue(r) => r.is::(), #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => TypeId::of::() == TypeId::of::(), + Self::Bit(_, _, _) => TypeId::of::() == TypeId::of::(), + #[cfg(not(feature = "no_index"))] + Self::BitField(_, _, _, _) => TypeId::of::() == TypeId::of::(), #[cfg(not(feature = "no_index"))] Self::BlobByte(_, _, _) => TypeId::of::() == TypeId::of::(), #[cfg(not(feature = "no_index"))] @@ -565,7 +580,9 @@ impl<'a> Target<'a> { Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::TempValue(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, value) => value, // Boolean is taken + Self::Bit(_, value, _) => value, // Boolean is taken + #[cfg(not(feature = "no_index"))] + Self::BitField(_, _, value, _) => value, // INT is taken #[cfg(not(feature = "no_index"))] Self::BlobByte(_, _, value) => value, // Byte is taken #[cfg(not(feature = "no_index"))] @@ -596,7 +613,7 @@ impl<'a> Target<'a> { #[cfg(not(feature = "no_closure"))] Self::LockGuard(_) => (), #[cfg(not(feature = "no_index"))] - Self::BitField(value, index, new_val) => { + Self::Bit(value, new_val, index) => { // Replace the bit at the specified index position let new_bit = new_val.as_bool().map_err(|err| { Box::new(EvalAltResult::ErrorMismatchDataType( @@ -610,18 +627,34 @@ impl<'a> Target<'a> { let index = *index; - if index < std::mem::size_of_val(value) * 8 { - let mask = 1 << index; - if new_bit { - *value |= mask; - } else { - *value &= !mask; - } + let mask = 1 << index; + if new_bit { + *value |= mask; } else { - unreachable!("bit-field index out of bounds: {}", index); + *value &= !mask; } } #[cfg(not(feature = "no_index"))] + Self::BitField(value, mask, new_val, shift) => { + let shift = *shift; + let mask = *mask; + + // Replace the bit at the specified index position + let new_value = new_val.as_int().map_err(|err| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "integer".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let new_value = (new_value << shift) & mask; + let value = &mut *value.write_lock::().expect("`INT`"); + + *value &= !mask; + *value |= new_value; + } + #[cfg(not(feature = "no_index"))] Self::BlobByte(value, index, new_val) => { // Replace the byte at the specified index position let new_byte = new_val.as_int().map_err(|err| { @@ -696,9 +729,8 @@ impl Deref for Target<'_> { Self::LockGuard((r, _)) => &**r, Self::TempValue(ref r) => r, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, ref r) - | Self::BlobByte(_, _, ref r) - | Self::StringChar(_, _, ref r) => r, + Self::Bit(_, ref r, _) | Self::BitField(_, _, ref r, _) => r, + Self::BlobByte(_, _, ref r) | Self::StringChar(_, _, ref r) => r, } } } @@ -719,9 +751,8 @@ impl DerefMut for Target<'_> { Self::LockGuard((r, _)) => r.deref_mut(), Self::TempValue(ref mut r) => r, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, ref mut r) - | Self::BlobByte(_, _, ref mut r) - | Self::StringChar(_, _, ref mut r) => r, + Self::Bit(_, ref mut r, _) | Self::BitField(_, _, ref mut r, _) => r, + Self::BlobByte(_, _, ref mut r) | Self::StringChar(_, _, ref mut r) => r, } } } @@ -2078,6 +2109,67 @@ impl Engine { .unwrap_or_else(|| Target::from(Dynamic::UNIT))) } + #[cfg(not(feature = "no_index"))] + Dynamic(Union::Int(value, _, _)) + if idx.is::() || idx.is::() => + { + #[cfg(not(feature = "only_i32"))] + type BASE = u64; + #[cfg(feature = "only_i32")] + type BASE = u32; + + // val_int[range] + const BITS: usize = std::mem::size_of::() * 8; + + let (shift, mask) = if let Some(range) = idx.read_lock::() { + let start = range.start; + let end = range.end; + + if start < 0 || start as usize >= BITS { + return Err(EvalAltResult::ErrorBitFieldBounds(BITS, start, idx_pos).into()); + } else if end < 0 || end as usize >= BITS { + return Err(EvalAltResult::ErrorBitFieldBounds(BITS, end, idx_pos).into()); + } else if end <= start { + (0, 0) + } else if end as usize == BITS && start == 0 { + // -1 = all bits set + (0, -1) + } else { + ( + start as u8, + // 2^bits - 1 + (((2 as BASE).pow((end - start) as u32) - 1) as INT) << start, + ) + } + } else if let Some(range) = idx.read_lock::() { + let start = *range.start(); + let end = *range.end(); + + if start < 0 || start as usize >= BITS { + return Err(EvalAltResult::ErrorBitFieldBounds(BITS, start, idx_pos).into()); + } else if end < 0 || end as usize >= BITS { + return Err(EvalAltResult::ErrorBitFieldBounds(BITS, end, idx_pos).into()); + } else if end < start { + (0, 0) + } else if end as usize == BITS - 1 && start == 0 { + // -1 = all bits set + (0, -1) + } else { + ( + start as u8, + // 2^bits - 1 + (((2 as BASE).pow((end - start + 1) as u32) - 1) as INT) << start, + ) + } + } else { + unreachable!("`Range` or `RangeInclusive`"); + }; + + let field_value = (*value & mask) >> shift; + + Ok(Target::BitField(target, mask, field_value.into(), shift)) + } + #[cfg(not(feature = "no_index"))] Dynamic(Union::Int(value, _, _)) => { // val_int[idx] @@ -2085,38 +2177,38 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; - let bits = std::mem::size_of_val(value) * 8; + const BITS: usize = std::mem::size_of::() * 8; let (bit_value, offset) = if index >= 0 { let offset = index as usize; ( - if offset >= bits { + if offset >= BITS { return Err( - EvalAltResult::ErrorBitFieldBounds(bits, index, idx_pos).into() + EvalAltResult::ErrorBitFieldBounds(BITS, index, idx_pos).into() ); } else { (*value & (1 << offset)) != 0 }, - offset, + offset as u8, ) } else if let Some(abs_index) = index.checked_abs() { let offset = abs_index as usize; ( // Count from end if negative - if offset > bits { + if offset > BITS { return Err( - EvalAltResult::ErrorBitFieldBounds(bits, index, idx_pos).into() + EvalAltResult::ErrorBitFieldBounds(BITS, index, idx_pos).into() ); } else { - (*value & (1 << (bits - offset))) != 0 + (*value & (1 << (BITS - offset))) != 0 }, - offset, + offset as u8, ) } else { - return Err(EvalAltResult::ErrorBitFieldBounds(bits, index, idx_pos).into()); + return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, idx_pos).into()); }; - Ok(Target::BitField(target, offset, bit_value.into())) + Ok(Target::Bit(target, bit_value.into(), offset)) } #[cfg(not(feature = "no_index"))] @@ -2660,48 +2752,80 @@ impl Engine { // Switch statement Stmt::Switch(match_expr, x, _) => { - let (table, def_stmt) = x.as_ref(); + let (table, def_stmt, ranges) = x.as_ref(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)?; - if value.is_hashable() { + let stmt_block = if value.is_hashable() { let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); - table.get(&hash).and_then(|t| { - if let Some(condition) = &t.0 { - match self - .eval_expr(scope, mods, state, lib, this_ptr, &condition, level) + // First check hashes + if let Some(t) = table.get(&hash) { + if let Some(ref c) = t.0 { + if self + .eval_expr(scope, mods, state, lib, this_ptr, &c, level) .and_then(|v| { v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - condition.position(), - ) + self.make_type_mismatch_err::(typ, c.position()) }) - }) { - Ok(true) => (), - Ok(false) => return None, - Err(err) => return Some(Err(err)), + })? + { + Some(&t.1) + } else { + None } + } else { + Some(&t.1) + } + } else if value.is::() && !ranges.is_empty() { + // Then check integer ranges + let value = value.as_int().expect("`INT`"); + let mut result = None; + + for (_, _, _, condition, stmt_block) in + ranges.iter().filter(|&&(start, end, inclusive, _, _)| { + (!inclusive && (start..end).contains(&value)) + || (inclusive && (start..=end).contains(&value)) + }) + { + if let Some(c) = condition { + if !self + .eval_expr(scope, mods, state, lib, this_ptr, &c, level) + .and_then(|v| { + v.as_bool().map_err(|typ| { + self.make_type_mismatch_err::(typ, c.position()) + }) + })? + { + continue; + } + } + + result = Some(stmt_block); + break; } - let statements = &t.1; - - Some(if !statements.is_empty() { - self.eval_stmt_block( - scope, mods, state, lib, this_ptr, statements, true, true, level, - ) - } else { - Ok(Dynamic::UNIT) - }) - }) + result + } else { + // Nothing matches + None + } } else { - // Non-hashable values never match any specific clause + // Non-hashable None - } - .unwrap_or_else(|| { + }; + + if let Some(statements) = stmt_block { + if !statements.is_empty() { + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, statements, true, true, level, + ) + } else { + Ok(Dynamic::UNIT) + } + } else { // Default match clause if !def_stmt.is_empty() { self.eval_stmt_block( @@ -2710,7 +2834,7 @@ impl Engine { } else { Ok(Dynamic::UNIT) } - }) + } } // Loop @@ -3163,6 +3287,18 @@ impl Engine { .map_err(|err| err.fill_position(stmt.position())) } + // Has a system function a Rust-native override? + pub(crate) fn has_native_fn_override(&self, hash_script: u64, arg_types: &[TypeId]) -> bool { + let hash_params = calc_fn_params_hash(arg_types.iter().cloned()); + let hash = combine_hashes(hash_script, hash_params); + + // First check the global namespace and packages, but skip modules that are standard because + // they should never conflict with system functions. + self.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash)) + // Then check sub-modules + || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash)) + } + /// Check a result to ensure that the data size is within allowable limit. fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult { if let Ok(ref mut r) = result { diff --git a/src/func/builtin.rs b/src/func/builtin.rs index ef2ac6d1..e2bb1c74 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -352,6 +352,16 @@ pub fn get_builtin_binary_op_fn( "&" => Some(impl_op!(INT => as_int & as_int)), "|" => Some(impl_op!(INT => as_int | as_int)), "^" => Some(impl_op!(INT => as_int ^ as_int)), + ".." => Some(|_, args| { + let x = args[0].as_int().expect(BUILTIN); + let y = args[1].as_int().expect(BUILTIN); + Ok((x..y).into()) + }), + "..=" => Some(|_, args| { + let x = args[0].as_int().expect(BUILTIN); + let y = args[1].as_int().expect(BUILTIN); + Ok((x..=y).into()) + }), _ => None, }; } diff --git a/src/lib.rs b/src/lib.rs index 077245c1..23b851ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,9 +115,14 @@ pub type FLOAT = f64; #[cfg(feature = "f32_float")] pub type FLOAT = f32; +pub type ExclusiveRange = std::ops::Range; +pub type InclusiveRange = std::ops::RangeInclusive; + pub use ast::{FnAccess, AST}; pub use custom_syntax::Expression; -pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; +pub use engine::{ + Engine, EvalContext, OP_CONTAINS, OP_EQUALS, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE, +}; pub use func::{NativeCallContext, RegisterNativeFunction}; pub use module::{FnNamespace, Module}; pub use tokenizer::Position; diff --git a/src/optimizer.rs b/src/optimizer.rs index 0e8040f7..c1170a35 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -9,14 +9,10 @@ use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; use crate::tokenizer::Token; use crate::types::dynamic::AccessMode; -use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope, - StaticVec, AST, -}; +use crate::{calc_fn_hash, Dynamic, Engine, FnPtr, Position, Scope, StaticVec, AST, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ - any::TypeId, convert::TryFrom, hash::{Hash, Hasher}, mem, @@ -155,17 +151,6 @@ impl<'a> OptimizerState<'a> { .ok() .map(|(v, _)| v) } - // Has a system function a Rust-native override? - pub fn has_native_fn_override(&self, hash_script: u64, arg_types: &[TypeId]) -> bool { - let hash_params = calc_fn_params_hash(arg_types.iter().cloned()); - let hash = combine_hashes(hash_script, hash_params); - - // First check the global namespace and packages, but skip modules that are standard because - // they should never conflict with system functions. - self.engine.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash)) - // Then check sub-modules - || self.engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash)) - } } /// Optimize a block of [statements][Stmt]. @@ -478,21 +463,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b value.hash(hasher); let hash = hasher.finish(); - state.set_dirty(); let table = &mut x.0; + // First check hashes if let Some(block) = table.get_mut(&hash) { if let Some(mut condition) = mem::take(&mut block.0) { // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let def_block = mem::take(&mut *x.1); - let def_stmt = optimize_stmt_block(def_block, state, true, true, false); let def_pos = if x.1.position().is_none() { *pos } else { x.1.position() }; + let def_stmt = + optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); *stmt = Stmt::If( condition, @@ -505,46 +490,121 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } else { // Promote the matched case let new_pos = block.1.position(); - let statements = mem::take(&mut *block.1); - let statements = optimize_stmt_block(statements, state, true, true, false); + let statements = + optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false); *stmt = Stmt::Block(statements.into_boxed_slice(), new_pos); } - } else { - // Promote the default case - let def_block = mem::take(&mut *x.1); - let def_stmt = optimize_stmt_block(def_block, state, true, true, false); - let def_pos = if x.1.position().is_none() { - *pos - } else { - x.1.position() - }; - *stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos); + + state.set_dirty(); + return; } + + // Then check ranges + let ranges = &mut x.2; + + if value.is::() && !ranges.is_empty() { + let value = value.as_int().expect("`INT`"); + + // Only one range or all ranges without conditions + if ranges.len() == 1 || ranges.iter().all(|(_, _, _, c, _)| c.is_none()) { + for (_, _, _, condition, stmt_block) in + ranges + .iter_mut() + .filter(|&&mut (start, end, inclusive, _, _)| { + (!inclusive && (start..end).contains(&value)) + || (inclusive && (start..=end).contains(&value)) + }) + { + if let Some(mut condition) = mem::take(condition) { + // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } + optimize_expr(&mut condition, state, false); + + let def_block = mem::take(&mut *x.1); + let def_stmt = optimize_stmt_block(def_block, state, true, true, false); + let def_pos = if x.1.position().is_none() { + *pos + } else { + x.1.position() + }; + + *stmt = Stmt::If( + condition, + Box::new(( + mem::take(stmt_block), + Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(), + )), + match_expr.position(), + ); + } else { + // Promote the matched case + let new_pos = stmt_block.position(); + let statements = mem::take(&mut **stmt_block); + let statements = + optimize_stmt_block(statements, state, true, true, false); + *stmt = Stmt::Block(statements.into_boxed_slice(), new_pos); + } + + state.set_dirty(); + return; + } + } else { + // Multiple ranges - clear the table and just keep the right ranges + if !table.is_empty() { + state.set_dirty(); + } + + table.clear(); + + let old_ranges_len = ranges.len(); + + ranges.retain(|&mut (start, end, inclusive, _, _)| { + (!inclusive && (start..end).contains(&value)) + || (inclusive && (start..=end).contains(&value)) + }); + + if ranges.len() != old_ranges_len { + state.set_dirty(); + } + + for (_, _, _, condition, stmt_block) in ranges.iter_mut() { + let statements = mem::take(&mut **stmt_block); + **stmt_block = + optimize_stmt_block(statements, state, preserve_result, true, false); + + if let Some(mut c) = mem::take(condition) { + optimize_expr(&mut c, state, false); + match c { + Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(), + _ => *condition = Some(c), + } + } + } + return; + } + } + + // Promote the default case + state.set_dirty(); + let def_pos = if x.1.position().is_none() { + *pos + } else { + x.1.position() + }; + let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); + *stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos); } // switch Stmt::Switch(match_expr, x, _) => { optimize_expr(match_expr, state, false); x.0.values_mut().for_each(|block| { - let condition = mem::take(&mut block.0).map_or_else( - || Expr::Unit(Position::NONE), - |mut condition| { - optimize_expr(&mut condition, state, false); - condition - }, - ); + let statements = mem::take(block.1.deref_mut()); + *block.1 = optimize_stmt_block(statements, state, preserve_result, true, false); - match condition { - Expr::Unit(_) | Expr::BoolConstant(true, _) => (), - _ => { - block.0 = Some(condition); - - *block.1 = optimize_stmt_block( - mem::take(block.1.deref_mut()), - state, - preserve_result, - true, - false, - ); + if let Some(mut condition) = mem::take(&mut block.0) { + optimize_expr(&mut condition, state, false); + match condition { + Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(), + _ => block.0 = Some(condition), } } }); @@ -990,7 +1050,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && !state.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => { + _ if x.args.len() == 2 && !state.engine.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => { if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) .and_then(|f| { #[cfg(not(feature = "no_function"))] diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 146e1bff..6de5634b 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -121,12 +121,15 @@ macro_rules! gen_arithmetic_functions { pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } + #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: $arg_type) -> bool { x == 0 } + #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: $arg_type) -> bool { x % 2 != 0 } + #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: $arg_type) -> bool { x % 2 == 0 } @@ -209,12 +212,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { #[export_module] mod int_functions { + #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: INT) -> bool { x == 0 } + #[rhai_fn(get = "is_odd", name = "is_odd")] pub fn is_odd(x: INT) -> bool { x % 2 != 0 } + #[rhai_fn(get = "is_even", name = "is_even")] pub fn is_even(x: INT) -> bool { x % 2 == 0 } @@ -335,6 +341,7 @@ mod f32_functions { x => Ok(x as INT), } } + #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f32) -> bool { x == 0.0 } @@ -442,6 +449,7 @@ mod f64_functions { x => Ok(x as INT), } } + #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f64) -> bool { x == 0.0 } @@ -536,6 +544,7 @@ pub mod decimal_functions { 1 } } + #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: Decimal) -> bool { x.is_zero() } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index befc3d7d..9ceeda20 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,7 +3,10 @@ use crate::engine::OP_EQUALS; use crate::plugin::*; -use crate::{def_package, Array, Dynamic, EvalAltResult, FnPtr, NativeCallContext, Position, INT}; +use crate::{ + def_package, Array, Dynamic, EvalAltResult, ExclusiveRange, FnPtr, InclusiveRange, + NativeCallContext, Position, INT, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, cmp::Ordering, mem}; @@ -145,6 +148,18 @@ mod array_functions { array.reverse(); } } + #[rhai_fn(name = "splice")] + pub fn splice_range(array: &mut Array, range: ExclusiveRange, replace: Array) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + splice(array, start, end - start, replace) + } + #[rhai_fn(name = "splice")] + pub fn splice_inclusive_range(array: &mut Array, range: InclusiveRange, replace: Array) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + splice(array, start, end - start + 1, replace) + } pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) { if array.is_empty() { *array = replace; @@ -173,6 +188,18 @@ mod array_functions { array.splice(start..start + len, replace.into_iter()); } + #[rhai_fn(name = "extract")] + pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + extract(array, start, end - start) + } + #[rhai_fn(name = "extract")] + pub fn extract_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + extract(array, start, end - start + 1) + } pub fn extract(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); @@ -911,6 +938,18 @@ mod array_functions { drain(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "drain")] + pub fn drain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + drain_range(array, start, end - start) + } + #[rhai_fn(name = "drain")] + pub fn drain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + drain_range(array, start, end - start + 1) + } + #[rhai_fn(name = "drain")] pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); @@ -993,6 +1032,18 @@ mod array_functions { retain(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "retain")] + pub fn retain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + retain_range(array, start, end - start) + } + #[rhai_fn(name = "retain")] + pub fn retain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + retain_range(array, start, end - start + 1) + } + #[rhai_fn(name = "retain")] pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 8b0380bc..aea9851c 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -2,7 +2,10 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Blob, Dynamic, EvalAltResult, NativeCallContext, Position, INT}; +use crate::{ + def_package, Blob, Dynamic, EvalAltResult, ExclusiveRange, InclusiveRange, NativeCallContext, + Position, INT, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; @@ -184,6 +187,18 @@ mod blob_functions { blob.reverse(); } } + #[rhai_fn(name = "splice")] + pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + splice(blob, start, end - start, replace) + } + #[rhai_fn(name = "splice")] + pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + splice(blob, start, end - start + 1, replace) + } pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) { if blob.is_empty() { *blob = replace; @@ -212,6 +227,18 @@ mod blob_functions { blob.splice(start..start + len, replace.into_iter()); } + #[rhai_fn(name = "extract")] + pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + extract(blob, start, end - start) + } + #[rhai_fn(name = "extract")] + pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + extract(blob, start, end - start + 1) + } pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -264,6 +291,18 @@ mod blob_functions { result } } + #[rhai_fn(name = "drain")] + pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + drain(blob, start, end - start) + } + #[rhai_fn(name = "drain")] + pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + drain(blob, start, end - start + 1) + } pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -288,6 +327,18 @@ mod blob_functions { blob.drain(start..start + len).collect() } + #[rhai_fn(name = "retain")] + pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + retain(blob, start, end - start) + } + #[rhai_fn(name = "retain")] + pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + retain(blob, start, end - start + 1) + } pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); @@ -374,9 +425,33 @@ mod blob_functions { } } + #[rhai_fn(name = "parse_le_int")] + pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_le_int(blob, start, end - start) + } + #[rhai_fn(name = "parse_le_int")] + pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_le_int(blob, start, end - start + 1) + } pub fn parse_le_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, true) } + #[rhai_fn(name = "parse_be_int")] + pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_be_int(blob, start, end - start) + } + #[rhai_fn(name = "parse_be_int")] + pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_be_int(blob, start, end - start + 1) + } pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, false) } @@ -418,11 +493,35 @@ mod blob_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } + #[rhai_fn(name = "write_le_int")] + pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + write_le_int(blob, start, end - start, value) + } + #[rhai_fn(name = "write_le_int")] + pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + write_le_int(blob, start, end - start + 1, value) + } #[rhai_fn(name = "write_le")] pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, true) } #[rhai_fn(name = "write_be")] + pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + write_be_int(blob, start, end - start, value) + } + #[rhai_fn(name = "write_be")] + pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + write_be_int(blob, start, end - start + 1, value) + } + #[rhai_fn(name = "write_be")] pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, false) } @@ -466,11 +565,39 @@ mod blob_functions { } } + #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "parse_le_float")] + pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_le_float(blob, start, end - start) + } + #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "parse_le_float")] + pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_le_float(blob, start, end - start + 1) + } #[cfg(not(feature = "no_float"))] pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { parse_float(blob, start, len, true) } #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "parse_be_float")] + pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + parse_be_float(blob, start, end - start) + } + #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "parse_be_float")] + pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + parse_be_float(blob, start, end - start + 1) + } + #[cfg(not(feature = "no_float"))] pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { parse_float(blob, start, len, false) } @@ -514,12 +641,40 @@ mod blob_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "write_le_float")] + pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + write_le_float(blob, start, end - start, value) + } + #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "write_le_float")] + pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + write_le_float(blob, start, end - start + 1, value) + } + #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_le")] pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, true) } #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "write_be")] + pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + write_be_float(blob, start, end - start, value) + } + #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "write_be")] + pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + write_be_float(blob, start, end - start + 1, value) + } + #[cfg(not(feature = "no_float"))] + #[rhai_fn(name = "write_be")] pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, false) } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 6f334973..8372d603 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,7 +1,8 @@ +use crate::plugin::*; use crate::types::dynamic::Variant; -use crate::{def_package, EvalAltResult, INT}; +use crate::{def_package, EvalAltResult, ExclusiveRange, InclusiveRange, INT}; use std::iter::{ExactSizeIterator, FusedIterator}; -use std::ops::Range; +use std::ops::{Range, RangeInclusive}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -271,6 +272,7 @@ macro_rules! reg_range { ($lib:ident | $x:expr => $( $y:ty ),*) => { $( $lib.set_iterator::>(); + $lib.set_iterator::>(); let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to)); #[cfg(feature = "metadata")] @@ -449,6 +451,22 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { // Register string iterator lib.set_iterator::(); + let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + Ok(CharsStream::new(string, from, to - from)) + }); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "range: Range", "Iterator"]); + + let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + Ok(CharsStream::new(string, from, to-from + 1)) + }); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive", "Iterator"]); + let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len))); #[cfg(feature = "metadata")] lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator"]); @@ -461,9 +479,29 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { #[cfg(feature = "metadata")] lib.update_fn_metadata(_hash, &["string: &str", "Iterator"]); + let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator"]); + // Register bit-field iterator lib.set_iterator::(); + let _hash = lib.set_native_fn("bits", |value, range: ExclusiveRange| { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + BitRange::new(value, from, to - from) + }); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "range: Range", "Iterator"]); + + let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + BitRange::new(value, from, to - from + 1) + }); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive", "Iterator"]); + let _hash = lib.set_native_fn("bits", BitRange::new); #[cfg(feature = "metadata")] lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator"]); @@ -472,7 +510,51 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { #[cfg(feature = "metadata")] lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator"]); - let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX)); + let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) ); #[cfg(feature = "metadata")] lib.update_fn_metadata(_hash, &["value: INT", "Iterator"]); + + let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range", "Iterator"]); + + combine_with_exported_module!(lib, "range", range_functions); }); + +#[export_module] +mod range_functions { + #[rhai_fn(get = "start", name = "start", pure)] + pub fn range_start(range: &mut ExclusiveRange) -> INT { + range.start + } + #[rhai_fn(get = "end", name = "end", pure)] + pub fn range_end(range: &mut ExclusiveRange) -> INT { + range.end + } + #[rhai_fn(name = "contains", pure)] + pub fn range_contains(range: &mut ExclusiveRange, value: INT) -> bool { + range.contains(&value) + } + #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] + pub fn range_is_inclusive(range: &mut ExclusiveRange) -> bool { + let _range = range; + false + } + #[rhai_fn(get = "start", name = "start", pure)] + pub fn range_inclusive_start(range: &mut InclusiveRange) -> INT { + *range.start() + } + #[rhai_fn(get = "end", name = "end", pure)] + pub fn range_inclusive_end(range: &mut InclusiveRange) -> INT { + *range.end() + } + #[rhai_fn(name = "contains", pure)] + pub fn range_inclusive_contains(range: &mut InclusiveRange, value: INT) -> bool { + range.contains(&value) + } + #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] + pub fn range_inclusive_is_inclusive(range: &mut InclusiveRange) -> bool { + let _range = range; + true + } +} diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 9e7c7e0d..d6c5523b 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, EvalAltResult, INT}; +use crate::{def_package, EvalAltResult, ExclusiveRange, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -256,6 +256,21 @@ mod bit_field_functions { Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) } } + #[rhai_fn(name = "get_bits", return_raw)] + pub fn get_bits_range(value: INT, range: ExclusiveRange) -> Result> { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + get_bits(value, from, to - from) + } + #[rhai_fn(name = "get_bits", return_raw)] + pub fn get_bits_range_inclusive( + value: INT, + range: InclusiveRange, + ) -> Result> { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + get_bits(value, from, to - from + 1) + } #[rhai_fn(return_raw)] pub fn get_bits(value: INT, index: INT, bits: INT) -> Result> { if bits < 1 { @@ -298,6 +313,26 @@ mod bit_field_functions { Ok(((value & (mask << index)) >> index) & mask) } + #[rhai_fn(name = "set_bits", return_raw)] + pub fn set_bits_range( + value: &mut INT, + range: ExclusiveRange, + new_value: INT, + ) -> Result<(), Box> { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + set_bits(value, from, to - from, new_value) + } + #[rhai_fn(name = "set_bits", return_raw)] + pub fn set_bits_range_inclusive( + value: &mut INT, + range: InclusiveRange, + new_value: INT, + ) -> Result<(), Box> { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + set_bits(value, from, to - from + 1, new_value) + } #[rhai_fn(return_raw)] pub fn set_bits( value: &mut INT, diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e7cdfee4..e95c15e3 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Dynamic, StaticVec, INT}; +use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; @@ -302,6 +302,26 @@ mod string_functions { } } + #[rhai_fn(name = "sub_string")] + pub fn sub_string_range( + ctx: NativeCallContext, + string: &str, + range: ExclusiveRange, + ) -> ImmutableString { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + sub_string(ctx, string, start, end - start) + } + #[rhai_fn(name = "sub_string")] + pub fn sub_string_inclusive_range( + ctx: NativeCallContext, + string: &str, + range: InclusiveRange, + ) -> ImmutableString { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + sub_string(ctx, string, start, end - start + 1) + } pub fn sub_string( ctx: NativeCallContext, string: &str, @@ -365,6 +385,18 @@ mod string_functions { } } + #[rhai_fn(name = "crop")] + pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + crop(string, start, end - start) + } + #[rhai_fn(name = "crop")] + pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + crop(string, start, end - start + 1) + } #[rhai_fn(name = "crop")] pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { if string.is_empty() { diff --git a/src/parser.rs b/src/parser.rs index 25bc5272..1698aa25 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,8 +15,9 @@ use crate::tokenizer::{ }; use crate::types::dynamic::AccessMode; use crate::{ - calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier, - ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST, + calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, ExclusiveRange, + Identifier, ImmutableString, InclusiveRange, LexError, ParseError, ParseErrorType, Position, + Scope, Shared, StaticVec, AST, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -1003,6 +1004,7 @@ fn parse_switch( } let mut table = BTreeMap::, StmtBlock)>>::new(); + let mut ranges = StaticVec::<(INT, INT, bool, Option, StmtBlock)>::new(); let mut def_pos = Position::NONE; let mut def_stmt = None; @@ -1033,6 +1035,7 @@ fn parse_switch( (None, None) } (Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)), + _ if def_stmt.is_some() => return Err(PERR::WrongSwitchDefaultCase.into_err(def_pos)), _ => { @@ -1047,21 +1050,31 @@ fn parse_switch( } }; - let hash = if let Some(expr) = expr { + let (hash, range) = if let Some(expr) = expr { let value = expr.get_literal_value().ok_or_else(|| { PERR::ExprExpected("a literal".to_string()).into_err(expr.position()) })?; - let hasher = &mut get_hasher(); - value.hash(hasher); - let hash = hasher.finish(); - if table.contains_key(&hash) { - return Err(PERR::DuplicatedSwitchCase.into_err(expr.position())); + let guard = value.read_lock::(); + + if let Some(range) = guard { + (None, Some((range.start, range.end, false))) + } else if let Some(range) = value.read_lock::() { + (None, Some((*range.start(), *range.end(), true))) + } else if value.is::() && !ranges.is_empty() { + return Err(PERR::WrongSwitchIntegerCase.into_err(expr.position())); + } else { + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); + + if table.contains_key(&hash) { + return Err(PERR::DuplicatedSwitchCase.into_err(expr.position())); + } + (Some(hash), None) } - - Some(hash) } else { - None + (None, None) }; match input.next().expect(NEVER_ENDS) { @@ -1080,12 +1093,19 @@ fn parse_switch( let need_comma = !stmt.is_self_terminated(); - def_stmt = match hash { - Some(hash) => { + def_stmt = match (hash, range) { + (None, Some(range)) => { + ranges.push((range.0, range.1, range.2, condition, stmt.into())); + None + } + (Some(hash), None) => { table.insert(hash, (condition, stmt.into()).into()); None } - None => Some(stmt.into()), + (None, None) => Some(stmt.into()), + (Some(_), Some(_)) => { + unreachable!("cannot have both a hash and a range in a `switch` statement") + } }; match input.peek().expect(NEVER_ENDS) { @@ -1115,7 +1135,7 @@ fn parse_switch( Ok(Stmt::Switch( item, - (table, def_stmt_block).into(), + (table, def_stmt_block, ranges).into(), settings.pos, )) } @@ -1975,18 +1995,6 @@ fn parse_binary_op( args.shrink_to_fit(); root = match op_token { - Token::Plus - | Token::Minus - | Token::Multiply - | Token::Divide - | Token::LeftShift - | Token::RightShift - | Token::Modulo - | Token::PowerOf - | Token::Ampersand - | Token::Pipe - | Token::XOr => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), - // '!=' defaults to true when passed invalid operands Token::NotEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), @@ -2058,7 +2066,7 @@ fn parse_binary_op( .into_fn_call_expr(pos) } - op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)), + _ => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), }; } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index c039a8dc..9e93f582 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -356,6 +356,10 @@ pub enum Token { Comma, /// `.` Period, + /// `..` + ExclusiveRange, + /// `..=` + InclusiveRange, /// `#{` MapStart, /// `=` @@ -507,6 +511,8 @@ impl Token { Underscore => "_", Comma => ",", Period => ".", + ExclusiveRange => "..", + InclusiveRange => "..=", MapStart => "#{", Equals => "=", True => "true", @@ -701,6 +707,8 @@ impl Token { "_" => Underscore, "," => Comma, "." => Period, + ".." => ExclusiveRange, + "..=" => InclusiveRange, "#{" => MapStart, "=" => Equals, "true" => True, @@ -805,6 +813,8 @@ impl Token { Colon | // #{ foo: - is unary Comma | // ( ... , -expr ) - is unary //Period | + ExclusiveRange | // .. - is unary + InclusiveRange | // ..= - is unary LeftBrace | // { -expr } - is unary // RightBrace | { expr } - expr not unary & is closing LeftParen | // ( -expr ) - is unary @@ -868,6 +878,8 @@ impl Token { | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign => 0, + ExclusiveRange | InclusiveRange => 10, + Or | XOr | Pipe => 30, And | Ampersand => 60, @@ -921,11 +933,12 @@ impl Token { match self { LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus | UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift - | RightShift | SemiColon | Colon | DoubleColon | Comma | Period | MapStart | Equals - | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo - | NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign - | MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign - | OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true, + | RightShift | SemiColon | Colon | DoubleColon | Comma | Period | ExclusiveRange + | InclusiveRange | MapStart | Equals | LessThan | GreaterThan | LessThanEqualsTo + | GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand + | And | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => true, _ => false, } @@ -1794,13 +1807,20 @@ fn get_next_token_inner( ('.', '.') => { eat_next(stream, pos); - - if stream.peek_next() == Some('.') { - eat_next(stream, pos); - return Some((Token::Reserved("...".into()), start_pos)); - } else { - return Some((Token::Reserved("..".into()), start_pos)); - } + return Some(( + match stream.peek_next() { + Some('.') => { + eat_next(stream, pos); + Token::Reserved("...".into()) + } + Some('=') => { + eat_next(stream, pos); + Token::InclusiveRange + } + _ => Token::ExclusiveRange, + }, + start_pos, + )); } ('.', _) => return Some((Token::Period, start_pos)), diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 9f142049..8186f4bd 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -2,7 +2,7 @@ use crate::func::native::SendSync; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; -use crate::{FnPtr, ImmutableString, INT}; +use crate::{ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -588,6 +588,12 @@ pub(crate) fn map_std_type_name(name: &str) -> &str { if name == type_name::() { return "timestamp"; } + if name == type_name::() { + return "range"; + } + if name == type_name::() { + return "range="; + } name } @@ -655,6 +661,14 @@ impl fmt::Display for Dynamic { return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } + if _type_id == TypeId::of::() { + let range = _value_any.downcast_ref::().expect(CHECKED); + return write!(f, "{}..{}", range.start, range.end); + } else if _type_id == TypeId::of::() { + let range = _value_any.downcast_ref::().expect(CHECKED); + return write!(f, "{}..={}", range.start(), range.end()); + } + f.write_str((***value).type_name()) } @@ -737,6 +751,14 @@ impl fmt::Debug for Dynamic { return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } + if _type_id == TypeId::of::() { + let range = _value_any.downcast_ref::().expect(CHECKED); + return write!(f, "{}..{}", range.start, range.end); + } else if _type_id == TypeId::of::() { + let range = _value_any.downcast_ref::().expect(CHECKED); + return write!(f, "{}..={}", range.start(), range.end()); + } + f.write_str((***value).type_name()) } @@ -809,6 +831,13 @@ impl Default for Dynamic { } } +#[cfg(not(feature = "no_float"))] +#[cfg(feature = "f32_float")] +use std::f32::consts as FloatConstants; +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "f32_float"))] +use std::f64::consts as FloatConstants; + impl Dynamic { /// A [`Dynamic`] containing a `()`. pub const UNIT: Self = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite)); @@ -818,20 +847,24 @@ impl Dynamic { pub const FALSE: Self = Self::from_bool(false); /// A [`Dynamic`] containing the integer zero. pub const ZERO: Self = Self::from_int(0); - /// A [`Dynamic`] containing the integer one. + /// A [`Dynamic`] containing the integer 1. pub const ONE: Self = Self::from_int(1); - /// A [`Dynamic`] containing the integer two. + /// A [`Dynamic`] containing the integer 2. pub const TWO: Self = Self::from_int(2); - /// A [`Dynamic`] containing the integer three. + /// A [`Dynamic`] containing the integer 3. pub const THREE: Self = Self::from_int(3); - /// A [`Dynamic`] containing the integer ten. + /// A [`Dynamic`] containing the integer 10. pub const TEN: Self = Self::from_int(10); - /// A [`Dynamic`] containing the integer one hundred. + /// A [`Dynamic`] containing the integer 100. pub const HUNDRED: Self = Self::from_int(100); - /// A [`Dynamic`] containing the integer one thousand. + /// A [`Dynamic`] containing the integer 1,000. pub const THOUSAND: Self = Self::from_int(1000); - /// A [`Dynamic`] containing the integer negative one. + /// A [`Dynamic`] containing the integer 1,000,000. + pub const MILLION: Self = Self::from_int(1000000); + /// A [`Dynamic`] containing the integer -1. pub const NEGATIVE_ONE: Self = Self::from_int(-1); + /// A [`Dynamic`] containing the integer -2. + pub const NEGATIVE_TWO: Self = Self::from_int(-2); /// A [`Dynamic`] containing `0.0`. /// /// Not available under `no_float`. @@ -862,54 +895,97 @@ impl Dynamic { /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_THOUSAND: Self = Self::from_float(1000.0); + /// A [`Dynamic`] containing `1000000.0`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_MILLION: Self = Self::from_float(1000000.0); /// A [`Dynamic`] containing `-1.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_NEGATIVE_ONE: Self = Self::from_float(-1.0); + /// A [`Dynamic`] containing `-2.0`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_NEGATIVE_TWO: Self = Self::from_float(-2.0); + /// A [`Dynamic`] containing `0.5`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_HALF: Self = Self::from_float(0.5); + /// A [`Dynamic`] containing `0.25`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_QUARTER: Self = Self::from_float(0.25); + /// A [`Dynamic`] containing `0.2`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_FIFTH: Self = Self::from_float(0.2); + /// A [`Dynamic`] containing `0.1`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_TENTH: Self = Self::from_float(0.1); + /// A [`Dynamic`] containing `0.01`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_HUNDREDTH: Self = Self::from_float(0.01); + /// A [`Dynamic`] containing `0.001`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_THOUSANDTH: Self = Self::from_float(0.001); + /// A [`Dynamic`] containing `0.000001`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_MILLIONTH: Self = Self::from_float(0.000001); /// A [`Dynamic`] containing π. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] - pub const FLOAT_PI: Self = Self::from_float( - #[cfg(not(feature = "f32_float"))] - { - std::f64::consts::PI - }, - #[cfg(feature = "f32_float")] - { - std::f32::consts::PI - }, - ); + pub const FLOAT_PI: Self = Self::from_float(FloatConstants::PI); /// A [`Dynamic`] containing π/2. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] - pub const FLOAT_HALF_PI: Self = Self::from_float( - #[cfg(not(feature = "f32_float"))] - { - std::f64::consts::PI / 2.0 - }, - #[cfg(feature = "f32_float")] - { - std::f32::consts::PI / 2.0 - }, - ); + pub const FLOAT_HALF_PI: Self = Self::from_float(FloatConstants::FRAC_PI_2); + /// A [`Dynamic`] containing π/4. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_QUARTER_PI: Self = Self::from_float(FloatConstants::FRAC_PI_4); /// A [`Dynamic`] containing 2π. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] - pub const FLOAT_TWO_PI: Self = Self::from_float( - #[cfg(not(feature = "f32_float"))] - { - std::f64::consts::PI * 2.0 - }, - #[cfg(feature = "f32_float")] - { - std::f32::consts::PI * 2.0 - }, - ); + pub const FLOAT_TWO_PI: Self = Self::from_float(FloatConstants::TAU); + /// A [`Dynamic`] containing 1/π. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_INVERSE_PI: Self = Self::from_float(FloatConstants::FRAC_1_PI); + /// A [`Dynamic`] containing _e_. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_E: Self = Self::from_float(FloatConstants::E); + /// A [`Dynamic`] containing `log` _e_. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_LOG_E: Self = Self::from_float(FloatConstants::LOG10_E); + /// A [`Dynamic`] containing `ln 10`. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + pub const FLOAT_LN_10: Self = Self::from_float(FloatConstants::LN_10); /// Create a new [`Dynamic`] from a [`bool`]. #[inline(always)] @@ -2264,3 +2340,16 @@ impl From>> for Dynamic { Self(Union::Shared(value, DEFAULT_TAG_VALUE, ReadWrite)) } } + +impl From for Dynamic { + #[inline(always)] + fn from(value: ExclusiveRange) -> Self { + Dynamic::from(value) + } +} +impl From for Dynamic { + #[inline(always)] + fn from(value: InclusiveRange) -> Self { + Dynamic::from(value) + } +} diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 1e9a591c..d814aa3a 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -108,6 +108,8 @@ pub enum ParseErrorType { DuplicatedSwitchCase, /// A variable name is duplicated. Wrapped value is the variable name. DuplicatedVariable(String), + /// An integer case of a `switch` statement is after a range case. + WrongSwitchIntegerCase, /// The default case of a `switch` statement is not the last. WrongSwitchDefaultCase, /// The case condition of a `switch` statement is not appropriate. @@ -260,8 +262,9 @@ impl fmt::Display for ParseErrorType { Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max), Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s), Self::UnexpectedEOF => f.write_str("Script is incomplete"), - Self::WrongSwitchDefaultCase => f.write_str("Default switch case is not the last"), - Self::WrongSwitchCaseCondition => f.write_str("Default switch case cannot have condition"), + Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"), + Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"), + Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"), Self::PropertyExpected => f.write_str("Expecting name of a property"), Self::VariableExpected => f.write_str("Expecting name of a variable"), Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"), diff --git a/tests/bit_shift.rs b/tests/bit_shift.rs index c35b33d5..b077c5a6 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_shift.rs @@ -26,10 +26,12 @@ fn test_bit_fields() -> Result<(), Box> { 9 ); assert_eq!(engine.eval::("let x = 10; get_bits(x, 1, 3)")?, 5); + assert_eq!(engine.eval::("let x = 10; x[1..=3]")?, 5); assert_eq!( engine.eval::("let x = 10; set_bits(x, 1, 3, 7); x")?, 14 ); + assert_eq!(engine.eval::("let x = 10; x[1..4] = 7; x")?, 14); assert_eq!( engine.eval::( " @@ -45,6 +47,21 @@ fn test_bit_fields() -> Result<(), Box> { )?, 5 ); + assert_eq!( + engine.eval::( + " + let x = 0b001101101010001; + let count = 0; + + for b in bits(x, 2..=11) { + if b { count += 1; } + } + + count + " + )?, + 5 + ); Ok(()) } diff --git a/tests/blobs.rs b/tests/blobs.rs index 7036f116..d478c740 100644 --- a/tests/blobs.rs +++ b/tests/blobs.rs @@ -81,57 +81,83 @@ fn test_blobs_parse() -> Result<(), Box> { let engine = Engine::new(); assert_eq!( - engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,0)" - )?, + engine + .eval::("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,0)")?, 0 ); + assert_eq!( + engine + .eval::("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,9)")?, + 0x0908070605040302 + ); + assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,9)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2..=11)" )?, 0x0908070605040302 ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_be_int(x,2,10)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2..11)" + )?, + 0x0908070605040302 + ); + + assert_eq!( + engine.eval::( + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_be_int(x,2,10)" )?, 0x0203040506070809 ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,-5,99)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_be_int(x,2..12)" + )?, + 0x0203040506070809 + ); + + assert_eq!( + engine.eval::( + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-5,99)" )?, 0x0f0e0d0c0b ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,-5,2)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-5,2)" )?, 0x0c0b ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,-99,99)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-99,99)" )?, 0x0706050403020100 ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } write_be(x, 3, 3, -98765432); parse_be_int(x, 3, 3)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } write_be(x, 3, 3, -98765432); parse_be_int(x, 3, 3)" )?, 0xffffff0000000000_u64 as INT ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } write_le(x, 3, 3, -98765432); parse_le_int(x, 3, 3)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } write_be(x, 3..=5, -98765432); parse_be_int(x, 3..6)" + )?, + 0xffffff0000000000_u64 as INT + ); + + assert_eq!( + engine.eval::( + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } write_le(x, 3, 3, -98765432); parse_le_int(x, 3, 3)" )?, 0x1cf588 ); @@ -145,57 +171,62 @@ fn test_blobs_parse() -> Result<(), Box> { let engine = Engine::new(); assert_eq!( - engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,0)" - )?, + engine + .eval::("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,0)")?, 0 ); assert_eq!( - engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,9)" - )?, + engine + .eval::("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,9)")?, 0x05040302 ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_be_int(x,2,10)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_be_int(x,2,10)" )?, 0x02030405 ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,-5,99)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-5,99)" )?, 0x0e0d0c0b ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,-5,2)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-5,2)" )?, 0x0c0b ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,-99,99)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-99,99)" )?, 0x03020100 ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } write_be(x, 3, 3, -98765432); parse_be_int(x, 3, 3)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } write_be(x, 3, 3, -98765432); parse_be_int(x, 3, 3)" )?, 0xfa1cf500_u32 as INT ); assert_eq!( engine.eval::( - "let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } write_le(x, 3, 3, -98765432); parse_le_int(x, 3, 3)" + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } write_be(x, 3..=5, -98765432); parse_be_int(x, 3..6)" + )?, + 0xfa1cf500_u32 as INT + ); + + assert_eq!( + engine.eval::( + "let x = blob(16, 0); for n in 0..16 { x[n] = n; } write_le(x, 3, 3, -98765432); parse_le_int(x, 3, 3)" )?, 0x1cf588 ); diff --git a/tests/for.rs b/tests/for.rs index 36adf7f2..59e7223a 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -64,6 +64,28 @@ fn test_for_loop() -> Result<(), Box> { 45 ); + assert_eq!( + engine.eval::( + " + let sum = 0; + for x in 1..10 { sum += x; } + sum + " + )?, + 45 + ); + + assert_eq!( + engine.eval::( + " + let sum = 0; + for x in 1..=10 { sum += x; } + sum + " + )?, + 55 + ); + assert_eq!( engine.eval::( " @@ -247,18 +269,37 @@ fn test_for_overflow() -> Result<(), Box> { fn test_for_string() -> Result<(), Box> { let engine = Engine::new(); - let script = r#" - let s = "hello"; - let sum = 0; + assert_eq!( + engine.eval::( + r#" + let s = "hello"; + let sum = 0; - for ch in chars(s) { - sum += to_int(ch); - } + for ch in chars(s) { + sum += to_int(ch); + } - sum - "#; + sum + "# + )?, + 532 + ); - assert_eq!(engine.eval::(script)?, 532); + assert_eq!( + engine.eval::( + r#" + let s = "hello"; + let sum = 0; + + for ch in chars(s, 2..=3) { + sum += to_int(ch); + } + + sum + "# + )?, + 216 + ); Ok(()) } diff --git a/tests/modules.rs b/tests/modules.rs index e42198d9..b1587f10 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -205,7 +205,7 @@ fn test_module_resolver() -> Result<(), Box> { r#" let sum = 0; - for x in range(0, 10) { + for x in 0..10 { import "hello" as h; sum += h::answer; } @@ -229,7 +229,7 @@ fn test_module_resolver() -> Result<(), Box> { sum += h::answer; } - for x in range(0, 10) { + for x in 0..10 { foo(); } @@ -249,7 +249,7 @@ fn test_module_resolver() -> Result<(), Box> { import "hello" as h; } - for x in range(0, 10) { + for x in 0..10 { foo(); } "#, diff --git a/tests/operations.rs b/tests/operations.rs index d35813c2..13c0cb48 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -4,6 +4,8 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_max_operations() -> Result<(), Box> { let mut engine = Engine::new(); + #[cfg(not(feature = "no_optimize"))] + engine.set_optimization_level(rhai::OptimizationLevel::None); engine.set_max_operations(500); engine.on_progress(|count| { @@ -17,14 +19,14 @@ fn test_max_operations() -> Result<(), Box> { assert!(matches!( *engine - .eval::<()>("for x in range(0, 500) {}") + .eval::<()>("for x in 0..500 {}") .expect_err("should error"), EvalAltResult::ErrorTooManyOperations(_) )); engine.set_max_operations(0); - engine.eval::<()>("for x in range(0, 10000) {}")?; + engine.eval::<()>("for x in 0..10000 {}")?; Ok(()) } @@ -101,7 +103,7 @@ fn test_max_operations_eval() -> Result<(), Box> { *engine .eval::<()>( r#" - let script = "for x in range(0, 500) {}"; + let script = "for x in 0..500 {}"; eval(script); "# ) @@ -115,6 +117,8 @@ fn test_max_operations_eval() -> Result<(), Box> { #[test] fn test_max_operations_progress() -> Result<(), Box> { let mut engine = Engine::new(); + #[cfg(not(feature = "no_optimize"))] + engine.set_optimization_level(rhai::OptimizationLevel::None); engine.set_max_operations(500); engine.on_progress(|count| { @@ -127,7 +131,7 @@ fn test_max_operations_progress() -> Result<(), Box> { assert!(matches!( *engine - .eval::<()>("for x in range(0, 500) {}") + .eval::<()>("for x in 0..500 {}") .expect_err("should error"), EvalAltResult::ErrorTerminated(x, _) if x.as_int()? == 42 )); diff --git a/tests/serde.rs b/tests/serde.rs index 25929ac5..32b0c64e 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -784,7 +784,7 @@ fn test_serde_blob() -> Result<(), Box> { let r = engine.eval::( " let x = blob(10); - for i in range(0, 10) { x[i] = i; } + for i in 0..10 { x[i] = i; } x ", )?; diff --git a/tests/switch.rs b/tests/switch.rs index b3bbc803..b3afbb6f 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -6,6 +6,33 @@ fn test_switch() -> Result<(), Box> { let mut scope = Scope::new(); scope.push("x", 42 as INT); + assert_eq!( + engine.eval::("switch 2 { 1 => (), 2 => 'a', 42 => true }")?, + 'a' + ); + assert_eq!( + engine.eval::<()>("switch 3 { 1 => (), 2 => 'a', 42 => true }")?, + () + ); + assert_eq!( + engine.eval::("switch 3 { 1 => (), 2 => 'a', 42 => true, _ => 123 }")?, + 123 + ); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + "switch 2 { 1 => (), 2 if x < 40 => 'a', 42 => true, _ => 123 }" + )?, + 123 + ); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + "switch 2 { 1 => (), 2 if x > 40 => 'a', 42 => true, _ => 123 }" + )?, + 'a' + ); + assert_eq!( engine.eval_with_scope::(&mut scope, "switch x { 1 => (), 2 => 'a', 42 => true }")?, true @@ -210,3 +237,66 @@ mod test_switch_enum { Ok(()) } } + +#[test] +fn test_switch_ranges() -> Result<(), Box> { + let engine = Engine::new(); + let mut scope = Scope::new(); + scope.push("x", 42 as INT); + + assert_eq!( + engine.eval_with_scope::( + &mut scope, + "switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 30..100 => true }" + )?, + 'a' + ); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + "switch x { 10..20 => (), 20..=42 if x < 40 => 'a', 25..45 => 'z', 30..100 => true }" + )?, + 'z' + ); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + "switch x { 42 => 'x', 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 30..100 => true, 'w' => true }" + )?, + 'x' + ); + assert!(matches!( + *engine.compile("switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42 => 'x', 30..100 => true }") + .expect_err("should error").0, + ParseErrorType::WrongSwitchIntegerCase + )); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + " + switch 5 { + 'a' => true, + 0..10 if x+2==1+2 => print(40+2), + _ => 'x' + } + " + )?, + 'x' + ); + assert_eq!( + engine.eval_with_scope::( + &mut scope, + " + switch 5 { + 'a' => true, + 0..10 if x+2==1+2 => print(40+2), + 2..12 => 'z', + _ => 'x' + } + " + )?, + 'z' + ); + + Ok(()) +} diff --git a/tests/time.rs b/tests/time.rs index 43778b38..b2f0294b 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -42,7 +42,7 @@ fn test_timestamp() -> Result<(), Box> { assert!(engine.eval::( " let time1 = timestamp(); - for x in range(0, 10000) {} + for x in 0..10000 {} let time2 = timestamp(); time1 <= time2 "