commit
8ddab4c8ef
10
CHANGELOG.md
10
CHANGELOG.md
@ -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
|
||||
|
22
Cargo.toml
22
Cargo.toml
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
|
@ -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`
|
||||
|
@ -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`
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.`);
|
||||
|
@ -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}`);
|
||||
|
12
scripts/function_decl4.rhai
Normal file
12
scripts/function_decl4.rhai
Normal 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
10
scripts/if2.rhai
Normal 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
11
scripts/switch.rhai
Normal 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}>`)
|
||||
}
|
||||
}
|
164
src/ast.rs
164
src/ast.rs
@ -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.
|
||||
|
115
src/dynamic.rs
115
src/dynamic.rs
@ -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"))]
|
||||
|
470
src/engine.rs
470
src/engine.rs
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)?;
|
||||
///
|
||||
|
335
src/fn_call.rs
335
src/fn_call.rs
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(¶ms.join(", "));
|
||||
@ -1649,7 +1649,7 @@ impl fmt::Debug for NamespaceRef {
|
||||
.path
|
||||
.iter()
|
||||
.map(|Ident { name, .. }| name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.collect::<StaticVec<_>>()
|
||||
.join("::"),
|
||||
)
|
||||
}
|
||||
|
117
src/optimize.rs
117
src/optimize.rs
@ -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() => {
|
||||
|
@ -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")]
|
||||
|
@ -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(),
|
||||
|
@ -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]
|
||||
|
@ -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),
|
||||
|
268
src/parser.rs
268
src/parser.rs
@ -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
|
||||
};
|
||||
|
@ -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)?,
|
||||
|
||||
|
@ -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)?;
|
||||
|
@ -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
|
||||
|
@ -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('\\');
|
||||
|
@ -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(())
|
||||
|
@ -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)?;
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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>(
|
||||
|
18
tests/for.rs
18
tests/for.rs
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user