commit
1290556c0a
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v1.2-fixes
|
||||
- v1.3-fixes
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
|
11
CHANGELOG.md
11
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
|
||||
=============
|
||||
|
||||
|
@ -3,7 +3,7 @@ members = [".", "codegen"]
|
||||
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
description = "Embedded scripting for Rust"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ let now = timestamp();
|
||||
|
||||
let list = [];
|
||||
|
||||
for i in range(0, MAX) {
|
||||
for i in 0..MAX {
|
||||
list.push(i);
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
}
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 % 123 == 0 {
|
||||
/// *logger.write().unwrap() = ops;
|
||||
/// None
|
||||
/// } else {
|
||||
@ -166,10 +166,10 @@ 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);
|
||||
/// assert_eq!(*result.read().unwrap(), 984);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
50
src/ast.rs
50
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<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,
|
||||
})
|
||||
}
|
||||
|
260
src/engine.rs
260
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, Identifier, ImmutableString,
|
||||
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,10 @@ 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(_, _, _, _)
|
||||
| Self::BlobByte(_, _, _)
|
||||
| Self::StringChar(_, _, _) => false,
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a temp value?
|
||||
@ -520,7 +533,10 @@ 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(_, _, _, _)
|
||||
| Self::BlobByte(_, _, _)
|
||||
| Self::StringChar(_, _, _) => false,
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a shared value?
|
||||
@ -534,7 +550,10 @@ 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(_, _, _, _)
|
||||
| Self::BlobByte(_, _, _)
|
||||
| Self::StringChar(_, _, _) => false,
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a specific type?
|
||||
@ -548,7 +567,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 +586,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 +619,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 +633,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::<crate::INT>().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,7 +735,8 @@ impl Deref for Target<'_> {
|
||||
Self::LockGuard((r, _)) => &**r,
|
||||
Self::TempValue(ref r) => r,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, ref r)
|
||||
Self::Bit(_, ref r, _)
|
||||
| Self::BitField(_, _, ref r, _)
|
||||
| Self::BlobByte(_, _, ref r)
|
||||
| Self::StringChar(_, _, ref r) => r,
|
||||
}
|
||||
@ -719,7 +759,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::Bit(_, ref mut r, _)
|
||||
| Self::BitField(_, _, ref mut r, _)
|
||||
| Self::BlobByte(_, _, ref mut r)
|
||||
| Self::StringChar(_, _, ref mut r) => r,
|
||||
}
|
||||
@ -2078,6 +2119,67 @@ impl Engine {
|
||||
.unwrap_or_else(|| Target::from(Dynamic::UNIT)))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Int(value, _, _))
|
||||
if idx.is::<crate::ExclusiveRange>() || idx.is::<crate::InclusiveRange>() =>
|
||||
{
|
||||
#[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::<crate::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::<crate::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 +2187,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 +2762,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;
|
||||
}
|
||||
}
|
||||
|
||||
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 +2844,7 @@ impl Engine {
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loop
|
||||
@ -3163,6 +3297,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 {
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
162
src/optimizer.rs
162
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::<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() {
|
||||
*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"))]
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,32 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||
#[cfg(feature = "metadata")]
|
||||
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
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>"]);
|
||||
@ -472,7 +513,54 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||
#[cfg(feature = "metadata")]
|
||||
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator<Item=bool>"]);
|
||||
|
||||
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<Item=bool>"]);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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,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::<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();
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
59
tests/for.rs
59
tests/for.rs
@ -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,18 +269,37 @@ fn test_for_overflow() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_for_string() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
let script = r#"
|
||||
let s = "hello";
|
||||
let sum = 0;
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
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::<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(())
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
"#,
|
||||
|
@ -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
|
||||
));
|
||||
|
@ -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
|
||||
",
|
||||
)?;
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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
|
||||
"
|
||||
|
Loading…
Reference in New Issue
Block a user