commit
8ddab4c8ef
10
CHANGELOG.md
10
CHANGELOG.md
@ -8,11 +8,21 @@ Bug fixes
|
|||||||
---------
|
---------
|
||||||
|
|
||||||
* Fixed incorrect optimization regarding chain-indexing with non-numeric index.
|
* 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 features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* New syntax for `for` statement to include counter variable.
|
||||||
* An integer value can now be indexed to get/set a single bit.
|
* 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
|
Version 0.20.2
|
||||||
|
22
Cargo.toml
22
Cargo.toml
@ -18,9 +18,9 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
smallvec = { version = "1.6", default-features = false, features = ["union"] }
|
smallvec = { version = "1.6", default-features = false, features = ["union"] }
|
||||||
ahash = { version = "0.7", default-features = false }
|
ahash = { version = "0.7", default-features = false }
|
||||||
num-traits = { version = "0.2", default_features = false }
|
num-traits = { version = "0.2", default-features = false }
|
||||||
smartstring = { version = "0.2.6", default_features = false }
|
smartstring = { version = "0.2.6", default-features = false }
|
||||||
rhai_codegen = { version = "0.3.7", path = "codegen", default_features = false }
|
rhai_codegen = { version = "0.4.0", path = "codegen", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["smartstring/std", "ahash/std", "num-traits/std"] # remove 'smartstring/std' when smartstring is updated to support no-std
|
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]
|
[dependencies.no-std-compat]
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
default_features = false
|
default-features = false
|
||||||
features = ["alloc"]
|
features = ["alloc"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.libm]
|
[dependencies.libm]
|
||||||
version = "0.2"
|
version = "0.2"
|
||||||
default_features = false
|
default-features = false
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.core-error]
|
[dependencies.core-error]
|
||||||
version = "0.0"
|
version = "0.0"
|
||||||
default_features = false
|
default-features = false
|
||||||
features = ["alloc"]
|
features = ["alloc"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
default_features = false
|
default-features = false
|
||||||
features = ["derive", "alloc"]
|
features = ["derive", "alloc"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde_json]
|
[dependencies.serde_json]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
default_features = false
|
default-features = false
|
||||||
features = ["alloc"]
|
features = ["alloc"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.unicode-xid]
|
[dependencies.unicode-xid]
|
||||||
version = "0.2"
|
version = "0.2"
|
||||||
default_features = false
|
default-features = false
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.rust_decimal]
|
[dependencies.rust_decimal]
|
||||||
version = "1.14"
|
version = "1.14.2"
|
||||||
default_features = false
|
default-features = false
|
||||||
features = ["maths"]
|
features = ["maths"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rhai_codegen"
|
name = "rhai_codegen"
|
||||||
version = "0.3.7"
|
version = "0.4.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["jhwgh1968", "Stephen Chung"]
|
authors = ["jhwgh1968", "Stephen Chung"]
|
||||||
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
|
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
|
||||||
|
@ -744,7 +744,7 @@ impl ExportedFn {
|
|||||||
is_string = true;
|
is_string = true;
|
||||||
is_ref = true;
|
is_ref = true;
|
||||||
quote_spanned!(arg_type.span() =>
|
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!?"),
|
_ => panic!("internal error: why wasn't this found earlier!?"),
|
||||||
@ -753,7 +753,7 @@ impl ExportedFn {
|
|||||||
is_string = true;
|
is_string = true;
|
||||||
is_ref = false;
|
is_ref = false;
|
||||||
quote_spanned!(arg_type.span() =>
|
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;
|
let mut namespace = FnNamespaceAccess::Internal;
|
||||||
|
|
||||||
match function.params().special {
|
match function.params().special {
|
||||||
FnSpecialAccess::None => {}
|
FnSpecialAccess::None => (),
|
||||||
FnSpecialAccess::Index(_) | FnSpecialAccess::Property(_) => {
|
FnSpecialAccess::Index(_) | FnSpecialAccess::Property(_) => {
|
||||||
let reg_name = fn_literal.value();
|
let reg_name = fn_literal.value();
|
||||||
if reg_name.starts_with(FN_GET)
|
if reg_name.starts_with(FN_GET)
|
||||||
|
@ -525,7 +525,7 @@ mod generate_tests {
|
|||||||
impl PluginFunction for Token {
|
impl PluginFunction for Token {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
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)))
|
Ok(Dynamic::from(special_print(&arg0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,7 +934,7 @@ mod generate_tests {
|
|||||||
impl PluginFunction for print_out_to_token {
|
impl PluginFunction for print_out_to_token {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
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)))
|
Ok(Dynamic::from(print_out_to(&arg0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,7 +987,7 @@ mod generate_tests {
|
|||||||
impl PluginFunction for print_out_to_token {
|
impl PluginFunction for print_out_to_token {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
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)))
|
Ok(Dynamic::from(print_out_to(arg0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
||||||
--> $DIR/rhai_fn_non_clonable_return.rs:11:8
|
--> $DIR/rhai_fn_non_clonable_return.rs:11:8
|
||||||
|
|
|
|
||||||
11 | pub fn test_fn(input: f32) -> NonClonable {
|
11 | pub fn test_fn(input: f32) -> NonClonable {
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/src/dynamic.rs
|
::: $WORKSPACE/src/dynamic.rs
|
||||||
|
|
|
|
||||||
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
|
||||||
| ----- required by this bound in `rhai::Dynamic::from`
|
| ----- required by this bound in `rhai::Dynamic::from`
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
||||||
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
||||||
|
|
|
|
||||||
12 | pub fn test_fn(input: f32) -> NonClonable {
|
12 | pub fn test_fn(input: f32) -> NonClonable {
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/src/dynamic.rs
|
::: $WORKSPACE/src/dynamic.rs
|
||||||
|
|
|
|
||||||
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
|
||||||
| ----- required by this bound in `rhai::Dynamic::from`
|
| ----- required by this bound in `rhai::Dynamic::from`
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
// This script runs for-loops
|
// 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 (a, i) in arr {
|
||||||
for b in [10, 20] {
|
for (b, j) in ['x', 42, (), 123, 99, 0.5] {
|
||||||
print(`${a}, ${b}`);
|
if b > 100 { continue; }
|
||||||
|
|
||||||
|
print(`(${i}, ${j}) = (${a}, ${b})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if a == 3 { break; }
|
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
|
//print(a); // <- if you uncomment this line, the script will fail to run
|
||||||
// because 'a' is not defined here
|
// 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);
|
print(i);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ for i in range(0, MAX) {
|
|||||||
list.push(i);
|
list.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print(`Time = ${now.elapsed} seconds...`);
|
||||||
|
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
||||||
for i in list {
|
for i in list {
|
||||||
@ -19,4 +21,4 @@ for i in list {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print(`Sum = ${sum}`);
|
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
|
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).
|
//! Module defining the AST (abstract syntax tree).
|
||||||
|
|
||||||
|
use crate::dynamic::Union;
|
||||||
use crate::fn_native::shared_make_mut;
|
use crate::fn_native::shared_make_mut;
|
||||||
use crate::module::NamespaceRef;
|
use crate::module::NamespaceRef;
|
||||||
use crate::token::Token;
|
use crate::token::Token;
|
||||||
@ -90,7 +91,7 @@ impl fmt::Display for ScriptFnDef {
|
|||||||
self.params
|
self.params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.as_str())
|
.map(|s| s.as_str())
|
||||||
.collect::<Vec<_>>()
|
.collect::<StaticVec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -138,7 +139,11 @@ impl fmt::Display for ScriptFnMetadata<'_> {
|
|||||||
FnAccess::Private => "private ",
|
FnAccess::Private => "private ",
|
||||||
},
|
},
|
||||||
self.name,
|
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>,
|
statements: impl IntoIterator<Item = Stmt>,
|
||||||
functions: impl Into<Shared<Module>>,
|
functions: impl Into<Shared<Module>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let statements: StaticVec<_> = statements.into_iter().collect();
|
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
body: StmtBlock::new(statements, Position::NONE),
|
body: StmtBlock::new(statements.into_iter().collect(), Position::NONE),
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
@ -231,10 +235,9 @@ impl AST {
|
|||||||
functions: impl Into<Shared<Module>>,
|
functions: impl Into<Shared<Module>>,
|
||||||
source: impl Into<Identifier>,
|
source: impl Into<Identifier>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let statements: StaticVec<_> = statements.into_iter().collect();
|
|
||||||
Self {
|
Self {
|
||||||
source: Some(source.into()),
|
source: Some(source.into()),
|
||||||
body: StmtBlock::new(statements, Position::NONE),
|
body: StmtBlock::new(statements.into_iter().collect(), Position::NONE),
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
@ -417,15 +420,15 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let ast1 = engine.compile(r#"
|
/// let ast1 = engine.compile("
|
||||||
/// fn foo(x) { 42 + x }
|
/// fn foo(x) { 42 + x }
|
||||||
/// foo(1)
|
/// foo(1)
|
||||||
/// "#)?;
|
/// ")?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { `hello${n}` }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1'
|
/// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1'
|
||||||
///
|
///
|
||||||
@ -469,15 +472,15 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let mut ast1 = engine.compile(r#"
|
/// let mut ast1 = engine.compile("
|
||||||
/// fn foo(x) { 42 + x }
|
/// fn foo(x) { 42 + x }
|
||||||
/// foo(1)
|
/// foo(1)
|
||||||
/// "#)?;
|
/// ")?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { `hello${n}` }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// ast1.combine(ast2); // Combine 'ast2' into 'ast1'
|
/// ast1.combine(ast2); // Combine 'ast2' into 'ast1'
|
||||||
///
|
///
|
||||||
@ -523,16 +526,16 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let ast1 = engine.compile(r#"
|
/// let ast1 = engine.compile("
|
||||||
/// fn foo(x) { 42 + x }
|
/// fn foo(x) { 42 + x }
|
||||||
/// foo(1)
|
/// foo(1)
|
||||||
/// "#)?;
|
/// ")?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { `hello${n}` }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// fn error() { 0 }
|
/// fn error() { 0 }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
|
/// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
|
||||||
/// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params|
|
/// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params|
|
||||||
@ -606,16 +609,16 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let mut ast1 = engine.compile(r#"
|
/// let mut ast1 = engine.compile("
|
||||||
/// fn foo(x) { 42 + x }
|
/// fn foo(x) { 42 + x }
|
||||||
/// foo(1)
|
/// foo(1)
|
||||||
/// "#)?;
|
/// ")?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { `hello${n}` }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// fn error() { 0 }
|
/// fn error() { 0 }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
|
/// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
|
||||||
/// ast1.combine_filtered(ast2, |_, _, script, name, params|
|
/// ast1.combine_filtered(ast2, |_, _, script, name, params|
|
||||||
@ -664,9 +667,9 @@ impl AST {
|
|||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let mut ast = engine.compile(r#"
|
/// let mut ast = engine.compile(r#"
|
||||||
/// fn foo(n) { n + 1 }
|
/// fn foo(n) { n + 1 }
|
||||||
/// fn bar() { print("hello"); }
|
/// fn bar() { print("hello"); }
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// // Remove all functions except 'foo(_)'
|
/// // Remove all functions except 'foo(_)'
|
||||||
/// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1);
|
/// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1);
|
||||||
@ -866,8 +869,7 @@ pub struct StmtBlock(StaticVec<Stmt>, Position);
|
|||||||
|
|
||||||
impl StmtBlock {
|
impl StmtBlock {
|
||||||
/// Create a new [`StmtBlock`].
|
/// Create a new [`StmtBlock`].
|
||||||
pub fn new(statements: impl Into<StaticVec<Stmt>>, pos: Position) -> Self {
|
pub fn new(mut statements: StaticVec<Stmt>, pos: Position) -> Self {
|
||||||
let mut statements = statements.into();
|
|
||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
Self(statements, pos)
|
Self(statements, pos)
|
||||||
}
|
}
|
||||||
@ -943,14 +945,14 @@ pub enum Stmt {
|
|||||||
While(Expr, Box<StmtBlock>, Position),
|
While(Expr, Box<StmtBlock>, Position),
|
||||||
/// `do` `{` stmt `}` `while`|`until` expr
|
/// `do` `{` stmt `}` `while`|`until` expr
|
||||||
Do(Box<StmtBlock>, Expr, bool, Position),
|
Do(Box<StmtBlock>, Expr, bool, Position),
|
||||||
/// `for` id `in` expr `{` stmt `}`
|
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
||||||
For(Expr, Box<(Ident, StmtBlock)>, Position),
|
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
|
||||||
/// \[`export`\] `let` id `=` expr
|
/// \[`export`\] `let` id `=` expr
|
||||||
Let(Expr, Box<Ident>, bool, Position),
|
Let(Expr, Box<Ident>, bool, Position),
|
||||||
/// \[`export`\] `const` id `=` expr
|
/// \[`export`\] `const` id `=` expr
|
||||||
Const(Expr, Box<Ident>, bool, Position),
|
Const(Expr, Box<Ident>, bool, Position),
|
||||||
/// expr op`=` expr
|
/// expr op`=` expr
|
||||||
Assignment(Box<(Expr, Option<OpAssignment>, Expr)>, Position),
|
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
///
|
///
|
||||||
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
|
/// 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)]
|
#[inline(always)]
|
||||||
fn from(stmt: Stmt) -> Self {
|
fn from(stmt: Stmt) -> Self {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Block(mut block, pos) => {
|
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
||||||
Self(block.iter_mut().map(|v| mem::take(v)).collect(), pos)
|
|
||||||
}
|
|
||||||
Stmt::Noop(pos) => Self(Default::default(), pos),
|
Stmt::Noop(pos) => Self(Default::default(), pos),
|
||||||
_ => {
|
_ => {
|
||||||
let pos = stmt.position();
|
let pos = stmt.position();
|
||||||
@ -1167,7 +1167,7 @@ impl Stmt {
|
|||||||
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
||||||
condition.is_pure() && block.0.iter().all(Stmt::is_pure)
|
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::Let(_, _, _, _)
|
||||||
| Self::Const(_, _, _, _)
|
| Self::Const(_, _, _, _)
|
||||||
| Self::Assignment(_, _)
|
| Self::Assignment(_, _)
|
||||||
@ -1287,7 +1287,7 @@ impl Stmt {
|
|||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &(x.1).0 {
|
for s in &(x.2).0 {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1384,14 +1384,14 @@ pub struct BinaryExpr {
|
|||||||
/// # Volatile Data Structure
|
/// # Volatile Data Structure
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
pub struct OpAssignment {
|
pub struct OpAssignment<'a> {
|
||||||
pub hash_op_assign: u64,
|
pub hash_op_assign: u64,
|
||||||
pub hash_op: u64,
|
pub hash_op: u64,
|
||||||
pub op: &'static str,
|
pub op: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpAssignment {
|
impl OpAssignment<'_> {
|
||||||
/// Create a new [`OpAssignment`].
|
/// Create a new [`OpAssignment`].
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
@ -1520,7 +1520,7 @@ pub struct FnCallExpr {
|
|||||||
/// List of function call argument expressions.
|
/// List of function call argument expressions.
|
||||||
pub args: StaticVec<Expr>,
|
pub args: StaticVec<Expr>,
|
||||||
/// List of function call arguments that are constants.
|
/// List of function call arguments that are constants.
|
||||||
pub literal_args: smallvec::SmallVec<[(Dynamic, Position); 2]>,
|
pub constants: smallvec::SmallVec<[Dynamic; 2]>,
|
||||||
/// Function name.
|
/// Function name.
|
||||||
pub name: Identifier,
|
pub name: Identifier,
|
||||||
/// Does this function call capture the parent scope?
|
/// Does this function call capture the parent scope?
|
||||||
@ -1533,16 +1533,6 @@ impl FnCallExpr {
|
|||||||
pub fn is_qualified(&self) -> bool {
|
pub fn is_qualified(&self) -> bool {
|
||||||
self.namespace.is_some()
|
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`].
|
/// A type that wraps a floating-point number and implements [`Hash`].
|
||||||
@ -1719,6 +1709,8 @@ pub enum Expr {
|
|||||||
(ImmutableString, Position),
|
(ImmutableString, Position),
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
|
/// Stack slot
|
||||||
|
Stack(usize, Position),
|
||||||
/// { [statement][Stmt] ... }
|
/// { [statement][Stmt] ... }
|
||||||
Stmt(Box<StmtBlock>),
|
Stmt(Box<StmtBlock>),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
@ -1776,14 +1768,15 @@ impl fmt::Debug for Expr {
|
|||||||
Some((_, ref namespace)) => write!(f, "{}", namespace)?,
|
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)) {
|
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(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
Self::Property(x) => write!(f, "Property({})", (x.2).0),
|
Self::Property(x) => write!(f, "Property({})", (x.2).0),
|
||||||
|
Self::Stack(x, _) => write!(f, "StackSlot({})", x),
|
||||||
Self::Stmt(x) => {
|
Self::Stmt(x) => {
|
||||||
f.write_str("ExprStmtBlock")?;
|
f.write_str("ExprStmtBlock")?;
|
||||||
f.debug_list().entries(x.0.iter()).finish()
|
f.debug_list().entries(x.0.iter()).finish()
|
||||||
@ -1794,8 +1787,8 @@ impl fmt::Debug for Expr {
|
|||||||
ff.field("name", &x.name)
|
ff.field("name", &x.name)
|
||||||
.field("hash", &x.hashes)
|
.field("hash", &x.hashes)
|
||||||
.field("args", &x.args);
|
.field("args", &x.args);
|
||||||
if !x.literal_args.is_empty() {
|
if !x.constants.is_empty() {
|
||||||
ff.field("literal_args", &x.literal_args);
|
ff.field("constants", &x.constants);
|
||||||
}
|
}
|
||||||
if x.capture {
|
if x.capture {
|
||||||
ff.field("capture", &x.capture);
|
ff.field("capture", &x.capture);
|
||||||
@ -1826,11 +1819,11 @@ impl fmt::Debug for Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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]
|
#[inline]
|
||||||
pub fn get_constant_value(&self) -> Option<Dynamic> {
|
pub fn get_literal_value(&self) -> Option<Dynamic> {
|
||||||
Some(match self {
|
Some(match self {
|
||||||
Self::DynamicConstant(x, _) => x.as_ref().clone(),
|
Self::DynamicConstant(x, _) => x.as_ref().clone(),
|
||||||
Self::IntegerConstant(x, _) => (*x).into(),
|
Self::IntegerConstant(x, _) => (*x).into(),
|
||||||
@ -1845,7 +1838,7 @@ impl Expr {
|
|||||||
Self::Array(x, _) if self.is_constant() => {
|
Self::Array(x, _) if self.is_constant() => {
|
||||||
let mut arr = Array::with_capacity(x.len());
|
let mut arr = Array::with_capacity(x.len());
|
||||||
arr.extend(x.iter().map(|v| {
|
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")
|
.expect("never fails because a constant array always has a constant value")
|
||||||
}));
|
}));
|
||||||
Dynamic::from_array(arr)
|
Dynamic::from_array(arr)
|
||||||
@ -1857,7 +1850,7 @@ impl Expr {
|
|||||||
x.0.iter().for_each(|(k, v)| {
|
x.0.iter().for_each(|(k, v)| {
|
||||||
*map.get_mut(k.name.as_str())
|
*map.get_mut(k.name.as_str())
|
||||||
.expect("never fails because the template should contain all the keys") = v
|
.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")
|
.expect("never fails because a constant map always has a constant value")
|
||||||
});
|
});
|
||||||
Dynamic::from_map(map)
|
Dynamic::from_map(map)
|
||||||
@ -1866,6 +1859,22 @@ impl Expr {
|
|||||||
_ => return None,
|
_ => 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?
|
/// Is the expression a simple variable access?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool {
|
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool {
|
||||||
@ -1898,6 +1907,7 @@ impl Expr {
|
|||||||
| Self::Array(_, pos)
|
| Self::Array(_, pos)
|
||||||
| Self::Map(_, pos)
|
| Self::Map(_, pos)
|
||||||
| Self::Variable(_, pos, _)
|
| Self::Variable(_, pos, _)
|
||||||
|
| Self::Stack(_, pos)
|
||||||
| Self::FnCall(_, pos)
|
| Self::FnCall(_, pos)
|
||||||
| Self::Custom(_, pos) => *pos,
|
| Self::Custom(_, pos) => *pos,
|
||||||
|
|
||||||
@ -1936,6 +1946,7 @@ impl Expr {
|
|||||||
| Self::Dot(_, pos)
|
| Self::Dot(_, pos)
|
||||||
| Self::Index(_, pos)
|
| Self::Index(_, pos)
|
||||||
| Self::Variable(_, pos, _)
|
| Self::Variable(_, pos, _)
|
||||||
|
| Self::Stack(_, pos)
|
||||||
| Self::FnCall(_, pos)
|
| Self::FnCall(_, pos)
|
||||||
| Self::Custom(_, pos) => *pos = new_pos,
|
| Self::Custom(_, pos) => *pos = new_pos,
|
||||||
|
|
||||||
@ -1965,7 +1976,7 @@ impl Expr {
|
|||||||
|
|
||||||
Self::Stmt(x) => x.0.iter().all(Stmt::is_pure),
|
Self::Stmt(x) => x.0.iter().all(Stmt::is_pure),
|
||||||
|
|
||||||
Self::Variable(_, _, _) => true,
|
Self::Variable(_, _, _) | Self::Stack(_, _) => true,
|
||||||
|
|
||||||
_ => self.is_constant(),
|
_ => self.is_constant(),
|
||||||
}
|
}
|
||||||
@ -1990,7 +2001,8 @@ impl Expr {
|
|||||||
| Self::IntegerConstant(_, _)
|
| Self::IntegerConstant(_, _)
|
||||||
| Self::CharConstant(_, _)
|
| Self::CharConstant(_, _)
|
||||||
| Self::StringConstant(_, _)
|
| Self::StringConstant(_, _)
|
||||||
| Self::Unit(_) => true,
|
| Self::Unit(_)
|
||||||
|
| Self::Stack(_, _) => true,
|
||||||
|
|
||||||
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant),
|
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant),
|
||||||
|
|
||||||
@ -2050,6 +2062,8 @@ impl Expr {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Self::Custom(_, _) => false,
|
Self::Custom(_, _) => false,
|
||||||
|
|
||||||
|
Self::Stack(_, _) => unreachable!("Expr::Stack should not occur naturally"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Recursively walk this expression.
|
/// 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"))]
|
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
|
|
||||||
|
const CHECKED: &str = "never fails because the type was checked";
|
||||||
|
|
||||||
mod private {
|
mod private {
|
||||||
use crate::fn_native::SendSync;
|
use crate::fn_native::SendSync;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
@ -471,29 +473,27 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for 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) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
std::mem::discriminant(&self.0).hash(state);
|
||||||
|
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
Union::Unit(_, _, _) => ().hash(state),
|
Union::Unit(_, _, _) => ().hash(state),
|
||||||
Union::Bool(value, _, _) => value.hash(state),
|
Union::Bool(b, _, _) => b.hash(state),
|
||||||
Union::Str(s, _, _) => s.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),
|
Union::Int(i, _, _) => i.hash(state),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Union::Float(f, _, _) => f.hash(state),
|
Union::Float(f, _, _) => f.hash(state),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Array(a, _, _) => a.as_ref().hash(state),
|
Union::Array(a, _, _) => a.as_ref().hash(state),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(m, _, _) => m.iter().for_each(|(key, value)| {
|
Union::Map(m, _, _) => m.as_ref().hash(state),
|
||||||
key.hash(state);
|
Union::FnPtr(f, _, _) => f.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),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Union::Shared(cell, _, _) => {
|
Union::Shared(cell, _, _) => {
|
||||||
@ -505,6 +505,55 @@ impl Hash for Dynamic {
|
|||||||
(*value).hash(state)
|
(*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()),
|
_ => unimplemented!("{} cannot be hashed", self.type_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -573,8 +622,6 @@ impl fmt::Display for Dynamic {
|
|||||||
let _type_id = value.type_id();
|
let _type_id = value.type_id();
|
||||||
let _value_any = value.as_any();
|
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_i32"))]
|
||||||
#[cfg(not(feature = "only_i64"))]
|
#[cfg(not(feature = "only_i64"))]
|
||||||
if _type_id == TypeId::of::<u8>() {
|
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);
|
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"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -1749,15 +1796,45 @@ impl Dynamic {
|
|||||||
/// Convert the [`Dynamic`] into a [`String`] and return it.
|
/// Convert the [`Dynamic`] into a [`String`] and return it.
|
||||||
/// If there are other references to the same string, a cloned copy is returned.
|
/// 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.
|
/// 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)]
|
#[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> {
|
pub fn take_string(self) -> Result<String, &'static str> {
|
||||||
self.take_immutable_string()
|
self.as_string()
|
||||||
.map(ImmutableString::into_owned)
|
}
|
||||||
|
/// 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.
|
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
#[inline(always)]
|
#[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 {
|
match self.0 {
|
||||||
Union::Str(s, _, _) => Ok(s),
|
Union::Str(s, _, _) => Ok(s),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[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::utils::get_hasher;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
|
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
|
||||||
Shared, StaticVec,
|
Shared, StaticVec, INT,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -328,13 +328,13 @@ impl From<(Dynamic, Position)> for ChainArgument {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Target<'a> {
|
pub enum Target<'a> {
|
||||||
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
/// 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.
|
/// The target is a mutable reference to a Shared `Dynamic` value.
|
||||||
/// It holds both the access guard and the original shared value.
|
/// It holds both the access guard and the original shared value.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
LockGuard((crate::dynamic::DynamicWriteLock<'a, Dynamic>, Dynamic)),
|
LockGuard((crate::dynamic::DynamicWriteLock<'a, Dynamic>, Dynamic)),
|
||||||
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
/// 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].
|
/// 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.
|
/// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -351,25 +351,24 @@ impl<'a> Target<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_ref(&self) -> bool {
|
pub fn is_ref(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(_) => true,
|
Self::RefMut(_) => true,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard(_) => true,
|
Self::LockGuard(_) => true,
|
||||||
Self::Value(_) => false,
|
Self::TempValue(_) => false,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, _) => false,
|
Self::BitField(_, _, _) => false,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::StringChar(_, _, _) => false,
|
Self::StringChar(_, _, _) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the `Target` an owned value?
|
/// Is the `Target` a temp value?
|
||||||
#[allow(dead_code)]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_value(&self) -> bool {
|
pub fn is_temp_value(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(_) => false,
|
Self::RefMut(_) => false,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard(_) => false,
|
Self::LockGuard(_) => false,
|
||||||
Self::Value(_) => true,
|
Self::TempValue(_) => true,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, _) => false,
|
Self::BitField(_, _, _) => false,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -381,10 +380,10 @@ impl<'a> Target<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_shared(&self) -> bool {
|
pub fn is_shared(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => r.is_shared(),
|
Self::RefMut(r) => r.is_shared(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard(_) => true,
|
Self::LockGuard(_) => true,
|
||||||
Self::Value(r) => r.is_shared(),
|
Self::TempValue(r) => r.is_shared(),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, _) => false,
|
Self::BitField(_, _, _) => false,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -396,10 +395,10 @@ impl<'a> Target<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => r.is::<T>(),
|
Self::RefMut(r) => r.is::<T>(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard((r, _)) => r.is::<T>(),
|
Self::LockGuard((r, _)) => r.is::<T>(),
|
||||||
Self::Value(r) => r.is::<T>(),
|
Self::TempValue(r) => r.is::<T>(),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, _) => TypeId::of::<T>() == TypeId::of::<bool>(),
|
Self::BitField(_, _, _) => TypeId::of::<T>() == TypeId::of::<bool>(),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -410,10 +409,10 @@ impl<'a> Target<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn take_or_clone(self) -> Dynamic {
|
pub fn take_or_clone(self) -> Dynamic {
|
||||||
match self {
|
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"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard((_, orig)) => orig, // Original value is simply taken
|
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"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, value) => value, // Boolean is taken
|
Self::BitField(_, _, value) => value, // Boolean is taken
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -424,7 +423,7 @@ impl<'a> Target<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn take_ref(self) -> Option<&'a mut Dynamic> {
|
pub fn take_ref(self) -> Option<&'a mut Dynamic> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => Some(r),
|
Self::RefMut(r) => Some(r),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,47 +437,24 @@ impl<'a> Target<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn propagate_changed_value(&mut self) -> Result<(), Box<EvalAltResult>> {
|
pub fn propagate_changed_value(&mut self) -> Result<(), Box<EvalAltResult>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(_) | Self::Value(_) => Ok(()),
|
Self::RefMut(_) | Self::TempValue(_) => (),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard(_) => Ok(()),
|
Self::LockGuard(_) => (),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, value) => {
|
Self::BitField(value, index, new_val) => {
|
||||||
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`");
|
|
||||||
|
|
||||||
// Replace the bit at the specified index position
|
// Replace the bit at the specified index position
|
||||||
let new_bit = new_val.as_bool().map_err(|err| {
|
let new_bit = new_val.as_bool().map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||||
"bool".to_string(),
|
"bool".to_string(),
|
||||||
err.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;
|
let index = *index;
|
||||||
|
|
||||||
if index < std::mem::size_of_val(value) * 8 {
|
if index < std::mem::size_of_val(value) * 8 {
|
||||||
@ -488,23 +464,25 @@ impl<'a> Target<'a> {
|
|||||||
} else {
|
} else {
|
||||||
*value &= !mask;
|
*value &= !mask;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("bit-field index out of bounds: {}", index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::StringChar(s, index, _) => {
|
Self::StringChar(s, index, new_val) => {
|
||||||
let s = &mut *s
|
|
||||||
.write_lock::<ImmutableString>()
|
|
||||||
.expect("never fails because `StringChar` always holds an `ImmutableString`");
|
|
||||||
|
|
||||||
// Replace the character at the specified index position
|
// Replace the character at the specified index position
|
||||||
let new_ch = new_val.as_char().map_err(|err| {
|
let new_ch = new_val.as_char().map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||||
"char".to_string(),
|
"char".to_string(),
|
||||||
err.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;
|
let index = *index;
|
||||||
|
|
||||||
*s = s
|
*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)]
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Dynamic {
|
fn deref(&self) -> &Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => *r,
|
Self::RefMut(r) => *r,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard((r, _)) => &**r,
|
Self::LockGuard((r, _)) => &**r,
|
||||||
Self::Value(ref r) => r,
|
Self::TempValue(ref r) => r,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, ref r) => r,
|
Self::BitField(_, _, ref r) => r,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -567,10 +545,10 @@ impl DerefMut for Target<'_> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn deref_mut(&mut self) -> &mut Dynamic {
|
fn deref_mut(&mut self) -> &mut Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => *r,
|
Self::RefMut(r) => *r,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Self::LockGuard((r, _)) => r.deref_mut(),
|
Self::LockGuard((r, _)) => r.deref_mut(),
|
||||||
Self::Value(ref mut r) => r,
|
Self::TempValue(ref mut r) => r,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::BitField(_, _, ref mut r) => r,
|
Self::BitField(_, _, ref mut r) => r,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -589,7 +567,7 @@ impl AsMut<Dynamic> for Target<'_> {
|
|||||||
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self::Value(value.into())
|
Self::TempValue(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1238,40 +1216,45 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx[rhs] op= new_val
|
// xxx[rhs] op= new_val
|
||||||
_ if new_val.is_some() => {
|
_ 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`");
|
new_val.expect("never fails because `new_val` is `Some`");
|
||||||
let idx_val = idx_val.as_index_value();
|
let idx_val = idx_val.as_index_value();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
let mut idx_val_for_setter = idx_val.clone();
|
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,
|
mods, state, lib, target, idx_val, pos, true, false, level,
|
||||||
) {
|
) {
|
||||||
// Indexed value is a reference - update directly
|
// Indexed value is a reference - update directly
|
||||||
Ok(obj_ptr) => {
|
Ok(ref mut obj_ptr) => {
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
||||||
new_pos,
|
)
|
||||||
)?;
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
return Ok((Dynamic::UNIT, true));
|
None
|
||||||
}
|
}
|
||||||
// Can't index - try to call an index setter
|
// Can't index - try to call an index setter
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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
|
// Any other error
|
||||||
Err(err) => return Err(err),
|
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
|
self.check_data_size(target.as_ref())
|
||||||
let hash_set =
|
.map_err(|err| err.fill_position(root.1))?;
|
||||||
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,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok((Dynamic::UNIT, true))
|
Ok((Dynamic::UNIT, true))
|
||||||
}
|
}
|
||||||
@ -1308,15 +1291,20 @@ impl Engine {
|
|||||||
// {xxx:map}.id op= ???
|
// {xxx:map}.id op= ???
|
||||||
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
|
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
|
||||||
let (name, pos) = &x.2;
|
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)) =
|
let ((new_val, new_pos), (op_info, op_pos)) =
|
||||||
new_val.expect("never fails because `new_val` is `Some`");
|
new_val.expect("never fails because `new_val` is `Some`");
|
||||||
self.eval_op_assignment(
|
let index = name.into();
|
||||||
mods, state, lib, op_info, op_pos, val, root, new_val, new_pos,
|
{
|
||||||
)?;
|
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))
|
Ok((Dynamic::UNIT, true))
|
||||||
}
|
}
|
||||||
// {xxx:map}.id
|
// {xxx:map}.id
|
||||||
@ -1326,7 +1314,6 @@ impl Engine {
|
|||||||
let val = self.get_indexed_mut(
|
let val = self.get_indexed_mut(
|
||||||
mods, state, lib, target, index, *pos, false, false, level,
|
mods, state, lib, target, index, *pos, false, false, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok((val.take_or_clone(), false))
|
Ok((val.take_or_clone(), false))
|
||||||
}
|
}
|
||||||
// xxx.id op= ???
|
// xxx.id op= ???
|
||||||
@ -1361,10 +1348,22 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})?;
|
})?;
|
||||||
let obj_ptr = (&mut orig_val).into();
|
|
||||||
self.eval_op_assignment(
|
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;
|
new_val = orig_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1547,6 +1546,8 @@ impl Engine {
|
|||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
self.check_data_size(target.as_ref())
|
||||||
|
.map_err(|err| err.fill_position(root.1))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((result, may_be_changed))
|
Ok((result, may_be_changed))
|
||||||
@ -1667,23 +1668,19 @@ impl Engine {
|
|||||||
match expr {
|
match expr {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::FnCall(x, _) if _parent_chain_type == ChainType::Dot && !x.is_qualified() => {
|
Expr::FnCall(x, _) if _parent_chain_type == ChainType::Dot && !x.is_qualified() => {
|
||||||
let arg_values = x
|
let crate::ast::FnCallExpr {
|
||||||
.args
|
args, constants, ..
|
||||||
.iter()
|
} = x.as_ref();
|
||||||
.map(|arg_expr| {
|
let mut arg_values = StaticVec::with_capacity(args.len());
|
||||||
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 pos = x
|
for index in 0..args.len() {
|
||||||
.args
|
let (value, _) = self.get_arg_value(
|
||||||
.iter()
|
scope, mods, state, lib, this_ptr, level, args, constants, index,
|
||||||
.map(|arg_expr| arg_expr.position())
|
)?;
|
||||||
.chain(x.literal_args.iter().map(|(_, pos)| *pos))
|
arg_values.push(value.flatten());
|
||||||
.next()
|
}
|
||||||
.unwrap_or_default();
|
|
||||||
|
let pos = x.args.get(0).map(Expr::position).unwrap_or_default();
|
||||||
|
|
||||||
idx_values.push((arg_values, pos).into());
|
idx_values.push((arg_values, pos).into());
|
||||||
}
|
}
|
||||||
@ -1713,23 +1710,19 @@ impl Engine {
|
|||||||
Expr::FnCall(x, _)
|
Expr::FnCall(x, _)
|
||||||
if _parent_chain_type == ChainType::Dot && !x.is_qualified() =>
|
if _parent_chain_type == ChainType::Dot && !x.is_qualified() =>
|
||||||
{
|
{
|
||||||
let arg_values = x
|
let crate::ast::FnCallExpr {
|
||||||
.args
|
args, constants, ..
|
||||||
.iter()
|
} = x.as_ref();
|
||||||
.map(|arg_expr| {
|
let mut arg_values = StaticVec::with_capacity(args.len());
|
||||||
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 pos = x
|
for index in 0..args.len() {
|
||||||
.args
|
let (value, _) = self.get_arg_value(
|
||||||
.iter()
|
scope, mods, state, lib, this_ptr, level, args, constants, index,
|
||||||
.map(|arg_expr| arg_expr.position())
|
)?;
|
||||||
.chain(x.literal_args.iter().map(|(_, pos)| *pos))
|
arg_values.push(value.flatten());
|
||||||
.next()
|
}
|
||||||
.unwrap_or_default();
|
|
||||||
|
let pos = x.args.get(0).map(Expr::position).unwrap_or_default();
|
||||||
|
|
||||||
(arg_values, pos).into()
|
(arg_values, pos).into()
|
||||||
}
|
}
|
||||||
@ -1779,7 +1772,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value at the indexed position of a base type.
|
/// 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")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
fn get_indexed_mut<'t>(
|
fn get_indexed_mut<'t>(
|
||||||
&self,
|
&self,
|
||||||
@ -2000,18 +1993,23 @@ impl Engine {
|
|||||||
|
|
||||||
for expr in x.iter() {
|
for expr in x.iter() {
|
||||||
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
|
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
mods,
|
mods,
|
||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
Some(OpAssignment::new(TOKEN_OP_CONCAT)),
|
Some(OpAssignment::new(TOKEN_OP_CONCAT)),
|
||||||
pos,
|
pos,
|
||||||
(&mut result).into(),
|
&mut (&mut result).into(),
|
||||||
("", Position::NONE),
|
("", Position::NONE),
|
||||||
item,
|
item,
|
||||||
expr.position(),
|
)
|
||||||
)?;
|
.map_err(|err| err.fill_position(expr.position()))?;
|
||||||
|
|
||||||
pos = expr.position();
|
pos = expr.position();
|
||||||
|
|
||||||
|
self.check_data_size(&result)
|
||||||
|
.map_err(|err| err.fill_position(pos))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@ -2055,7 +2053,7 @@ impl Engine {
|
|||||||
namespace,
|
namespace,
|
||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
literal_args: c_args,
|
constants,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
let namespace = namespace
|
let namespace = namespace
|
||||||
@ -2063,8 +2061,8 @@ impl Engine {
|
|||||||
.expect("never fails because function call is qualified");
|
.expect("never fails because function call is qualified");
|
||||||
let hash = hashes.native_hash();
|
let hash = hashes.native_hash();
|
||||||
self.make_qualified_function_call(
|
self.make_qualified_function_call(
|
||||||
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
|
scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash,
|
||||||
level,
|
*pos, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2075,12 +2073,12 @@ impl Engine {
|
|||||||
capture,
|
capture,
|
||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
literal_args: c_args,
|
constants,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
self.make_function_call(
|
self.make_function_call(
|
||||||
scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture,
|
scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos,
|
||||||
level,
|
*capture, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2136,11 +2134,8 @@ impl Engine {
|
|||||||
_ => unreachable!("expression cannot be evaluated: {:?}", expr),
|
_ => unreachable!("expression cannot be evaluated: {:?}", expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
self.check_return_value(result)
|
||||||
self.check_data_size(&result)
|
.map_err(|err| err.fill_position(expr.position()))
|
||||||
.map_err(|err| err.fill_position(expr.position()))?;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statements block.
|
/// Evaluate a statements block.
|
||||||
@ -2219,6 +2214,8 @@ impl Engine {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate an op-assignment statement.
|
||||||
|
/// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and should be set afterwards.
|
||||||
pub(crate) fn eval_op_assignment(
|
pub(crate) fn eval_op_assignment(
|
||||||
&self,
|
&self,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
@ -2226,10 +2223,9 @@ impl Engine {
|
|||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
op_info: Option<OpAssignment>,
|
op_info: Option<OpAssignment>,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
mut target: Target,
|
target: &mut Target,
|
||||||
root: (&str, Position),
|
root: (&str, Position),
|
||||||
mut new_val: Dynamic,
|
mut new_val: Dynamic,
|
||||||
new_val_pos: Position,
|
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
if target.is_read_only() {
|
if target.is_read_only() {
|
||||||
// Assignment to constant variable
|
// Assignment to constant variable
|
||||||
@ -2279,14 +2275,12 @@ impl Engine {
|
|||||||
err => return err.map(|_| ()),
|
err => return err.map(|_| ()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target.propagate_changed_value()?;
|
|
||||||
} else {
|
} else {
|
||||||
// Normal assignment
|
// Normal assignment
|
||||||
target.set_value(new_val, new_val_pos)?;
|
*target.as_mut() = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
target.propagate_changed_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statement.
|
/// Evaluate a statement.
|
||||||
@ -2323,7 +2317,7 @@ impl Engine {
|
|||||||
let rhs_val = self
|
let rhs_val = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
let (lhs_ptr, pos) =
|
let (mut lhs_ptr, pos) =
|
||||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
|
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
|
||||||
|
|
||||||
let var_name = lhs_expr
|
let var_name = lhs_expr
|
||||||
@ -2342,13 +2336,19 @@ impl Engine {
|
|||||||
mods,
|
mods,
|
||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
op_info.clone(),
|
*op_info,
|
||||||
*op_pos,
|
*op_pos,
|
||||||
lhs_ptr,
|
&mut lhs_ptr,
|
||||||
(var_name, pos),
|
(var_name, pos),
|
||||||
rhs_val,
|
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)
|
Ok(Dynamic::UNIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2521,7 +2521,7 @@ impl Engine {
|
|||||||
|
|
||||||
// For loop
|
// For loop
|
||||||
Stmt::For(expr, x, _) => {
|
Stmt::For(expr, x, _) => {
|
||||||
let (Ident { name, .. }, statements) = x.as_ref();
|
let (Ident { name, .. }, counter, statements) = x.as_ref();
|
||||||
let iter_obj = self
|
let iter_obj = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -2550,17 +2550,40 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
// Add the loop variable
|
// Add the loop variables
|
||||||
let var_name: Cow<'_, str> = if state.is_global() {
|
let orig_scope_len = scope.len();
|
||||||
name.to_string().into()
|
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 {
|
} 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;
|
let index = scope.len() - 1;
|
||||||
state.scope_level += 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 loop_var = scope.get_mut_by_index(index);
|
||||||
let value = iter_value.flatten();
|
let value = iter_value.flatten();
|
||||||
|
|
||||||
@ -2600,7 +2623,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.scope_level -= 1;
|
state.scope_level -= 1;
|
||||||
scope.rewind(scope.len() - 1);
|
scope.rewind(orig_scope_len);
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
EvalAltResult::ErrorFor(expr.position()).into()
|
EvalAltResult::ErrorFor(expr.position()).into()
|
||||||
@ -2620,7 +2643,7 @@ impl Engine {
|
|||||||
namespace,
|
namespace,
|
||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
literal_args: c_args,
|
constants,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
let namespace = namespace
|
let namespace = namespace
|
||||||
@ -2628,8 +2651,8 @@ impl Engine {
|
|||||||
.expect("never fails because function call is qualified");
|
.expect("never fails because function call is qualified");
|
||||||
let hash = hashes.native_hash();
|
let hash = hashes.native_hash();
|
||||||
self.make_qualified_function_call(
|
self.make_qualified_function_call(
|
||||||
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
|
scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash,
|
||||||
level,
|
*pos, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2640,12 +2663,12 @@ impl Engine {
|
|||||||
capture,
|
capture,
|
||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
literal_args: c_args,
|
constants,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
self.make_function_call(
|
self.make_function_call(
|
||||||
scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture,
|
scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos,
|
||||||
level,
|
*capture, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2672,8 +2695,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
_ => {
|
_ => {
|
||||||
use crate::INT;
|
|
||||||
|
|
||||||
let mut err_map: Map = Default::default();
|
let mut err_map: Map = Default::default();
|
||||||
let err_pos = err.take_position();
|
let err_pos = err.take_position();
|
||||||
|
|
||||||
@ -2904,20 +2925,73 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
self.check_return_value(result)
|
||||||
self.check_data_size(&result)
|
.map_err(|err| err.fill_position(stmt.position()))
|
||||||
.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
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
/// Check a result to ensure that the data size is within allowable limit.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn check_data_size(&self, result: &RhaiResult) -> Result<(), Box<EvalAltResult>> {
|
#[inline(always)]
|
||||||
let result = match result {
|
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
|
||||||
Err(_) => return Ok(()),
|
result.and_then(|r| self.check_data_size(&r).map(|_| r))
|
||||||
Ok(r) => 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
|
// If no data size limits, just return
|
||||||
let mut _has_limit = self.limits.max_string_size.is_some();
|
let mut _has_limit = self.limits.max_string_size.is_some();
|
||||||
@ -2934,59 +3008,7 @@ impl Engine {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively calculate the size of a value (especially `Array` and `Map`)
|
let (_arr, _map, s) = calc_size(value);
|
||||||
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);
|
|
||||||
|
|
||||||
if s > self
|
if s > self
|
||||||
.limits
|
.limits
|
||||||
|
@ -538,8 +538,9 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
/// Panics if the type is [`Array`], [`Map`], [`String`],
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
|
||||||
|
/// Indexers for arrays, object maps, strings and integers cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -595,6 +596,9 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
panic!("Cannot register indexer for strings.");
|
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)
|
self.register_fn(crate::engine::FN_IDX_GET, get_fn)
|
||||||
}
|
}
|
||||||
@ -606,8 +610,9 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
/// Panics if the type is [`Array`], [`Map`], [`String`],
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
|
||||||
|
/// Indexers for arrays, object maps, strings and integers cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -669,6 +674,9 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
panic!("Cannot register indexer for strings.");
|
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)
|
self.register_result_fn(crate::engine::FN_IDX_GET, get_fn)
|
||||||
}
|
}
|
||||||
@ -678,8 +686,9 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
/// Panics if the type is [`Array`], [`Map`], [`String`],
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
|
||||||
|
/// Indexers for arrays, object maps, strings and integers cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -737,6 +746,9 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
panic!("Cannot register indexer for strings.");
|
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)
|
self.register_fn(crate::engine::FN_IDX_SET, set_fn)
|
||||||
}
|
}
|
||||||
@ -746,8 +758,9 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
/// Panics if the type is [`Array`], [`Map`], [`String`],
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
|
||||||
|
/// Indexers for arrays, object maps, strings and integers cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -812,6 +825,9 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
panic!("Cannot register indexer for strings.");
|
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)
|
self.register_result_fn(crate::engine::FN_IDX_SET, set_fn)
|
||||||
}
|
}
|
||||||
@ -821,8 +837,9 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
/// Panics if the type is [`Array`], [`Map`], [`String`],
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT].
|
||||||
|
/// Indexers for arrays, object maps, strings and integers cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -1337,7 +1354,8 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// let map = engine.parse_json(
|
/// let map = engine.parse_json(
|
||||||
/// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"#
|
/// 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.len(), 4);
|
||||||
/// assert_eq!(map["a"].as_int().unwrap(), 123);
|
/// assert_eq!(map["a"].as_int().unwrap(), 123);
|
||||||
@ -1876,7 +1894,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
args: impl crate::fn_args::FuncArgs,
|
args: impl crate::FuncArgs,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mut arg_values: crate::StaticVec<_> = Default::default();
|
let mut arg_values: crate::StaticVec<_> = Default::default();
|
||||||
args.parse(&mut arg_values);
|
args.parse(&mut arg_values);
|
||||||
@ -1886,14 +1904,14 @@ impl Engine {
|
|||||||
|
|
||||||
let typ = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
return result.try_cast().ok_or_else(|| {
|
result.try_cast().ok_or_else(|| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name(type_name::<T>()).into(),
|
self.map_type_name(type_name::<T>()).into(),
|
||||||
typ.into(),
|
typ.into(),
|
||||||
Position::NONE,
|
Position::NONE,
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
|
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
|
||||||
/// and optionally a value for binding to the `this` pointer.
|
/// and optionally a value for binding to the `this` pointer.
|
||||||
|
@ -40,11 +40,12 @@ pub trait FuncArgs {
|
|||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
/// let mut scope = Scope::new();
|
/// let mut scope = Scope::new();
|
||||||
///
|
///
|
||||||
/// let ast = engine.compile(r#"
|
/// let ast = engine.compile(
|
||||||
/// fn hello(x, y, z) {
|
/// "
|
||||||
/// if x { `hello ${y}` } else { y + z }
|
/// fn hello(x, y, z) {
|
||||||
/// }
|
/// if x { `hello ${y}` } else { y + z }
|
||||||
/// "#)?;
|
/// }
|
||||||
|
/// ")?;
|
||||||
///
|
///
|
||||||
/// let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?;
|
/// 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::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
|
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")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
iter::once,
|
|
||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,7 +139,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
self.map_type_name((*a).type_name())
|
self.map_type_name((*a).type_name())
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<StaticVec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -332,6 +331,7 @@ impl Engine {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.or_else(|| state_source.as_ref())
|
.or_else(|| state_source.as_ref())
|
||||||
.map(|s| s.as_str());
|
.map(|s| s.as_str());
|
||||||
|
|
||||||
let result = if func.is_plugin_fn() {
|
let result = if func.is_plugin_fn() {
|
||||||
func.get_plugin_fn()
|
func.get_plugin_fn()
|
||||||
.call((self, fn_name, source, mods, lib).into(), args)
|
.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)
|
// See if the function match print/debug (which requires special processing)
|
||||||
return Ok(match fn_name {
|
return Ok(match fn_name {
|
||||||
KEYWORD_PRINT => {
|
KEYWORD_PRINT => {
|
||||||
let text = result.take_immutable_string().map_err(|typ| {
|
let text = result.as_immutable_string().map_err(|typ| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
typ.into(),
|
typ.into(),
|
||||||
@ -357,7 +357,7 @@ impl Engine {
|
|||||||
((self.print)(&text).into(), false)
|
((self.print)(&text).into(), false)
|
||||||
}
|
}
|
||||||
KEYWORD_DEBUG => {
|
KEYWORD_DEBUG => {
|
||||||
let text = result.take_immutable_string().map_err(|typ| {
|
let text = result.as_immutable_string().map_err(|typ| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
typ.into(),
|
typ.into(),
|
||||||
@ -515,11 +515,12 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Merge in encapsulated environment, if any
|
// 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 {
|
let (unified_lib, unified) = if let Some(ref env_lib) = fn_def.lib {
|
||||||
state.push_fn_resolution_cache();
|
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)
|
(lib_merged.as_ref(), true)
|
||||||
} else {
|
} else {
|
||||||
(lib, false)
|
(lib, false)
|
||||||
@ -627,6 +628,12 @@ impl Engine {
|
|||||||
_capture_scope: Option<Scope>,
|
_capture_scope: Option<Scope>,
|
||||||
_level: usize,
|
_level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> 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.
|
// Check for data race.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
ensure_no_data_race(fn_name, args, is_ref)?;
|
ensure_no_data_race(fn_name, args, is_ref)?;
|
||||||
@ -638,7 +645,7 @@ impl Engine {
|
|||||||
return Ok((
|
return Ok((
|
||||||
self.map_type_name(args[0].type_name()).to_string().into(),
|
self.map_type_name(args[0].type_name()).to_string().into(),
|
||||||
false,
|
false,
|
||||||
));
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle is_def_fn()
|
// Handle is_def_fn()
|
||||||
@ -668,39 +675,15 @@ impl Engine {
|
|||||||
// Handle is_shared()
|
// Handle is_shared()
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => {
|
crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => {
|
||||||
return EvalAltResult::ErrorRuntime(
|
return no_method_err(fn_name, pos)
|
||||||
format!(
|
|
||||||
"'{}' should not be called this way. Try {}(...);",
|
|
||||||
fn_name, fn_name
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => {
|
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => {
|
||||||
return EvalAltResult::ErrorRuntime(
|
return no_method_err(fn_name, pos)
|
||||||
format!(
|
|
||||||
"'{}' should not be called this way. Try {}(...);",
|
|
||||||
fn_name, fn_name
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => {
|
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => {
|
||||||
return EvalAltResult::ErrorRuntime(
|
return no_method_err(fn_name, pos)
|
||||||
format!(
|
|
||||||
"'{}' should not be called this way. Try {}(...);",
|
|
||||||
fn_name, fn_name
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -798,17 +781,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Native function call
|
// Native function call
|
||||||
self.call_native_fn(
|
let hash = hash.native_hash();
|
||||||
mods,
|
self.call_native_fn(mods, state, lib, fn_name, hash, args, is_ref, false, pos)
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
fn_name,
|
|
||||||
hash.native_hash(),
|
|
||||||
args,
|
|
||||||
is_ref,
|
|
||||||
false,
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a list of statements with no `this` pointer.
|
/// Evaluate a list of statements with no `this` pointer.
|
||||||
@ -911,8 +885,11 @@ impl Engine {
|
|||||||
// Recalculate hashes
|
// Recalculate hashes
|
||||||
let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len));
|
let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len));
|
||||||
// Arguments are passed as-is, adding the curried arguments
|
// Arguments are passed as-is, adding the curried arguments
|
||||||
let mut curry: StaticVec<_> = fn_ptr.curry().iter().cloned().collect();
|
let mut curry = StaticVec::with_capacity(fn_ptr.num_curried());
|
||||||
let mut args: StaticVec<_> = curry.iter_mut().chain(call_args.iter_mut()).collect();
|
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
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -945,11 +922,12 @@ impl Engine {
|
|||||||
calc_fn_hash(fn_name, args_len + 1),
|
calc_fn_hash(fn_name, args_len + 1),
|
||||||
);
|
);
|
||||||
// Replace the first argument with the object pointer, adding the curried arguments
|
// Replace the first argument with the object pointer, adding the curried arguments
|
||||||
let mut curry: StaticVec<_> = fn_ptr.curry().iter().cloned().collect();
|
let mut curry = StaticVec::with_capacity(fn_ptr.num_curried());
|
||||||
let mut args: StaticVec<_> = once(target.as_mut())
|
curry.extend(fn_ptr.curry().iter().cloned());
|
||||||
.chain(curry.iter_mut())
|
let mut args = StaticVec::with_capacity(curry.len() + call_args.len() + 1);
|
||||||
.chain(call_args.iter_mut())
|
args.push(target.as_mut());
|
||||||
.collect();
|
args.extend(curry.iter_mut());
|
||||||
|
args.extend(call_args.iter_mut());
|
||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -979,7 +957,7 @@ impl Engine {
|
|||||||
.curry()
|
.curry()
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.chain(call_args.iter_mut().map(|v| mem::take(v)))
|
.chain(call_args.iter_mut().map(mem::take))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1020,8 +998,9 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Attached object pointer in front of the arguments
|
// Attached object pointer in front of the arguments
|
||||||
let mut args: StaticVec<_> =
|
let mut args = StaticVec::with_capacity(call_args.len() + 1);
|
||||||
once(target.as_mut()).chain(call_args.iter_mut()).collect();
|
args.push(target.as_mut());
|
||||||
|
args.extend(call_args.iter_mut());
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, fn_name, hash, &mut args, is_ref, true, pos, None, level,
|
mods, state, lib, fn_name, hash, &mut args, is_ref, true, pos, None, level,
|
||||||
@ -1039,6 +1018,28 @@ impl Engine {
|
|||||||
Ok((result, updated))
|
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.
|
/// Call a function in normal function-call style.
|
||||||
pub(crate) fn make_function_call(
|
pub(crate) fn make_function_call(
|
||||||
&self,
|
&self,
|
||||||
@ -1049,7 +1050,7 @@ impl Engine {
|
|||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
args_expr: &[Expr],
|
args_expr: &[Expr],
|
||||||
literal_args: &[(Dynamic, Position)],
|
constants: &[Dynamic],
|
||||||
mut hashes: FnCallHashes,
|
mut hashes: FnCallHashes,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
capture_scope: bool,
|
capture_scope: bool,
|
||||||
@ -1058,20 +1059,15 @@ impl Engine {
|
|||||||
// Handle call() - Redirect function call
|
// Handle call() - Redirect function call
|
||||||
let redirected;
|
let redirected;
|
||||||
let mut args_expr = args_expr;
|
let mut args_expr = args_expr;
|
||||||
let mut literal_args = literal_args;
|
let mut total_args = args_expr.len();
|
||||||
let mut total_args = args_expr.len() + literal_args.len();
|
|
||||||
let mut curry = StaticVec::new();
|
let mut curry = StaticVec::new();
|
||||||
let mut name = fn_name;
|
let mut name = fn_name;
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
// Handle call()
|
// Handle call()
|
||||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
let (arg, arg_pos) = self.get_arg_value(
|
||||||
|| Ok(literal_args[0].clone()),
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
|arg| {
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
|
||||||
.map(|v| (v, arg.position()))
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !arg.is::<FnPtr>() {
|
if !arg.is::<FnPtr>() {
|
||||||
@ -1089,11 +1085,7 @@ impl Engine {
|
|||||||
name = &redirected;
|
name = &redirected;
|
||||||
|
|
||||||
// Skip the first argument
|
// Skip the first argument
|
||||||
if !args_expr.is_empty() {
|
args_expr = &args_expr[1..];
|
||||||
args_expr = &args_expr[1..];
|
|
||||||
} else {
|
|
||||||
literal_args = &literal_args[1..];
|
|
||||||
}
|
|
||||||
total_args -= 1;
|
total_args -= 1;
|
||||||
|
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
@ -1106,17 +1098,13 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// Handle Fn()
|
// Handle Fn()
|
||||||
KEYWORD_FN_PTR if total_args == 1 => {
|
KEYWORD_FN_PTR if total_args == 1 => {
|
||||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
let (arg, arg_pos) = self.get_arg_value(
|
||||||
|| Ok(literal_args[0].clone()),
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
|arg| {
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
|
||||||
.map(|v| (v, arg.position()))
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Fn - only in function call style
|
// Fn - only in function call style
|
||||||
return arg
|
return arg
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))
|
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))
|
||||||
.and_then(|s| FnPtr::try_from(s))
|
.and_then(|s| FnPtr::try_from(s))
|
||||||
.map(Into::<Dynamic>::into)
|
.map(Into::<Dynamic>::into)
|
||||||
@ -1125,12 +1113,8 @@ impl Engine {
|
|||||||
|
|
||||||
// Handle curry()
|
// Handle curry()
|
||||||
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
||||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
let (arg, arg_pos) = self.get_arg_value(
|
||||||
|| Ok(literal_args[0].clone()),
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
|arg| {
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
|
||||||
.map(|v| (v, arg.position()))
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !arg.is::<FnPtr>() {
|
if !arg.is::<FnPtr>() {
|
||||||
@ -1143,14 +1127,11 @@ impl Engine {
|
|||||||
let (name, mut fn_curry) = arg.cast::<FnPtr>().take_data();
|
let (name, mut fn_curry) = arg.cast::<FnPtr>().take_data();
|
||||||
|
|
||||||
// Append the new curried arguments to the existing list.
|
// Append the new curried arguments to the existing list.
|
||||||
if !args_expr.is_empty() {
|
for index in 1..args_expr.len() {
|
||||||
args_expr.iter().skip(1).try_for_each(|expr| {
|
let (value, _) = self.get_arg_value(
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||||
.map(|value| fn_curry.push(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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
||||||
@ -1159,9 +1140,8 @@ impl Engine {
|
|||||||
// Handle is_shared()
|
// Handle is_shared()
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
||||||
let arg = args_expr.get(0).map_or_else(
|
let (arg, _) = self.get_arg_value(
|
||||||
|| Ok(literal_args[0].0.clone()),
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
|arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level),
|
|
||||||
)?;
|
)?;
|
||||||
return Ok(arg.is_shared().into());
|
return Ok(arg.is_shared().into());
|
||||||
}
|
}
|
||||||
@ -1169,27 +1149,17 @@ impl Engine {
|
|||||||
// Handle is_def_fn()
|
// Handle is_def_fn()
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
||||||
let (arg, arg_pos) = if !args_expr.is_empty() {
|
let (arg, arg_pos) = self.get_arg_value(
|
||||||
(
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?,
|
)?;
|
||||||
args_expr[0].position(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
literal_args[0].clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let fn_name = arg
|
let fn_name = arg
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.map_err(|err| self.make_type_mismatch_err::<ImmutableString>(err, arg_pos))?;
|
.map_err(|err| self.make_type_mismatch_err::<ImmutableString>(err, arg_pos))?;
|
||||||
|
|
||||||
let (arg, arg_pos) = if args_expr.len() > 1 {
|
let (arg, arg_pos) = self.get_arg_value(
|
||||||
(
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 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 num_params = arg
|
let num_params = arg
|
||||||
.as_int()
|
.as_int()
|
||||||
@ -1206,15 +1176,11 @@ impl Engine {
|
|||||||
|
|
||||||
// Handle is_def_var()
|
// Handle is_def_var()
|
||||||
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
||||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
let (arg, arg_pos) = self.get_arg_value(
|
||||||
|| Ok(literal_args[0].clone()),
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
|arg| {
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
|
||||||
.map(|v| (v, arg.position()))
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
let var_name = arg
|
let var_name = arg
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.map_err(|err| self.make_type_mismatch_err::<ImmutableString>(err, arg_pos))?;
|
.map_err(|err| self.make_type_mismatch_err::<ImmutableString>(err, arg_pos))?;
|
||||||
return Ok(scope.contains(&var_name).into());
|
return Ok(scope.contains(&var_name).into());
|
||||||
}
|
}
|
||||||
@ -1223,25 +1189,14 @@ impl Engine {
|
|||||||
KEYWORD_EVAL if total_args == 1 => {
|
KEYWORD_EVAL if total_args == 1 => {
|
||||||
// eval - only in function call style
|
// eval - only in function call style
|
||||||
let prev_len = scope.len();
|
let prev_len = scope.len();
|
||||||
let (script, script_pos) = args_expr.get(0).map_or_else(
|
let (value, pos) = self.get_arg_value(
|
||||||
|| Ok(literal_args[0].clone()),
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||||
|script_expr| {
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)
|
|
||||||
.map(|v| (v, script_expr.position()))
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
let script = script.take_immutable_string().map_err(|typ| {
|
let script = &value
|
||||||
self.make_type_mismatch_err::<ImmutableString>(typ, script_pos)
|
.as_immutable_string()
|
||||||
})?;
|
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
||||||
let result = self.eval_script_expr_in_place(
|
let result =
|
||||||
scope,
|
self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1);
|
||||||
mods,
|
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
&script,
|
|
||||||
script_pos,
|
|
||||||
level + 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
// all variable offsets from this point on will be mis-aligned.
|
// all variable offsets from this point on will be mis-aligned.
|
||||||
@ -1255,7 +1210,7 @@ impl Engine {
|
|||||||
state
|
state
|
||||||
.source
|
.source
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.to_string())
|
.map(Identifier::to_string)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
err,
|
err,
|
||||||
pos,
|
pos,
|
||||||
@ -1267,8 +1222,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal function call - except for Fn, curry, call and eval (handled above)
|
// Normal function call - except for Fn, curry, call and eval (handled above)
|
||||||
let mut arg_values: StaticVec<_>;
|
let mut arg_values = StaticVec::with_capacity(args_expr.len());
|
||||||
let mut args: StaticVec<_>;
|
let mut args = StaticVec::with_capacity(args_expr.len() + curry.len());
|
||||||
let mut is_ref = false;
|
let mut is_ref = false;
|
||||||
let capture = if capture_scope && !scope.is_empty() {
|
let capture = if capture_scope && !scope.is_empty() {
|
||||||
Some(scope.clone_visible())
|
Some(scope.clone_visible())
|
||||||
@ -1276,24 +1231,20 @@ impl Engine {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if args_expr.is_empty() && literal_args.is_empty() && curry.is_empty() {
|
if args_expr.is_empty() && curry.is_empty() {
|
||||||
// No arguments
|
// No arguments
|
||||||
args = Default::default();
|
|
||||||
} else {
|
} else {
|
||||||
// If the first argument is a variable, and there is no curried arguments,
|
// 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
|
// convert to method-call style in order to leverage potential &mut first argument and
|
||||||
// avoid cloning the value
|
// avoid cloning the value
|
||||||
if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) {
|
if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) {
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
arg_values = args_expr
|
for index in 1..args_expr.len() {
|
||||||
.iter()
|
let (value, _) = self.get_arg_value(
|
||||||
.skip(1)
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||||
.map(|expr| {
|
)?;
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
arg_values.push(value.flatten());
|
||||||
.map(Dynamic::flatten)
|
}
|
||||||
})
|
|
||||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
|
|
||||||
let (mut target, _pos) =
|
let (mut target, _pos) =
|
||||||
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
||||||
@ -1310,27 +1261,26 @@ impl Engine {
|
|||||||
#[cfg(feature = "no_closure")]
|
#[cfg(feature = "no_closure")]
|
||||||
let target_is_shared = false;
|
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.insert(0, target.take_or_clone().flatten());
|
||||||
arg_values.iter_mut().collect()
|
args.extend(arg_values.iter_mut())
|
||||||
} else {
|
} else {
|
||||||
// Turn it into a method call only if the object is not shared and not a simple value
|
// Turn it into a method call only if the object is not shared and not a simple value
|
||||||
is_ref = true;
|
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");
|
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 {
|
} else {
|
||||||
// func(..., ...)
|
// func(..., ...)
|
||||||
arg_values = args_expr
|
for index in 0..args_expr.len() {
|
||||||
.iter()
|
let (value, _) = self.get_arg_value(
|
||||||
.map(|expr| {
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
)?;
|
||||||
.map(Dynamic::flatten)
|
arg_values.push(value.flatten());
|
||||||
})
|
}
|
||||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
args.extend(curry.iter_mut());
|
||||||
.collect::<Result<_, _>>()?;
|
args.extend(arg_values.iter_mut());
|
||||||
|
|
||||||
args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1351,38 +1301,33 @@ impl Engine {
|
|||||||
namespace: &NamespaceRef,
|
namespace: &NamespaceRef,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
args_expr: &[Expr],
|
args_expr: &[Expr],
|
||||||
literal_args: &[(Dynamic, Position)],
|
constants: &[Dynamic],
|
||||||
hash: u64,
|
hash: u64,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> 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 first_arg_value = None;
|
||||||
let mut args: StaticVec<_>;
|
|
||||||
|
|
||||||
if args_expr.is_empty() && literal_args.is_empty() {
|
if args_expr.is_empty() {
|
||||||
// No arguments
|
// No arguments
|
||||||
args = Default::default();
|
|
||||||
} else {
|
} else {
|
||||||
// See if the first argument is a variable (not namespace-qualified).
|
// See if the first argument is a variable (not namespace-qualified).
|
||||||
// If so, convert to method-call style in order to leverage potential
|
// If so, convert to method-call style in order to leverage potential
|
||||||
// &mut first argument and avoid cloning the value
|
// &mut first argument and avoid cloning the value
|
||||||
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
arg_values = args_expr
|
for index in 0..args_expr.len() {
|
||||||
.iter()
|
if index == 0 {
|
||||||
.enumerate()
|
arg_values.push(Default::default());
|
||||||
.map(|(i, expr)| {
|
} else {
|
||||||
// Skip the first argument
|
let (value, _) = self.get_arg_value(
|
||||||
if i == 0 {
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||||
Ok(Default::default())
|
)?;
|
||||||
} else {
|
arg_values.push(value.flatten());
|
||||||
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<_, _>>()?;
|
|
||||||
|
|
||||||
// Get target reference to first argument
|
// Get target reference to first argument
|
||||||
let (target, _pos) =
|
let (target, _pos) =
|
||||||
@ -1396,9 +1341,9 @@ impl Engine {
|
|||||||
#[cfg(feature = "no_closure")]
|
#[cfg(feature = "no_closure")]
|
||||||
let target_is_shared = false;
|
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();
|
arg_values[0] = target.take_or_clone().flatten();
|
||||||
args = arg_values.iter_mut().collect();
|
args.extend(arg_values.iter_mut());
|
||||||
} else {
|
} else {
|
||||||
// Turn it into a method call only if the object is not shared and not a simple value
|
// Turn it into a method call only if the object is not shared and not a simple value
|
||||||
let (first, rest) = arg_values
|
let (first, rest) = arg_values
|
||||||
@ -1406,20 +1351,18 @@ impl Engine {
|
|||||||
.expect("never fails because the arguments list is not empty");
|
.expect("never fails because the arguments list is not empty");
|
||||||
first_arg_value = Some(first);
|
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");
|
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 {
|
} else {
|
||||||
// func(..., ...) or func(mod::x, ...)
|
// func(..., ...) or func(mod::x, ...)
|
||||||
arg_values = args_expr
|
for index in 0..args_expr.len() {
|
||||||
.iter()
|
let (value, _) = self.get_arg_value(
|
||||||
.map(|expr| {
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
)?;
|
||||||
.map(Dynamic::flatten)
|
arg_values.push(value.flatten());
|
||||||
})
|
}
|
||||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
args.extend(arg_values.iter_mut());
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
|
|
||||||
args = arg_values.iter_mut().collect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,7 @@ use crate::{
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
fmt,
|
fmt, mem,
|
||||||
iter::once,
|
|
||||||
mem,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
/// 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
|
/// A general function pointer, which may carry additional (i.e. curried) argument values
|
||||||
/// to be passed onto a function during a call.
|
/// to be passed onto a function during a call.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct FnPtr(Identifier, StaticVec<Dynamic>);
|
pub struct FnPtr(Identifier, StaticVec<Dynamic>);
|
||||||
|
|
||||||
impl FnPtr {
|
impl FnPtr {
|
||||||
@ -344,28 +342,23 @@ impl FnPtr {
|
|||||||
this_ptr: Option<&mut Dynamic>,
|
this_ptr: Option<&mut Dynamic>,
|
||||||
mut arg_values: impl AsMut<[Dynamic]>,
|
mut arg_values: impl AsMut<[Dynamic]>,
|
||||||
) -> RhaiResult {
|
) -> 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() {
|
if self.num_curried() > 0 {
|
||||||
arg_values.as_mut()
|
args_data = StaticVec::with_capacity(self.num_curried() + arg_values.len());
|
||||||
} else {
|
args_data.extend(self.curry().iter().cloned());
|
||||||
args_data = self
|
args_data.extend(arg_values.iter_mut().map(mem::take));
|
||||||
.curry()
|
arg_values = args_data.as_mut();
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.chain(arg_values.as_mut().iter_mut().map(mem::take))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
args_data.as_mut()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_method = this_ptr.is_some();
|
let is_method = this_ptr.is_some();
|
||||||
|
|
||||||
let mut args: StaticVec<_> = if let Some(obj) = this_ptr {
|
let mut args = StaticVec::with_capacity(arg_values.len() + 1);
|
||||||
once(obj).chain(arg_values.iter_mut()).collect()
|
if let Some(obj) = this_ptr {
|
||||||
} else {
|
args.push(obj);
|
||||||
arg_values.iter_mut().collect()
|
}
|
||||||
};
|
args.extend(arg_values.iter_mut());
|
||||||
|
|
||||||
ctx.call_fn_dynamic_raw(self.fn_name(), is_method, &mut args)
|
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>() {
|
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||||
// If T is `String`, data must be `ImmutableString`, so map directly to it
|
// If T is `String`, data must be `ImmutableString`, so map directly to it
|
||||||
let value = mem::take(data)
|
let value = mem::take(data)
|
||||||
.take_string()
|
.as_string()
|
||||||
.expect("never fails because the type was checked");
|
.expect("never fails because the type was checked");
|
||||||
unsafe_try_cast(value).expect("never fails because the type was checked")
|
unsafe_try_cast(value).expect("never fails because the type was checked")
|
||||||
} else {
|
} else {
|
||||||
|
@ -71,7 +71,7 @@ impl FuncInfo {
|
|||||||
let mut sig = format!("{}(", self.name);
|
let mut sig = format!("{}(", self.name);
|
||||||
|
|
||||||
if !self.param_names.is_empty() {
|
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();
|
self.param_names.iter().map(|s| s.as_str().into()).collect();
|
||||||
let return_type = params.pop().unwrap_or_else(|| "()".into());
|
let return_type = params.pop().unwrap_or_else(|| "()".into());
|
||||||
sig.push_str(¶ms.join(", "));
|
sig.push_str(¶ms.join(", "));
|
||||||
@ -1649,7 +1649,7 @@ impl fmt::Debug for NamespaceRef {
|
|||||||
.path
|
.path
|
||||||
.iter()
|
.iter()
|
||||||
.map(|Ident { name, .. }| name.as_str())
|
.map(|Ident { name, .. }| name.as_str())
|
||||||
.collect::<Vec<_>>()
|
.collect::<StaticVec<_>>()
|
||||||
.join("::"),
|
.join("::"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
117
src/optimize.rs
117
src/optimize.rs
@ -208,7 +208,7 @@ fn optimize_stmt_block(
|
|||||||
state.push_var(
|
state.push_var(
|
||||||
&x.name,
|
&x.name,
|
||||||
AccessMode::ReadOnly,
|
AccessMode::ReadOnly,
|
||||||
value_expr.get_constant_value(),
|
value_expr.get_literal_value(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,7 +297,7 @@ fn optimize_stmt_block(
|
|||||||
Stmt::Noop(*pos)
|
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]
|
[.., second_last_stmt, last_stmt]
|
||||||
if !last_stmt.returns_value() && is_pure(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)
|
&& x.0.is_variable_access(true)
|
||||||
&& matches!(&x.2, Expr::FnCall(x2, _)
|
&& matches!(&x.2, Expr::FnCall(x2, _)
|
||||||
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
|
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)
|
&& 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 = Token::lookup_from_syntax(&x2.name).unwrap();
|
||||||
let op_assignment = op.make_op_assignment().unwrap();
|
let op_assignment = op.make_op_assignment().unwrap();
|
||||||
x.1 = Some(OpAssignment::new(op_assignment));
|
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 {
|
} else {
|
||||||
let (value, pos) = mem::take(&mut x2.literal_args[0]);
|
x.2 = value;
|
||||||
Expr::DynamicConstant(Box::new(value), pos)
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
@ -451,7 +454,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
|
|
||||||
// switch const { ... }
|
// switch const { ... }
|
||||||
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
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();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
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 }
|
// for id in expr { block }
|
||||||
Stmt::For(iterable, x, _) => {
|
Stmt::For(iterable, x, _) => {
|
||||||
optimize_expr(iterable, state, false);
|
optimize_expr(iterable, state, false);
|
||||||
let body = mem::take(x.1.statements()).into_vec();
|
let body = mem::take(x.2.statements()).into_vec();
|
||||||
*x.1.statements() = optimize_stmt_block(body, state, false, true, false).into();
|
*x.2.statements() = optimize_stmt_block(body, state, false, true, false).into();
|
||||||
}
|
}
|
||||||
// let id = expr;
|
// let id = expr;
|
||||||
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state, false),
|
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"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(_, _) if expr.is_constant() => {
|
Expr::Array(_, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
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 .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(_, _) if expr.is_constant() => {
|
Expr::Map(_, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
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, .. }
|
// #{ key:value, .. }
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -905,14 +908,23 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
|
|||||||
Expr::FnCall(x, pos)
|
Expr::FnCall(x, pos)
|
||||||
if !x.is_qualified() // Non-qualified
|
if !x.is_qualified() // Non-qualified
|
||||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||||
&& x.args_count() == 1
|
&& x.args.len() == 1
|
||||||
&& x.literal_args.len() == 1
|
&& x.args[0].is_constant()
|
||||||
&& x.literal_args[0].0.is::<ImmutableString>()
|
|
||||||
&& x.name == KEYWORD_FN_PTR
|
&& x.name == KEYWORD_FN_PTR
|
||||||
=> {
|
=> {
|
||||||
state.set_dirty();
|
let fn_name = match x.args[0] {
|
||||||
let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.literal_args[0].0).as_str_ref().unwrap().into(), Default::default());
|
Expr::Stack(slot, _) => Some(x.constants[slot].clone()),
|
||||||
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
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
|
// 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)
|
Expr::FnCall(x, pos)
|
||||||
if !x.is_qualified() // Non-qualified
|
if !x.is_qualified() // Non-qualified
|
||||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
&& 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
|
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
||||||
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
||||||
=> {
|
=> {
|
||||||
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap())
|
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
|
||||||
.chain(x.literal_args.iter().map(|(v, _)| v).cloned())
|
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
||||||
.collect();
|
_ => e.get_literal_value().unwrap()
|
||||||
|
}).collect();
|
||||||
|
|
||||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).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));
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
||||||
|
|
||||||
// Move constant arguments to the right
|
// Move constant arguments
|
||||||
while x.args.last().map(Expr::is_constant).unwrap_or(false) {
|
for arg in x.args.iter_mut() {
|
||||||
let arg = x.args.pop().unwrap();
|
if let Some(value) = arg.get_literal_value() {
|
||||||
let arg_pos = arg.position();
|
state.set_dirty();
|
||||||
x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
|
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
|
// 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)
|
// First search for script-defined functions (can override built-in)
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[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")]
|
#[cfg(feature = "no_function")]
|
||||||
let has_script_fn = false;
|
let has_script_fn = false;
|
||||||
|
|
||||||
if !has_script_fn {
|
if !has_script_fn {
|
||||||
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap())
|
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
|
||||||
.chain(x.literal_args.iter().map(|(v, _)| v).cloned())
|
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
||||||
.collect();
|
_ => e.get_literal_value().unwrap()
|
||||||
|
}).collect();
|
||||||
|
|
||||||
// Save the typename of the first argument if it is `type_of()`
|
// Save the typename of the first argument if it is `type_of()`
|
||||||
// This is to avoid `call_args` being passed into the closure
|
// 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)
|
if let Some(mut result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), &mut arg_values)
|
||||||
.or_else(|| {
|
.or_else(|| if !arg_for_type_of.is_empty() {
|
||||||
if !arg_for_type_of.is_empty() {
|
// Handle `type_of()`
|
||||||
// Handle `type_of()`
|
Some(arg_for_type_of.to_string().into())
|
||||||
Some(arg_for_type_of.to_string().into())
|
} else {
|
||||||
} else {
|
None
|
||||||
None
|
}).map(Expr::from)
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(Expr::from)
|
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
result.set_position(*pos);
|
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
|
// id(args ..) -> optimize function call arguments
|
||||||
Expr::FnCall(x, _) => {
|
Expr::FnCall(x, _) => for arg in x.args.iter_mut() {
|
||||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
optimize_expr(arg, state, false);
|
||||||
|
|
||||||
// Move constant arguments to the right
|
// Move constant arguments
|
||||||
while x.args.last().map(Expr::is_constant).unwrap_or(false) {
|
if let Some(value) = arg.get_literal_value() {
|
||||||
let arg = x.args.pop().unwrap();
|
state.set_dirty();
|
||||||
let arg_pos = arg.position();
|
x.constants.push(value);
|
||||||
x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
|
*arg = Expr::Stack(x.constants.len()-1, arg.position());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
x.args.shrink_to_fit();
|
|
||||||
x.literal_args.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// constant-name
|
// constant-name
|
||||||
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
|
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")]
|
#[cfg(feature = "unchecked")]
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
// Register range function with step
|
// Range iterator with step
|
||||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||||
struct StepRange<T>(T, T, T)
|
struct StepRange<T>(T, T, T)
|
||||||
where
|
where
|
||||||
@ -126,7 +126,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register range function with step
|
// Bit-field iterator with step
|
||||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||||
struct BitRange(INT, INT, usize);
|
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 {
|
macro_rules! reg_range {
|
||||||
($lib:ident | $x:expr => $( $y:ty ),*) => {
|
($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>"]);
|
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>();
|
lib.set_iterator::<BitRange>();
|
||||||
|
|
||||||
let _hash = lib.set_native_fn("bits", |value, from, len| BitRange::new(value, from, len));
|
let _hash = lib.set_native_fn("bits", |value, from, len| BitRange::new(value, from, len));
|
||||||
#[cfg(feature = "metadata")]
|
#[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));
|
let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX));
|
||||||
#[cfg(feature = "metadata")]
|
#[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));
|
let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX));
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
|
@ -28,7 +28,7 @@ pub fn print_with_func(
|
|||||||
) -> crate::ImmutableString {
|
) -> crate::ImmutableString {
|
||||||
match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) {
|
match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) {
|
||||||
Ok(result) if result.is::<crate::ImmutableString>() => result
|
Ok(result) if result.is::<crate::ImmutableString>() => result
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.expect("never fails as the result is `ImmutableString`"),
|
.expect("never fails as the result is `ImmutableString`"),
|
||||||
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
|
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
|
||||||
Err(_) => ctx.engine().map_type_name(value.type_name()).into(),
|
Err(_) => ctx.engine().map_type_name(value.type_name()).into(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use crate::plugin::*;
|
use crate::plugin::*;
|
||||||
use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT};
|
use crate::{def_package, Dynamic, StaticVec, INT};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{any::TypeId, mem};
|
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, {
|
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
|
||||||
combine_with_exported_module!(lib, "string", string_functions);
|
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]
|
#[export_module]
|
||||||
|
@ -93,6 +93,8 @@ pub enum ParseErrorType {
|
|||||||
UnknownOperator(String),
|
UnknownOperator(String),
|
||||||
/// Expecting a particular token but not finding one. Wrapped values are the token and description.
|
/// Expecting a particular token but not finding one. Wrapped values are the token and description.
|
||||||
MissingToken(String, String),
|
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
|
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error
|
||||||
/// description (if any).
|
/// description (if any).
|
||||||
MalformedCallExpr(String),
|
MalformedCallExpr(String),
|
||||||
@ -116,6 +118,8 @@ pub enum ParseErrorType {
|
|||||||
DuplicatedProperty(String),
|
DuplicatedProperty(String),
|
||||||
/// A `switch` case is duplicated.
|
/// A `switch` case is duplicated.
|
||||||
DuplicatedSwitchCase,
|
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.
|
/// The default case of a `switch` statement is not the last.
|
||||||
WrongSwitchDefaultCase,
|
WrongSwitchDefaultCase,
|
||||||
/// The case condition of a `switch` statement is not appropriate.
|
/// The case condition of a `switch` statement is not appropriate.
|
||||||
@ -194,12 +198,14 @@ impl ParseErrorType {
|
|||||||
Self::BadInput(err) => err.desc(),
|
Self::BadInput(err) => err.desc(),
|
||||||
Self::UnknownOperator(_) => "Unknown operator",
|
Self::UnknownOperator(_) => "Unknown operator",
|
||||||
Self::MissingToken(_, _) => "Expecting a certain token that is missing",
|
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::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||||
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||||
Self::MalformedInExpr(_) => "Invalid 'in' expression",
|
Self::MalformedInExpr(_) => "Invalid 'in' expression",
|
||||||
Self::MalformedCapture(_) => "Invalid capturing",
|
Self::MalformedCapture(_) => "Invalid capturing",
|
||||||
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||||
Self::DuplicatedSwitchCase => "Duplicated switch case",
|
Self::DuplicatedSwitchCase => "Duplicated switch case",
|
||||||
|
Self::DuplicatedVariable(_) => "Duplicated variable name",
|
||||||
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
|
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
|
||||||
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
|
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
|
||||||
Self::PropertyExpected => "Expecting name of a property",
|
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)
|
write!(f, "Duplicated property '{}' for object map literal", s)
|
||||||
}
|
}
|
||||||
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
|
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
|
||||||
|
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
|
||||||
|
|
||||||
Self::ExprExpected(s) => write!(f, "Expecting {} expression", 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::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) if s.is_empty() => f.write_str(self.desc()),
|
||||||
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
|
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::module::NamespaceRef;
|
||||||
use crate::optimize::optimize_into_ast;
|
use crate::optimize::optimize_into_ast;
|
||||||
use crate::optimize::OptimizationLevel;
|
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::{
|
use crate::token::{
|
||||||
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
|
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
|
||||||
};
|
};
|
||||||
@ -27,7 +30,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::FLOAT;
|
use crate::{syntax::MARKER_FLOAT, FLOAT};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
use crate::FnAccess;
|
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 )
|
/// Parse ( expr )
|
||||||
fn parse_paren_expr(
|
fn parse_paren_expr(
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
@ -860,7 +879,7 @@ fn parse_switch(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let hash = if let Some(expr) = expr {
|
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();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
@ -1232,33 +1251,26 @@ fn parse_primary(
|
|||||||
}
|
}
|
||||||
// module access
|
// module access
|
||||||
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => {
|
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => {
|
||||||
match input.next().expect(NEVER_ENDS) {
|
let (id2, pos2) = parse_var_name(input)?;
|
||||||
(Token::Identifier(id2), pos2) => {
|
let (_, mut namespace, var_name) = *x;
|
||||||
let (_, mut namespace, var_name) = *x;
|
let var_name_def = Ident {
|
||||||
let var_name_def = Ident {
|
name: var_name,
|
||||||
name: var_name,
|
pos: var_pos,
|
||||||
pos: var_pos,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((_, ref mut namespace)) = namespace {
|
if let Some((_, ref mut namespace)) = namespace {
|
||||||
namespace.push(var_name_def);
|
namespace.push(var_name_def);
|
||||||
} else {
|
} else {
|
||||||
let mut ns: NamespaceRef = Default::default();
|
let mut ns: NamespaceRef = Default::default();
|
||||||
ns.push(var_name_def);
|
ns.push(var_name_def);
|
||||||
namespace = Some((42, ns));
|
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)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Variable(
|
||||||
|
None,
|
||||||
|
pos2,
|
||||||
|
Box::new((None, namespace, state.get_identifier(id2))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// Indexing
|
// Indexing
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -1589,8 +1601,8 @@ fn make_dot_expr(
|
|||||||
Expr::FnCall(mut func, func_pos) => {
|
Expr::FnCall(mut func, func_pos) => {
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
func.hashes = FnCallHashes::from_script_and_native(
|
func.hashes = FnCallHashes::from_script_and_native(
|
||||||
calc_fn_hash(&func.name, func.args_count()),
|
calc_fn_hash(&func.name, func.args.len()),
|
||||||
calc_fn_hash(&func.name, func.args_count() + 1),
|
calc_fn_hash(&func.name, func.args.len() + 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
let rhs = Expr::Dot(
|
let rhs = Expr::Dot(
|
||||||
@ -1621,7 +1633,7 @@ fn make_dot_expr(
|
|||||||
}
|
}
|
||||||
// lhs.Fn() or lhs.eval()
|
// lhs.Fn() or lhs.eval()
|
||||||
(_, Expr::FnCall(x, pos))
|
(_, Expr::FnCall(x, pos))
|
||||||
if x.is_args_empty()
|
if x.args.is_empty()
|
||||||
&& [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL]
|
&& [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL]
|
||||||
.contains(&x.name.as_ref()) =>
|
.contains(&x.name.as_ref()) =>
|
||||||
{
|
{
|
||||||
@ -1645,8 +1657,8 @@ fn make_dot_expr(
|
|||||||
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
func.hashes = FnCallHashes::from_script_and_native(
|
func.hashes = FnCallHashes::from_script_and_native(
|
||||||
calc_fn_hash(&func.name, func.args_count()),
|
calc_fn_hash(&func.name, func.args.len()),
|
||||||
calc_fn_hash(&func.name, func.args_count() + 1),
|
calc_fn_hash(&func.name, func.args.len() + 1),
|
||||||
);
|
);
|
||||||
let rhs = Expr::FnCall(func, func_pos);
|
let rhs = Expr::FnCall(func, func_pos);
|
||||||
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
||||||
@ -1886,18 +1898,13 @@ fn parse_custom_syntax(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match required_token.as_str() {
|
match required_token.as_str() {
|
||||||
MARKER_IDENT => match input.next().expect(NEVER_ENDS) {
|
MARKER_IDENT => {
|
||||||
(Token::Identifier(s), pos) => {
|
let (name, pos) = parse_var_name(input)?;
|
||||||
let name = state.get_identifier(s);
|
let name = state.get_identifier(name);
|
||||||
segments.push(name.clone().into());
|
segments.push(name.clone().into());
|
||||||
tokens.push(state.get_identifier(MARKER_IDENT));
|
tokens.push(state.get_identifier(MARKER_IDENT));
|
||||||
keywords.push(Expr::Variable(None, pos, Box::new((None, None, name))));
|
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_EXPR => {
|
MARKER_EXPR => {
|
||||||
keywords.push(parse_expr(input, state, lib, settings)?);
|
keywords.push(parse_expr(input, state, lib, settings)?);
|
||||||
let keyword = state.get_identifier(MARKER_EXPR);
|
let keyword = state.get_identifier(MARKER_EXPR);
|
||||||
@ -1913,6 +1920,60 @@ fn parse_custom_syntax(
|
|||||||
}
|
}
|
||||||
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
|
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) {
|
s => match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
(t, _) if t.syntax().as_ref() == s => {
|
(t, _) if t.syntax().as_ref() == s => {
|
||||||
@ -2151,17 +2212,36 @@ fn parse_for(
|
|||||||
settings.pos = eat_token(input, Token::For);
|
settings.pos = eat_token(input, Token::For);
|
||||||
|
|
||||||
// for name ...
|
// for name ...
|
||||||
let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
|
let (name, name_pos, counter_name, counter_pos) = if match_token(input, Token::LeftParen).0 {
|
||||||
// Variable name
|
// ( name, counter )
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
let (name, name_pos) = parse_var_name(input)?;
|
||||||
// Reserved keyword
|
let (has_comma, pos) = match_token(input, Token::Comma);
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
if !has_comma {
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
return Err(PERR::MissingToken(
|
||||||
|
Token::Comma.into(),
|
||||||
|
"after the iteration variable name".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos));
|
||||||
}
|
}
|
||||||
// Bad identifier
|
let (counter_name, counter_pos) = parse_var_name(input)?;
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
|
||||||
// Not a variable name
|
if counter_name == name {
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
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 ...
|
// for name in ...
|
||||||
@ -2180,8 +2260,18 @@ fn parse_for(
|
|||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
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 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));
|
state.stack.push((loop_var.clone(), AccessMode::ReadWrite));
|
||||||
|
|
||||||
settings.is_breakable = true;
|
settings.is_breakable = true;
|
||||||
@ -2196,6 +2286,10 @@ fn parse_for(
|
|||||||
name: loop_var,
|
name: loop_var,
|
||||||
pos: name_pos,
|
pos: name_pos,
|
||||||
},
|
},
|
||||||
|
counter_var.map(|name| Ident {
|
||||||
|
name,
|
||||||
|
pos: counter_pos.expect("never fails because `counter_var` is `Some`"),
|
||||||
|
}),
|
||||||
body.into(),
|
body.into(),
|
||||||
)),
|
)),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
@ -2218,14 +2312,7 @@ fn parse_let(
|
|||||||
settings.pos = input.next().expect(NEVER_ENDS).1;
|
settings.pos = input.next().expect(NEVER_ENDS).1;
|
||||||
|
|
||||||
// let name ...
|
// let name ...
|
||||||
let (name, pos) = match input.next().expect(NEVER_ENDS) {
|
let (name, pos) = parse_var_name(input)?;
|
||||||
(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 = state.get_identifier(name);
|
let name = state.get_identifier(name);
|
||||||
let var_def = Ident {
|
let var_def = Ident {
|
||||||
@ -2274,15 +2361,7 @@ fn parse_import(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// import expr as name ...
|
// import expr as name ...
|
||||||
let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
|
let (name, name_pos) = parse_var_name(input)?;
|
||||||
(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 = state.get_identifier(name);
|
let name = state.get_identifier(name);
|
||||||
state.modules.push(name.clone());
|
state.modules.push(name.clone());
|
||||||
|
|
||||||
@ -2331,27 +2410,18 @@ fn parse_export(
|
|||||||
let mut exports = Vec::with_capacity(4);
|
let mut exports = Vec::with_capacity(4);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (id, id_pos) = match input.next().expect(NEVER_ENDS) {
|
let (id, id_pos) = parse_var_name(input)?;
|
||||||
(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 rename = if match_token(input, Token::As).0 {
|
let rename = if match_token(input, Token::As).0 {
|
||||||
match input.next().expect(NEVER_ENDS) {
|
let (name, pos) = parse_var_name(input)?;
|
||||||
(Token::Identifier(s), pos) => Some(Ident {
|
if exports.iter().any(|(_, alias)| match alias {
|
||||||
name: state.get_identifier(s),
|
Some(Ident { name: alias, .. }) if alias == &name => true,
|
||||||
pos,
|
_ => false,
|
||||||
}),
|
}) {
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
return Err(PERR::DuplicatedVariable(name).into_err(pos));
|
||||||
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 = state.get_identifier(name);
|
||||||
|
Some(Ident { name, pos })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -2724,25 +2794,19 @@ fn parse_try_catch(
|
|||||||
|
|
||||||
// try { body } catch (
|
// try { body } catch (
|
||||||
let var_def = if match_token(input, Token::LeftParen).0 {
|
let var_def = if match_token(input, Token::LeftParen).0 {
|
||||||
let id = match input.next().expect(NEVER_ENDS) {
|
let (name, pos) = parse_var_name(input)?;
|
||||||
(Token::Identifier(s), pos) => Ident {
|
let (matched, err_pos) = match_token(input, Token::RightParen);
|
||||||
name: state.get_identifier(s),
|
|
||||||
pos,
|
|
||||||
},
|
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (matched, pos) = match_token(input, Token::RightParen);
|
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
Token::RightParen.into(),
|
Token::RightParen.into(),
|
||||||
"to enclose the catch variable".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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -113,7 +113,7 @@ impl EvalAltResult {
|
|||||||
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
|
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
|
||||||
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
|
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
|
||||||
Self::ErrorBitFieldBounds(_, _, _) => "Bit-field 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::ErrorVariableNotFound(_, _) => "Variable not found",
|
||||||
Self::ErrorModuleNotFound(_, _) => "Module not found",
|
Self::ErrorModuleNotFound(_, _) => "Module not found",
|
||||||
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
|
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::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)?,
|
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for type '{}'", s)?,
|
||||||
|
|
||||||
|
@ -542,7 +542,7 @@ impl SerializeMap for DynamicSerializer {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
{
|
{
|
||||||
let key = std::mem::take(&mut self._key)
|
let key = std::mem::take(&mut self._key)
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.map_err(|typ| {
|
.map_err(|typ| {
|
||||||
EvalAltResult::ErrorMismatchDataType(
|
EvalAltResult::ErrorMismatchDataType(
|
||||||
"string".into(),
|
"string".into(),
|
||||||
@ -572,7 +572,7 @@ impl SerializeMap for DynamicSerializer {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
{
|
{
|
||||||
let _key: Dynamic = _key.serialize(&mut *self)?;
|
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)
|
EvalAltResult::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE)
|
||||||
})?;
|
})?;
|
||||||
let _value = _value.serialize(&mut *self)?;
|
let _value = _value.serialize(&mut *self)?;
|
||||||
|
@ -5,8 +5,8 @@ use crate::engine::EvalContext;
|
|||||||
use crate::fn_native::SendSync;
|
use crate::fn_native::SendSync;
|
||||||
use crate::token::{is_valid_identifier, Token};
|
use crate::token::{is_valid_identifier, Token};
|
||||||
use crate::{
|
use crate::{
|
||||||
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared,
|
Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult,
|
||||||
StaticVec,
|
Shared, StaticVec,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -14,6 +14,11 @@ use std::prelude::v1::*;
|
|||||||
pub const MARKER_EXPR: &str = "$expr$";
|
pub const MARKER_EXPR: &str = "$expr$";
|
||||||
pub const MARKER_BLOCK: &str = "$block$";
|
pub const MARKER_BLOCK: &str = "$block$";
|
||||||
pub const MARKER_IDENT: &str = "$ident$";
|
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.
|
/// A general expression evaluation trait object.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
@ -58,6 +63,11 @@ impl Expression<'_> {
|
|||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
self.0.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<'_, '_, '_, '_, '_, '_, '_> {
|
impl EvalContext<'_, '_, '_, '_, '_, '_, '_> {
|
||||||
@ -132,7 +142,15 @@ impl Engine {
|
|||||||
|
|
||||||
let seg = match s {
|
let seg = match s {
|
||||||
// Markers not in first position
|
// 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
|
// Standard or reserved keyword/symbol not in first position
|
||||||
s if !segments.is_empty() && token.is_some() => {
|
s if !segments.is_empty() && token.is_some() => {
|
||||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||||
|
@ -1095,7 +1095,7 @@ pub fn parse_string_literal(
|
|||||||
|
|
||||||
match next_char {
|
match next_char {
|
||||||
// \r - ignore if followed by \n
|
// \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() => {
|
'\\' if !verbatim && escape.is_empty() => {
|
||||||
escape.push('\\');
|
escape.push('\\');
|
||||||
|
@ -208,13 +208,13 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let x = [1, 2, 3];
|
let x = [1, 2, 3];
|
||||||
x.reduce(|sum, v, i| {
|
x.reduce(|sum, v, i| {
|
||||||
if i == 0 { sum = 10 }
|
if i == 0 { sum = 10 }
|
||||||
sum + v * v
|
sum + v * v
|
||||||
})
|
})
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
24
|
24
|
||||||
);
|
);
|
||||||
@ -231,40 +231,40 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let x = [1, 2, 3];
|
let x = [1, 2, 3];
|
||||||
x.reduce_rev(|sum, v, i| { if i == 2 { sum = 10 } sum + v * v })
|
x.reduce_rev(|sum, v, i| { if i == 2 { sum = 10 } sum + v * v })
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
24
|
24
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(
|
assert!(engine.eval::<bool>(
|
||||||
r#"
|
"
|
||||||
let x = [1, 2, 3];
|
let x = [1, 2, 3];
|
||||||
x.some(|v| v > 1)
|
x.some(|v| v > 1)
|
||||||
"#
|
"
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(
|
assert!(engine.eval::<bool>(
|
||||||
r#"
|
"
|
||||||
let x = [1, 2, 3];
|
let x = [1, 2, 3];
|
||||||
x.some(|v, i| v * i == 0)
|
x.some(|v, i| v * i == 0)
|
||||||
"#
|
"
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
assert!(!engine.eval::<bool>(
|
assert!(!engine.eval::<bool>(
|
||||||
r#"
|
"
|
||||||
let x = [1, 2, 3];
|
let x = [1, 2, 3];
|
||||||
x.all(|v| v > 1)
|
x.all(|v| v > 1)
|
||||||
"#
|
"
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(
|
assert!(engine.eval::<bool>(
|
||||||
r#"
|
"
|
||||||
let x = [1, 2, 3];
|
let x = [1, 2, 3];
|
||||||
x.all(|v, i| v > i)
|
x.all(|v, i| v > i)
|
||||||
"#
|
"
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -79,11 +79,11 @@ fn test_call_fn_args() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let ast = engine.compile(
|
let ast = engine.compile(
|
||||||
r#"
|
"
|
||||||
fn hello(x, y, z) {
|
fn hello(x, y, z) {
|
||||||
if x { `hello ${y}` } else { y + z }
|
if x { `hello ${y}` } else { y + z }
|
||||||
}
|
}
|
||||||
"#,
|
",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?;
|
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"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let addition = |x, y| { x + y };
|
let addition = |x, y| { x + y };
|
||||||
let curried = addition.curry(2);
|
let curried = addition.curry(2);
|
||||||
|
|
||||||
call_with_arg(curried, 40)
|
call_with_arg(curried, 40)
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
@ -65,7 +65,7 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let x = 8;
|
let x = 8;
|
||||||
|
|
||||||
let res = |y, z| {
|
let res = |y, z| {
|
||||||
@ -75,55 +75,55 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}.curry(15).call(2);
|
}.curry(15).call(2);
|
||||||
|
|
||||||
res + (|| x - 3).call()
|
res + (|| x - 3).call()
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 41;
|
let a = 41;
|
||||||
let foo = |x| { a += x };
|
let foo = |x| { a += x };
|
||||||
foo.call(1);
|
foo.call(1);
|
||||||
a
|
a
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(
|
assert!(engine.eval::<bool>(
|
||||||
r#"
|
"
|
||||||
let a = 41;
|
let a = 41;
|
||||||
let foo = |x| { a += x };
|
let foo = |x| { a += x };
|
||||||
a.is_shared()
|
a.is_shared()
|
||||||
"#
|
"
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(
|
assert!(engine.eval::<bool>(
|
||||||
r#"
|
"
|
||||||
let a = 41;
|
let a = 41;
|
||||||
let foo = |x| { a += x };
|
let foo = |x| { a += x };
|
||||||
is_shared(a)
|
is_shared(a)
|
||||||
"#
|
"
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
engine.register_fn("plus_one", |x: INT| x + 1);
|
engine.register_fn("plus_one", |x: INT| x + 1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 41;
|
let a = 41;
|
||||||
let f = || plus_one(a);
|
let f = || plus_one(a);
|
||||||
f.call()
|
f.call()
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 40;
|
let a = 40;
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
@ -133,19 +133,19 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
f.call(x)
|
f.call(x)
|
||||||
};
|
};
|
||||||
f.call(1)
|
f.call(1)
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 21;
|
let a = 21;
|
||||||
let f = |x| a += x;
|
let f = |x| a += x;
|
||||||
f.call(a);
|
f.call(a);
|
||||||
a
|
a
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
@ -163,13 +163,13 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 41;
|
let a = 41;
|
||||||
let b = 0;
|
let b = 0;
|
||||||
let f = || b.custom_call(|| a + 1);
|
let f = || b.custom_call(|| a + 1);
|
||||||
|
|
||||||
f.call()
|
f.call()
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
@ -231,13 +231,13 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 1;
|
let a = 1;
|
||||||
let b = 40;
|
let b = 40;
|
||||||
let foo = |x| { this += a + x };
|
let foo = |x| { this += a + x };
|
||||||
b.call(foo, 1);
|
b.call(foo, 1);
|
||||||
b
|
b
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
@ -245,12 +245,12 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.eval::<INT>(
|
.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let a = 20;
|
let a = 20;
|
||||||
let foo = |x| { this += a + x };
|
let foo = |x| { this += a + x };
|
||||||
a.call(foo, 1);
|
a.call(foo, 1);
|
||||||
a
|
a
|
||||||
"#
|
"
|
||||||
)
|
)
|
||||||
.expect_err("should error"),
|
.expect_err("should error"),
|
||||||
EvalAltResult::ErrorDataRace(_, _)
|
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
|
// 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()]);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,12 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
let /* I am a
|
let /* I am a
|
||||||
multi-line
|
multi-line
|
||||||
comment, yay!
|
comment, yay!
|
||||||
*/ x = 42; x
|
*/ x = 42; x
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
42
|
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!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.eval::<Map>(
|
.eval::<Map>(
|
||||||
|
18
tests/for.rs
18
tests/for.rs
@ -37,6 +37,22 @@ fn test_for_loop() -> Result<(), Box<EvalAltResult>> {
|
|||||||
35
|
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!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
@ -235,7 +251,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let s = "hello";
|
let s = "hello";
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
||||||
for ch in s {
|
for ch in chars(s) {
|
||||||
sum += to_int(ch);
|
sum += to_int(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,28 +210,28 @@ fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
fn foo(y) { x += y; x }
|
fn foo(y) { x += y; x }
|
||||||
|
|
||||||
let x = 41;
|
let x = 41;
|
||||||
let y = 999;
|
let y = 999;
|
||||||
|
|
||||||
foo!(1) + x
|
foo!(1) + x
|
||||||
"#
|
"
|
||||||
)?,
|
)?,
|
||||||
83
|
83
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine
|
assert!(engine
|
||||||
.eval::<INT>(
|
.eval::<INT>(
|
||||||
r#"
|
"
|
||||||
fn foo(y) { x += y; x }
|
fn foo(y) { x += y; x }
|
||||||
|
|
||||||
let x = 41;
|
let x = 41;
|
||||||
let y = 999;
|
let y = 999;
|
||||||
|
|
||||||
foo(1) + x
|
foo(1) + x
|
||||||
"#
|
"
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
@ -239,14 +239,14 @@ fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.compile(
|
.compile(
|
||||||
r#"
|
"
|
||||||
fn foo() { this += x; }
|
fn foo() { this += x; }
|
||||||
|
|
||||||
let x = 41;
|
let x = 41;
|
||||||
let y = 999;
|
let y = 999;
|
||||||
|
|
||||||
y.foo!();
|
y.foo!();
|
||||||
"#
|
"
|
||||||
)
|
)
|
||||||
.expect_err("should error")
|
.expect_err("should error")
|
||||||
.0,
|
.0,
|
||||||
|
@ -102,7 +102,7 @@ fn test_serde_ser_struct() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert!(obj["b"].as_bool().unwrap());
|
assert!(obj["b"].as_bool().unwrap());
|
||||||
assert_eq!(Ok(42), map["int"].as_int());
|
assert_eq!(Ok(42), map["int"].as_int());
|
||||||
assert_eq!(seq.len(), 3);
|
assert_eq!(seq.len(), 3);
|
||||||
assert_eq!("kitty", seq.remove(1).take_string().unwrap());
|
assert_eq!("kitty", seq.remove(1).as_string().unwrap());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -116,10 +116,10 @@ fn test_serde_ser_unit_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let d = to_dynamic(MyEnum::VariantFoo)?;
|
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)?;
|
let d = to_dynamic(MyEnum::VariantBar)?;
|
||||||
assert_eq!("VariantBar", d.take_string().unwrap());
|
assert_eq!("VariantBar", d.as_string().unwrap());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ fn test_serde_ser_externally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
"VariantUnit",
|
"VariantUnit",
|
||||||
to_dynamic(MyEnum::VariantUnit)?
|
to_dynamic(MyEnum::VariantUnit)?
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -203,7 +203,7 @@ fn test_serde_ser_internally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantEmptyStruct",
|
"VariantEmptyStruct",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -214,7 +214,7 @@ fn test_serde_ser_internally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantStruct",
|
"VariantStruct",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -247,7 +247,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantUnit",
|
"VariantUnit",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -260,7 +260,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantUnitTuple",
|
"VariantUnitTuple",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -274,7 +274,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantNewtype",
|
"VariantNewtype",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -289,7 +289,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantTuple",
|
"VariantTuple",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
);
|
);
|
||||||
@ -305,7 +305,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"VariantEmptyStruct",
|
"VariantEmptyStruct",
|
||||||
map.remove("tag")
|
map.remove("tag")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.take_immutable_string()
|
.as_immutable_string()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.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>();
|
let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"VariantStruct",
|
"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>();
|
let mut map_inner = map.remove("content").unwrap().cast::<Map>();
|
||||||
assert!(map.is_empty());
|
assert!(map.is_empty());
|
||||||
|
@ -19,19 +19,24 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
engine.register_custom_syntax(
|
engine.register_custom_syntax(
|
||||||
&[
|
&[
|
||||||
"exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$",
|
"exec", "[", "$ident$", ";", "$int$", "]", "->", "$block$", "while", "$expr$",
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
|context, inputs| {
|
|context, inputs| {
|
||||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
let stmt = inputs.get(1).unwrap();
|
let max = inputs[1].get_literal_value().unwrap().as_int().unwrap();
|
||||||
let condition = inputs.get(2).unwrap();
|
let stmt = inputs.get(2).unwrap();
|
||||||
|
let condition = inputs.get(3).unwrap();
|
||||||
|
|
||||||
context.scope_mut().push(var_name.clone(), 0 as INT);
|
context.scope_mut().push(var_name.clone(), 0 as INT);
|
||||||
|
|
||||||
let mut count: INT = 0;
|
let mut count: INT = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
if count >= max {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
context.eval_expression_tree(stmt)?;
|
context.eval_expression_tree(stmt)?;
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
@ -63,17 +68,17 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
let x = 0;
|
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
|
foo
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
210
|
150
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
let x = 0;
|
let x = 0;
|
||||||
exec [x] -> { x += 1 } while x < 42;
|
exec [x;100] -> { x += 1 } while x < 42;
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -82,7 +87,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
exec [x] -> { x += 1 } while x < 42;
|
exec [x;100] -> { x += 1 } while x < 42;
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -92,11 +97,11 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
let foo = 123;
|
let foo = 123;
|
||||||
exec [x] -> { x += 1 } while x < 42;
|
exec [x;15] -> { x += 1 } while x < 42;
|
||||||
foo + x + x1 + x2 + x3
|
foo + x + x1 + x2 + x3
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
171
|
144
|
||||||
);
|
);
|
||||||
|
|
||||||
// The first symbol must be an identifier
|
// The first symbol must be an identifier
|
||||||
|
Loading…
Reference in New Issue
Block a user