Merge pull request #417 from schungx/master

Code cleanup updates.
This commit is contained in:
Stephen Chung 2021-06-12 10:40:15 +08:00 committed by GitHub
commit 8ddab4c8ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1158 additions and 845 deletions

View File

@ -8,11 +8,21 @@ Bug fixes
---------
* Fixed incorrect optimization regarding chain-indexing with non-numeric index.
* Variable values are checked for over-sized violations after assignments and setters.
Breaking changes
----------------
* To keep the API consistent, strings are no longer iterable by default. Use the `chars` method to iterator the characters in a string.
* `Dynamic::take_string` and `Dynamic::take_immutable_string` are renamed to `Dynamic::as_string` and `Dynamic::as_immutable_string` respectively.
New features
------------
* New syntax for `for` statement to include counter variable.
* An integer value can now be indexed to get/set a single bit.
* The `bits` method of an integer can be used to iterate through its bits.
* New `$bool$`, `$int$`, `$float$` and `$string$` expression types for custom syntax.
Version 0.20.2

View File

@ -18,9 +18,9 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"]
[dependencies]
smallvec = { version = "1.6", default-features = false, features = ["union"] }
ahash = { version = "0.7", default-features = false }
num-traits = { version = "0.2", default_features = false }
smartstring = { version = "0.2.6", default_features = false }
rhai_codegen = { version = "0.3.7", path = "codegen", default_features = false }
num-traits = { version = "0.2", default-features = false }
smartstring = { version = "0.2.6", default-features = false }
rhai_codegen = { version = "0.4.0", path = "codegen", default-features = false }
[features]
default = ["smartstring/std", "ahash/std", "num-traits/std"] # remove 'smartstring/std' when smartstring is updated to support no-std
@ -59,41 +59,41 @@ codegen-units = 1
[dependencies.no-std-compat]
version = "0.4"
default_features = false
default-features = false
features = ["alloc"]
optional = true
[dependencies.libm]
version = "0.2"
default_features = false
default-features = false
optional = true
[dependencies.core-error]
version = "0.0"
default_features = false
default-features = false
features = ["alloc"]
optional = true
[dependencies.serde]
version = "1.0"
default_features = false
default-features = false
features = ["derive", "alloc"]
optional = true
[dependencies.serde_json]
version = "1.0"
default_features = false
default-features = false
features = ["alloc"]
optional = true
[dependencies.unicode-xid]
version = "0.2"
default_features = false
default-features = false
optional = true
[dependencies.rust_decimal]
version = "1.14"
default_features = false
version = "1.14.2"
default-features = false
features = ["maths"]
optional = true

View File

@ -1,6 +1,6 @@
[package]
name = "rhai_codegen"
version = "0.3.7"
version = "0.4.0"
edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"

View File

@ -744,7 +744,7 @@ impl ExportedFn {
is_string = true;
is_ref = true;
quote_spanned!(arg_type.span() =>
mem::take(args[#i]).take_immutable_string().unwrap()
mem::take(args[#i]).as_immutable_string().unwrap()
)
}
_ => panic!("internal error: why wasn't this found earlier!?"),
@ -753,7 +753,7 @@ impl ExportedFn {
is_string = true;
is_ref = false;
quote_spanned!(arg_type.span() =>
mem::take(args[#i]).take_string().unwrap()
mem::take(args[#i]).as_string().unwrap()
)
}
_ => {

View File

@ -130,7 +130,7 @@ pub fn generate_body(
let mut namespace = FnNamespaceAccess::Internal;
match function.params().special {
FnSpecialAccess::None => {}
FnSpecialAccess::None => (),
FnSpecialAccess::Index(_) | FnSpecialAccess::Property(_) => {
let reg_name = fn_literal.value();
if reg_name.starts_with(FN_GET)

View File

@ -525,7 +525,7 @@ mod generate_tests {
impl PluginFunction for Token {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).take_immutable_string().unwrap();
let arg0 = mem::take(args[0usize]).as_immutable_string().unwrap();
Ok(Dynamic::from(special_print(&arg0)))
}

View File

@ -934,7 +934,7 @@ mod generate_tests {
impl PluginFunction for print_out_to_token {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).take_immutable_string().unwrap();
let arg0 = mem::take(args[0usize]).as_immutable_string().unwrap();
Ok(Dynamic::from(print_out_to(&arg0)))
}
@ -987,7 +987,7 @@ mod generate_tests {
impl PluginFunction for print_out_to_token {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).take_string().unwrap();
let arg0 = mem::take(args[0usize]).as_string().unwrap();
Ok(Dynamic::from(print_out_to(arg0)))
}

View File

@ -1,10 +1,10 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_fn_non_clonable_return.rs:11:8
|
11 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
::: $WORKSPACE/src/dynamic.rs
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from`
--> $DIR/rhai_fn_non_clonable_return.rs:11:8
|
11 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
::: $WORKSPACE/src/dynamic.rs
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from`

View File

@ -1,10 +1,10 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
12 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
::: $WORKSPACE/src/dynamic.rs
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from`
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
12 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
::: $WORKSPACE/src/dynamic.rs
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from`

View File

@ -1,10 +1,12 @@
// This script runs for-loops
let arr = [1, 2, 3, 4];
let arr = [1, true, 123.456, "hello", 3, 42];
for a in arr {
for b in [10, 20] {
print(`${a}, ${b}`);
for (a, i) in arr {
for (b, j) in ['x', 42, (), 123, 99, 0.5] {
if b > 100 { continue; }
print(`(${i}, ${j}) = (${a}, ${b})`);
}
if a == 3 { break; }
@ -12,6 +14,6 @@ for a in arr {
//print(a); // <- if you uncomment this line, the script will fail to run
// because 'a' is not defined here
for i in range(0, 5) { // runs through a range from 0 to 4
for i in range(5, 0, -1) { // runs from 5 down to 1
print(i);
}

View File

@ -12,6 +12,8 @@ for i in range(0, MAX) {
list.push(i);
}
print(`Time = ${now.elapsed} seconds...`);
let sum = 0;
for i in list {
@ -19,4 +21,4 @@ for i in list {
}
print(`Sum = ${sum}`);
print(`Finished. Run time = ${now.elapsed} seconds.`);
print(`Finished. Total run time = ${now.elapsed} seconds.`);

View File

@ -6,6 +6,6 @@ fn f(a, b, c, d, e, f) {
a - b * c - d * e - f + global::KEY
}
print("f() call should be 42:");
let result = f(100, 5, 2, 9, 6, 32);
print(f(100, 5, 2, 9, 6, 32));
print(`result should be 42: ${result}`);

View File

@ -0,0 +1,12 @@
// This script defines a function that acts as a method
// Use 'this' to refer to the object of a method call
fn action(x, y) {
this = this.abs() + x * y; // 'this' can be modified
}
let obj = -40;
obj.action(1, 2); // call 'action' as method
print(`obj should now be 42: ${obj}`);

10
scripts/if2.rhai Normal file
View File

@ -0,0 +1,10 @@
let a = 42;
let b = 123;
let x = if a <= b { // if-expression
b - a
} else {
a - b
} * 10;
print(`x should be 810: ${x}`);

11
scripts/switch.rhai Normal file
View File

@ -0,0 +1,11 @@
let arr = [42, 123.456, "hello", true, 'x', 999, 1];
for item in arr {
switch item {
42 => print("The Answer!"),
123.456 => print(`Floating point... ${item}`),
"hello" => print(`${item} world!`),
999 => print(`A number: ${item}`),
_ => print(`Something else: <${item}>`)
}
}

View File

@ -1,5 +1,6 @@
//! Module defining the AST (abstract syntax tree).
use crate::dynamic::Union;
use crate::fn_native::shared_make_mut;
use crate::module::NamespaceRef;
use crate::token::Token;
@ -90,7 +91,7 @@ impl fmt::Display for ScriptFnDef {
self.params
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.collect::<StaticVec<_>>()
.join(", ")
)
}
@ -138,7 +139,11 @@ impl fmt::Display for ScriptFnMetadata<'_> {
FnAccess::Private => "private ",
},
self.name,
self.params.iter().cloned().collect::<Vec<_>>().join(", ")
self.params
.iter()
.cloned()
.collect::<StaticVec<_>>()
.join(", ")
)
}
}
@ -215,10 +220,9 @@ impl AST {
statements: impl IntoIterator<Item = Stmt>,
functions: impl Into<Shared<Module>>,
) -> Self {
let statements: StaticVec<_> = statements.into_iter().collect();
Self {
source: None,
body: StmtBlock::new(statements, Position::NONE),
body: StmtBlock::new(statements.into_iter().collect(), Position::NONE),
functions: functions.into(),
#[cfg(not(feature = "no_module"))]
resolver: None,
@ -231,10 +235,9 @@ impl AST {
functions: impl Into<Shared<Module>>,
source: impl Into<Identifier>,
) -> Self {
let statements: StaticVec<_> = statements.into_iter().collect();
Self {
source: Some(source.into()),
body: StmtBlock::new(statements, Position::NONE),
body: StmtBlock::new(statements.into_iter().collect(), Position::NONE),
functions: functions.into(),
#[cfg(not(feature = "no_module"))]
resolver: None,
@ -417,15 +420,15 @@ impl AST {
///
/// let engine = Engine::new();
///
/// let ast1 = engine.compile(r#"
/// fn foo(x) { 42 + x }
/// foo(1)
/// "#)?;
/// let ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// foo("!")
/// "#)?;
/// fn foo(n) { `hello${n}` }
/// foo("!")
/// "#)?;
///
/// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1'
///
@ -469,15 +472,15 @@ impl AST {
///
/// let engine = Engine::new();
///
/// let mut ast1 = engine.compile(r#"
/// fn foo(x) { 42 + x }
/// foo(1)
/// "#)?;
/// let mut ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// foo("!")
/// "#)?;
/// fn foo(n) { `hello${n}` }
/// foo("!")
/// "#)?;
///
/// ast1.combine(ast2); // Combine 'ast2' into 'ast1'
///
@ -523,16 +526,16 @@ impl AST {
///
/// let engine = Engine::new();
///
/// let ast1 = engine.compile(r#"
/// fn foo(x) { 42 + x }
/// foo(1)
/// "#)?;
/// let ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// fn error() { 0 }
/// foo("!")
/// "#)?;
/// fn foo(n) { `hello${n}` }
/// fn error() { 0 }
/// foo("!")
/// "#)?;
///
/// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
/// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params|
@ -606,16 +609,16 @@ impl AST {
///
/// let engine = Engine::new();
///
/// let mut ast1 = engine.compile(r#"
/// fn foo(x) { 42 + x }
/// foo(1)
/// "#)?;
/// let mut ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// fn error() { 0 }
/// foo("!")
/// "#)?;
/// fn foo(n) { `hello${n}` }
/// fn error() { 0 }
/// foo("!")
/// "#)?;
///
/// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
/// ast1.combine_filtered(ast2, |_, _, script, name, params|
@ -664,9 +667,9 @@ impl AST {
/// let engine = Engine::new();
///
/// let mut ast = engine.compile(r#"
/// fn foo(n) { n + 1 }
/// fn bar() { print("hello"); }
/// "#)?;
/// fn foo(n) { n + 1 }
/// fn bar() { print("hello"); }
/// "#)?;
///
/// // Remove all functions except 'foo(_)'
/// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1);
@ -866,8 +869,7 @@ pub struct StmtBlock(StaticVec<Stmt>, Position);
impl StmtBlock {
/// Create a new [`StmtBlock`].
pub fn new(statements: impl Into<StaticVec<Stmt>>, pos: Position) -> Self {
let mut statements = statements.into();
pub fn new(mut statements: StaticVec<Stmt>, pos: Position) -> Self {
statements.shrink_to_fit();
Self(statements, pos)
}
@ -943,14 +945,14 @@ pub enum Stmt {
While(Expr, Box<StmtBlock>, Position),
/// `do` `{` stmt `}` `while`|`until` expr
Do(Box<StmtBlock>, Expr, bool, Position),
/// `for` id `in` expr `{` stmt `}`
For(Expr, Box<(Ident, StmtBlock)>, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
/// \[`export`\] `let` id `=` expr
Let(Expr, Box<Ident>, bool, Position),
/// \[`export`\] `const` id `=` expr
Const(Expr, Box<Ident>, bool, Position),
/// expr op`=` expr
Assignment(Box<(Expr, Option<OpAssignment>, Expr)>, Position),
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
/// func `(` expr `,` ... `)`
///
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
@ -1000,9 +1002,7 @@ impl From<Stmt> for StmtBlock {
#[inline(always)]
fn from(stmt: Stmt) -> Self {
match stmt {
Stmt::Block(mut block, pos) => {
Self(block.iter_mut().map(|v| mem::take(v)).collect(), pos)
}
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
Stmt::Noop(pos) => Self(Default::default(), pos),
_ => {
let pos = stmt.position();
@ -1167,7 +1167,7 @@ impl Stmt {
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
condition.is_pure() && block.0.iter().all(Stmt::is_pure)
}
Self::For(iterable, x, _) => iterable.is_pure() && (x.1).0.iter().all(Stmt::is_pure),
Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure),
Self::Let(_, _, _, _)
| Self::Const(_, _, _, _)
| Self::Assignment(_, _)
@ -1287,7 +1287,7 @@ impl Stmt {
if !e.walk(path, on_node) {
return false;
}
for s in &(x.1).0 {
for s in &(x.2).0 {
if !s.walk(path, on_node) {
return false;
}
@ -1384,14 +1384,14 @@ pub struct BinaryExpr {
/// # Volatile Data Structure
///
/// This type is volatile and may change.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct OpAssignment {
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct OpAssignment<'a> {
pub hash_op_assign: u64,
pub hash_op: u64,
pub op: &'static str,
pub op: &'a str,
}
impl OpAssignment {
impl OpAssignment<'_> {
/// Create a new [`OpAssignment`].
///
/// # Panics
@ -1520,7 +1520,7 @@ pub struct FnCallExpr {
/// List of function call argument expressions.
pub args: StaticVec<Expr>,
/// List of function call arguments that are constants.
pub literal_args: smallvec::SmallVec<[(Dynamic, Position); 2]>,
pub constants: smallvec::SmallVec<[Dynamic; 2]>,
/// Function name.
pub name: Identifier,
/// Does this function call capture the parent scope?
@ -1533,16 +1533,6 @@ impl FnCallExpr {
pub fn is_qualified(&self) -> bool {
self.namespace.is_some()
}
/// Are there no arguments to this function call?
#[inline(always)]
pub fn is_args_empty(&self) -> bool {
self.args.is_empty() && self.literal_args.is_empty()
}
/// Get the number of arguments to this function call.
#[inline(always)]
pub fn args_count(&self) -> usize {
self.args.len() + self.literal_args.len()
}
}
/// A type that wraps a floating-point number and implements [`Hash`].
@ -1719,6 +1709,8 @@ pub enum Expr {
(ImmutableString, Position),
)>,
),
/// Stack slot
Stack(usize, Position),
/// { [statement][Stmt] ... }
Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)`
@ -1776,14 +1768,15 @@ impl fmt::Debug for Expr {
Some((_, ref namespace)) => write!(f, "{}", namespace)?,
_ => (),
}
write!(f, "{}", x.2)?;
f.write_str(&x.2)?;
match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
Some(n) => write!(f, ", {}", n)?,
Some(n) => write!(f, " #{}", n)?,
_ => (),
}
f.write_str(")")
}
Self::Property(x) => write!(f, "Property({})", (x.2).0),
Self::Stack(x, _) => write!(f, "StackSlot({})", x),
Self::Stmt(x) => {
f.write_str("ExprStmtBlock")?;
f.debug_list().entries(x.0.iter()).finish()
@ -1794,8 +1787,8 @@ impl fmt::Debug for Expr {
ff.field("name", &x.name)
.field("hash", &x.hashes)
.field("args", &x.args);
if !x.literal_args.is_empty() {
ff.field("literal_args", &x.literal_args);
if !x.constants.is_empty() {
ff.field("constants", &x.constants);
}
if x.capture {
ff.field("capture", &x.capture);
@ -1826,11 +1819,11 @@ impl fmt::Debug for Expr {
}
impl Expr {
/// Get the [`Dynamic`] value of a constant expression.
/// Get the [`Dynamic`] value of a literal constant expression.
///
/// Returns [`None`] if the expression is not constant.
/// Returns [`None`] if the expression is not a literal constant.
#[inline]
pub fn get_constant_value(&self) -> Option<Dynamic> {
pub fn get_literal_value(&self) -> Option<Dynamic> {
Some(match self {
Self::DynamicConstant(x, _) => x.as_ref().clone(),
Self::IntegerConstant(x, _) => (*x).into(),
@ -1845,7 +1838,7 @@ impl Expr {
Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(x.len());
arr.extend(x.iter().map(|v| {
v.get_constant_value()
v.get_literal_value()
.expect("never fails because a constant array always has a constant value")
}));
Dynamic::from_array(arr)
@ -1857,7 +1850,7 @@ impl Expr {
x.0.iter().for_each(|(k, v)| {
*map.get_mut(k.name.as_str())
.expect("never fails because the template should contain all the keys") = v
.get_constant_value()
.get_literal_value()
.expect("never fails because a constant map always has a constant value")
});
Dynamic::from_map(map)
@ -1866,6 +1859,22 @@ impl Expr {
_ => return None,
})
}
/// Create an [`Expr`] from a [`Dynamic`] value.
#[inline]
pub fn from_dynamic(value: Dynamic, pos: Position) -> Self {
match value.0 {
Union::Unit(_, _, _) => Self::Unit(pos),
Union::Bool(b, _, _) => Self::BoolConstant(b, pos),
Union::Str(s, _, _) => Self::StringConstant(s, pos),
Union::Char(c, _, _) => Self::CharConstant(c, pos),
Union::Int(i, _, _) => Self::IntegerConstant(i, pos),
#[cfg(not(feature = "no_float"))]
Union::Float(f, _, _) => Self::FloatConstant(f, pos),
_ => Self::DynamicConstant(Box::new(value), pos),
}
}
/// Is the expression a simple variable access?
#[inline(always)]
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool {
@ -1898,6 +1907,7 @@ impl Expr {
| Self::Array(_, pos)
| Self::Map(_, pos)
| Self::Variable(_, pos, _)
| Self::Stack(_, pos)
| Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos,
@ -1936,6 +1946,7 @@ impl Expr {
| Self::Dot(_, pos)
| Self::Index(_, pos)
| Self::Variable(_, pos, _)
| Self::Stack(_, pos)
| Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos = new_pos,
@ -1965,7 +1976,7 @@ impl Expr {
Self::Stmt(x) => x.0.iter().all(Stmt::is_pure),
Self::Variable(_, _, _) => true,
Self::Variable(_, _, _) | Self::Stack(_, _) => true,
_ => self.is_constant(),
}
@ -1990,7 +2001,8 @@ impl Expr {
| Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::StringConstant(_, _)
| Self::Unit(_) => true,
| Self::Unit(_)
| Self::Stack(_, _) => true,
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant),
@ -2050,6 +2062,8 @@ impl Expr {
},
Self::Custom(_, _) => false,
Self::Stack(_, _) => unreachable!("Expr::Stack should not occur naturally"),
}
}
/// Recursively walk this expression.

View File

@ -33,6 +33,8 @@ use fmt::Debug;
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
use instant::Instant;
const CHECKED: &str = "never fails because the type was checked";
mod private {
use crate::fn_native::SendSync;
use std::any::Any;
@ -471,29 +473,27 @@ impl Dynamic {
}
impl Hash for Dynamic {
/// Hash the [`Dynamic`] value.
///
/// # Panics
///
/// Panics if the [`Dynamic`] value contains an unrecognized trait object.
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(&self.0).hash(state);
match &self.0 {
Union::Unit(_, _, _) => ().hash(state),
Union::Bool(value, _, _) => value.hash(state),
Union::Bool(b, _, _) => b.hash(state),
Union::Str(s, _, _) => s.hash(state),
Union::Char(ch, _, _) => ch.hash(state),
Union::Char(c, _, _) => c.hash(state),
Union::Int(i, _, _) => i.hash(state),
#[cfg(not(feature = "no_float"))]
Union::Float(f, _, _) => f.hash(state),
#[cfg(not(feature = "no_index"))]
Union::Array(a, _, _) => a.as_ref().hash(state),
#[cfg(not(feature = "no_object"))]
Union::Map(m, _, _) => m.iter().for_each(|(key, value)| {
key.hash(state);
value.hash(state);
}),
Union::FnPtr(f, _, _) if f.is_curried() => {
unimplemented!(
"{} with curried arguments cannot be hashed",
self.type_name()
)
}
Union::FnPtr(f, _, _) => f.fn_name().hash(state),
Union::Map(m, _, _) => m.as_ref().hash(state),
Union::FnPtr(f, _, _) => f.hash(state),
#[cfg(not(feature = "no_closure"))]
Union::Shared(cell, _, _) => {
@ -505,6 +505,55 @@ impl Hash for Dynamic {
(*value).hash(state)
}
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
Union::Variant(value, _, _) => {
let value = value.as_ref().as_ref();
let _type_id = value.type_id();
let _value_any = value.as_any();
if _type_id == TypeId::of::<u8>() {
TypeId::of::<u8>().hash(state);
_value_any.downcast_ref::<u8>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<u16>() {
TypeId::of::<u16>().hash(state);
_value_any.downcast_ref::<u16>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<u32>() {
TypeId::of::<u32>().hash(state);
_value_any.downcast_ref::<u32>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<u64>() {
TypeId::of::<u64>().hash(state);
_value_any.downcast_ref::<u64>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<i8>() {
TypeId::of::<i8>().hash(state);
_value_any.downcast_ref::<i8>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<i16>() {
TypeId::of::<i16>().hash(state);
_value_any.downcast_ref::<i16>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<i32>() {
TypeId::of::<i32>().hash(state);
_value_any.downcast_ref::<i32>().expect(CHECKED).hash(state);
} else if _type_id == TypeId::of::<i64>() {
TypeId::of::<i64>().hash(state);
_value_any.downcast_ref::<i64>().expect(CHECKED).hash(state);
}
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if _type_id == TypeId::of::<u128>() {
TypeId::of::<u128>().hash(state);
_value_any
.downcast_ref::<u128>()
.expect(CHECKED)
.hash(state);
} else if _type_id == TypeId::of::<i128>() {
TypeId::of::<i128>().hash(state);
_value_any
.downcast_ref::<i128>()
.expect(CHECKED)
.hash(state);
}
}
_ => unimplemented!("{} cannot be hashed", self.type_name()),
}
}
@ -573,8 +622,6 @@ impl fmt::Display for Dynamic {
let _type_id = value.type_id();
let _value_any = value.as_any();
const CHECKED: &str = "never fails because the type was checked";
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() {
@ -692,7 +739,7 @@ impl fmt::Debug for Dynamic {
return fmt::Debug::fmt(_value_any.downcast_ref::<i128>().expect(CHECKED), f);
}
write!(f, "{}", value.type_name())
f.write_str(value.type_name())
}
#[cfg(not(feature = "no_closure"))]
@ -1749,15 +1796,45 @@ impl Dynamic {
/// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated and will be removed in the future.
/// Use [`as_string`][Dynamic::as_string] instead.
#[inline(always)]
#[deprecated(
since = "0.20.3",
note = "this method is deprecated and will be removed in the future"
)]
pub fn take_string(self) -> Result<String, &'static str> {
self.take_immutable_string()
.map(ImmutableString::into_owned)
self.as_string()
}
/// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails.
#[inline(always)]
pub fn as_string(self) -> Result<String, &'static str> {
self.as_immutable_string().map(ImmutableString::into_owned)
}
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated and will be removed in the future.
/// Use [`as_immutable_string`][Dynamic::as_immutable_string] instead.
#[inline(always)]
#[deprecated(
since = "0.20.3",
note = "this method is deprecated and will be removed in the future"
)]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
self.as_immutable_string()
}
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails.
#[inline(always)]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
pub fn as_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 {
Union::Str(s, _, _) => Ok(s),
#[cfg(not(feature = "no_closure"))]

View File

@ -14,7 +14,7 @@ use crate::token::Token;
use crate::utils::get_hasher;
use crate::{
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
Shared, StaticVec,
Shared, StaticVec, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -328,13 +328,13 @@ impl From<(Dynamic, Position)> for ChainArgument {
#[derive(Debug)]
pub enum Target<'a> {
/// The target is a mutable reference to a `Dynamic` value somewhere.
Ref(&'a mut Dynamic),
RefMut(&'a mut Dynamic),
/// The target is a mutable reference to a Shared `Dynamic` value.
/// It holds both the access guard and the original shared value.
#[cfg(not(feature = "no_closure"))]
LockGuard((crate::dynamic::DynamicWriteLock<'a, Dynamic>, Dynamic)),
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
Value(Dynamic),
TempValue(Dynamic),
/// 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"))]
@ -351,25 +351,24 @@ impl<'a> Target<'a> {
#[inline(always)]
pub fn is_ref(&self) -> bool {
match self {
Self::Ref(_) => true,
Self::RefMut(_) => true,
#[cfg(not(feature = "no_closure"))]
Self::LockGuard(_) => true,
Self::Value(_) => false,
Self::TempValue(_) => false,
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, _) => false,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false,
}
}
/// Is the `Target` an owned value?
#[allow(dead_code)]
/// Is the `Target` a temp value?
#[inline(always)]
pub fn is_value(&self) -> bool {
pub fn is_temp_value(&self) -> bool {
match self {
Self::Ref(_) => false,
Self::RefMut(_) => false,
#[cfg(not(feature = "no_closure"))]
Self::LockGuard(_) => false,
Self::Value(_) => true,
Self::TempValue(_) => true,
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, _) => false,
#[cfg(not(feature = "no_index"))]
@ -381,10 +380,10 @@ impl<'a> Target<'a> {
#[inline(always)]
pub fn is_shared(&self) -> bool {
match self {
Self::Ref(r) => r.is_shared(),
Self::RefMut(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
Self::LockGuard(_) => true,
Self::Value(r) => r.is_shared(),
Self::TempValue(r) => r.is_shared(),
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, _) => false,
#[cfg(not(feature = "no_index"))]
@ -396,10 +395,10 @@ impl<'a> Target<'a> {
#[inline(always)]
pub fn is<T: Variant + Clone>(&self) -> bool {
match self {
Self::Ref(r) => r.is::<T>(),
Self::RefMut(r) => r.is::<T>(),
#[cfg(not(feature = "no_closure"))]
Self::LockGuard((r, _)) => r.is::<T>(),
Self::Value(r) => r.is::<T>(),
Self::TempValue(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, _) => TypeId::of::<T>() == TypeId::of::<bool>(),
#[cfg(not(feature = "no_index"))]
@ -410,10 +409,10 @@ impl<'a> Target<'a> {
#[inline(always)]
pub fn take_or_clone(self) -> Dynamic {
match self {
Self::Ref(r) => r.clone(), // Referenced value is cloned
Self::RefMut(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))]
Self::LockGuard((_, orig)) => orig, // Original value is simply taken
Self::Value(v) => v, // Owned 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
#[cfg(not(feature = "no_index"))]
@ -424,7 +423,7 @@ impl<'a> Target<'a> {
#[inline(always)]
pub fn take_ref(self) -> Option<&'a mut Dynamic> {
match self {
Self::Ref(r) => Some(r),
Self::RefMut(r) => Some(r),
_ => None,
}
}
@ -438,47 +437,24 @@ impl<'a> Target<'a> {
#[inline(always)]
pub fn propagate_changed_value(&mut self) -> Result<(), Box<EvalAltResult>> {
match self {
Self::Ref(_) | Self::Value(_) => Ok(()),
Self::RefMut(_) | Self::TempValue(_) => (),
#[cfg(not(feature = "no_closure"))]
Self::LockGuard(_) => Ok(()),
Self::LockGuard(_) => (),
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, value) => {
let new_val = value.clone();
self.set_value(new_val, Position::NONE)
}
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ch) => {
let new_val = ch.clone();
self.set_value(new_val, Position::NONE)
}
}
}
/// Update the value of the `Target`.
pub fn set_value(
&mut self,
new_val: Dynamic,
_pos: Position,
) -> Result<(), Box<EvalAltResult>> {
match self {
Self::Ref(r) => **r = new_val,
#[cfg(not(feature = "no_closure"))]
Self::LockGuard((r, _)) => **r = new_val,
Self::Value(_) => panic!("cannot update a value"),
#[cfg(not(feature = "no_index"))]
Self::BitField(value, index, _) => {
let value = &mut *value
.write_lock::<crate::INT>()
.expect("never fails because `BitField` always holds an `INT`");
Self::BitField(value, index, new_val) => {
// Replace the bit at the specified index position
let new_bit = new_val.as_bool().map_err(|err| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"bool".to_string(),
err.to_string(),
_pos,
Position::NONE,
))
})?;
let value = &mut *value
.write_lock::<crate::INT>()
.expect("never fails because `BitField` always holds an `INT`");
let index = *index;
if index < std::mem::size_of_val(value) * 8 {
@ -488,23 +464,25 @@ impl<'a> Target<'a> {
} else {
*value &= !mask;
}
} else {
unreachable!("bit-field index out of bounds: {}", index);
}
}
#[cfg(not(feature = "no_index"))]
Self::StringChar(s, index, _) => {
let s = &mut *s
.write_lock::<ImmutableString>()
.expect("never fails because `StringChar` always holds an `ImmutableString`");
Self::StringChar(s, index, new_val) => {
// Replace the character at the specified index position
let new_ch = new_val.as_char().map_err(|err| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"char".to_string(),
err.to_string(),
_pos,
Position::NONE,
))
})?;
let s = &mut *s
.write_lock::<ImmutableString>()
.expect("never fails because `StringChar` always holds an `ImmutableString`");
let index = *index;
*s = s
@ -534,7 +512,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
));
}
Self::Ref(value)
Self::RefMut(value)
}
}
@ -544,10 +522,10 @@ impl Deref for Target<'_> {
#[inline(always)]
fn deref(&self) -> &Dynamic {
match self {
Self::Ref(r) => *r,
Self::RefMut(r) => *r,
#[cfg(not(feature = "no_closure"))]
Self::LockGuard((r, _)) => &**r,
Self::Value(ref r) => r,
Self::TempValue(ref r) => r,
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, ref r) => r,
#[cfg(not(feature = "no_index"))]
@ -567,10 +545,10 @@ impl DerefMut for Target<'_> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Dynamic {
match self {
Self::Ref(r) => *r,
Self::RefMut(r) => *r,
#[cfg(not(feature = "no_closure"))]
Self::LockGuard((r, _)) => r.deref_mut(),
Self::Value(ref mut r) => r,
Self::TempValue(ref mut r) => r,
#[cfg(not(feature = "no_index"))]
Self::BitField(_, _, ref mut r) => r,
#[cfg(not(feature = "no_index"))]
@ -589,7 +567,7 @@ impl AsMut<Dynamic> for Target<'_> {
impl<T: Into<Dynamic>> From<T> for Target<'_> {
#[inline(always)]
fn from(value: T) -> Self {
Self::Value(value.into())
Self::TempValue(value.into())
}
}
@ -1238,40 +1216,45 @@ impl Engine {
}
// xxx[rhs] op= new_val
_ if new_val.is_some() => {
let ((mut new_val, new_pos), (op_info, op_pos)) =
let ((new_val, new_pos), (op_info, op_pos)) =
new_val.expect("never fails because `new_val` is `Some`");
let idx_val = idx_val.as_index_value();
#[cfg(not(feature = "no_index"))]
let mut idx_val_for_setter = idx_val.clone();
match self.get_indexed_mut(
let try_setter = match self.get_indexed_mut(
mods, state, lib, target, idx_val, pos, true, false, level,
) {
// Indexed value is a reference - update directly
Ok(obj_ptr) => {
Ok(ref mut obj_ptr) => {
self.eval_op_assignment(
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val,
new_pos,
)?;
return Ok((Dynamic::UNIT, true));
)
.map_err(|err| err.fill_position(new_pos))?;
None
}
// Can't index - try to call an index setter
#[cfg(not(feature = "no_index"))]
Err(err) if matches!(*err, EvalAltResult::ErrorIndexingType(_, _)) => {}
Err(err) if matches!(*err, EvalAltResult::ErrorIndexingType(_, _)) => {
Some(new_val)
}
// Any other error
Err(err) => return Err(err),
};
if let Some(mut new_val) = try_setter {
// Try to call index setter
let hash_set =
FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3));
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true,
new_pos, None, level,
)?;
}
// Try to call index setter
let hash_set =
FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3));
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, new_pos,
None, level,
)?;
self.check_data_size(target.as_ref())
.map_err(|err| err.fill_position(root.1))?;
Ok((Dynamic::UNIT, true))
}
@ -1308,15 +1291,20 @@ impl Engine {
// {xxx:map}.id op= ???
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
let (name, pos) = &x.2;
let index = name.into();
let val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, true, false, level,
)?;
let ((new_val, new_pos), (op_info, op_pos)) =
new_val.expect("never fails because `new_val` is `Some`");
self.eval_op_assignment(
mods, state, lib, op_info, op_pos, val, root, new_val, new_pos,
)?;
let index = name.into();
{
let mut val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, true, false, level,
)?;
self.eval_op_assignment(
mods, state, lib, op_info, op_pos, &mut val, root, new_val,
)
.map_err(|err| err.fill_position(new_pos))?;
}
self.check_data_size(target.as_ref())
.map_err(|err| err.fill_position(root.1))?;
Ok((Dynamic::UNIT, true))
}
// {xxx:map}.id
@ -1326,7 +1314,6 @@ impl Engine {
let val = self.get_indexed_mut(
mods, state, lib, target, index, *pos, false, false, level,
)?;
Ok((val.take_or_clone(), false))
}
// xxx.id op= ???
@ -1361,10 +1348,22 @@ impl Engine {
}
_ => Err(err),
})?;
let obj_ptr = (&mut orig_val).into();
self.eval_op_assignment(
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos,
)?;
mods,
state,
lib,
op_info,
op_pos,
&mut (&mut orig_val).into(),
root,
new_val,
)
.map_err(|err| err.fill_position(new_pos))?;
self.check_data_size(target.as_ref())
.map_err(|err| err.fill_position(root.1))?;
new_val = orig_val;
}
@ -1547,6 +1546,8 @@ impl Engine {
_ => Err(err),
},
)?;
self.check_data_size(target.as_ref())
.map_err(|err| err.fill_position(root.1))?;
}
Ok((result, may_be_changed))
@ -1667,23 +1668,19 @@ impl Engine {
match expr {
#[cfg(not(feature = "no_object"))]
Expr::FnCall(x, _) if _parent_chain_type == ChainType::Dot && !x.is_qualified() => {
let arg_values = x
.args
.iter()
.map(|arg_expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
.map(Dynamic::flatten)
})
.chain(x.literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<StaticVec<_>, _>>()?;
let crate::ast::FnCallExpr {
args, constants, ..
} = x.as_ref();
let mut arg_values = StaticVec::with_capacity(args.len());
let pos = x
.args
.iter()
.map(|arg_expr| arg_expr.position())
.chain(x.literal_args.iter().map(|(_, pos)| *pos))
.next()
.unwrap_or_default();
for index in 0..args.len() {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args, constants, index,
)?;
arg_values.push(value.flatten());
}
let pos = x.args.get(0).map(Expr::position).unwrap_or_default();
idx_values.push((arg_values, pos).into());
}
@ -1713,23 +1710,19 @@ impl Engine {
Expr::FnCall(x, _)
if _parent_chain_type == ChainType::Dot && !x.is_qualified() =>
{
let arg_values = x
.args
.iter()
.map(|arg_expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
.map(Dynamic::flatten)
})
.chain(x.literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<StaticVec<_>, _>>()?;
let crate::ast::FnCallExpr {
args, constants, ..
} = x.as_ref();
let mut arg_values = StaticVec::with_capacity(args.len());
let pos = x
.args
.iter()
.map(|arg_expr| arg_expr.position())
.chain(x.literal_args.iter().map(|(_, pos)| *pos))
.next()
.unwrap_or_default();
for index in 0..args.len() {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args, constants, index,
)?;
arg_values.push(value.flatten());
}
let pos = x.args.get(0).map(Expr::position).unwrap_or_default();
(arg_values, pos).into()
}
@ -1779,7 +1772,7 @@ impl Engine {
}
/// Get the value at the indexed position of a base type.
/// [`Position`] in [`EvalAltResult`] may be None and should be set afterwards.
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn get_indexed_mut<'t>(
&self,
@ -2000,18 +1993,23 @@ impl Engine {
for expr in x.iter() {
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
self.eval_op_assignment(
mods,
state,
lib,
Some(OpAssignment::new(TOKEN_OP_CONCAT)),
pos,
(&mut result).into(),
&mut (&mut result).into(),
("", Position::NONE),
item,
expr.position(),
)?;
)
.map_err(|err| err.fill_position(expr.position()))?;
pos = expr.position();
self.check_data_size(&result)
.map_err(|err| err.fill_position(pos))?;
}
assert!(
@ -2055,7 +2053,7 @@ impl Engine {
namespace,
hashes,
args,
literal_args: c_args,
constants,
..
} = x.as_ref();
let namespace = namespace
@ -2063,8 +2061,8 @@ impl Engine {
.expect("never fails because function call is qualified");
let hash = hashes.native_hash();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
level,
scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash,
*pos, level,
)
}
@ -2075,12 +2073,12 @@ impl Engine {
capture,
hashes,
args,
literal_args: c_args,
constants,
..
} = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture,
level,
scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos,
*capture, level,
)
}
@ -2136,11 +2134,8 @@ impl Engine {
_ => unreachable!("expression cannot be evaluated: {:?}", expr),
};
#[cfg(not(feature = "unchecked"))]
self.check_data_size(&result)
.map_err(|err| err.fill_position(expr.position()))?;
result
self.check_return_value(result)
.map_err(|err| err.fill_position(expr.position()))
}
/// Evaluate a statements block.
@ -2219,6 +2214,8 @@ impl Engine {
result
}
/// Evaluate an op-assignment statement.
/// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and should be set afterwards.
pub(crate) fn eval_op_assignment(
&self,
mods: &mut Imports,
@ -2226,10 +2223,9 @@ impl Engine {
lib: &[&Module],
op_info: Option<OpAssignment>,
op_pos: Position,
mut target: Target,
target: &mut Target,
root: (&str, Position),
mut new_val: Dynamic,
new_val_pos: Position,
) -> Result<(), Box<EvalAltResult>> {
if target.is_read_only() {
// Assignment to constant variable
@ -2279,14 +2275,12 @@ impl Engine {
err => return err.map(|_| ()),
}
}
target.propagate_changed_value()?;
} else {
// Normal assignment
target.set_value(new_val, new_val_pos)?;
*target.as_mut() = new_val;
}
Ok(())
target.propagate_changed_value()
}
/// Evaluate a statement.
@ -2323,7 +2317,7 @@ impl Engine {
let rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten();
let (lhs_ptr, pos) =
let (mut lhs_ptr, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
let var_name = lhs_expr
@ -2342,13 +2336,19 @@ impl Engine {
mods,
state,
lib,
op_info.clone(),
*op_info,
*op_pos,
lhs_ptr,
&mut lhs_ptr,
(var_name, pos),
rhs_val,
rhs_expr.position(),
)?;
)
.map_err(|err| err.fill_position(rhs_expr.position()))?;
if op_info.is_some() {
self.check_data_size(lhs_ptr.as_ref())
.map_err(|err| err.fill_position(lhs_expr.position()))?;
}
Ok(Dynamic::UNIT)
}
@ -2521,7 +2521,7 @@ impl Engine {
// For loop
Stmt::For(expr, x, _) => {
let (Ident { name, .. }, statements) = x.as_ref();
let (Ident { name, .. }, counter, statements) = x.as_ref();
let iter_obj = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten();
@ -2550,17 +2550,40 @@ impl Engine {
});
if let Some(func) = func {
// Add the loop variable
let var_name: Cow<'_, str> = if state.is_global() {
name.to_string().into()
// Add the loop variables
let orig_scope_len = scope.len();
let counter_index = if let Some(Ident { name, .. }) = counter {
scope.push(unsafe_cast_var_name_to_lifetime(name), 0 as INT);
Some(scope.len() - 1)
} else {
unsafe_cast_var_name_to_lifetime(name).into()
None
};
scope.push(var_name, ());
scope.push(unsafe_cast_var_name_to_lifetime(name), ());
let index = scope.len() - 1;
state.scope_level += 1;
for iter_value in func(iter_obj) {
for (x, iter_value) in func(iter_obj).enumerate() {
// Increment counter
if let Some(c) = counter_index {
#[cfg(not(feature = "unchecked"))]
if x > INT::MAX as usize {
return EvalAltResult::ErrorArithmetic(
format!("for-loop counter overflow: {}", x),
counter
.as_ref()
.expect("never fails because `counter` is `Some`")
.pos,
)
.into();
}
let mut counter_var = scope
.get_mut_by_index(c)
.write_lock::<INT>()
.expect("never fails because the counter always holds an `INT`");
*counter_var = x as INT;
}
let loop_var = scope.get_mut_by_index(index);
let value = iter_value.flatten();
@ -2600,7 +2623,7 @@ impl Engine {
}
state.scope_level -= 1;
scope.rewind(scope.len() - 1);
scope.rewind(orig_scope_len);
Ok(Dynamic::UNIT)
} else {
EvalAltResult::ErrorFor(expr.position()).into()
@ -2620,7 +2643,7 @@ impl Engine {
namespace,
hashes,
args,
literal_args: c_args,
constants,
..
} = x.as_ref();
let namespace = namespace
@ -2628,8 +2651,8 @@ impl Engine {
.expect("never fails because function call is qualified");
let hash = hashes.native_hash();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
level,
scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash,
*pos, level,
)
}
@ -2640,12 +2663,12 @@ impl Engine {
capture,
hashes,
args,
literal_args: c_args,
constants,
..
} = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture,
level,
scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos,
*capture, level,
)
}
@ -2672,8 +2695,6 @@ impl Engine {
}
#[cfg(not(feature = "no_object"))]
_ => {
use crate::INT;
let mut err_map: Map = Default::default();
let err_pos = err.take_position();
@ -2904,20 +2925,73 @@ impl Engine {
}
};
#[cfg(not(feature = "unchecked"))]
self.check_data_size(&result)
.map_err(|err| err.fill_position(stmt.position()))?;
self.check_return_value(result)
.map_err(|err| err.fill_position(stmt.position()))
}
/// Check a result to ensure that the data size is within allowable limit.
#[cfg(feature = "unchecked")]
#[inline(always)]
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
result
}
/// Check a result to ensure that the data size is within allowable limit.
#[cfg(not(feature = "unchecked"))]
fn check_data_size(&self, result: &RhaiResult) -> Result<(), Box<EvalAltResult>> {
let result = match result {
Err(_) => return Ok(()),
Ok(r) => r,
};
#[inline(always)]
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
result.and_then(|r| self.check_data_size(&r).map(|_| r))
}
#[cfg(feature = "unchecked")]
#[inline(always)]
fn check_data_size(&self, _value: &Dynamic) -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[cfg(not(feature = "unchecked"))]
fn check_data_size(&self, value: &Dynamic) -> Result<(), Box<EvalAltResult>> {
// Recursively calculate the size of a value (especially `Array` and `Map`)
fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
match value.0 {
#[cfg(not(feature = "no_index"))]
Union::Array(ref arr, _, _) => {
arr.iter()
.fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 {
Union::Array(_, _, _) => {
let (a, m, s) = calc_size(value);
(arrays + a + 1, maps + m, strings + s)
}
#[cfg(not(feature = "no_object"))]
Union::Map(_, _, _) => {
let (a, m, s) = calc_size(value);
(arrays + a + 1, maps + m, strings + s)
}
Union::Str(ref s, _, _) => (arrays + 1, maps, strings + s.len()),
_ => (arrays + 1, maps, strings),
})
}
#[cfg(not(feature = "no_object"))]
Union::Map(ref map, _, _) => {
map.values()
.fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 {
#[cfg(not(feature = "no_index"))]
Union::Array(_, _, _) => {
let (a, m, s) = calc_size(value);
(arrays + a, maps + m + 1, strings + s)
}
Union::Map(_, _, _) => {
let (a, m, s) = calc_size(value);
(arrays + a, maps + m + 1, strings + s)
}
Union::Str(ref s, _, _) => (arrays, maps + 1, strings + s.len()),
_ => (arrays, maps + 1, strings),
})
}
Union::Str(ref s, _, _) => (0, 0, s.len()),
_ => (0, 0, 0),
}
}
// If no data size limits, just return
let mut _has_limit = self.limits.max_string_size.is_some();
@ -2934,59 +3008,7 @@ impl Engine {
return Ok(());
}
// Recursively calculate the size of a value (especially `Array` and `Map`)
fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
match value {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr, _, _)) => {
let mut arrays = 0;
let mut maps = 0;
arr.iter().for_each(|value| match value {
Dynamic(Union::Array(_, _, _)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
}
#[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(_, _, _)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
}
_ => arrays += 1,
});
(arrays, maps, 0)
}
#[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map, _, _)) => {
let mut arrays = 0;
let mut maps = 0;
map.values().for_each(|value| match value {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(_, _, _)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
}
Dynamic(Union::Map(_, _, _)) => {
let (a, m, _) = calc_size(value);
arrays += a;
maps += m;
}
_ => maps += 1,
});
(arrays, maps, 0)
}
Dynamic(Union::Str(s, _, _)) => (0, 0, s.len()),
_ => (0, 0, 0),
}
}
let (_arr, _map, s) = calc_size(result);
let (_arr, _map, s) = calc_size(value);
if s > self
.limits

View File

@ -538,8 +538,9 @@ impl Engine {
///
/// # Panics
///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
/// Indexers for arrays, object maps and strings cannot be registered.
/// Panics if the type is [`Array`], [`Map`], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
///
/// # Example
///
@ -595,6 +596,9 @@ impl Engine {
{
panic!("Cannot register indexer for strings.");
}
if TypeId::of::<T>() == TypeId::of::<crate::INT>() {
panic!("Cannot register indexer for integers.");
}
self.register_fn(crate::engine::FN_IDX_GET, get_fn)
}
@ -606,8 +610,9 @@ impl Engine {
///
/// # Panics
///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
/// Indexers for arrays, object maps and strings cannot be registered.
/// Panics if the type is [`Array`], [`Map`], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
///
/// # Example
///
@ -669,6 +674,9 @@ impl Engine {
{
panic!("Cannot register indexer for strings.");
}
if TypeId::of::<T>() == TypeId::of::<crate::INT>() {
panic!("Cannot register indexer for integers.");
}
self.register_result_fn(crate::engine::FN_IDX_GET, get_fn)
}
@ -678,8 +686,9 @@ impl Engine {
///
/// # Panics
///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
/// Indexers for arrays, object maps and strings cannot be registered.
/// Panics if the type is [`Array`], [`Map`], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
///
/// # Example
///
@ -737,6 +746,9 @@ impl Engine {
{
panic!("Cannot register indexer for strings.");
}
if TypeId::of::<T>() == TypeId::of::<crate::INT>() {
panic!("Cannot register indexer for integers.");
}
self.register_fn(crate::engine::FN_IDX_SET, set_fn)
}
@ -746,8 +758,9 @@ impl Engine {
///
/// # Panics
///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
/// Indexers for arrays, object maps and strings cannot be registered.
/// Panics if the type is [`Array`], [`Map`], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
///
/// # Example
///
@ -812,6 +825,9 @@ impl Engine {
{
panic!("Cannot register indexer for strings.");
}
if TypeId::of::<T>() == TypeId::of::<crate::INT>() {
panic!("Cannot register indexer for integers.");
}
self.register_result_fn(crate::engine::FN_IDX_SET, set_fn)
}
@ -821,8 +837,9 @@ impl Engine {
///
/// # Panics
///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
/// Indexers for arrays, object maps and strings cannot be registered.
/// Panics if the type is [`Array`], [`Map`], [`String`],
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
/// Indexers for arrays, object maps, strings and integers cannot be registered.
///
/// # Example
///
@ -1337,7 +1354,8 @@ impl Engine {
///
/// let map = engine.parse_json(
/// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"#
/// .replace("{", "#{").as_str(), true)?;
/// .replace("{", "#{").as_str(),
/// true)?;
///
/// assert_eq!(map.len(), 4);
/// assert_eq!(map["a"].as_int().unwrap(), 123);
@ -1876,7 +1894,7 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
name: impl AsRef<str>,
args: impl crate::fn_args::FuncArgs,
args: impl crate::FuncArgs,
) -> Result<T, Box<EvalAltResult>> {
let mut arg_values: crate::StaticVec<_> = Default::default();
args.parse(&mut arg_values);
@ -1886,14 +1904,14 @@ impl Engine {
let typ = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
result.try_cast().ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
)
.into()
});
})
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
/// and optionally a value for binding to the `this` pointer.

View File

@ -40,11 +40,12 @@ pub trait FuncArgs {
/// let engine = Engine::new();
/// let mut scope = Scope::new();
///
/// let ast = engine.compile(r#"
/// fn hello(x, y, z) {
/// if x { `hello ${y}` } else { y + z }
/// }
/// "#)?;
/// let ast = engine.compile(
/// "
/// fn hello(x, y, z) {
/// if x { `hello ${y}` } else { y + z }
/// }
/// ")?;
///
/// let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?;
///

View File

@ -17,14 +17,13 @@ use crate::{
};
use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec,
Identifier, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
any::{type_name, TypeId},
convert::TryFrom,
iter::once,
mem,
};
@ -140,7 +139,7 @@ impl Engine {
} else {
self.map_type_name((*a).type_name())
})
.collect::<Vec<_>>()
.collect::<StaticVec<_>>()
.join(", ")
)
}
@ -332,6 +331,7 @@ impl Engine {
.as_ref()
.or_else(|| state_source.as_ref())
.map(|s| s.as_str());
let result = if func.is_plugin_fn() {
func.get_plugin_fn()
.call((self, fn_name, source, mods, lib).into(), args)
@ -347,7 +347,7 @@ impl Engine {
// See if the function match print/debug (which requires special processing)
return Ok(match fn_name {
KEYWORD_PRINT => {
let text = result.take_immutable_string().map_err(|typ| {
let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
@ -357,7 +357,7 @@ impl Engine {
((self.print)(&text).into(), false)
}
KEYWORD_DEBUG => {
let text = result.take_immutable_string().map_err(|typ| {
let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
@ -515,11 +515,12 @@ impl Engine {
);
// Merge in encapsulated environment, if any
let lib_merged: StaticVec<_>;
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
let (unified_lib, unified) = if let Some(ref env_lib) = fn_def.lib {
state.push_fn_resolution_cache();
lib_merged = once(env_lib.as_ref()).chain(lib.iter().cloned()).collect();
lib_merged.push(env_lib.as_ref());
lib_merged.extend(lib.iter().cloned());
(lib_merged.as_ref(), true)
} else {
(lib, false)
@ -627,6 +628,12 @@ impl Engine {
_capture_scope: Option<Scope>,
_level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
#[inline(always)]
fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let msg = format!("'{0}' should not be called this way. Try {0}(...);", name);
EvalAltResult::ErrorRuntime(msg.into(), pos).into()
}
// Check for data race.
#[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, args, is_ref)?;
@ -638,7 +645,7 @@ impl Engine {
return Ok((
self.map_type_name(args[0].type_name()).to_string().into(),
false,
));
))
}
// Handle is_def_fn()
@ -668,39 +675,15 @@ impl Engine {
// Handle is_shared()
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => {
return EvalAltResult::ErrorRuntime(
format!(
"'{}' should not be called this way. Try {}(...);",
fn_name, fn_name
)
.into(),
pos,
)
.into()
return no_method_err(fn_name, pos)
}
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => {
return EvalAltResult::ErrorRuntime(
format!(
"'{}' should not be called this way. Try {}(...);",
fn_name, fn_name
)
.into(),
pos,
)
.into()
return no_method_err(fn_name, pos)
}
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => {
return EvalAltResult::ErrorRuntime(
format!(
"'{}' should not be called this way. Try {}(...);",
fn_name, fn_name
)
.into(),
pos,
)
.into()
return no_method_err(fn_name, pos)
}
_ => (),
@ -798,17 +781,8 @@ impl Engine {
}
// Native function call
self.call_native_fn(
mods,
state,
lib,
fn_name,
hash.native_hash(),
args,
is_ref,
false,
pos,
)
let hash = hash.native_hash();
self.call_native_fn(mods, state, lib, fn_name, hash, args, is_ref, false, pos)
}
/// Evaluate a list of statements with no `this` pointer.
@ -911,8 +885,11 @@ impl Engine {
// Recalculate hashes
let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len));
// Arguments are passed as-is, adding the curried arguments
let mut curry: StaticVec<_> = fn_ptr.curry().iter().cloned().collect();
let mut args: StaticVec<_> = curry.iter_mut().chain(call_args.iter_mut()).collect();
let mut curry = StaticVec::with_capacity(fn_ptr.num_curried());
curry.extend(fn_ptr.curry().iter().cloned());
let mut args = StaticVec::with_capacity(curry.len() + call_args.len());
args.extend(curry.iter_mut());
args.extend(call_args.iter_mut());
// Map it to name(args) in function-call style
self.exec_fn_call(
@ -945,11 +922,12 @@ impl Engine {
calc_fn_hash(fn_name, args_len + 1),
);
// Replace the first argument with the object pointer, adding the curried arguments
let mut curry: StaticVec<_> = fn_ptr.curry().iter().cloned().collect();
let mut args: StaticVec<_> = once(target.as_mut())
.chain(curry.iter_mut())
.chain(call_args.iter_mut())
.collect();
let mut curry = StaticVec::with_capacity(fn_ptr.num_curried());
curry.extend(fn_ptr.curry().iter().cloned());
let mut args = StaticVec::with_capacity(curry.len() + call_args.len() + 1);
args.push(target.as_mut());
args.extend(curry.iter_mut());
args.extend(call_args.iter_mut());
// Map it to name(args) in function-call style
self.exec_fn_call(
@ -979,7 +957,7 @@ impl Engine {
.curry()
.iter()
.cloned()
.chain(call_args.iter_mut().map(|v| mem::take(v)))
.chain(call_args.iter_mut().map(mem::take))
.collect(),
)
}
@ -1020,8 +998,9 @@ impl Engine {
};
// Attached object pointer in front of the arguments
let mut args: StaticVec<_> =
once(target.as_mut()).chain(call_args.iter_mut()).collect();
let mut args = StaticVec::with_capacity(call_args.len() + 1);
args.push(target.as_mut());
args.extend(call_args.iter_mut());
self.exec_fn_call(
mods, state, lib, fn_name, hash, &mut args, is_ref, true, pos, None, level,
@ -1039,6 +1018,28 @@ impl Engine {
Ok((result, updated))
}
/// Evaluate an argument.
#[inline(always)]
pub(crate) fn get_arg_value(
&self,
scope: &mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
level: usize,
args_expr: &[Expr],
constants: &[Dynamic],
index: usize,
) -> Result<(Dynamic, Position), Box<EvalAltResult>> {
match args_expr[index] {
Expr::Stack(slot, pos) => Ok((constants[slot].clone(), pos)),
ref arg => self
.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position())),
}
}
/// Call a function in normal function-call style.
pub(crate) fn make_function_call(
&self,
@ -1049,7 +1050,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
fn_name: &str,
args_expr: &[Expr],
literal_args: &[(Dynamic, Position)],
constants: &[Dynamic],
mut hashes: FnCallHashes,
pos: Position,
capture_scope: bool,
@ -1058,20 +1059,15 @@ impl Engine {
// Handle call() - Redirect function call
let redirected;
let mut args_expr = args_expr;
let mut literal_args = literal_args;
let mut total_args = args_expr.len() + literal_args.len();
let mut total_args = args_expr.len();
let mut curry = StaticVec::new();
let mut name = fn_name;
match name {
// Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(literal_args[0].clone()),
|arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position()))
},
let (arg, arg_pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
if !arg.is::<FnPtr>() {
@ -1089,11 +1085,7 @@ impl Engine {
name = &redirected;
// Skip the first argument
if !args_expr.is_empty() {
args_expr = &args_expr[1..];
} else {
literal_args = &literal_args[1..];
}
args_expr = &args_expr[1..];
total_args -= 1;
// Recalculate hash
@ -1106,17 +1098,13 @@ impl Engine {
}
// Handle Fn()
KEYWORD_FN_PTR if total_args == 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(literal_args[0].clone()),
|arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position()))
},
let (arg, arg_pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
// Fn - only in function call style
return arg
.take_immutable_string()
.as_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))
.and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into)
@ -1125,12 +1113,8 @@ impl Engine {
// Handle curry()
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(literal_args[0].clone()),
|arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position()))
},
let (arg, arg_pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
if !arg.is::<FnPtr>() {
@ -1143,14 +1127,11 @@ impl Engine {
let (name, mut fn_curry) = arg.cast::<FnPtr>().take_data();
// Append the new curried arguments to the existing list.
if !args_expr.is_empty() {
args_expr.iter().skip(1).try_for_each(|expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|value| fn_curry.push(value))
})?;
fn_curry.extend(literal_args.iter().map(|(v, _)| v.clone()));
} else {
fn_curry.extend(literal_args.iter().skip(1).map(|(v, _)| v.clone()));
for index in 1..args_expr.len() {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
)?;
fn_curry.push(value);
}
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
@ -1159,9 +1140,8 @@ impl Engine {
// Handle is_shared()
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = args_expr.get(0).map_or_else(
|| Ok(literal_args[0].0.clone()),
|arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level),
let (arg, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
return Ok(arg.is_shared().into());
}
@ -1169,27 +1149,17 @@ impl Engine {
// Handle is_def_fn()
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let (arg, arg_pos) = if !args_expr.is_empty() {
(
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?,
args_expr[0].position(),
)
} else {
literal_args[0].clone()
};
let (arg, arg_pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
let fn_name = arg
.take_immutable_string()
.as_immutable_string()
.map_err(|err| self.make_type_mismatch_err::<ImmutableString>(err, arg_pos))?;
let (arg, arg_pos) = if args_expr.len() > 1 {
(
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?,
args_expr[1].position(),
)
} else {
literal_args[if args_expr.is_empty() { 1 } else { 0 }].clone()
};
let (arg, arg_pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 1,
)?;
let num_params = arg
.as_int()
@ -1206,15 +1176,11 @@ impl Engine {
// Handle is_def_var()
KEYWORD_IS_DEF_VAR if total_args == 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(literal_args[0].clone()),
|arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position()))
},
let (arg, arg_pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
let var_name = arg
.take_immutable_string()
.as_immutable_string()
.map_err(|err| self.make_type_mismatch_err::<ImmutableString>(err, arg_pos))?;
return Ok(scope.contains(&var_name).into());
}
@ -1223,25 +1189,14 @@ impl Engine {
KEYWORD_EVAL if total_args == 1 => {
// eval - only in function call style
let prev_len = scope.len();
let (script, script_pos) = args_expr.get(0).map_or_else(
|| Ok(literal_args[0].clone()),
|script_expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)
.map(|v| (v, script_expr.position()))
},
let (value, pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?;
let script = script.take_immutable_string().map_err(|typ| {
self.make_type_mismatch_err::<ImmutableString>(typ, script_pos)
})?;
let result = self.eval_script_expr_in_place(
scope,
mods,
state,
lib,
&script,
script_pos,
level + 1,
);
let script = &value
.as_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
let result =
self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1);
// IMPORTANT! If the eval defines new variables in the current scope,
// all variable offsets from this point on will be mis-aligned.
@ -1255,7 +1210,7 @@ impl Engine {
state
.source
.as_ref()
.map(|s| s.to_string())
.map(Identifier::to_string)
.unwrap_or_default(),
err,
pos,
@ -1267,8 +1222,8 @@ impl Engine {
}
// Normal function call - except for Fn, curry, call and eval (handled above)
let mut arg_values: StaticVec<_>;
let mut args: StaticVec<_>;
let mut arg_values = StaticVec::with_capacity(args_expr.len());
let mut args = StaticVec::with_capacity(args_expr.len() + curry.len());
let mut is_ref = false;
let capture = if capture_scope && !scope.is_empty() {
Some(scope.clone_visible())
@ -1276,24 +1231,20 @@ impl Engine {
None
};
if args_expr.is_empty() && literal_args.is_empty() && curry.is_empty() {
if args_expr.is_empty() && curry.is_empty() {
// No arguments
args = Default::default();
} else {
// If the first argument is a variable, and there is no curried arguments,
// convert to method-call style in order to leverage potential &mut first argument and
// avoid cloning the value
if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) {
// func(x, ...) -> x.func(...)
arg_values = args_expr
.iter()
.skip(1)
.map(|expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten)
})
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?;
for index in 1..args_expr.len() {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
)?;
arg_values.push(value.flatten());
}
let (mut target, _pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
@ -1310,27 +1261,26 @@ impl Engine {
#[cfg(feature = "no_closure")]
let target_is_shared = false;
args = if target_is_shared || target.is_value() {
if target_is_shared || target.is_temp_value() {
arg_values.insert(0, target.take_or_clone().flatten());
arg_values.iter_mut().collect()
args.extend(arg_values.iter_mut())
} else {
// Turn it into a method call only if the object is not shared and not a simple value
is_ref = true;
let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared");
once(obj_ref).chain(arg_values.iter_mut()).collect()
};
args.push(obj_ref);
args.extend(arg_values.iter_mut());
}
} else {
// func(..., ...)
arg_values = args_expr
.iter()
.map(|expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten)
})
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?;
args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
for index in 0..args_expr.len() {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
)?;
arg_values.push(value.flatten());
}
args.extend(curry.iter_mut());
args.extend(arg_values.iter_mut());
}
}
@ -1351,38 +1301,33 @@ impl Engine {
namespace: &NamespaceRef,
fn_name: &str,
args_expr: &[Expr],
literal_args: &[(Dynamic, Position)],
constants: &[Dynamic],
hash: u64,
pos: Position,
level: usize,
) -> RhaiResult {
let mut arg_values: StaticVec<_>;
let mut arg_values = StaticVec::with_capacity(args_expr.len());
let mut args = StaticVec::with_capacity(args_expr.len());
let mut first_arg_value = None;
let mut args: StaticVec<_>;
if args_expr.is_empty() && literal_args.is_empty() {
if args_expr.is_empty() {
// No arguments
args = Default::default();
} else {
// See if the first argument is a variable (not namespace-qualified).
// If so, convert to method-call style in order to leverage potential
// &mut first argument and avoid cloning the value
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
// func(x, ...) -> x.func(...)
arg_values = args_expr
.iter()
.enumerate()
.map(|(i, expr)| {
// Skip the first argument
if i == 0 {
Ok(Default::default())
} else {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten)
}
})
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?;
for index in 0..args_expr.len() {
if index == 0 {
arg_values.push(Default::default());
} else {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
)?;
arg_values.push(value.flatten());
}
}
// Get target reference to first argument
let (target, _pos) =
@ -1396,9 +1341,9 @@ impl Engine {
#[cfg(feature = "no_closure")]
let target_is_shared = false;
if target_is_shared || target.is_value() {
if target_is_shared || target.is_temp_value() {
arg_values[0] = target.take_or_clone().flatten();
args = arg_values.iter_mut().collect();
args.extend(arg_values.iter_mut());
} else {
// Turn it into a method call only if the object is not shared and not a simple value
let (first, rest) = arg_values
@ -1406,20 +1351,18 @@ impl Engine {
.expect("never fails because the arguments list is not empty");
first_arg_value = Some(first);
let obj_ref = target.take_ref().expect("never fails because `target` is a reference if it is not a value and not shared");
args = once(obj_ref).chain(rest.iter_mut()).collect();
args.push(obj_ref);
args.extend(rest.iter_mut());
}
} else {
// func(..., ...) or func(mod::x, ...)
arg_values = args_expr
.iter()
.map(|expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten)
})
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?;
args = arg_values.iter_mut().collect();
for index in 0..args_expr.len() {
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
)?;
arg_values.push(value.flatten());
}
args.extend(arg_values.iter_mut());
}
}

View File

@ -12,9 +12,7 @@ use crate::{
use std::prelude::v1::*;
use std::{
convert::{TryFrom, TryInto},
fmt,
iter::once,
mem,
fmt, mem,
};
/// Trait that maps to `Send + Sync` only under the `sync` feature.
@ -263,7 +261,7 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer, which may carry additional (i.e. curried) argument values
/// to be passed onto a function during a call.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Hash)]
pub struct FnPtr(Identifier, StaticVec<Dynamic>);
impl FnPtr {
@ -344,28 +342,23 @@ impl FnPtr {
this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
let mut args_data: StaticVec<_>;
let mut arg_values = arg_values.as_mut();
let mut args_data;
let arg_values = if self.curry().is_empty() {
arg_values.as_mut()
} else {
args_data = self
.curry()
.iter()
.cloned()
.chain(arg_values.as_mut().iter_mut().map(mem::take))
.collect();
args_data.as_mut()
if self.num_curried() > 0 {
args_data = StaticVec::with_capacity(self.num_curried() + arg_values.len());
args_data.extend(self.curry().iter().cloned());
args_data.extend(arg_values.iter_mut().map(mem::take));
arg_values = args_data.as_mut();
};
let is_method = this_ptr.is_some();
let mut args: StaticVec<_> = if let Some(obj) = this_ptr {
once(obj).chain(arg_values.iter_mut()).collect()
} else {
arg_values.iter_mut().collect()
};
let mut args = StaticVec::with_capacity(arg_values.len() + 1);
if let Some(obj) = this_ptr {
args.push(obj);
}
args.extend(arg_values.iter_mut());
ctx.call_fn_dynamic_raw(self.fn_name(), is_method, &mut args)
}

View File

@ -50,7 +50,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
} else if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it
let value = mem::take(data)
.take_string()
.as_string()
.expect("never fails because the type was checked");
unsafe_try_cast(value).expect("never fails because the type was checked")
} else {

View File

@ -71,7 +71,7 @@ impl FuncInfo {
let mut sig = format!("{}(", self.name);
if !self.param_names.is_empty() {
let mut params: Vec<String> =
let mut params: StaticVec<String> =
self.param_names.iter().map(|s| s.as_str().into()).collect();
let return_type = params.pop().unwrap_or_else(|| "()".into());
sig.push_str(&params.join(", "));
@ -1649,7 +1649,7 @@ impl fmt::Debug for NamespaceRef {
.path
.iter()
.map(|Ident { name, .. }| name.as_str())
.collect::<Vec<_>>()
.collect::<StaticVec<_>>()
.join("::"),
)
}

View File

@ -208,7 +208,7 @@ fn optimize_stmt_block(
state.push_var(
&x.name,
AccessMode::ReadOnly,
value_expr.get_constant_value(),
value_expr.get_literal_value(),
);
}
}
@ -297,7 +297,7 @@ fn optimize_stmt_block(
Stmt::Noop(*pos)
};
}
[.., second_last_stmt, Stmt::Noop(_)] if second_last_stmt.returns_value() => {}
[.., second_last_stmt, Stmt::Noop(_)] if second_last_stmt.returns_value() => (),
[.., second_last_stmt, last_stmt]
if !last_stmt.returns_value() && is_pure(last_stmt) =>
{
@ -369,7 +369,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
&& x.0.is_variable_access(true)
&& matches!(&x.2, Expr::FnCall(x2, _)
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
&& x2.args_count() == 2 && x2.args.len() >= 1
&& x2.args.len() == 2
&& x2.args[0].get_variable_name(true) == x.0.get_variable_name(true)
) =>
{
@ -379,12 +379,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
let op = Token::lookup_from_syntax(&x2.name).unwrap();
let op_assignment = op.make_op_assignment().unwrap();
x.1 = Some(OpAssignment::new(op_assignment));
x.2 = if x2.args.len() > 1 {
mem::take(&mut x2.args[1])
let value = mem::take(&mut x2.args[1]);
if let Expr::Stack(slot, pos) = value {
let value = mem::take(x2.constants.get_mut(slot).unwrap());
x.2 = Expr::from_dynamic(value, pos);
} else {
let (value, pos) = mem::take(&mut x2.literal_args[0]);
Expr::DynamicConstant(Box::new(value), pos)
};
x.2 = value;
}
}
_ => unreachable!(),
}
@ -451,7 +454,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
// switch const { ... }
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
let value = match_expr.get_constant_value().unwrap();
let value = match_expr.get_literal_value().unwrap();
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
@ -594,8 +597,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
// for id in expr { block }
Stmt::For(iterable, x, _) => {
optimize_expr(iterable, state, false);
let body = mem::take(x.1.statements()).into_vec();
*x.1.statements() = optimize_stmt_block(body, state, false, true, false).into();
let body = mem::take(x.2.statements()).into_vec();
*x.2.statements() = optimize_stmt_block(body, state, false, true, false).into();
}
// let id = expr;
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state, false),
@ -838,7 +841,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_constant_value().unwrap()), expr.position());
*expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position());
}
// [ items .. ]
#[cfg(not(feature = "no_index"))]
@ -847,7 +850,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_object"))]
Expr::Map(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_constant_value().unwrap()), expr.position());
*expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position());
}
// #{ key:value, .. }
#[cfg(not(feature = "no_object"))]
@ -905,14 +908,23 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
Expr::FnCall(x, pos)
if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.args_count() == 1
&& x.literal_args.len() == 1
&& x.literal_args[0].0.is::<ImmutableString>()
&& x.args.len() == 1
&& x.args[0].is_constant()
&& x.name == KEYWORD_FN_PTR
=> {
state.set_dirty();
let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.literal_args[0].0).as_str_ref().unwrap().into(), Default::default());
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
let fn_name = match x.args[0] {
Expr::Stack(slot, _) => Some(x.constants[slot].clone()),
Expr::StringConstant(ref s, _) => Some(s.clone().into()),
_ => None
};
if let Some(fn_name) = fn_name {
if fn_name.is::<ImmutableString>() {
state.set_dirty();
let fn_ptr = FnPtr::new_unchecked(fn_name.as_str_ref().unwrap().into(), Default::default());
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
}
}
}
// Do not call some special keywords
@ -924,13 +936,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
Expr::FnCall(x, pos)
if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.args_count() == 2 // binary call
&& x.args.len() == 2 // binary call
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
=> {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap())
.chain(x.literal_args.iter().map(|(v, _)| v).cloned())
.collect();
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_literal_value().unwrap()
}).collect();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
@ -953,15 +966,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
// Move constant arguments to the right
while x.args.last().map(Expr::is_constant).unwrap_or(false) {
let arg = x.args.pop().unwrap();
let arg_pos = arg.position();
x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
// Move constant arguments
for arg in x.args.iter_mut() {
if let Some(value) = arg.get_literal_value() {
state.set_dirty();
x.constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position());
}
}
x.args.shrink_to_fit();
x.literal_args.shrink_to_fit();
}
// Eagerly call functions
@ -972,14 +984,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
=> {
// First search for script-defined functions (can override built-in)
#[cfg(not(feature = "no_function"))]
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args_count()).is_some());
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len()).is_some());
#[cfg(feature = "no_function")]
let has_script_fn = false;
if !has_script_fn {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap())
.chain(x.literal_args.iter().map(|(v, _)| v).cloned())
.collect();
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_literal_value().unwrap()
}).collect();
// Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure
@ -990,15 +1003,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
};
if let Some(mut result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values)
.or_else(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into())
} else {
None
}
})
.map(Expr::from)
.or_else(|| if !arg_for_type_of.is_empty() {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into())
} else {
None
}).map(Expr::from)
{
state.set_dirty();
result.set_position(*pos);
@ -1011,19 +1021,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
}
// id(args ..) -> optimize function call arguments
Expr::FnCall(x, _) => {
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
Expr::FnCall(x, _) => for arg in x.args.iter_mut() {
optimize_expr(arg, state, false);
// Move constant arguments to the right
while x.args.last().map(Expr::is_constant).unwrap_or(false) {
let arg = x.args.pop().unwrap();
let arg_pos = arg.position();
x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
// Move constant arguments
if let Some(value) = arg.get_literal_value() {
state.set_dirty();
x.constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position());
}
x.args.shrink_to_fit();
x.literal_args.shrink_to_fit();
}
},
// constant-name
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {

View File

@ -10,7 +10,7 @@ use num_traits::{CheckedAdd as Add, CheckedSub as Sub};
#[cfg(feature = "unchecked")]
use std::ops::{Add, Sub};
// Register range function with step
// Range iterator with step
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepRange<T>(T, T, T)
where
@ -126,7 +126,7 @@ where
}
}
// Register range function with step
// Bit-field iterator with step
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct BitRange(INT, INT, usize);
@ -191,6 +191,64 @@ impl Iterator for BitRange {
}
}
// String iterator over characters
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct CharsStream(Vec<char>, usize);
impl CharsStream {
pub fn new(string: &str, from: INT, len: INT) -> Self {
if len <= 0 {
return Self(Default::default(), 0);
}
if from >= 0 {
return Self(
string
.chars()
.skip(from as usize)
.take(len as usize)
.collect(),
0,
);
}
#[cfg(not(feature = "unchecked"))]
return if let Some(abs_from) = from.checked_abs() {
let num_chars = string.chars().count();
let offset = if num_chars < (abs_from as usize) {
0
} else {
num_chars - (abs_from as usize)
};
Self(string.chars().skip(offset).take(len as usize).collect(), 0)
} else {
Self(string.chars().skip(0).take(len as usize).collect(), 0)
};
#[cfg(feature = "unchecked")]
return Self(
string
.chars()
.skip(from as usize)
.take(len as usize)
.collect(),
0,
);
}
}
impl Iterator for CharsStream {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
if self.1 >= self.0.len() {
None
} else {
let ch = self.0[self.1];
self.1 += 1;
Some(ch)
}
}
}
macro_rules! reg_range {
($lib:ident | $x:expr => $( $y:ty ),*) => {
$(
@ -370,15 +428,31 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]);
}
// Register string iterator
lib.set_iterator::<CharsStream>();
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>"]);
let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator<Item=char>"]);
let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]);
// Register bit-field iterator
lib.set_iterator::<BitRange>();
let _hash = lib.set_native_fn("bits", |value, from, len| BitRange::new(value, from, len));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: Decimal", "len: Decimal", "Iterator<Item=bool>"]);
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: Decimal", "Iterator<Item=bool>"]);
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));
#[cfg(feature = "metadata")]

View File

@ -28,7 +28,7 @@ pub fn print_with_func(
) -> crate::ImmutableString {
match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) {
Ok(result) if result.is::<crate::ImmutableString>() => result
.take_immutable_string()
.as_immutable_string()
.expect("never fails as the result is `ImmutableString`"),
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
Err(_) => ctx.engine().map_type_name(value.type_name()).into(),

View File

@ -1,7 +1,7 @@
#![allow(non_snake_case)]
use crate::plugin::*;
use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT};
use crate::{def_package, Dynamic, StaticVec, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, mem};
@ -10,12 +10,6 @@ use super::string_basic::{print_with_func, FUNC_TO_STRING};
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
combine_with_exported_module!(lib, "string", string_functions);
// Register string iterator
lib.set_iter(
TypeId::of::<ImmutableString>(),
|string| Box::new(string.cast::<ImmutableString>().chars().collect::<Vec<_>>().into_iter().map(Into::into))
);
});
#[export_module]

View File

@ -93,6 +93,8 @@ pub enum ParseErrorType {
UnknownOperator(String),
/// Expecting a particular token but not finding one. Wrapped values are the token and description.
MissingToken(String, String),
/// Expecting a particular symbol but not finding one. Wrapped value is the description.
MissingSymbol(String),
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error
/// description (if any).
MalformedCallExpr(String),
@ -116,6 +118,8 @@ pub enum ParseErrorType {
DuplicatedProperty(String),
/// A `switch` case is duplicated.
DuplicatedSwitchCase,
/// A variable name is duplicated. Wrapped value is the variable name.
DuplicatedVariable(String),
/// The default case of a `switch` statement is not the last.
WrongSwitchDefaultCase,
/// The case condition of a `switch` statement is not appropriate.
@ -194,12 +198,14 @@ impl ParseErrorType {
Self::BadInput(err) => err.desc(),
Self::UnknownOperator(_) => "Unknown operator",
Self::MissingToken(_, _) => "Expecting a certain token that is missing",
Self::MissingSymbol(_) => "Expecting a certain symbol that is missing",
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::MalformedCapture(_) => "Invalid capturing",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::DuplicatedSwitchCase => "Duplicated switch case",
Self::DuplicatedVariable(_) => "Duplicated variable name",
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
Self::PropertyExpected => "Expecting name of a property",
@ -247,6 +253,7 @@ impl fmt::Display for ParseErrorType {
write!(f, "Duplicated property '{}' for object map literal", s)
}
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
@ -264,6 +271,7 @@ impl fmt::Display for ParseErrorType {
}
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::MissingSymbol(s) => f.write_str(s),
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),

View File

@ -9,7 +9,10 @@ use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::module::NamespaceRef;
use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel;
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::syntax::{
CustomSyntax, MARKER_BLOCK, MARKER_BOOL, MARKER_EXPR, MARKER_IDENT, MARKER_INT, MARKER_STRING,
};
use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
};
@ -27,7 +30,7 @@ use std::{
};
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
use crate::{syntax::MARKER_FLOAT, FLOAT};
#[cfg(not(feature = "no_function"))]
use crate::FnAccess;
@ -264,6 +267,22 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
}
}
/// Parse a variable name.
fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseError> {
match input.next().expect(NEVER_ENDS) {
// Variable name
(Token::Identifier(s), pos) => Ok((s, pos)),
// Reserved keyword
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
Err(PERR::Reserved(s).into_err(pos))
}
// Bad identifier
(Token::LexError(err), pos) => Err(err.into_err(pos)),
// Not a variable name
(_, pos) => Err(PERR::VariableExpected.into_err(pos)),
}
}
/// Parse ( expr )
fn parse_paren_expr(
input: &mut TokenStream,
@ -860,7 +879,7 @@ fn parse_switch(
};
let hash = if let Some(expr) = expr {
if let Some(value) = expr.get_constant_value() {
if let Some(value) = expr.get_literal_value() {
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
@ -1232,33 +1251,26 @@ fn parse_primary(
}
// module access
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => {
match input.next().expect(NEVER_ENDS) {
(Token::Identifier(id2), pos2) => {
let (_, mut namespace, var_name) = *x;
let var_name_def = Ident {
name: var_name,
pos: var_pos,
};
let (id2, pos2) = parse_var_name(input)?;
let (_, mut namespace, var_name) = *x;
let var_name_def = Ident {
name: var_name,
pos: var_pos,
};
if let Some((_, ref mut namespace)) = namespace {
namespace.push(var_name_def);
} else {
let mut ns: NamespaceRef = Default::default();
ns.push(var_name_def);
namespace = Some((42, ns));
}
Expr::Variable(
None,
pos2,
Box::new((None, namespace, state.get_identifier(id2))),
)
}
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
return Err(PERR::Reserved(id2).into_err(pos2));
}
(_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)),
if let Some((_, ref mut namespace)) = namespace {
namespace.push(var_name_def);
} else {
let mut ns: NamespaceRef = Default::default();
ns.push(var_name_def);
namespace = Some((42, ns));
}
Expr::Variable(
None,
pos2,
Box::new((None, namespace, state.get_identifier(id2))),
)
}
// Indexing
#[cfg(not(feature = "no_index"))]
@ -1589,8 +1601,8 @@ fn make_dot_expr(
Expr::FnCall(mut func, func_pos) => {
// Recalculate hash
func.hashes = FnCallHashes::from_script_and_native(
calc_fn_hash(&func.name, func.args_count()),
calc_fn_hash(&func.name, func.args_count() + 1),
calc_fn_hash(&func.name, func.args.len()),
calc_fn_hash(&func.name, func.args.len() + 1),
);
let rhs = Expr::Dot(
@ -1621,7 +1633,7 @@ fn make_dot_expr(
}
// lhs.Fn() or lhs.eval()
(_, Expr::FnCall(x, pos))
if x.is_args_empty()
if x.args.is_empty()
&& [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL]
.contains(&x.name.as_ref()) =>
{
@ -1645,8 +1657,8 @@ fn make_dot_expr(
(lhs, Expr::FnCall(mut func, func_pos)) => {
// Recalculate hash
func.hashes = FnCallHashes::from_script_and_native(
calc_fn_hash(&func.name, func.args_count()),
calc_fn_hash(&func.name, func.args_count() + 1),
calc_fn_hash(&func.name, func.args.len()),
calc_fn_hash(&func.name, func.args.len() + 1),
);
let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
@ -1886,18 +1898,13 @@ fn parse_custom_syntax(
};
match required_token.as_str() {
MARKER_IDENT => match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => {
let name = state.get_identifier(s);
segments.push(name.clone().into());
tokens.push(state.get_identifier(MARKER_IDENT));
keywords.push(Expr::Variable(None, pos, Box::new((None, None, name))));
}
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos));
}
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
},
MARKER_IDENT => {
let (name, pos) = parse_var_name(input)?;
let name = state.get_identifier(name);
segments.push(name.clone().into());
tokens.push(state.get_identifier(MARKER_IDENT));
keywords.push(Expr::Variable(None, pos, Box::new((None, None, name))));
}
MARKER_EXPR => {
keywords.push(parse_expr(input, state, lib, settings)?);
let keyword = state.get_identifier(MARKER_EXPR);
@ -1913,6 +1920,60 @@ fn parse_custom_syntax(
}
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
},
MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
(b @ Token::True, pos) | (b @ Token::False, pos) => {
keywords.push(Expr::BoolConstant(b == Token::True, pos));
let keyword = state.get_identifier(MARKER_BOOL);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(
PERR::MissingSymbol("Expecting 'true' or 'false'".to_string())
.into_err(pos),
)
}
},
MARKER_INT => match input.next().expect(NEVER_ENDS) {
(Token::IntegerConstant(i), pos) => {
keywords.push(Expr::IntegerConstant(i, pos));
let keyword = state.get_identifier(MARKER_INT);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(
PERR::MissingSymbol("Expecting an integer number".to_string())
.into_err(pos),
)
}
},
#[cfg(not(feature = "no_float"))]
MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
(Token::FloatConstant(f), pos) => {
keywords.push(Expr::FloatConstant(f, pos));
let keyword = state.get_identifier(MARKER_FLOAT);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(PERR::MissingSymbol(
"Expecting a floating-point number".to_string(),
)
.into_err(pos))
}
},
MARKER_STRING => match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => {
keywords.push(Expr::StringConstant(state.get_identifier(s).into(), pos));
let keyword = state.get_identifier(MARKER_STRING);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(PERR::MissingSymbol("Expecting a string".to_string()).into_err(pos))
}
},
s => match input.next().expect(NEVER_ENDS) {
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(t, _) if t.syntax().as_ref() == s => {
@ -2151,17 +2212,36 @@ fn parse_for(
settings.pos = eat_token(input, Token::For);
// for name ...
let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
// Variable name
(Token::Identifier(s), pos) => (s, pos),
// Reserved keyword
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos));
let (name, name_pos, counter_name, counter_pos) = if match_token(input, Token::LeftParen).0 {
// ( name, counter )
let (name, name_pos) = parse_var_name(input)?;
let (has_comma, pos) = match_token(input, Token::Comma);
if !has_comma {
return Err(PERR::MissingToken(
Token::Comma.into(),
"after the iteration variable name".into(),
)
.into_err(pos));
}
// Bad identifier
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
// Not a variable name
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
let (counter_name, counter_pos) = parse_var_name(input)?;
if counter_name == name {
return Err(PERR::DuplicatedVariable(counter_name).into_err(counter_pos));
}
let (has_close_paren, pos) = match_token(input, Token::RightParen);
if !has_close_paren {
return Err(PERR::MissingToken(
Token::RightParen.into(),
"to close the iteration variable".into(),
)
.into_err(pos));
}
(name, name_pos, Some(counter_name), Some(counter_pos))
} else {
// name
let (name, name_pos) = parse_var_name(input)?;
(name, name_pos, None, None)
};
// for name in ...
@ -2180,8 +2260,18 @@ fn parse_for(
ensure_not_statement_expr(input, "a boolean")?;
let expr = parse_expr(input, state, lib, settings.level_up())?;
let loop_var = state.get_identifier(name);
let prev_stack_len = state.stack.len();
let counter_var = if let Some(name) = counter_name {
let counter_var = state.get_identifier(name);
state
.stack
.push((counter_var.clone(), AccessMode::ReadWrite));
Some(counter_var)
} else {
None
};
let loop_var = state.get_identifier(name);
state.stack.push((loop_var.clone(), AccessMode::ReadWrite));
settings.is_breakable = true;
@ -2196,6 +2286,10 @@ fn parse_for(
name: loop_var,
pos: name_pos,
},
counter_var.map(|name| Ident {
name,
pos: counter_pos.expect("never fails because `counter_var` is `Some`"),
}),
body.into(),
)),
settings.pos,
@ -2218,14 +2312,7 @@ fn parse_let(
settings.pos = input.next().expect(NEVER_ENDS).1;
// let name ...
let (name, pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => (s, pos),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos));
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
};
let (name, pos) = parse_var_name(input)?;
let name = state.get_identifier(name);
let var_def = Ident {
@ -2274,15 +2361,7 @@ fn parse_import(
}
// import expr as name ...
let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => (s, pos),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos));
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
};
let (name, name_pos) = parse_var_name(input)?;
let name = state.get_identifier(name);
state.modules.push(name.clone());
@ -2331,27 +2410,18 @@ fn parse_export(
let mut exports = Vec::with_capacity(4);
loop {
let (id, id_pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => (s.clone(), pos),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos));
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
};
let (id, id_pos) = parse_var_name(input)?;
let rename = if match_token(input, Token::As).0 {
match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => Some(Ident {
name: state.get_identifier(s),
pos,
}),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos));
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
let (name, pos) = parse_var_name(input)?;
if exports.iter().any(|(_, alias)| match alias {
Some(Ident { name: alias, .. }) if alias == &name => true,
_ => false,
}) {
return Err(PERR::DuplicatedVariable(name).into_err(pos));
}
let name = state.get_identifier(name);
Some(Ident { name, pos })
} else {
None
};
@ -2724,25 +2794,19 @@ fn parse_try_catch(
// try { body } catch (
let var_def = if match_token(input, Token::LeftParen).0 {
let id = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) => Ident {
name: state.get_identifier(s),
pos,
},
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
};
let (matched, pos) = match_token(input, Token::RightParen);
let (name, pos) = parse_var_name(input)?;
let (matched, err_pos) = match_token(input, Token::RightParen);
if !matched {
return Err(PERR::MissingToken(
Token::RightParen.into(),
"to enclose the catch variable".into(),
)
.into_err(pos));
.into_err(err_pos));
}
Some(id)
let name = state.get_identifier(name);
Some(Ident { name, pos })
} else {
None
};

View File

@ -113,7 +113,7 @@ impl EvalAltResult {
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorBitFieldBounds(_, _, _) => "Bit-field index out of bounds",
Self::ErrorFor(_) => "For loop expects an array, object map, or range",
Self::ErrorFor(_) => "For loop expects a type with an iterator defined",
Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorModuleNotFound(_, _) => "Module not found",
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
@ -172,7 +172,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?,
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for type '{}'", s)?,

View File

@ -542,7 +542,7 @@ impl SerializeMap for DynamicSerializer {
#[cfg(not(feature = "no_object"))]
{
let key = std::mem::take(&mut self._key)
.take_immutable_string()
.as_immutable_string()
.map_err(|typ| {
EvalAltResult::ErrorMismatchDataType(
"string".into(),
@ -572,7 +572,7 @@ impl SerializeMap for DynamicSerializer {
#[cfg(not(feature = "no_object"))]
{
let _key: Dynamic = _key.serialize(&mut *self)?;
let _key = _key.take_immutable_string().map_err(|typ| {
let _key = _key.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE)
})?;
let _value = _value.serialize(&mut *self)?;

View File

@ -5,8 +5,8 @@ use crate::engine::EvalContext;
use crate::fn_native::SendSync;
use crate::token::{is_valid_identifier, Token};
use crate::{
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared,
StaticVec,
Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult,
Shared, StaticVec,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -14,6 +14,11 @@ use std::prelude::v1::*;
pub const MARKER_EXPR: &str = "$expr$";
pub const MARKER_BLOCK: &str = "$block$";
pub const MARKER_IDENT: &str = "$ident$";
pub const MARKER_STRING: &str = "$string$";
pub const MARKER_INT: &str = "$int$";
#[cfg(not(feature = "no_float"))]
pub const MARKER_FLOAT: &str = "$float$";
pub const MARKER_BOOL: &str = "$bool$";
/// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))]
@ -58,6 +63,11 @@ impl Expression<'_> {
pub fn position(&self) -> Position {
self.0.position()
}
/// Get the value of this expression if it is a literal constant.
#[inline(always)]
pub fn get_literal_value(&self) -> Option<Dynamic> {
self.0.get_literal_value()
}
}
impl EvalContext<'_, '_, '_, '_, '_, '_, '_> {
@ -132,7 +142,15 @@ impl Engine {
let seg = match s {
// Markers not in first position
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK if !segments.is_empty() => s.into(),
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK | MARKER_BOOL | MARKER_INT
| MARKER_STRING
if !segments.is_empty() =>
{
s.into()
}
// Markers not in first position
#[cfg(not(feature = "no_float"))]
MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position
s if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved

View File

@ -1095,7 +1095,7 @@ pub fn parse_string_literal(
match next_char {
// \r - ignore if followed by \n
'\r' if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) => {}
'\r' if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) => (),
// \...
'\\' if !verbatim && escape.is_empty() => {
escape.push('\\');

View File

@ -208,13 +208,13 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
let x = [1, 2, 3];
x.reduce(|sum, v, i| {
if i == 0 { sum = 10 }
sum + v * v
})
"#
"
)?,
24
);
@ -231,40 +231,40 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
let x = [1, 2, 3];
x.reduce_rev(|sum, v, i| { if i == 2 { sum = 10 } sum + v * v })
"#
"
)?,
24
);
assert!(engine.eval::<bool>(
r#"
"
let x = [1, 2, 3];
x.some(|v| v > 1)
"#
"
)?);
assert!(engine.eval::<bool>(
r#"
"
let x = [1, 2, 3];
x.some(|v, i| v * i == 0)
"#
"
)?);
assert!(!engine.eval::<bool>(
r#"
"
let x = [1, 2, 3];
x.all(|v| v > 1)
"#
"
)?);
assert!(engine.eval::<bool>(
r#"
"
let x = [1, 2, 3];
x.all(|v, i| v > i)
"#
"
)?);
Ok(())

View File

@ -79,11 +79,11 @@ fn test_call_fn_args() -> Result<(), Box<EvalAltResult>> {
let mut scope = Scope::new();
let ast = engine.compile(
r#"
"
fn hello(x, y, z) {
if x { `hello ${y}` } else { y + z }
}
"#,
",
)?;
let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?;

View File

@ -25,12 +25,12 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
r#"
"
let addition = |x, y| { x + y };
let curried = addition.curry(2);
call_with_arg(curried, 40)
"#
"
)?,
42
);
@ -65,7 +65,7 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
let x = 8;
let res = |y, z| {
@ -75,55 +75,55 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
}.curry(15).call(2);
res + (|| x - 3).call()
"#
"
)?,
42
);
assert_eq!(
engine.eval::<INT>(
r#"
"
let a = 41;
let foo = |x| { a += x };
foo.call(1);
a
"#
"
)?,
42
);
assert!(engine.eval::<bool>(
r#"
"
let a = 41;
let foo = |x| { a += x };
a.is_shared()
"#
"
)?);
assert!(engine.eval::<bool>(
r#"
"
let a = 41;
let foo = |x| { a += x };
is_shared(a)
"#
"
)?);
engine.register_fn("plus_one", |x: INT| x + 1);
assert_eq!(
engine.eval::<INT>(
r#"
"
let a = 41;
let f = || plus_one(a);
f.call()
"#
"
)?,
42
);
assert_eq!(
engine.eval::<INT>(
r#"
"
let a = 40;
let f = |x| {
let f = |x| {
@ -133,19 +133,19 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
f.call(x)
};
f.call(1)
"#
"
)?,
42
);
assert_eq!(
engine.eval::<INT>(
r#"
"
let a = 21;
let f = |x| a += x;
f.call(a);
a
"#
"
)?,
42
);
@ -163,13 +163,13 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
let a = 41;
let b = 0;
let f = || b.custom_call(|| a + 1);
f.call()
"#
"
)?,
42
);
@ -231,13 +231,13 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
let a = 1;
let b = 40;
let foo = |x| { this += a + x };
b.call(foo, 1);
b
"#
"
)?,
42
);
@ -245,12 +245,12 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<INT>(
r#"
"
let a = 20;
let foo = |x| { this += a + x };
a.call(foo, 1);
a
"#
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataRace(_, _)
@ -343,7 +343,7 @@ fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
// Closure 'f' captures: the engine, the AST, and the curried function pointer
let f = move |x: INT| fn_ptr.call_dynamic(&context, None, [x.into()]);
assert_eq!(f(42)?.take_string(), Ok("hello42".to_string()));
assert_eq!(f(42)?.as_string(), Ok("hello42".to_string()));
Ok(())
}

View File

@ -11,12 +11,12 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
let /* I am a
multi-line
comment, yay!
*/ x = 42; x
"#
"
)?,
42
);

View File

@ -206,6 +206,18 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
)
);
assert!(matches!(
*engine
.consume(
"
let x = #{};
loop { x.a = x; }
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, _)
));
assert!(matches!(
*engine
.eval::<Map>(

View File

@ -37,6 +37,22 @@ fn test_for_loop() -> Result<(), Box<EvalAltResult>> {
35
);
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<INT>(
"
let sum = 0;
let inputs = [1, 2, 3, 4, 5];
for (x, i) in inputs {
sum += x * (i + 1);
}
sum
"
)?,
55
);
assert_eq!(
engine.eval::<INT>(
"
@ -235,7 +251,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
let s = "hello";
let sum = 0;
for ch in s {
for ch in chars(s) {
sum += to_int(ch);
}

View File

@ -210,28 +210,28 @@ fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r#"
"
fn foo(y) { x += y; x }
let x = 41;
let y = 999;
foo!(1) + x
"#
"
)?,
83
);
assert!(engine
.eval::<INT>(
r#"
"
fn foo(y) { x += y; x }
let x = 41;
let y = 999;
foo(1) + x
"#
"
)
.is_err());
@ -239,14 +239,14 @@ fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.compile(
r#"
"
fn foo() { this += x; }
let x = 41;
let y = 999;
y.foo!();
"#
"
)
.expect_err("should error")
.0,

View File

@ -102,7 +102,7 @@ fn test_serde_ser_struct() -> Result<(), Box<EvalAltResult>> {
assert!(obj["b"].as_bool().unwrap());
assert_eq!(Ok(42), map["int"].as_int());
assert_eq!(seq.len(), 3);
assert_eq!("kitty", seq.remove(1).take_string().unwrap());
assert_eq!("kitty", seq.remove(1).as_string().unwrap());
Ok(())
}
@ -116,10 +116,10 @@ fn test_serde_ser_unit_enum() -> Result<(), Box<EvalAltResult>> {
}
let d = to_dynamic(MyEnum::VariantFoo)?;
assert_eq!("VariantFoo", d.take_string().unwrap());
assert_eq!("VariantFoo", d.as_string().unwrap());
let d = to_dynamic(MyEnum::VariantBar)?;
assert_eq!("VariantBar", d.take_string().unwrap());
assert_eq!("VariantBar", d.as_string().unwrap());
Ok(())
}
@ -145,7 +145,7 @@ fn test_serde_ser_externally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
"VariantUnit",
to_dynamic(MyEnum::VariantUnit)?
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -203,7 +203,7 @@ fn test_serde_ser_internally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantEmptyStruct",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -214,7 +214,7 @@ fn test_serde_ser_internally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantStruct",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -247,7 +247,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantUnit",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -260,7 +260,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantUnitTuple",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -274,7 +274,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantNewtype",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -289,7 +289,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantTuple",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -305,7 +305,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantEmptyStruct",
map.remove("tag")
.unwrap()
.take_immutable_string()
.as_immutable_string()
.unwrap()
.as_str()
);
@ -316,7 +316,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>();
assert_eq!(
"VariantStruct",
map.remove("tag").unwrap().take_string().unwrap()
map.remove("tag").unwrap().as_string().unwrap()
);
let mut map_inner = map.remove("content").unwrap().cast::<Map>();
assert!(map.is_empty());

View File

@ -19,19 +19,24 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.register_custom_syntax(
&[
"exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$",
"exec", "[", "$ident$", ";", "$int$", "]", "->", "$block$", "while", "$expr$",
],
true,
|context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).unwrap();
let max = inputs[1].get_literal_value().unwrap().as_int().unwrap();
let stmt = inputs.get(2).unwrap();
let condition = inputs.get(3).unwrap();
context.scope_mut().push(var_name.clone(), 0 as INT);
let mut count: INT = 0;
loop {
if count >= max {
break;
}
context.eval_expression_tree(stmt)?;
count += 1;
@ -63,17 +68,17 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>(
"
let x = 0;
let foo = (exec [x] -> { x += 2 } while x < 42) * 10;
let foo = (exec [x;15] -> { x += 2 } while x < 42) * 10;
foo
"
)?,
210
150
);
assert_eq!(
engine.eval::<INT>(
"
let x = 0;
exec [x] -> { x += 1 } while x < 42;
exec [x;100] -> { x += 1 } while x < 42;
x
"
)?,
@ -82,7 +87,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
"
exec [x] -> { x += 1 } while x < 42;
exec [x;100] -> { x += 1 } while x < 42;
x
"
)?,
@ -92,11 +97,11 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>(
"
let foo = 123;
exec [x] -> { x += 1 } while x < 42;
exec [x;15] -> { x += 1 } while x < 42;
foo + x + x1 + x2 + x3
"
)?,
171
144
);
// The first symbol must be an identifier