Add ranges.

This commit is contained in:
Stephen Chung 2021-12-15 12:06:17 +08:00
parent 7251f34bce
commit ef14079c61
35 changed files with 1206 additions and 269 deletions

View File

@ -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
=============

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 }

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -8,7 +8,7 @@ let now = timestamp();
let list = [];
for i in range(0, MAX) {
for i in 0..MAX {
list.push(i);
}

View File

@ -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]);
}
*/

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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<u64, Box<(Option<Expr>, StmtBlock)>>, StmtBlock)>,
Box<(
BTreeMap<u64, Box<(Option<Expr>, StmtBlock)>>,
StmtBlock,
StaticVec<(INT, INT, bool, Option<Expr>, 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,
})
}

View File

@ -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::<T>(),
Self::TempValue(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, _) => TypeId::of::<T>() == TypeId::of::<bool>(),
Self::Bit(_, _, _) => TypeId::of::<T>() == TypeId::of::<bool>(),
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, _, _) => TypeId::of::<T>() == TypeId::of::<INT>(),
#[cfg(not(feature = "no_index"))]
Self::BlobByte(_, _, _) => TypeId::of::<T>() == TypeId::of::<crate::Blob>(),
#[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,16 +627,32 @@ 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;
}
} else {
unreachable!("bit-field index out of bounds: {}", index);
}
#[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::<crate::INT>().expect("`INT`");
*value &= !mask;
*value |= new_value;
}
#[cfg(not(feature = "no_index"))]
Self::BlobByte(value, index, new_val) => {
@ -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::<InclusiveRange>() || idx.is::<ExclusiveRange>() =>
{
#[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::<INT>() * 8;
let (shift, mask) = if let Some(range) = idx.read_lock::<ExclusiveRange>() {
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::<InclusiveRange>() {
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::<crate::INT>(typ, idx_pos))?;
let bits = std::mem::size_of_val(value) * 8;
const BITS: usize = std::mem::size_of::<INT>() * 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::<bool>(
typ,
condition.position(),
)
self.make_type_mismatch_err::<bool>(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::<INT>() && !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::<bool>(typ, c.position())
})
})?
{
continue;
}
}
let statements = &t.1;
result = Some(stmt_block);
break;
}
Some(if !statements.is_empty() {
result
} else {
// Nothing matches
None
}
} else {
// Non-hashable
None
};
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 {
// Non-hashable values never match any specific clause
None
}
.unwrap_or_else(|| {
} 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 {

View File

@ -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,
};
}

View File

@ -115,9 +115,14 @@ pub type FLOAT = f64;
#[cfg(feature = "f32_float")]
pub type FLOAT = f32;
pub type ExclusiveRange = std::ops::Range<INT>;
pub type InclusiveRange = std::ops::RangeInclusive<INT>;
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;

View File

@ -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,14 +463,62 @@ 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_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,
Box::new((
mem::take(&mut block.1),
Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(),
)),
match_expr.position(),
);
} else {
// Promote the matched case
let new_pos = block.1.position();
let statements =
optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false);
*stmt = Stmt::Block(statements.into_boxed_slice(), new_pos);
}
state.set_dirty();
return;
}
// Then check ranges
let ranges = &mut x.2;
if value.is::<INT>() && !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() {
@ -497,54 +530,81 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*stmt = Stmt::If(
condition,
Box::new((
mem::take(&mut block.1),
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 = block.1.position();
let statements = mem::take(&mut *block.1);
let statements = optimize_stmt_block(statements, state, true, true, false);
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
let def_block = mem::take(&mut *x.1);
let def_stmt = optimize_stmt_block(def_block, state, true, true, false);
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| {
let statements = mem::take(block.1.deref_mut());
*block.1 = optimize_stmt_block(statements, state, preserve_result, true, false);
if let Some(mut condition) = mem::take(&mut block.0) {
optimize_expr(&mut condition, state, false);
condition
},
);
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,
);
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"))]

View File

@ -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()
}

View File

@ -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();

View File

@ -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)
}

View File

@ -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::<Range<$y>>();
$lib.set_iterator::<RangeInclusive<$y>>();
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::<CharsStream>();
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<Item=char>"]);
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<Item=char>"]);
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<Item=char>"]);
@ -461,9 +479,29 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]);
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<Item=char>"]);
// Register bit-field iterator
lib.set_iterator::<BitRange>();
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<Item=bool>"]);
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<Item=bool>"]);
let _hash = lib.set_native_fn("bits", BitRange::new);
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]);
@ -475,4 +513,48 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
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<Item=bool>"]);
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<Item=bool>"]);
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
}
}

View File

@ -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<INT, Box<EvalAltResult>> {
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<INT, Box<EvalAltResult>> {
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<INT, Box<EvalAltResult>> {
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<EvalAltResult>> {
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<EvalAltResult>> {
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,

View File

@ -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() {

View File

@ -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::<u64, Box<(Option<Expr>, StmtBlock)>>::new();
let mut ranges = StaticVec::<(INT, INT, bool, Option<Expr>, 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,10 +1050,20 @@ 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 guard = value.read_lock::<ExclusiveRange>();
if let Some(range) = guard {
(None, Some((range.start, range.end, false)))
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
(None, Some((*range.start(), *range.end(), true)))
} else if value.is::<INT>() && !ranges.is_empty() {
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.position()));
} else {
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
@ -1058,10 +1071,10 @@ fn parse_switch(
if table.contains_key(&hash) {
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
}
Some(hash)
(Some(hash), None)
}
} 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),
};
}
}

View File

@ -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('.') {
return Some((
match stream.peek_next() {
Some('.') => {
eat_next(stream, pos);
return Some((Token::Reserved("...".into()), start_pos));
} else {
return Some((Token::Reserved("..".into()), start_pos));
Token::Reserved("...".into())
}
Some('=') => {
eat_next(stream, pos);
Token::InclusiveRange
}
_ => Token::ExclusiveRange,
},
start_pos,
));
}
('.', _) => return Some((Token::Period, start_pos)),

View File

@ -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::<Instant>() {
return "timestamp";
}
if name == type_name::<ExclusiveRange>() {
return "range";
}
if name == type_name::<InclusiveRange>() {
return "range=";
}
name
}
@ -655,6 +661,14 @@ impl fmt::Display for Dynamic {
return fmt::Display::fmt(_value_any.downcast_ref::<i128>().expect(CHECKED), f);
}
if _type_id == TypeId::of::<ExclusiveRange>() {
let range = _value_any.downcast_ref::<ExclusiveRange>().expect(CHECKED);
return write!(f, "{}..{}", range.start, range.end);
} else if _type_id == TypeId::of::<InclusiveRange>() {
let range = _value_any.downcast_ref::<InclusiveRange>().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::<i128>().expect(CHECKED), f);
}
if _type_id == TypeId::of::<ExclusiveRange>() {
let range = _value_any.downcast_ref::<ExclusiveRange>().expect(CHECKED);
return write!(f, "{}..{}", range.start, range.end);
} else if _type_id == TypeId::of::<InclusiveRange>() {
let range = _value_any.downcast_ref::<InclusiveRange>().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<crate::Shared<crate::Locked<Dynamic>>> for Dynamic {
Self(Union::Shared(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl From<ExclusiveRange> for Dynamic {
#[inline(always)]
fn from(value: ExclusiveRange) -> Self {
Dynamic::from(value)
}
}
impl From<InclusiveRange> for Dynamic {
#[inline(always)]
fn from(value: InclusiveRange) -> Self {
Dynamic::from(value)
}
}

View File

@ -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"),

View File

@ -26,10 +26,12 @@ fn test_bit_fields() -> Result<(), Box<EvalAltResult>> {
9
);
assert_eq!(engine.eval::<INT>("let x = 10; get_bits(x, 1, 3)")?, 5);
assert_eq!(engine.eval::<INT>("let x = 10; x[1..=3]")?, 5);
assert_eq!(
engine.eval::<INT>("let x = 10; set_bits(x, 1, 3, 7); x")?,
14
);
assert_eq!(engine.eval::<INT>("let x = 10; x[1..4] = 7; x")?, 14);
assert_eq!(
engine.eval::<INT>(
"
@ -45,6 +47,21 @@ fn test_bit_fields() -> Result<(), Box<EvalAltResult>> {
)?,
5
);
assert_eq!(
engine.eval::<INT>(
"
let x = 0b001101101010001;
let count = 0;
for b in bits(x, 2..=11) {
if b { count += 1; }
}
count
"
)?,
5
);
Ok(())
}

View File

@ -81,57 +81,83 @@ fn test_blobs_parse() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
"let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,0)"
)?,
engine
.eval::<INT>("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,0)")?,
0
);
assert_eq!(
engine
.eval::<INT>("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,9)")?,
0x0908070605040302
);
assert_eq!(
engine.eval::<INT>(
"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::<INT>(
"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::<INT>(
"let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_be_int(x,2,10)"
)?,
0x0203040506070809
);
assert_eq!(
engine.eval::<INT>(
"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::<INT>(
"let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,-5,99)"
)?,
0x0f0e0d0c0b
);
assert_eq!(
engine.eval::<INT>(
"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::<INT>(
"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::<INT>(
"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::<INT>(
"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::<INT>(
"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<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
"let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,0)"
)?,
engine
.eval::<INT>("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,0)")?,
0
);
assert_eq!(
engine.eval::<INT>(
"let x = blob(16, 0); for n in range(0, 16) { x[n] = n; } parse_le_int(x,2,9)"
)?,
engine
.eval::<INT>("let x = blob(16, 0); for n in 0..16 { x[n] = n; } parse_le_int(x,2,9)")?,
0x05040302
);
assert_eq!(
engine.eval::<INT>(
"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::<INT>(
"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::<INT>(
"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::<INT>(
"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::<INT>(
"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::<INT>(
"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::<INT>(
"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
);

View File

@ -64,6 +64,28 @@ fn test_for_loop() -> Result<(), Box<EvalAltResult>> {
45
);
assert_eq!(
engine.eval::<INT>(
"
let sum = 0;
for x in 1..10 { sum += x; }
sum
"
)?,
45
);
assert_eq!(
engine.eval::<INT>(
"
let sum = 0;
for x in 1..=10 { sum += x; }
sum
"
)?,
55
);
assert_eq!(
engine.eval::<INT>(
"
@ -247,7 +269,9 @@ fn test_for_overflow() -> Result<(), Box<EvalAltResult>> {
fn test_for_string() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let script = r#"
assert_eq!(
engine.eval::<INT>(
r#"
let s = "hello";
let sum = 0;
@ -256,9 +280,26 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
}
sum
"#;
"#
)?,
532
);
assert_eq!(engine.eval::<INT>(script)?, 532);
assert_eq!(
engine.eval::<INT>(
r#"
let s = "hello";
let sum = 0;
for ch in chars(s, 2..=3) {
sum += to_int(ch);
}
sum
"#
)?,
216
);
Ok(())
}

View File

@ -205,7 +205,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
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<EvalAltResult>> {
import "hello" as h;
}
for x in range(0, 10) {
for x in 0..10 {
foo();
}
"#,

View File

@ -4,6 +4,8 @@ use rhai::{Engine, EvalAltResult, INT};
#[test]
fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
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<EvalAltResult>> {
*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<EvalAltResult>> {
#[test]
fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
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<EvalAltResult>> {
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
));

View File

@ -784,7 +784,7 @@ fn test_serde_blob() -> Result<(), Box<EvalAltResult>> {
let r = engine.eval::<Dynamic>(
"
let x = blob(10);
for i in range(0, 10) { x[i] = i; }
for i in 0..10 { x[i] = i; }
x
",
)?;

View File

@ -6,6 +6,33 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
let mut scope = Scope::new();
scope.push("x", 42 as INT);
assert_eq!(
engine.eval::<char>("switch 2 { 1 => (), 2 => 'a', 42 => true }")?,
'a'
);
assert_eq!(
engine.eval::<()>("switch 3 { 1 => (), 2 => 'a', 42 => true }")?,
()
);
assert_eq!(
engine.eval::<INT>("switch 3 { 1 => (), 2 => 'a', 42 => true, _ => 123 }")?,
123
);
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
"switch 2 { 1 => (), 2 if x < 40 => 'a', 42 => true, _ => 123 }"
)?,
123
);
assert_eq!(
engine.eval_with_scope::<char>(
&mut scope,
"switch 2 { 1 => (), 2 if x > 40 => 'a', 42 => true, _ => 123 }"
)?,
'a'
);
assert_eq!(
engine.eval_with_scope::<bool>(&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<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
scope.push("x", 42 as INT);
assert_eq!(
engine.eval_with_scope::<char>(
&mut scope,
"switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 30..100 => true }"
)?,
'a'
);
assert_eq!(
engine.eval_with_scope::<char>(
&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::<char>(
&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::<char>(
&mut scope,
"
switch 5 {
'a' => true,
0..10 if x+2==1+2 => print(40+2),
_ => 'x'
}
"
)?,
'x'
);
assert_eq!(
engine.eval_with_scope::<char>(
&mut scope,
"
switch 5 {
'a' => true,
0..10 if x+2==1+2 => print(40+2),
2..12 => 'z',
_ => 'x'
}
"
)?,
'z'
);
Ok(())
}

View File

@ -42,7 +42,7 @@ fn test_timestamp() -> Result<(), Box<EvalAltResult>> {
assert!(engine.eval::<bool>(
"
let time1 = timestamp();
for x in range(0, 10000) {}
for x in 0..10000 {}
let time2 = timestamp();
time1 <= time2
"