Merge pull request #307 from schungx/master

Better treatment of constants and doc-comments for functions.
This commit is contained in:
Stephen Chung 2020-12-12 20:20:31 +08:00 committed by GitHub
commit b5879f9304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1040 additions and 738 deletions

View File

@ -6,7 +6,7 @@ members = [
[package] [package]
name = "rhai" name = "rhai"
version = "0.19.7" version = "0.19.8"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

View File

@ -1,6 +1,29 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.19.8
==============
Bug fixes
---------
* Constants are no longer propagated by the optimizer if shadowed by a non-constant variable.
* Constants passed as the `this` parameter to Rhai functions now throws an error if assigned to.
Breaking changes
----------------
* `Engine::on_progress` now takes `u64` instead of `&u64`.
* The closure for `Engine::on_debug` now takes an additional `Position` parameter.
* `AST::iter_functions` now returns `ScriptFnMetadata`.
Enhancements
------------
* Capturing a constant variable in a closure is now supported, with no cloning.
* Provides position info for `debug` statements.
Version 0.19.7 Version 0.19.7
============== ==============

View File

@ -164,14 +164,19 @@ fn bench_eval_switch(bench: &mut Bencher) {
let rem = 0; let rem = 0;
for x in range(0, 10) { for x in range(0, 10) {
rem = x % 5; rem = x % 10;
sum += switch rem { sum += switch rem {
0 => 10, 0 => 10,
1 => 12, 1 => 12,
2 => 42, 2 => 42,
3 => 1, 3 => 1,
_ => 9 4 => 12,
5 => 42,
6 => 1,
7 => 12,
8 => 42,
9 => 1,
} }
} }
"#; "#;
@ -191,19 +196,18 @@ fn bench_eval_nested_if(bench: &mut Bencher) {
let rem = 0; let rem = 0;
for x in range(0, 10) { for x in range(0, 10) {
rem = x % 5; rem = x % 10;
sum += if rem == 0 { sum += if rem == 0 { 10 }
10 else if rem == 1 { 12 }
} else if rem == 1 { else if rem == 2 { 42 }
12 else if rem == 3 { 1 }
} else if rem == 2 { else if rem == 4 { 12 }
42 else if rem == 5 { 42 }
} else if rem == 3 { else if rem == 6 { 1 }
1 else if rem == 7 { 12 }
} else{ else if rem == 8 { 42 }
9 else if rem == 9 { 1 };
};
} }
"#; "#;

View File

@ -75,7 +75,7 @@ The Rhai Scripting Language
5. [Variables](language/variables.md) 5. [Variables](language/variables.md)
6. [Constants](language/constants.md) 6. [Constants](language/constants.md)
7. [Logic Operators](language/logic.md) 7. [Logic Operators](language/logic.md)
8. [Other Operators](language/other-op.md) 8. [Assignment Operators](language/assignment-op.md)
9. [If Statement](language/if.md) 9. [If Statement](language/if.md)
10. [Switch Expression](language/switch.md) 10. [Switch Expression](language/switch.md)
11. [While Loop](language/while.md) 11. [While Loop](language/while.md)

View File

@ -1,5 +1,5 @@
{ {
"version": "0.19.7", "version": "0.19.8",
"repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoHome": "https://github.com/jonathandturner/rhai/blob/master",
"repoTree": "https://github.com/jonathandturner/rhai/tree/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master",
"rootUrl": "", "rootUrl": "",

View File

@ -64,6 +64,15 @@ are spliced into the script text in order to turn on/off certain sections.
For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object
to the [`Engine`] for use in compilation and evaluation. to the [`Engine`] for use in compilation and evaluation.
### Caveat
If the [constants] are modified later on (yes, it is possible, via Rust functions),
the modified values will not show up in the optimized script.
Only the initialization values of [constants] are ever retained.
This is almost never a problem because real-world scripts seldom modify a constant,
but the possibility is always there.
Eager Operator Evaluations Eager Operator Evaluations
------------------------- -------------------------

View File

@ -11,6 +11,14 @@ There are three levels of optimization: `None`, `Simple` and `Full`.
(i.e. it only relies on static analysis and [built-in operators] for constant [standard types], (i.e. it only relies on static analysis and [built-in operators] for constant [standard types],
and will not perform any external function calls). and will not perform any external function calls).
However, it is important to bear in mind that _constants propagation_ is performed with the
caveat that, if [constants] are modified later on (yes, it is possible, via Rust functions),
the modified values will not show up in the optimized script. Only the initialization values
of [constants] are ever retained.
Furthermore, overriding a [built-in operator][built-in operators] in the [`Engine`] afterwards
has no effect after the optimizer replaces an expression with its calculated value.
* `Full` is _much_ more aggressive, _including_ calling external functions on constant arguments to determine their result. * `Full` is _much_ more aggressive, _including_ calling external functions on constant arguments to determine their result.
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.

View File

@ -1,12 +1,9 @@
Other Operators Compound Assignment Operators
=============== =============================
{{#include ../links.md}} {{#include ../links.md}}
Compound Assignment Operators
----------------------------
```rust ```rust
let number = 9; let number = 9;
@ -64,3 +61,6 @@ my_obj += #{c:3, d:4, e:5};
my_obj.len() == 5; my_obj.len() == 5;
``` ```
In fact, the `+` and `+=` operators are usually [overloaded][function overloading] when
something is to be _added_ to an existing type.

View File

@ -61,9 +61,31 @@ r"
``` ```
Constants Can be Modified, Just Not Reassigned Constants Can be Modified via Rust
--------------------------------------------- ---------------------------------
A custom type stored as a constant can be modified via its registered API - A custom type stored as a constant cannot be modified via script, but _can_ be modified via
being a constant only prevents it from being re-assigned or operated upon by Rhai; a registered Rust function that takes a first `&mut` parameter - because there is no way for
mutating it via a Rust function is still allowed. Rhai to know whether the Rust function modifies its argument!
```rust
const x = 42; // a constant
x.increment(); // call 'increment' defined in Rust with '&mut' first parameter
x == 43; // value of 'x' is changed!
fn double() {
this *= 2; // function squares 'this'
}
x.double(); // <- error: cannot modify constant 'this'
x == 43; // value of 'x' is unchanged by script
```
This is important to keep in mind because the script [optimizer][script optimization]
by default does _constant propagation_ as a operation.
If a constant is eventually modified by a Rust function, the optimizer will not see
the updated value and will propagate the original initialization value instead.

View File

@ -57,24 +57,6 @@ f.call(2) == 42;
``` ```
Constants are Not Captured
--------------------------
Constants are never shared. Their values are simply cloned.
```rust
const x = 42; // constant variable 'x'
let f = |y| x += y; // constant 'x' is cloned and not captured
x.is_shared() == false; // 'x' is not shared
f.call(10); // the cloned copy of 'x' is changed
x == 42; // 'x' is not changed
```
Beware: Captured Variables are Truly Shared Beware: Captured Variables are Truly Shared
------------------------------------------ ------------------------------------------

View File

@ -22,10 +22,12 @@ When embedding Rhai into an application, it is usually necessary to trap `print`
(for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods: (for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods:
```rust ```rust
// Any function or closure that takes an '&str' argument can be used to override // Any function or closure that takes an '&str' argument can be used to override 'print'.
// 'print' and 'debug'
engine.on_print(|x| println!("hello: {}", x)); engine.on_print(|x| println!("hello: {}", x));
engine.on_debug(|x| println!("DEBUG: {}", x));
// Any function or closure that takes a '&str' and a 'Position' argument can be used to
// override 'debug'.
engine.on_debug(|x, pos| println!("DEBUG at {:?}: {}", pos, x));
// Example: quick-'n-dirty logging // Example: quick-'n-dirty logging
let logbook = Arc::new(RwLock::new(Vec::<String>::new())); let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
@ -35,7 +37,9 @@ let log = logbook.clone();
engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s))); engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s)));
let log = logbook.clone(); let log = logbook.clone();
engine.on_debug(move |s| log.write().unwrap().push(format!("DEBUG: {}", s))); engine.on_debug(move |s, pos| log.write().unwrap().push(
format!("DEBUG at {:?}: {}", pos, s)
));
// Evaluate script // Evaluate script
engine.eval::<()>(script)?; engine.eval::<()>(script)?;

View File

@ -68,6 +68,8 @@
[variable]: {{rootUrl}}/language/variables.md [variable]: {{rootUrl}}/language/variables.md
[variables]: {{rootUrl}}/language/variables.md [variables]: {{rootUrl}}/language/variables.md
[constant]: {{rootUrl}}/language/constants.md
[constants]: {{rootUrl}}/language/constants.md
[string]: {{rootUrl}}/language/strings-chars.md [string]: {{rootUrl}}/language/strings-chars.md
[strings]: {{rootUrl}}/language/strings-chars.md [strings]: {{rootUrl}}/language/strings-chars.md

View File

@ -13,7 +13,7 @@ the `Engine::on_progress` method:
```rust ```rust
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.on_progress(|&count| { // parameter is '&u64' - number of operations already performed engine.on_progress(|count| { // parameter is number of operations already performed
if count % 1000 == 0 { if count % 1000 == 0 {
println!("{}", count); // print out a progress log every 1,000 operations println!("{}", count); // print out a progress log every 1,000 operations
} }

View File

@ -142,9 +142,7 @@ fn main() {
.for_each(|f| println!("{}", f)); .for_each(|f| println!("{}", f));
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
main_ast main_ast.iter_functions().for_each(|f| println!("{}", f));
.iter_functions()
.for_each(|(_, _, _, _, f)| println!("{}", f));
println!(); println!();
continue; continue;

View File

@ -1,6 +1,6 @@
//! Module defining the AST (abstract syntax tree). //! Module defining the AST (abstract syntax tree).
use crate::dynamic::Union; use crate::dynamic::{AccessMode, 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::stdlib::{ use crate::stdlib::{
@ -82,7 +82,9 @@ pub struct ScriptFnDef {
pub params: StaticVec<ImmutableString>, pub params: StaticVec<ImmutableString>,
/// Access to external variables. /// Access to external variables.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub externals: crate::stdlib::collections::HashSet<ImmutableString>, pub externals: Vec<ImmutableString>,
/// Comment block for function.
pub fn_comments: Vec<String>,
} }
impl fmt::Display for ScriptFnDef { impl fmt::Display for ScriptFnDef {
@ -105,36 +107,85 @@ impl fmt::Display for ScriptFnDef {
} }
} }
/// A type containing a script-defined function's metadata.
#[derive(Debug, Clone, Hash)]
pub struct ScriptFnMetadata {
pub comments: Vec<String>,
pub access: FnAccess,
pub fn_name: ImmutableString,
pub params: Vec<ImmutableString>,
}
impl fmt::Display for ScriptFnMetadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}({}) -> Dynamic",
if self.access.is_private() {
"private "
} else {
""
},
self.fn_name,
self.params
.iter()
.map(|p| p.as_str())
.collect::<Vec<_>>()
.join(", ")
)
}
}
impl Into<ScriptFnMetadata> for &ScriptFnDef {
fn into(self) -> ScriptFnMetadata {
ScriptFnMetadata {
comments: self.fn_comments.clone(),
access: self.access,
fn_name: self.name.clone(),
params: self.params.iter().cloned().collect(),
}
}
}
/// Compiled AST (abstract syntax tree) of a Rhai script. /// Compiled AST (abstract syntax tree) of a Rhai script.
/// ///
/// # Thread Safety /// # Thread Safety
/// ///
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. /// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AST( pub struct AST {
/// Global statements. /// Global statements.
Vec<Stmt>, statements: Vec<Stmt>,
/// Script-defined functions. /// Script-defined functions.
Shared<Module>, functions: Shared<Module>,
); }
impl Default for AST { impl Default for AST {
fn default() -> Self { fn default() -> Self {
Self(Vec::with_capacity(16), Default::default()) Self {
statements: Vec::with_capacity(16),
functions: Default::default(),
}
} }
} }
impl AST { impl AST {
/// Create a new [`AST`]. /// Create a new [`AST`].
#[inline(always)] #[inline(always)]
pub fn new(statements: impl IntoIterator<Item = Stmt>, lib: impl Into<Shared<Module>>) -> Self { pub fn new(
Self(statements.into_iter().collect(), lib.into()) statements: impl IntoIterator<Item = Stmt>,
functions: impl Into<Shared<Module>>,
) -> Self {
Self {
statements: statements.into_iter().collect(),
functions: functions.into(),
}
} }
/// Get the statements. /// Get the statements.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
pub(crate) fn statements(&self) -> &[Stmt] { pub(crate) fn statements(&self) -> &[Stmt] {
&self.0 &self.statements
} }
/// _(INTERNALS)_ Get the statements. /// _(INTERNALS)_ Get the statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -142,26 +193,26 @@ impl AST {
#[deprecated(note = "this method is volatile and may change")] #[deprecated(note = "this method is volatile and may change")]
#[inline(always)] #[inline(always)]
pub fn statements(&self) -> &[Stmt] { pub fn statements(&self) -> &[Stmt] {
&self.0 &self.statements
} }
/// Get a mutable reference to the statements. /// Get a mutable reference to the statements.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline(always)] #[inline(always)]
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> { pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> {
&mut self.0 &mut self.statements
} }
/// Get the internal shared [`Module`] containing all script-defined functions. /// Get the internal shared [`Module`] containing all script-defined functions.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub(crate) fn shared_lib(&self) -> Shared<Module> { pub(crate) fn shared_lib(&self) -> Shared<Module> {
self.1.clone() self.functions.clone()
} }
/// Get the internal [`Module`] containing all script-defined functions. /// Get the internal [`Module`] containing all script-defined functions.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
pub(crate) fn lib(&self) -> &Module { pub(crate) fn lib(&self) -> &Module {
&self.1 &self.functions
} }
/// _(INTERNALS)_ Get the internal [`Module`] containing all script-defined functions. /// _(INTERNALS)_ Get the internal [`Module`] containing all script-defined functions.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -169,7 +220,7 @@ impl AST {
#[deprecated(note = "this method is volatile and may change")] #[deprecated(note = "this method is volatile and may change")]
#[inline(always)] #[inline(always)]
pub fn lib(&self) -> &Module { pub fn lib(&self) -> &Module {
&self.1 &self.functions
} }
/// Clone the [`AST`]'s functions into a new [`AST`]. /// Clone the [`AST`]'s functions into a new [`AST`].
/// No statements are cloned. /// No statements are cloned.
@ -191,14 +242,20 @@ impl AST {
mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self { ) -> Self {
let mut functions: Module = Default::default(); let mut functions: Module = Default::default();
functions.merge_filtered(&self.1, &mut filter); functions.merge_filtered(&self.functions, &mut filter);
Self(Default::default(), functions.into()) Self {
statements: Default::default(),
functions: functions.into(),
}
} }
/// Clone the [`AST`]'s script statements into a new [`AST`]. /// Clone the [`AST`]'s script statements into a new [`AST`].
/// No functions are cloned. /// No functions are cloned.
#[inline(always)] #[inline(always)]
pub fn clone_statements_only(&self) -> Self { pub fn clone_statements_only(&self) -> Self {
Self(self.0.clone(), Default::default()) Self {
statements: self.statements.clone(),
functions: Default::default(),
}
} }
/// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version
/// is returned. /// is returned.
@ -363,21 +420,24 @@ impl AST {
other: &Self, other: &Self,
mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self { ) -> Self {
let Self(statements, functions) = self; let Self {
statements,
functions,
} = self;
let ast = match (statements.is_empty(), other.0.is_empty()) { let ast = match (statements.is_empty(), other.statements.is_empty()) {
(false, false) => { (false, false) => {
let mut statements = statements.clone(); let mut statements = statements.clone();
statements.extend(other.0.iter().cloned()); statements.extend(other.statements.iter().cloned());
statements statements
} }
(false, true) => statements.clone(), (false, true) => statements.clone(),
(true, false) => other.0.clone(), (true, false) => other.statements.clone(),
(true, true) => vec![], (true, true) => vec![],
}; };
let mut functions = functions.as_ref().clone(); let mut functions = functions.as_ref().clone();
functions.merge_filtered(&other.1, &mut filter); functions.merge_filtered(&other.functions, &mut filter);
Self::new(ast, functions) Self::new(ast, functions)
} }
@ -438,9 +498,9 @@ impl AST {
other: Self, other: Self,
mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool, mut filter: impl FnMut(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
self.0.extend(other.0.into_iter()); self.statements.extend(other.statements.into_iter());
if !other.1.is_empty() { if !other.functions.is_empty() {
shared_make_mut(&mut self.1).merge_filtered(&other.1, &mut filter); shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &mut filter);
} }
self self
} }
@ -473,29 +533,29 @@ impl AST {
&mut self, &mut self,
filter: impl FnMut(FnNamespace, FnAccess, &str, usize) -> bool, filter: impl FnMut(FnNamespace, FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
if !self.1.is_empty() { if !self.functions.is_empty() {
shared_make_mut(&mut self.1).retain_script_functions(filter); shared_make_mut(&mut self.functions).retain_script_functions(filter);
} }
self self
} }
/// Iterate through all functions /// Iterate through all functions
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn iter_functions<'a>( pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = ScriptFnMetadata> + 'a {
&'a self, self.functions
) -> impl Iterator<Item = (FnNamespace, FnAccess, &str, usize, &ScriptFnDef)> + 'a { .iter_script_fn()
self.1.iter_script_fn() .map(|(_, _, _, _, fn_def)| fn_def.into())
} }
/// Clear all function definitions in the [`AST`]. /// Clear all function definitions in the [`AST`].
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn clear_functions(&mut self) { pub fn clear_functions(&mut self) {
self.1 = Default::default(); self.functions = Default::default();
} }
/// Clear all statements in the [`AST`], leaving only function definitions. /// Clear all statements in the [`AST`], leaving only function definitions.
#[inline(always)] #[inline(always)]
pub fn clear_statements(&mut self) { pub fn clear_statements(&mut self) {
self.0 = vec![]; self.statements = vec![];
} }
} }
@ -618,9 +678,9 @@ pub enum Stmt {
/// `for` id `in` expr `{` stmt `}` /// `for` id `in` expr `{` stmt `}`
For(Expr, Box<(String, Stmt)>, Position), For(Expr, Box<(String, Stmt)>, Position),
/// \[`export`\] `let` id `=` expr /// \[`export`\] `let` id `=` expr
Let(Box<Ident>, Option<Expr>, bool, Position), Let(Box<IdentX>, Option<Expr>, bool, Position),
/// \[`export`\] `const` id `=` expr /// \[`export`\] `const` id `=` expr
Const(Box<Ident>, Option<Expr>, bool, Position), Const(Box<IdentX>, Option<Expr>, bool, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position),
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
@ -940,10 +1000,10 @@ impl Expr {
Self::FloatConstant(x, _) => (*x).into(), Self::FloatConstant(x, _) => (*x).into(),
Self::CharConstant(x, _) => (*x).into(), Self::CharConstant(x, _) => (*x).into(),
Self::StringConstant(x, _) => x.clone().into(), Self::StringConstant(x, _) => x.clone().into(),
Self::FnPointer(x, _) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( Self::FnPointer(x, _) => Dynamic(Union::FnPtr(
x.clone(), Box::new(FnPtr::new_unchecked(x.clone(), Default::default())),
Default::default(), AccessMode::ReadOnly,
)))), )),
Self::BoolConstant(x, _) => (*x).into(), Self::BoolConstant(x, _) => (*x).into(),
Self::Unit(_) => ().into(), Self::Unit(_) => ().into(),
@ -954,7 +1014,7 @@ impl Expr {
x.len(), x.len(),
)); ));
arr.extend(x.iter().map(|v| v.get_constant_value().unwrap())); arr.extend(x.iter().map(|v| v.get_constant_value().unwrap()));
Dynamic(Union::Array(Box::new(arr))) Dynamic(Union::Array(Box::new(arr), AccessMode::ReadOnly))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -967,7 +1027,7 @@ impl Expr {
x.iter() x.iter()
.map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())), .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())),
); );
Dynamic(Union::Map(Box::new(map))) Dynamic(Union::Map(Box::new(map), AccessMode::ReadOnly))
} }
_ => return None, _ => return None,
@ -1167,7 +1227,7 @@ mod tests {
assert_eq!(size_of::<Option<crate::ast::Expr>>(), 16); assert_eq!(size_of::<Option<crate::ast::Expr>>(), 16);
assert_eq!(size_of::<crate::ast::Stmt>(), 32); assert_eq!(size_of::<crate::ast::Stmt>(), 32);
assert_eq!(size_of::<Option<crate::ast::Stmt>>(), 32); assert_eq!(size_of::<Option<crate::ast::Stmt>>(), 32);
assert_eq!(size_of::<crate::Scope>(), 72); assert_eq!(size_of::<crate::Scope>(), 48);
assert_eq!(size_of::<crate::LexError>(), 56); assert_eq!(size_of::<crate::LexError>(), 56);
assert_eq!(size_of::<crate::ParseError>(), 16); assert_eq!(size_of::<crate::ParseError>(), 16);
assert_eq!(size_of::<crate::EvalAltResult>(), 72); assert_eq!(size_of::<crate::EvalAltResult>(), 72);

View File

@ -116,6 +116,25 @@ impl dyn Variant {
} }
} }
/// Modes of access.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum AccessMode {
/// Mutable.
ReadWrite,
/// Immutable.
ReadOnly,
}
impl AccessMode {
/// Is the access type [`ReadOnly`]?
pub fn is_read_only(self) -> bool {
match self {
Self::ReadWrite => false,
Self::ReadOnly => true,
}
}
}
/// Dynamic type containing any value. /// Dynamic type containing any value.
pub struct Dynamic(pub(crate) Union); pub struct Dynamic(pub(crate) Union);
@ -123,25 +142,25 @@ pub struct Dynamic(pub(crate) Union);
/// ///
/// Most variants are boxed to reduce the size. /// Most variants are boxed to reduce the size.
pub enum Union { pub enum Union {
Unit(()), Unit((), AccessMode),
Bool(bool), Bool(bool, AccessMode),
Str(ImmutableString), Str(ImmutableString, AccessMode),
Char(char), Char(char, AccessMode),
Int(INT), Int(INT, AccessMode),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Float(FLOAT), Float(FLOAT, AccessMode),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Array(Box<Array>), Array(Box<Array>, AccessMode),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Map(Box<Map>), Map(Box<Map>, AccessMode),
FnPtr(Box<FnPtr>), FnPtr(Box<FnPtr>, AccessMode),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
TimeStamp(Box<Instant>), TimeStamp(Box<Instant>, AccessMode),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>, AccessMode),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Shared(crate::Shared<crate::Locked<Dynamic>>), Shared(crate::Shared<crate::Locked<Dynamic>>, AccessMode),
} }
/// Underlying [`Variant`] read guard for [`Dynamic`]. /// Underlying [`Variant`] read guard for [`Dynamic`].
@ -236,7 +255,7 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn is_variant(&self) -> bool { pub fn is_variant(&self) -> bool {
match self.0 { match self.0 {
Union::Variant(_) => true, Union::Variant(_, _) => true,
_ => false, _ => false,
} }
} }
@ -246,7 +265,7 @@ impl Dynamic {
pub fn is_shared(&self) -> bool { pub fn is_shared(&self) -> bool {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => true, Union::Shared(_, _) => true,
_ => false, _ => false,
} }
} }
@ -272,29 +291,29 @@ impl Dynamic {
/// Otherwise, this call panics if the data is currently borrowed for write. /// Otherwise, this call panics if the data is currently borrowed for write.
pub fn type_id(&self) -> TypeId { pub fn type_id(&self) -> TypeId {
match &self.0 { match &self.0 {
Union::Unit(_) => TypeId::of::<()>(), Union::Unit(_, _) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(), Union::Bool(_, _) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<ImmutableString>(), Union::Str(_, _) => TypeId::of::<ImmutableString>(),
Union::Char(_) => TypeId::of::<char>(), Union::Char(_, _) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(), Union::Int(_, _) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_) => TypeId::of::<FLOAT>(), Union::Float(_, _) => TypeId::of::<FLOAT>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_) => TypeId::of::<Array>(), Union::Array(_, _) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_, _) => TypeId::of::<Map>(),
Union::FnPtr(_) => TypeId::of::<FnPtr>(), Union::FnPtr(_, _) => TypeId::of::<FnPtr>(),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => TypeId::of::<Instant>(), Union::TimeStamp(_, _) => TypeId::of::<Instant>(),
Union::Variant(value) => (***value).type_id(), Union::Variant(value, _) => (***value).type_id(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).type_id(), Union::Shared(cell, _) => (*cell.borrow()).type_id(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_id(), Union::Shared(cell, _) => (*cell.read().unwrap()).type_id(),
} }
} }
/// Get the name of the type of the value held by this [`Dynamic`]. /// Get the name of the type of the value held by this [`Dynamic`].
@ -305,32 +324,32 @@ impl Dynamic {
/// Otherwise, this call panics if the data is currently borrowed for write. /// Otherwise, this call panics if the data is currently borrowed for write.
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
match &self.0 { match &self.0 {
Union::Unit(_) => "()", Union::Unit(_, _) => "()",
Union::Bool(_) => "bool", Union::Bool(_, _) => "bool",
Union::Str(_) => "string", Union::Str(_, _) => "string",
Union::Char(_) => "char", Union::Char(_, _) => "char",
Union::Int(_) => type_name::<INT>(), Union::Int(_, _) => type_name::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_) => type_name::<FLOAT>(), Union::Float(_, _) => type_name::<FLOAT>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_) => "array", Union::Array(_, _) => "array",
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => "map", Union::Map(_, _) => "map",
Union::FnPtr(_) => "Fn", Union::FnPtr(_, _) => "Fn",
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => "timestamp", Union::TimeStamp(_, _) => "timestamp",
Union::Variant(value) => (***value).type_name(), Union::Variant(value, _) => (***value).type_name(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => cell Union::Shared(cell, _) => cell
.try_borrow() .try_borrow()
.map(|v| (*v).type_name()) .map(|v| (*v).type_name())
.unwrap_or("<shared>"), .unwrap_or("<shared>"),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_name(), Union::Shared(cell, _) => (*cell.read().unwrap()).type_name(),
} }
} }
} }
@ -340,17 +359,17 @@ impl Hash for Dynamic {
mem::discriminant(self).hash(state); mem::discriminant(self).hash(state);
match &self.0 { match &self.0 {
Union::Unit(_) => ().hash(state), Union::Unit(_, _) => ().hash(state),
Union::Bool(value) => value.hash(state), Union::Bool(value, _) => value.hash(state),
Union::Str(s) => s.hash(state), Union::Str(s, _) => s.hash(state),
Union::Char(ch) => ch.hash(state), Union::Char(ch, _) => ch.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.to_le_bytes().hash(state), Union::Float(f, _) => f.to_le_bytes().hash(state),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(a) => (**a).hash(state), Union::Array(a, _) => (**a).hash(state),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(m) => { Union::Map(m, _) => {
let mut buf: crate::StaticVec<_> = m.iter().collect(); let mut buf: crate::StaticVec<_> = m.iter().collect();
buf.sort_by(|(a, _), (b, _)| a.cmp(b)); buf.sort_by(|(a, _), (b, _)| a.cmp(b));
@ -362,10 +381,10 @@ impl Hash for Dynamic {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).hash(state), Union::Shared(cell, _) => (*cell.borrow()).hash(state),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).hash(state), Union::Shared(cell, _) => (*cell.read().unwrap()).hash(state),
_ => unimplemented!(), _ => unimplemented!(),
} }
@ -404,29 +423,29 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
impl fmt::Display for Dynamic { impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
Union::Unit(_) => write!(f, ""), Union::Unit(_, _) => write!(f, ""),
Union::Bool(value) => fmt::Display::fmt(value, f), Union::Bool(value, _) => fmt::Display::fmt(value, f),
Union::Str(value) => fmt::Display::fmt(value, f), Union::Str(value, _) => fmt::Display::fmt(value, f),
Union::Char(value) => fmt::Display::fmt(value, f), Union::Char(value, _) => fmt::Display::fmt(value, f),
Union::Int(value) => fmt::Display::fmt(value, f), Union::Int(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => fmt::Display::fmt(value, f), Union::Float(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => { Union::Map(value, _) => {
f.write_str("#")?; f.write_str("#")?;
fmt::Debug::fmt(value, f) fmt::Debug::fmt(value, f)
} }
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => f.write_str("<timestamp>"), Union::TimeStamp(_, _) => f.write_str("<timestamp>"),
Union::Variant(value) => f.write_str((*value).type_name()), Union::Variant(value, _) => f.write_str((*value).type_name()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
if let Ok(v) = cell.try_borrow() { if let Ok(v) = cell.try_borrow() {
fmt::Display::fmt(&*v, f) fmt::Display::fmt(&*v, f)
} else { } else {
@ -435,7 +454,7 @@ impl fmt::Display for Dynamic {
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Display::fmt(&*cell.read().unwrap(), f), Union::Shared(cell, _) => fmt::Display::fmt(&*cell.read().unwrap(), f),
} }
} }
} }
@ -443,29 +462,29 @@ impl fmt::Display for Dynamic {
impl fmt::Debug for Dynamic { impl fmt::Debug for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match &self.0 {
Union::Unit(value) => fmt::Debug::fmt(value, f), Union::Unit(value, _) => fmt::Debug::fmt(value, f),
Union::Bool(value) => fmt::Debug::fmt(value, f), Union::Bool(value, _) => fmt::Debug::fmt(value, f),
Union::Str(value) => fmt::Debug::fmt(value, f), Union::Str(value, _) => fmt::Debug::fmt(value, f),
Union::Char(value) => fmt::Debug::fmt(value, f), Union::Char(value, _) => fmt::Debug::fmt(value, f),
Union::Int(value) => fmt::Debug::fmt(value, f), Union::Int(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => fmt::Debug::fmt(value, f), Union::Float(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => { Union::Map(value, _) => {
f.write_str("#")?; f.write_str("#")?;
fmt::Debug::fmt(value, f) fmt::Debug::fmt(value, f)
} }
Union::FnPtr(value) => fmt::Debug::fmt(value, f), Union::FnPtr(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => write!(f, "<timestamp>"), Union::TimeStamp(_, _) => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), Union::Variant(value, _) => write!(f, "{}", (*value).type_name()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
if let Ok(v) = cell.try_borrow() { if let Ok(v) = cell.try_borrow() {
write!(f, "{:?} (shared)", *v) write!(f, "{:?} (shared)", *v)
} else { } else {
@ -474,33 +493,40 @@ impl fmt::Debug for Dynamic {
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Debug::fmt(&*cell.read().unwrap(), f), Union::Shared(cell, _) => fmt::Debug::fmt(&*cell.read().unwrap(), f),
} }
} }
} }
impl Clone for Dynamic { impl Clone for Dynamic {
/// Clone the [`Dynamic`] value.
///
/// ## WARNING
///
/// The cloned copy is marked [`AccessType::Normal`] even if the original is constant.
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self.0 { match self.0 {
Union::Unit(value) => Self(Union::Unit(value)), Union::Unit(value, _) => Self(Union::Unit(value, AccessMode::ReadWrite)),
Union::Bool(value) => Self(Union::Bool(value)), Union::Bool(value, _) => Self(Union::Bool(value, AccessMode::ReadWrite)),
Union::Str(ref value) => Self(Union::Str(value.clone())), Union::Str(ref value, _) => Self(Union::Str(value.clone(), AccessMode::ReadWrite)),
Union::Char(value) => Self(Union::Char(value)), Union::Char(value, _) => Self(Union::Char(value, AccessMode::ReadWrite)),
Union::Int(value) => Self(Union::Int(value)), Union::Int(value, _) => Self(Union::Int(value, AccessMode::ReadWrite)),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => Self(Union::Float(value)), Union::Float(value, _) => Self(Union::Float(value, AccessMode::ReadWrite)),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(ref value) => Self(Union::Array(value.clone())), Union::Array(ref value, _) => Self(Union::Array(value.clone(), AccessMode::ReadWrite)),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())), Union::Map(ref value, _) => Self(Union::Map(value.clone(), AccessMode::ReadWrite)),
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::FnPtr(ref value, _) => Self(Union::FnPtr(value.clone(), AccessMode::ReadWrite)),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(ref value) => Self(Union::TimeStamp(value.clone())), Union::TimeStamp(ref value, _) => {
Self(Union::TimeStamp(value.clone(), AccessMode::ReadWrite))
}
Union::Variant(ref value) => (***value).clone_into_dynamic(), Union::Variant(ref value, _) => (***value).clone_into_dynamic(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), Union::Shared(ref cell, _) => Self(Union::Shared(cell.clone(), AccessMode::ReadWrite)),
} }
} }
} }
@ -514,27 +540,96 @@ impl Default for Dynamic {
impl Dynamic { impl Dynamic {
/// A [`Dynamic`] containing a `()`. /// A [`Dynamic`] containing a `()`.
pub const UNIT: Dynamic = Self(Union::Unit(())); pub const UNIT: Dynamic = Self(Union::Unit((), AccessMode::ReadWrite));
/// A [`Dynamic`] containing a `true`. /// A [`Dynamic`] containing a `true`.
pub const TRUE: Dynamic = Self(Union::Bool(true)); pub const TRUE: Dynamic = Self(Union::Bool(true, AccessMode::ReadWrite));
/// A [`Dynamic`] containing a [`false`]. /// A [`Dynamic`] containing a [`false`].
pub const FALSE: Dynamic = Self(Union::Bool(false)); pub const FALSE: Dynamic = Self(Union::Bool(false, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the integer zero. /// A [`Dynamic`] containing the integer zero.
pub const ZERO: Dynamic = Self(Union::Int(0)); pub const ZERO: Dynamic = Self(Union::Int(0, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the integer one. /// A [`Dynamic`] containing the integer one.
pub const ONE: Dynamic = Self(Union::Int(1)); pub const ONE: Dynamic = Self(Union::Int(1, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the integer negative one. /// A [`Dynamic`] containing the integer negative one.
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1)); pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the floating-point zero. /// A [`Dynamic`] containing the floating-point zero.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0)); pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the floating-point one. /// A [`Dynamic`] containing the floating-point one.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0)); pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the floating-point negative one. /// A [`Dynamic`] containing the floating-point negative one.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0)); pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0, AccessMode::ReadWrite));
/// Get the [`AccessMode`] for this [`Dynamic`].
pub(crate) fn access_mode(&self) -> AccessMode {
match self.0 {
Union::Unit(_, access)
| Union::Bool(_, access)
| Union::Str(_, access)
| Union::Char(_, access)
| Union::Int(_, access)
| Union::FnPtr(_, access)
| Union::Variant(_, access) => access,
#[cfg(not(feature = "no_float"))]
Union::Float(_, access) => access,
#[cfg(not(feature = "no_index"))]
Union::Array(_, access) => access,
#[cfg(not(feature = "no_object"))]
Union::Map(_, access) => access,
#[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, access) => access,
#[cfg(not(feature = "no_closure"))]
Union::Shared(_, access) => access,
}
}
/// Set the [`AccessMode`] for this [`Dynamic`].
pub(crate) fn set_access_mode(&mut self, typ: AccessMode) {
match &mut self.0 {
Union::Unit(_, access)
| Union::Bool(_, access)
| Union::Str(_, access)
| Union::Char(_, access)
| Union::Int(_, access)
| Union::FnPtr(_, access)
| Union::Variant(_, access) => *access = typ,
#[cfg(not(feature = "no_float"))]
Union::Float(_, access) => *access = typ,
#[cfg(not(feature = "no_index"))]
Union::Array(_, access) => *access = typ,
#[cfg(not(feature = "no_object"))]
Union::Map(_, access) => *access = typ,
#[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, access) => *access = typ,
#[cfg(not(feature = "no_closure"))]
Union::Shared(_, access) => *access = typ,
}
}
/// Is this [`Dynamic`] read-only?
///
/// Constant [`Dynamic`] values are read-only. If a [`&mut Dynamic`][Dynamic] to such a constant
/// is passed to a Rust function, the function can use this information to return an error of
/// [`EvalAltResult::ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant]
/// if its value is going to be modified. This safe-guards constant values from being modified
/// from within Rust functions.
pub fn is_read_only(&self) -> bool {
match self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(_, access) if access.is_read_only() => true,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(ref cell, _) => cell.borrow().access_mode().is_read_only(),
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(ref cell, _) => cell.read().unwrap().access_mode().is_read_only(),
_ => self.access_mode().is_read_only(),
}
}
/// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is.
/// ///
/// # Safety /// # Safety
@ -651,7 +746,7 @@ impl Dynamic {
} }
} }
Self(Union::Variant(Box::new(boxed))) Self(Union::Variant(Box::new(boxed), AccessMode::ReadWrite))
} }
/// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>`
/// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature. /// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature.
@ -668,10 +763,12 @@ impl Dynamic {
/// Panics under the `no_closure` feature. /// Panics under the `no_closure` feature.
#[inline(always)] #[inline(always)]
pub fn into_shared(self) -> Self { pub fn into_shared(self) -> Self {
let _access = self.access_mode();
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
return match self.0 { return match self.0 {
Union::Shared(..) => self, Union::Shared(_, _) => self,
_ => Self(Union::Shared(crate::Locked::new(self).into())), _ => Self(Union::Shared(crate::Locked::new(self).into(), _access)),
}; };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -707,11 +804,11 @@ impl Dynamic {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell) => return cell.borrow().clone().try_cast(), Union::Shared(cell, _) => return cell.borrow().clone().try_cast(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(), Union::Shared(cell, _) => return cell.read().unwrap().clone().try_cast(),
_ => (), _ => (),
} }
@ -721,7 +818,7 @@ impl Dynamic {
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match self.0 { return match self.0 {
Union::Int(value) => unsafe_try_cast(value), Union::Int(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
@ -729,35 +826,35 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<FLOAT>() { if TypeId::of::<T>() == TypeId::of::<FLOAT>() {
return match self.0 { return match self.0 {
Union::Float(value) => unsafe_try_cast(value), Union::Float(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 { return match self.0 {
Union::Bool(value) => unsafe_try_cast(value), Union::Bool(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 { return match self.0 {
Union::Str(value) => unsafe_try_cast(value), Union::Str(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
return match self.0 { return match self.0 {
Union::Str(value) => unsafe_try_cast(value.into_owned()), Union::Str(value, _) => unsafe_try_cast(value.into_owned()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match self.0 { return match self.0 {
Union::Char(value) => unsafe_try_cast(value), Union::Char(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
@ -765,7 +862,7 @@ impl Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
return match self.0 { return match self.0 {
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Array(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
@ -773,14 +870,14 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() { if TypeId::of::<T>() == TypeId::of::<Map>() {
return match self.0 { return match self.0 {
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Map(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match self.0 { return match self.0 {
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::FnPtr(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
@ -788,22 +885,22 @@ impl Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match self.0 { return match self.0 {
Union::TimeStamp(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::TimeStamp(value, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 { return match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value, _) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
match self.0 { match self.0 {
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), Union::Variant(value, _) => (*value).as_box_any().downcast().map(|x| *x).ok(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => unreachable!(), Union::Shared(_, _) => unreachable!(),
_ => None, _ => None,
} }
} }
@ -859,7 +956,7 @@ impl Dynamic {
pub fn flatten_clone(&self) -> Self { pub fn flatten_clone(&self) -> Self {
match &self.0 { match &self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return cell.borrow().clone(); return cell.borrow().clone();
@ -879,7 +976,7 @@ impl Dynamic {
pub fn flatten(self) -> Self { pub fn flatten(self) -> Self {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => crate::fn_native::shared_try_take(cell).map_or_else( Union::Shared(cell, _) => crate::fn_native::shared_try_take(cell).map_or_else(
|cell| { |cell| {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return cell.borrow().clone(); return cell.borrow().clone();
@ -907,7 +1004,7 @@ impl Dynamic {
pub fn is_locked(&self) -> bool { pub fn is_locked(&self) -> bool {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref _cell) => { Union::Shared(ref _cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return _cell.try_borrow().is_err(); return _cell.try_borrow().is_err();
@ -930,7 +1027,7 @@ impl Dynamic {
pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> { pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(ref cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let data = cell.borrow(); let data = cell.borrow();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -962,7 +1059,7 @@ impl Dynamic {
pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> { pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => { Union::Shared(ref cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let data = cell.borrow_mut(); let data = cell.borrow_mut();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -991,71 +1088,71 @@ impl Dynamic {
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match &self.0 { return match &self.0 {
Union::Int(value) => <dyn Any>::downcast_ref::<T>(value), Union::Int(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<FLOAT>() { if TypeId::of::<T>() == TypeId::of::<FLOAT>() {
return match &self.0 { return match &self.0 {
Union::Float(value) => <dyn Any>::downcast_ref::<T>(value), Union::Float(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match &self.0 { return match &self.0 {
Union::Bool(value) => <dyn Any>::downcast_ref::<T>(value), Union::Bool(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match &self.0 { return match &self.0 {
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value), Union::Str(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
return match &self.0 { return match &self.0 {
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Str(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match &self.0 { return match &self.0 {
Union::Char(value) => <dyn Any>::downcast_ref::<T>(value), Union::Char(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
return match &self.0 { return match &self.0 {
Union::Array(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Array(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() { if TypeId::of::<T>() == TypeId::of::<Map>() {
return match &self.0 { return match &self.0 {
Union::Map(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::Map(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match &self.0 { return match &self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::FnPtr(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match &self.0 { return match &self.0 {
Union::TimeStamp(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()), Union::TimeStamp(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match &self.0 { return match &self.0 {
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value), Union::Unit(value, _) => <dyn Any>::downcast_ref::<T>(value),
_ => None, _ => None,
}; };
} }
@ -1064,9 +1161,9 @@ impl Dynamic {
} }
match &self.0 { match &self.0 {
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(), Union::Variant(value, _) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None, Union::Shared(_, _) => None,
_ => None, _ => None,
} }
} }
@ -1080,65 +1177,65 @@ impl Dynamic {
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match &mut self.0 { return match &mut self.0 {
Union::Int(value) => <dyn Any>::downcast_mut::<T>(value), Union::Int(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<FLOAT>() { if TypeId::of::<T>() == TypeId::of::<FLOAT>() {
return match &mut self.0 { return match &mut self.0 {
Union::Float(value) => <dyn Any>::downcast_mut::<T>(value), Union::Float(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match &mut self.0 { return match &mut self.0 {
Union::Bool(value) => <dyn Any>::downcast_mut::<T>(value), Union::Bool(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match &mut self.0 { return match &mut self.0 {
Union::Str(value) => <dyn Any>::downcast_mut::<T>(value), Union::Str(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match &mut self.0 { return match &mut self.0 {
Union::Char(value) => <dyn Any>::downcast_mut::<T>(value), Union::Char(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
return match &mut self.0 { return match &mut self.0 {
Union::Array(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::Array(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() { if TypeId::of::<T>() == TypeId::of::<Map>() {
return match &mut self.0 { return match &mut self.0 {
Union::Map(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::Map(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match &mut self.0 { return match &mut self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::FnPtr(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match &mut self.0 { return match &mut self.0 {
Union::TimeStamp(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()), Union::TimeStamp(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match &mut self.0 { return match &mut self.0 {
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value), Union::Unit(value, _) => <dyn Any>::downcast_mut::<T>(value),
_ => None, _ => None,
}; };
} }
@ -1147,9 +1244,9 @@ impl Dynamic {
} }
match &mut self.0 { match &mut self.0 {
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), Union::Variant(value, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None, Union::Shared(_, _) => None,
_ => None, _ => None,
} }
} }
@ -1158,9 +1255,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_int(&self) -> Result<INT, &'static str> { pub fn as_int(&self) -> Result<INT, &'static str> {
match self.0 { match self.0 {
Union::Int(n) => Ok(n), Union::Int(n, _) => Ok(n),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1170,9 +1267,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_float(&self) -> Result<FLOAT, &'static str> { pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 { match self.0 {
Union::Float(n) => Ok(n), Union::Float(n, _) => Ok(n),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1181,9 +1278,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_bool(&self) -> Result<bool, &'static str> { pub fn as_bool(&self) -> Result<bool, &'static str> {
match self.0 { match self.0 {
Union::Bool(b) => Ok(b), Union::Bool(b, _) => Ok(b),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1192,9 +1289,9 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_char(&self) -> Result<char, &'static str> { pub fn as_char(&self) -> Result<char, &'static str> {
match self.0 { match self.0 {
Union::Char(n) => Ok(n), Union::Char(n, _) => Ok(n),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1205,8 +1302,8 @@ impl Dynamic {
#[inline(always)] #[inline(always)]
pub fn as_str(&self) -> Result<&str, &'static str> { pub fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 { match &self.0 {
Union::Str(s) => Ok(s), Union::Str(s, _) => Ok(s),
Union::FnPtr(f) => Ok(f.fn_name()), Union::FnPtr(f, _) => Ok(f.fn_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -1223,16 +1320,16 @@ impl Dynamic {
#[inline] #[inline]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> { pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), Union::Str(s, _) => Ok(s),
Union::FnPtr(f) => Ok(f.take_data().0), Union::FnPtr(f, _) => Ok(f.take_data().0),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => { Union::Shared(cell, _) => {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ {
let inner = cell.borrow(); let inner = cell.borrow();
match &inner.0 { match &inner.0 {
Union::Str(s) => Ok(s.clone()), Union::Str(s, _) => Ok(s.clone()),
Union::FnPtr(f) => Ok(f.clone().take_data().0), Union::FnPtr(f, _) => Ok(f.clone().take_data().0),
_ => Err((*inner).type_name()), _ => Err((*inner).type_name()),
} }
} }
@ -1240,8 +1337,8 @@ impl Dynamic {
{ {
let inner = cell.read().unwrap(); let inner = cell.read().unwrap();
match &inner.0 { match &inner.0 {
Union::Str(s) => Ok(s.clone()), Union::Str(s, _) => Ok(s.clone()),
Union::FnPtr(f) => Ok(f.clone().take_data().0), Union::FnPtr(f, _) => Ok(f.clone().take_data().0),
_ => Err((*inner).type_name()), _ => Err((*inner).type_name()),
} }
} }
@ -1254,56 +1351,58 @@ impl Dynamic {
impl From<()> for Dynamic { impl From<()> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: ()) -> Self { fn from(value: ()) -> Self {
Self(Union::Unit(value)) Self(Union::Unit(value, AccessMode::ReadWrite))
} }
} }
impl From<bool> for Dynamic { impl From<bool> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: bool) -> Self { fn from(value: bool) -> Self {
Self(Union::Bool(value)) Self(Union::Bool(value, AccessMode::ReadWrite))
} }
} }
impl From<INT> for Dynamic { impl From<INT> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: INT) -> Self { fn from(value: INT) -> Self {
Self(Union::Int(value)) Self(Union::Int(value, AccessMode::ReadWrite))
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FLOAT> for Dynamic { impl From<FLOAT> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FLOAT) -> Self { fn from(value: FLOAT) -> Self {
Self(Union::Float(value)) Self(Union::Float(value, AccessMode::ReadWrite))
} }
} }
impl From<char> for Dynamic { impl From<char> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: char) -> Self { fn from(value: char) -> Self {
Self(Union::Char(value)) Self(Union::Char(value, AccessMode::ReadWrite))
} }
} }
impl<S: Into<ImmutableString>> From<S> for Dynamic { impl<S: Into<ImmutableString>> From<S> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: S) -> Self { fn from(value: S) -> Self {
Self(Union::Str(value.into())) Self(Union::Str(value.into(), AccessMode::ReadWrite))
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<crate::stdlib::vec::Vec<T>> for Dynamic { impl<T: Variant + Clone> From<crate::stdlib::vec::Vec<T>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: crate::stdlib::vec::Vec<T>) -> Self { fn from(value: crate::stdlib::vec::Vec<T>) -> Self {
Self(Union::Array(Box::new( Self(Union::Array(
value.into_iter().map(Dynamic::from).collect(), Box::new(value.into_iter().map(Dynamic::from).collect()),
))) AccessMode::ReadWrite,
))
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<&[T]> for Dynamic { impl<T: Variant + Clone> From<&[T]> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: &[T]) -> Self { fn from(value: &[T]) -> Self {
Self(Union::Array(Box::new( Self(Union::Array(
value.iter().cloned().map(Dynamic::from).collect(), Box::new(value.iter().cloned().map(Dynamic::from).collect()),
))) AccessMode::ReadWrite,
))
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -1312,30 +1411,33 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<crate::stdlib::collectio
{ {
#[inline(always)] #[inline(always)]
fn from(value: crate::stdlib::collections::HashMap<K, T>) -> Self { fn from(value: crate::stdlib::collections::HashMap<K, T>) -> Self {
Self(Union::Map(Box::new( Self(Union::Map(
Box::new(
value value
.into_iter() .into_iter()
.map(|(k, v)| (k.into(), Dynamic::from(v))) .map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(), .collect(),
))) ),
AccessMode::ReadWrite,
))
} }
} }
impl From<FnPtr> for Dynamic { impl From<FnPtr> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FnPtr) -> Self { fn from(value: FnPtr) -> Self {
Self(Union::FnPtr(Box::new(value))) Self(Union::FnPtr(Box::new(value), AccessMode::ReadWrite))
} }
} }
impl From<Box<FnPtr>> for Dynamic { impl From<Box<FnPtr>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Box<FnPtr>) -> Self { fn from(value: Box<FnPtr>) -> Self {
Self(Union::FnPtr(value)) Self(Union::FnPtr(value, AccessMode::ReadWrite))
} }
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
impl From<Instant> for Dynamic { impl From<Instant> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Instant) -> Self { fn from(value: Instant) -> Self {
Self(Union::TimeStamp(Box::new(value))) Self(Union::TimeStamp(Box::new(value), AccessMode::ReadWrite))
} }
} }

View File

@ -1,14 +1,16 @@
//! Main module defining the script evaluation [`Engine`]. //! Main module defining the script evaluation [`Engine`].
use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_call::run_builtin_op_assignment; use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{CallableFunction, Callback, IteratorFn, OnVarCallback}; use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
OnVarCallback,
};
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage}; use crate::packages::{Package, PackagesCollection, StandardPackage};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::scope::EntryType as ScopeEntryType;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
borrow::Cow, borrow::Cow,
@ -623,11 +625,11 @@ pub struct Engine {
pub(crate) resolve_var: Option<OnVarCallback>, pub(crate) resolve_var: Option<OnVarCallback>,
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
pub(crate) print: Callback<str, ()>, pub(crate) print: OnPrintCallback,
/// Callback closure for implementing the `debug` command. /// Callback closure for implementing the `debug` command.
pub(crate) debug: Callback<str, ()>, pub(crate) debug: OnDebugCallback,
/// Callback closure for progress reporting. /// Callback closure for progress reporting.
pub(crate) progress: Option<Callback<u64, Option<Dynamic>>>, pub(crate) progress: Option<OnProgressCallback>,
/// Optimize the AST after compilation. /// Optimize the AST after compilation.
pub(crate) optimization_level: OptimizationLevel, pub(crate) optimization_level: OptimizationLevel,
@ -676,7 +678,7 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool {
fn_name.starts_with(FN_ANONYMOUS) fn_name.starts_with(FN_ANONYMOUS)
} }
/// Print/debug to stdout /// Print to stdout
#[inline(always)] #[inline(always)]
fn default_print(_s: &str) { fn default_print(_s: &str) {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -684,6 +686,14 @@ fn default_print(_s: &str) {
println!("{}", _s); println!("{}", _s);
} }
/// Debug to stdout
#[inline(always)]
fn default_debug(_s: &str, _pos: Position) {
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
println!("{:?} | {}", _pos, _s);
}
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards. /// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
pub fn search_imports( pub fn search_imports(
@ -740,7 +750,7 @@ impl Engine {
// default print/debug implementations // default print/debug implementations
print: Box::new(default_print), print: Box::new(default_print),
debug: Box::new(default_print), debug: Box::new(default_debug),
// progress callback // progress callback
progress: None, progress: None,
@ -796,7 +806,7 @@ impl Engine {
resolve_var: None, resolve_var: None,
print: Box::new(|_| {}), print: Box::new(|_| {}),
debug: Box::new(|_| {}), debug: Box::new(|_, _| {}),
progress: None, progress: None,
optimization_level: if cfg!(feature = "no_optimize") { optimization_level: if cfg!(feature = "no_optimize") {
@ -833,7 +843,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr, expr: &'a Expr,
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> {
match expr { match expr {
Expr::Variable(v) => match v.as_ref() { Expr::Variable(v) => match v.as_ref() {
// Qualified variable // Qualified variable
@ -850,7 +860,9 @@ impl Engine {
})?; })?;
// Module variables are constant // Module variables are constant
Ok((target.clone().into(), name, ScopeEntryType::Constant, *pos)) let mut target = target.clone();
target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), name, *pos))
} }
// Normal variable access // Normal variable access
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
@ -868,7 +880,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr, expr: &'a Expr,
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> {
let (index, _, _, IdentX { name, pos }) = match expr { let (index, _, _, IdentX { name, pos }) = match expr {
Expr::Variable(v) => v.as_ref(), Expr::Variable(v) => v.as_ref(),
_ => unreachable!(), _ => unreachable!(),
@ -877,7 +889,7 @@ impl Engine {
// Check if the variable is `this` // Check if the variable is `this`
if name.as_str() == KEYWORD_THIS { if name.as_str() == KEYWORD_THIS {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); return Ok(((*val).into(), KEYWORD_THIS, *pos));
} else { } else {
return EvalAltResult::ErrorUnboundThis(*pos).into(); return EvalAltResult::ErrorUnboundThis(*pos).into();
} }
@ -901,10 +913,11 @@ impl Engine {
this_ptr, this_ptr,
level: 0, level: 0,
}; };
if let Some(result) = if let Some(mut result) =
resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))?
{ {
return Ok((result.into(), name, ScopeEntryType::Constant, *pos)); result.set_access_mode(AccessMode::ReadOnly);
return Ok((result.into(), name, *pos));
} }
} }
@ -918,7 +931,7 @@ impl Engine {
.0 .0
}; };
let (val, typ) = scope.get_mut(index); let val = scope.get_mut(index);
// Check for data race - probably not necessary because the only place it should conflict is in a method call // Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter. // when the object variable is also used as a parameter.
@ -926,7 +939,7 @@ impl Engine {
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
// } // }
Ok((val.into(), name, typ, *pos)) Ok((val.into(), name, *pos))
} }
/// Chain-evaluate a dot/index chain. /// Chain-evaluate a dot/index chain.
@ -1012,8 +1025,8 @@ impl Engine {
let args = &mut [target_val, &mut idx_val2, &mut new_val.0]; let args = &mut [target_val, &mut idx_val2, &mut new_val.0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, mods, state, lib, FN_IDX_SET, 0, args, is_ref, true, false,
None, level, new_val.1, None, None, level,
) )
.map_err(|err| match *err { .map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _) EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
@ -1057,9 +1070,8 @@ impl Engine {
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
self.make_method_call( self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, *native, false, mods, state, lib, name, *hash, target, args, def_value, *native, false,
level, *pos, level,
) )
.map_err(|err| err.fill_position(*pos))
} }
// xxx.module::fn_name(...) - syntax error // xxx.module::fn_name(...) - syntax error
Expr::FnCall(_, _) => unreachable!(), Expr::FnCall(_, _) => unreachable!(),
@ -1090,8 +1102,8 @@ impl Engine {
let mut new_val = new_val; let mut new_val = new_val;
let mut args = [target_val, &mut new_val.as_mut().unwrap().0]; let mut args = [target_val, &mut new_val.as_mut().unwrap().0];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, 0, &mut args, is_ref, true, false, None, mods, state, lib, setter, 0, &mut args, is_ref, true, false, *pos,
None, level, None, None, level,
) )
.map(|(v, _)| (v, true)) .map(|(v, _)| (v, true))
.map_err(|err| err.fill_position(*pos)) .map_err(|err| err.fill_position(*pos))
@ -1101,8 +1113,8 @@ impl Engine {
let ((getter, _), IdentX { pos, .. }) = x.as_ref(); let ((getter, _), IdentX { pos, .. }) = x.as_ref();
let mut args = [target_val]; let mut args = [target_val];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, getter, 0, &mut args, is_ref, true, false, None, mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos,
None, level, None, None, level,
) )
.map(|(v, _)| (v, false)) .map(|(v, _)| (v, false))
.map_err(|err| err.fill_position(*pos)) .map_err(|err| err.fill_position(*pos))
@ -1129,12 +1141,10 @@ impl Engine {
} = x.as_ref(); } = x.as_ref();
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
let (val, _) = self let (val, _) = self.make_method_call(
.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, mods, state, lib, name, *hash, target, args, def_value,
*native, false, level, *native, false, *pos, level,
) )?;
.map_err(|err| err.fill_position(*pos))?;
val.into() val.into()
} }
// {xxx:map}.module::fn_name(...) - syntax error // {xxx:map}.module::fn_name(...) - syntax error
@ -1160,7 +1170,7 @@ impl Engine {
let (mut val, updated) = self let (mut val, updated) = self
.exec_fn_call( .exec_fn_call(
mods, state, lib, getter, 0, args, is_ref, true, false, mods, state, lib, getter, 0, args, is_ref, true, false,
None, None, level, *pos, None, None, level,
) )
.map_err(|err| err.fill_position(*pos))?; .map_err(|err| err.fill_position(*pos))?;
@ -1187,7 +1197,7 @@ impl Engine {
arg_values[1] = val; arg_values[1] = val;
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, setter, 0, arg_values, is_ref, true, mods, state, lib, setter, 0, arg_values, is_ref, true,
false, None, None, level, false, *pos, None, None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -1213,12 +1223,10 @@ impl Engine {
} = f.as_ref(); } = f.as_ref();
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
let (mut val, _) = self let (mut val, _) = self.make_method_call(
.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, mods, state, lib, name, *hash, target, args, def_value,
*native, false, level, *native, false, *pos, level,
) )?;
.map_err(|err| err.fill_position(*pos))?;
let val = &mut val; let val = &mut val;
let target = &mut val.into(); let target = &mut val.into();
@ -1279,17 +1287,14 @@ impl Engine {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(*var_pos))?; .map_err(|err| err.fill_position(*var_pos))?;
let (target, _, typ, pos) = let (target, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
// Constants cannot be modified // Constants cannot be modified
match typ { if target.as_ref().is_read_only() && new_val.is_some() {
ScopeEntryType::Constant if new_val.is_some() => {
return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos) return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos)
.into(); .into();
} }
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
}
let obj_ptr = &mut target.into(); let obj_ptr = &mut target.into();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
@ -1410,7 +1415,7 @@ impl Engine {
match target { match target {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr)) => { Dynamic(Union::Array(arr, _)) => {
// val_array[idx] // val_array[idx]
let index = idx let index = idx
.as_int() .as_int()
@ -1430,7 +1435,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map)) => { Dynamic(Union::Map(map, _)) => {
// val_map[idx] // val_map[idx]
Ok(if _create { Ok(if _create {
let index = idx.take_immutable_string().map_err(|err| { let index = idx.take_immutable_string().map_err(|err| {
@ -1450,7 +1455,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Str(s)) => { Dynamic(Union::Str(s, _)) => {
// val_string[idx] // val_string[idx]
let chars_len = s.chars().count(); let chars_len = s.chars().count();
let index = idx let index = idx
@ -1474,8 +1479,8 @@ impl Engine {
let mut idx = idx; let mut idx = idx;
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, 0, args, _is_ref, true, false, None, None, _mods, state, _lib, FN_IDX_GET, 0, args, _is_ref, true, false, idx_pos, None,
_level, None, _level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
.map_err(|err| match *err { .map_err(|err| match *err {
@ -1517,7 +1522,7 @@ impl Engine {
match rhs_value { match rhs_value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value, _)) => {
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
let def_value = Some(false.into()); let def_value = Some(false.into());
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
@ -1529,11 +1534,12 @@ impl Engine {
let hash = let hash =
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id())); calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()));
let pos = rhs.position();
if self if self
.call_native_fn( .call_native_fn(
mods, state, lib, OP_EQUALS, hash, args, false, false, def_value, mods, state, lib, OP_EQUALS, hash, args, false, false, pos, def_value,
) )?
.map_err(|err| err.fill_position(rhs.position()))?
.0 .0
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
@ -1545,16 +1551,16 @@ impl Engine {
Ok(false.into()) Ok(false.into())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value)) => match lhs_value { Dynamic(Union::Map(rhs_value, _)) => match lhs_value {
// Only allows string or char // Only allows string or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()), Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()), Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
}, },
Dynamic(Union::Str(rhs_value)) => match lhs_value { Dynamic(Union::Str(rhs_value, _)) => match lhs_value {
// Only allows string or char // Only allows string or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_str()).into()), Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains(s.as_str()).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains(c).into()), Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains(c).into()),
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
}, },
_ => EvalAltResult::ErrorInExpr(rhs.position()).into(), _ => EvalAltResult::ErrorInExpr(rhs.position()).into(),
@ -1570,23 +1576,21 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
no_const: bool, _no_const: bool,
level: usize, level: usize,
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
match expr { match expr {
// var - point directly to the value // var - point directly to the value
Expr::Variable(_) => { Expr::Variable(_) => {
let (target, _, typ, pos) = let (mut target, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
Ok((
match typ {
// If necessary, constants are cloned // If necessary, constants are cloned
ScopeEntryType::Constant if no_const => target.into_owned(), if target.as_ref().is_read_only() {
_ => target, target = target.into_owned();
}, }
pos,
)) Ok((target, pos))
} }
// var[...] // var[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1601,7 +1605,7 @@ impl Engine {
let idx = self.eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)?; let idx = self.eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)?;
let idx_pos = x.rhs.position(); let idx_pos = x.rhs.position();
let (mut target, pos) = self.eval_expr_as_target( let (mut target, pos) = self.eval_expr_as_target(
scope, mods, state, lib, this_ptr, &x.lhs, no_const, level, scope, mods, state, lib, this_ptr, &x.lhs, _no_const, level,
)?; )?;
let is_ref = target.is_ref(); let is_ref = target.is_ref();
@ -1628,7 +1632,7 @@ impl Engine {
// var.prop // var.prop
Expr::Property(ref p) => { Expr::Property(ref p) => {
let (mut target, _) = self.eval_expr_as_target( let (mut target, _) = self.eval_expr_as_target(
scope, mods, state, lib, this_ptr, &x.lhs, no_const, level, scope, mods, state, lib, this_ptr, &x.lhs, _no_const, level,
)?; )?;
let is_ref = target.is_ref(); let is_ref = target.is_ref();
@ -1655,11 +1659,10 @@ impl Engine {
let ((getter, _), IdentX { pos, .. }) = p.as_ref(); let ((getter, _), IdentX { pos, .. }) = p.as_ref();
let mut args = [target.as_mut()]; let mut args = [target.as_mut()];
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, getter, 0, &mut args, is_ref, true, false, None, mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos,
None, level, None, None, level,
) )
.map(|(v, _)| (v.into(), *pos)) .map(|(v, _)| (v.into(), *pos))
.map_err(|err| err.fill_position(*pos))
} }
} }
// var.??? // var.???
@ -1706,8 +1709,7 @@ impl Engine {
} }
} }
Expr::Variable(_) => { Expr::Variable(_) => {
let (val, _, _, _) = let (val, _, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
Ok(val.take_or_clone()) Ok(val.take_or_clone())
} }
Expr::Property(_) => unreachable!(), Expr::Property(_) => unreachable!(),
@ -1736,7 +1738,7 @@ impl Engine {
for item in x.as_ref() { for item in x.as_ref() {
arr.push(self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?); arr.push(self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?);
} }
Ok(Dynamic(Union::Array(Box::new(arr)))) Ok(Dynamic(Union::Array(Box::new(arr), AccessMode::ReadWrite)))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -1749,7 +1751,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
); );
} }
Ok(Dynamic(Union::Map(Box::new(map)))) Ok(Dynamic(Union::Map(Box::new(map), AccessMode::ReadWrite)))
} }
// Normal function call // Normal function call
@ -1766,9 +1768,8 @@ impl Engine {
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
self.make_function_call( self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, def_value, *hash, *native, scope, mods, state, lib, this_ptr, name, args, def_value, *hash, *native,
false, *cap_scope, level, false, *pos, *cap_scope, level,
) )
.map_err(|err| err.fill_position(*pos))
} }
// Namespace-qualified function call // Namespace-qualified function call
@ -1785,9 +1786,8 @@ impl Engine {
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
self.make_qualified_function_call( self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, def_value, *hash, scope, mods, state, lib, this_ptr, namespace, name, args, def_value, *hash,
level, *pos, level,
) )
.map_err(|err| err.fill_position(*pos))
} }
Expr::In(x, _) => { Expr::In(x, _) => {
@ -1913,7 +1913,7 @@ impl Engine {
let mut rhs_val = self let mut 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 (mut lhs_ptr, name, typ, pos) = let (mut lhs_ptr, name, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() { if !lhs_ptr.is_ref() {
@ -1923,29 +1923,28 @@ impl Engine {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
match typ { if lhs_ptr.as_ref().is_read_only() {
// Assignment to constant variable // Assignment to constant variable
ScopeEntryType::Constant => Err(Box::new( Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos), name.to_string(),
)), pos,
)))
} else if op.is_empty() {
// Normal assignment // Normal assignment
ScopeEntryType::Normal if op.is_empty() => {
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val; *lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
} else { } else {
*lhs_ptr.as_mut() = rhs_val; *lhs_ptr.as_mut() = rhs_val;
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} } else {
// Op-assignment - in order of precedence: // Op-assignment - in order of precedence:
ScopeEntryType::Normal => {
// 1) Native registered overriding function // 1) Native registered overriding function
// 2) Built-in implementation // 2) Built-in implementation
// 3) Map to `var = var op rhs` // 3) Map to `var = var op rhs`
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types = let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
let hash_fn = calc_native_fn_hash(empty(), op, arg_types); let hash_fn = calc_native_fn_hash(empty(), op, arg_types);
match self match self
@ -1987,12 +1986,10 @@ impl Engine {
let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val]; let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val];
// Run function // Run function
let (value, _) = self let (value, _) = self.exec_fn_call(
.exec_fn_call( mods, state, lib, op, 0, args, false, false, false, *op_pos, None,
mods, state, lib, op, 0, args, false, false, false, None,
None, level, None, level,
) )?;
.map_err(|err| err.fill_position(*op_pos))?;
let value = value.flatten(); let value = value.flatten();
@ -2006,7 +2003,6 @@ impl Engine {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} }
}
// lhs op= rhs // lhs op= rhs
Stmt::Assignment(x, op_pos) => { Stmt::Assignment(x, op_pos) => {
@ -2027,10 +2023,10 @@ impl Engine {
let result = self let result = self
.exec_fn_call( .exec_fn_call(
mods, state, lib, op, 0, args, false, false, false, None, None, level, mods, state, lib, op, 0, args, false, false, false, *op_pos, None,
None, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)?;
.map_err(|err| err.fill_position(*op_pos))?;
Some((result, rhs_expr.position())) Some((result, rhs_expr.position()))
}; };
@ -2175,7 +2171,7 @@ impl Engine {
state.scope_level += 1; state.scope_level += 1;
for iter_value in func(iter_obj) { for iter_value in func(iter_obj) {
let (loop_var, _) = scope.get_mut(index); let loop_var = scope.get_mut(index);
let value = iter_value.flatten(); let value = iter_value.flatten();
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() { if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
@ -2249,8 +2245,10 @@ impl Engine {
.map(|_| ().into()); .map(|_| ().into());
if let Some(result_err) = result.as_ref().err() { if let Some(result_err) = result.as_ref().err() {
if let EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_)), pos) = if let EvalAltResult::ErrorRuntime(
result_err.as_ref() Dynamic(Union::Unit(_, _)),
pos,
) = result_err.as_ref()
{ {
err.set_position(*pos); err.set_position(*pos);
result = Err(Box::new(err)); result = Err(Box::new(err));
@ -2293,8 +2291,8 @@ impl Engine {
// Let/const statement // Let/const statement
Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => { Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => {
let entry_type = match stmt { let entry_type = match stmt {
Stmt::Let(_, _, _, _) => ScopeEntryType::Normal, Stmt::Let(_, _, _, _) => AccessMode::ReadWrite,
Stmt::Const(_, _, _, _) => ScopeEntryType::Constant, Stmt::Const(_, _, _, _) => AccessMode::ReadOnly,
_ => unreachable!(), _ => unreachable!(),
}; };
@ -2306,9 +2304,9 @@ impl Engine {
}; };
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
( (
var_def.name.clone().into(), var_def.name.to_string().into(),
if *export { if *export {
Some(var_def.name.to_string()) Some(var_def.name.clone())
} else { } else {
None None
}, },
@ -2375,7 +2373,7 @@ impl Engine {
// Mark scope variables as public // Mark scope variables as public
if let Some(index) = scope.get_index(name).map(|(i, _)| i) { if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name); let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
scope.add_entry_alias(index, alias.to_string()); scope.add_entry_alias(index, alias.clone());
} else { } else {
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos) return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos)
.into(); .into();
@ -2387,17 +2385,14 @@ impl Engine {
// Share statement // Share statement
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => { Stmt::Share(x) => {
match scope.get_index(&x.name) { if let Some((index, _)) = scope.get_index(&x.name) {
Some((index, ScopeEntryType::Normal)) => { let val = scope.get_mut(index);
let (val, _) = scope.get_mut(index);
if !val.is_shared() { if !val.is_shared() {
// Replace the variable with a shared value. // Replace the variable with a shared value.
*val = crate::stdlib::mem::take(val).into_shared(); *val = crate::stdlib::mem::take(val).into_shared();
} }
} }
_ => (),
}
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
}; };
@ -2445,18 +2440,18 @@ impl Engine {
fn calc_size(value: &Dynamic) -> (usize, usize, usize) { fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
match value { match value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr)) => { Dynamic(Union::Array(arr, _)) => {
let mut arrays = 0; let mut arrays = 0;
let mut maps = 0; let mut maps = 0;
arr.iter().for_each(|value| match value { arr.iter().for_each(|value| match value {
Dynamic(Union::Array(_)) => { Dynamic(Union::Array(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(_)) => { Dynamic(Union::Map(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2467,18 +2462,18 @@ impl Engine {
(arrays, maps, 0) (arrays, maps, 0)
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map)) => { Dynamic(Union::Map(map, _)) => {
let mut arrays = 0; let mut arrays = 0;
let mut maps = 0; let mut maps = 0;
map.values().for_each(|value| match value { map.values().for_each(|value| match value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(_)) => { Dynamic(Union::Array(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
} }
Dynamic(Union::Map(_)) => { Dynamic(Union::Map(_, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2488,7 +2483,7 @@ impl Engine {
(arrays, maps, 0) (arrays, maps, 0)
} }
Dynamic(Union::Str(s)) => (0, 0, s.len()), Dynamic(Union::Str(s, _)) => (0, 0, s.len()),
_ => (0, 0, 0), _ => (0, 0, 0),
} }
} }
@ -2497,13 +2492,13 @@ impl Engine {
// Simply return all errors // Simply return all errors
Err(_) => return result, Err(_) => return result,
// String with limit // String with limit
Ok(Dynamic(Union::Str(_))) if self.max_string_size() > 0 => (), Ok(Dynamic(Union::Str(_, _))) if self.max_string_size() > 0 => (),
// Array with limit // Array with limit
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Ok(Dynamic(Union::Array(_))) if self.max_array_size() > 0 => (), Ok(Dynamic(Union::Array(_, _))) if self.max_array_size() > 0 => (),
// Map with limit // Map with limit
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Ok(Dynamic(Union::Map(_))) if self.max_map_size() > 0 => (), Ok(Dynamic(Union::Map(_, _))) if self.max_map_size() > 0 => (),
// Everything else is simply returned // Everything else is simply returned
Ok(_) => return result, Ok(_) => return result,
}; };
@ -2549,7 +2544,7 @@ impl Engine {
// Report progress - only in steps // Report progress - only in steps
if let Some(progress) = &self.progress { if let Some(progress) = &self.progress {
if let Some(token) = progress(&state.operations) { if let Some(token) = progress(state.operations) {
// Terminate script if progress returns a termination token // Terminate script if progress returns a termination token
return EvalAltResult::ErrorTerminated(token, Position::NONE).into(); return EvalAltResult::ErrorTerminated(token, Position::NONE).into();
} }

View File

@ -1611,7 +1611,17 @@ impl Engine {
crate::fn_call::ensure_no_data_race(name, args, false)?; crate::fn_call::ensure_no_data_race(name, args, false)?;
} }
self.call_script_fn(scope, &mut mods, &mut state, lib, this_ptr, fn_def, args, 0) self.call_script_fn(
scope,
&mut mods,
&mut state,
lib,
this_ptr,
fn_def,
args,
Position::NONE,
0,
)
} }
/// Optimize the [`AST`] with constants defined in an external Scope. /// Optimize the [`AST`] with constants defined in an external Scope.
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
@ -1726,7 +1736,7 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// engine.on_progress(move |&ops| { /// engine.on_progress(move |ops| {
/// if ops > 10000 { /// if ops > 10000 {
/// Some("Over 10,000 operations!".into()) /// Some("Over 10,000 operations!".into())
/// } else if ops % 800 == 0 { /// } else if ops % 800 == 0 {
@ -1748,7 +1758,7 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn on_progress( pub fn on_progress(
&mut self, &mut self,
callback: impl Fn(&u64) -> Option<Dynamic> + SendSync + 'static, callback: impl Fn(u64) -> Option<Dynamic> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.progress = Some(Box::new(callback)); self.progress = Some(Box::new(callback));
self self
@ -1798,16 +1808,21 @@ impl Engine {
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// let logger = result.clone(); /// let logger = result.clone();
/// engine.on_debug(move |s| logger.write().unwrap().push_str(s)); /// engine.on_debug(move |s, pos| logger.write().unwrap().push_str(
/// &format!("{:?} > {}", pos, s)
/// ));
/// ///
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"let x = "hello"; debug(x);"#)?;
/// ///
/// assert_eq!(*result.read().unwrap(), r#""hello""#); /// assert_eq!(*result.read().unwrap(), r#"1:18 > "hello""#);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { pub fn on_debug(
&mut self,
callback: impl Fn(&str, Position) + SendSync + 'static,
) -> &mut Self {
self.debug = Box::new(callback); self.debug = Box::new(callback);
self self
} }

View File

@ -8,7 +8,6 @@ use crate::engine::{
use crate::fn_native::FnCallArgs; use crate::fn_native::FnCallArgs;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::scope::EntryType as ScopeEntryType;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
@ -150,7 +149,6 @@ pub fn ensure_no_data_race(
impl Engine { impl Engine {
/// Call a native Rust function registered with the [`Engine`]. /// Call a native Rust function registered with the [`Engine`].
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -167,6 +165,7 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
pub_only: bool, pub_only: bool,
pos: Position,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -205,20 +204,23 @@ impl Engine {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
Position::NONE, pos,
) )
})?) })?)
.into(), .into(),
false, false,
), ),
KEYWORD_DEBUG => ( KEYWORD_DEBUG => (
(self.debug)(result.as_str().map_err(|typ| { (self.debug)(
result.as_str().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(),
Position::NONE, pos,
)
})?,
pos,
) )
})?)
.into(), .into(),
false, false,
), ),
@ -248,7 +250,7 @@ impl Engine {
prop, prop,
self.map_type_name(args[0].type_name()) self.map_type_name(args[0].type_name())
), ),
Position::NONE, pos,
) )
.into(); .into();
} }
@ -263,7 +265,7 @@ impl Engine {
self.map_type_name(args[0].type_name()), self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()), self.map_type_name(args[1].type_name()),
), ),
Position::NONE, pos,
) )
.into(); .into();
} }
@ -277,7 +279,7 @@ impl Engine {
self.map_type_name(args[0].type_name()), self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()), self.map_type_name(args[1].type_name()),
), ),
Position::NONE, pos,
) )
.into(); .into();
} }
@ -291,7 +293,7 @@ impl Engine {
self.map_type_name(args[0].type_name()), self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()), self.map_type_name(args[1].type_name()),
), ),
Position::NONE, pos,
) )
.into(); .into();
} }
@ -310,13 +312,12 @@ impl Engine {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
), ),
Position::NONE, pos,
) )
.into() .into()
} }
/// Call a script-defined function. /// Call a script-defined function.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -333,6 +334,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
fn_def: &crate::ast::ScriptFnDef, fn_def: &crate::ast::ScriptFnDef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
pos: Position,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -341,7 +343,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if level > self.max_call_levels() { if level > self.max_call_levels() {
return Err(Box::new(EvalAltResult::ErrorStackOverflow(Position::NONE))); return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
} }
let orig_scope_level = state.scope_level; let orig_scope_level = state.scope_level;
@ -360,7 +362,7 @@ impl Engine {
.map(|(name, value)| { .map(|(name, value)| {
let var_name: crate::stdlib::borrow::Cow<'_, str> = let var_name: crate::stdlib::borrow::Cow<'_, str> =
crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into(); crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into();
(var_name, ScopeEntryType::Normal, value) (var_name, value)
}), }),
); );
@ -393,17 +395,14 @@ impl Engine {
EvalAltResult::ErrorInFunctionCall( EvalAltResult::ErrorInFunctionCall(
format!("{} > {}", fn_def.name, name), format!("{} > {}", fn_def.name, name),
err, err,
Position::NONE, pos,
) )
.into() .into()
} }
// System errors are passed straight-through // System errors are passed straight-through
err if err.is_system_exception() => Err(Box::new(err)), err if err.is_system_exception() => Err(Box::new(err)),
// Other errors are wrapped in `ErrorInFunctionCall` // Other errors are wrapped in `ErrorInFunctionCall`
_ => { _ => EvalAltResult::ErrorInFunctionCall(fn_def.name.to_string(), err, pos).into(),
EvalAltResult::ErrorInFunctionCall(fn_def.name.to_string(), err, Position::NONE)
.into()
}
}); });
// Remove all local variables // Remove all local variables
@ -457,7 +456,6 @@ impl Engine {
} }
/// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Perform an actual function call, native Rust or scripted, taking care of special functions.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -475,6 +473,7 @@ impl Engine {
is_ref: bool, is_ref: bool,
_is_method: bool, _is_method: bool,
pub_only: bool, pub_only: bool,
pos: Position,
_capture_scope: Option<Scope>, _capture_scope: Option<Scope>,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
_level: usize, _level: usize,
@ -512,7 +511,7 @@ impl Engine {
fn_name, fn_name fn_name, fn_name
) )
.into(), .into(),
Position::NONE, pos,
) )
.into() .into()
} }
@ -540,15 +539,10 @@ impl Engine {
if !func.externals.is_empty() { if !func.externals.is_empty() {
captured captured
.into_iter() .into_iter()
.filter(|(name, _, _, _)| func.externals.contains(name.as_ref())) .filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name))
.for_each(|(name, typ, value, _)| { .for_each(|(name, value, _)| {
// Consume the scope values. // Consume the scope values.
match typ { scope.push_dynamic(name, value);
ScopeEntryType::Normal => scope.push(name, value),
ScopeEntryType::Constant => {
scope.push_constant(name, value)
}
};
}); });
} }
} }
@ -564,6 +558,7 @@ impl Engine {
&mut Some(*first), &mut Some(*first),
func, func,
rest, rest,
pos,
_level, _level,
)? )?
} else { } else {
@ -572,8 +567,9 @@ impl Engine {
let mut backup: ArgBackup = Default::default(); let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args); backup.change_first_arg_to_copy(is_ref, args);
let result = self let result = self.call_script_fn(
.call_script_fn(scope, mods, state, lib, &mut None, func, args, _level); scope, mods, state, lib, &mut None, func, args, pos, _level,
);
// Restore the original reference // Restore the original reference
backup.restore_first_arg(args); backup.restore_first_arg(args);
@ -593,6 +589,7 @@ impl Engine {
args, args,
is_ref, is_ref,
pub_only, pub_only,
pos,
def_val, def_val,
) )
} }
@ -600,7 +597,7 @@ impl Engine {
// Normal native function call // Normal native function call
_ => self.call_native_fn( _ => self.call_native_fn(
mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val, mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val,
), ),
} }
} }
@ -631,7 +628,6 @@ impl Engine {
} }
/// Evaluate a text string as a script - used primarily for 'eval'. /// Evaluate a text string as a script - used primarily for 'eval'.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
fn eval_script_expr( fn eval_script_expr(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -639,6 +635,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
script: &str, script: &str,
pos: Position,
_level: usize, _level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -652,7 +649,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _level > self.max_call_levels() { if _level > self.max_call_levels() {
return Err(Box::new(EvalAltResult::ErrorStackOverflow(Position::NONE))); return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
} }
// Compile the script text // Compile the script text
@ -678,7 +675,6 @@ impl Engine {
} }
/// Call a dot method. /// Call a dot method.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub(crate) fn make_method_call( pub(crate) fn make_method_call(
&self, &self,
@ -692,6 +688,7 @@ impl Engine {
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
native: bool, native: bool,
pub_only: bool, pub_only: bool,
pos: Position,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let is_ref = target.is_ref(); let is_ref = target.is_ref();
@ -722,7 +719,8 @@ impl Engine {
// 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(
mods, state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level, mods, state, lib, fn_name, hash, args, false, false, pub_only, pos, None, def_val,
level,
) )
} else if fn_name == KEYWORD_FN_PTR_CALL } else if fn_name == KEYWORD_FN_PTR_CALL
&& call_args.len() > 0 && call_args.len() > 0
@ -749,7 +747,8 @@ impl Engine {
// 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(
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, pos, None, def_val,
level,
) )
} else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() { } else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call // Curry call
@ -817,7 +816,8 @@ impl Engine {
let args = arg_values.as_mut(); let args = arg_values.as_mut();
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, pos, None, def_val,
level,
) )
}?; }?;
@ -830,7 +830,6 @@ impl Engine {
} }
/// Call a function in normal function-call style. /// Call a function in normal function-call style.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
pub(crate) fn make_function_call( pub(crate) fn make_function_call(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -844,6 +843,7 @@ impl Engine {
mut hash_script: u64, mut hash_script: u64,
native: bool, native: bool,
pub_only: bool, pub_only: bool,
pos: Position,
capture_scope: bool, capture_scope: bool,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
@ -962,9 +962,8 @@ impl Engine {
let script = script.as_str().map_err(|typ| { let script = script.as_str().map_err(|typ| {
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position()) self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
})?; })?;
let result = self let pos = args_expr[0].position();
.eval_script_expr(scope, mods, state, lib, script, level + 1) let result = self.eval_script_expr(scope, mods, state, lib, script, pos, level + 1);
.map_err(|err| err.fill_position(args_expr[0].position()));
// 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.
@ -1000,13 +999,12 @@ impl Engine {
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let (target, _, typ, 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])?;
let target = match typ { if target.as_ref().is_read_only() {
ScopeEntryType::Normal => target, target = target.into_owned();
ScopeEntryType::Constant => target.into_owned(), }
};
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
@ -1036,13 +1034,13 @@ impl Engine {
let args = args.as_mut(); let args = args.as_mut();
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, name, hash, args, is_ref, false, pub_only, capture, def_val, level, mods, state, lib, name, hash, args, is_ref, false, pub_only, pos, capture, def_val,
level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
} }
/// Call a namespace-qualified function in normal function-call style. /// Call a namespace-qualified function in normal function-call style.
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
pub(crate) fn make_qualified_function_call( pub(crate) fn make_qualified_function_call(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1055,6 +1053,7 @@ impl Engine {
args_expr: impl AsRef<[Expr]>, args_expr: impl AsRef<[Expr]>,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
hash_script: u64, hash_script: u64,
pos: Position,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let args_expr = args_expr.as_ref(); let args_expr = args_expr.as_ref();
@ -1087,7 +1086,7 @@ impl Engine {
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Get target reference to first argument // Get target reference to first argument
let (target, _, _, pos) = let (target, _, pos) =
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
self.inc_operations(state) self.inc_operations(state)
@ -1150,7 +1149,9 @@ impl Engine {
let args = args.as_mut(); let args = args.as_mut();
let new_scope = &mut Default::default(); let new_scope = &mut Default::default();
let fn_def = f.get_fn_def().clone(); let fn_def = f.get_fn_def().clone();
self.call_script_fn(new_scope, mods, state, lib, &mut None, &fn_def, args, level) self.call_script_fn(
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
)
} }
Some(f) if f.is_plugin_fn() => f Some(f) if f.is_plugin_fn() => f
.get_plugin_fn() .get_plugin_fn()
@ -1184,7 +1185,7 @@ impl Engine {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
), ),
Position::NONE, pos,
) )
.into(), .into(),
} }

View File

@ -164,6 +164,7 @@ impl<'e, 'a, 'm, 'pm> NativeCallContext<'e, 'a, 'm, 'pm> {
is_method, is_method,
is_method, is_method,
public_only, public_only,
Position::NONE,
None, None,
def_value, def_value,
0, 0,
@ -343,18 +344,32 @@ pub type FnPlugin = dyn PluginFunction;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnPlugin = dyn PluginFunction + Send + Sync; pub type FnPlugin = dyn PluginFunction + Send + Sync;
/// A standard callback function. /// A standard callback function for progress reporting.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + 'static>; pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + 'static>;
/// A standard callback function. /// A standard callback function for progress reporting.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + Send + Sync + 'static>; pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + Send + Sync + 'static>;
/// A standard callback function. /// A standard callback function for printing.
#[cfg(not(feature = "sync"))]
pub type OnPrintCallback = Box<dyn Fn(&str) + 'static>;
/// A standard callback function for printing.
#[cfg(feature = "sync")]
pub type OnPrintCallback = Box<dyn Fn(&str) + Send + Sync + 'static>;
/// A standard callback function for debugging.
#[cfg(not(feature = "sync"))]
pub type OnDebugCallback = Box<dyn Fn(&str, Position) + 'static>;
/// A standard callback function for debugging.
#[cfg(feature = "sync")]
pub type OnDebugCallback = Box<dyn Fn(&str, Position) + Send + Sync + 'static>;
/// A standard callback function for variable access.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnVarCallback = pub type OnVarCallback =
Box<dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static>; Box<dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static>;
/// A standard callback function. /// A standard callback function for variable access.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnVarCallback = Box< pub type OnVarCallback = Box<
dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>

View File

@ -115,7 +115,7 @@ pub type FLOAT = f64;
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
pub type FLOAT = f32; pub type FLOAT = f32;
pub use ast::{FnAccess, AST}; pub use ast::{FnAccess, ScriptFnMetadata, AST};
pub use dynamic::Dynamic; pub use dynamic::Dynamic;
pub use engine::{Engine, EvalContext}; pub use engine::{Engine, EvalContext};
pub use fn_native::{FnPtr, NativeCallContext, Shared}; pub use fn_native::{FnPtr, NativeCallContext, Shared};

View File

@ -120,9 +120,9 @@ impl FuncInfo {
#[derive(Clone)] #[derive(Clone)]
pub struct Module { pub struct Module {
/// Sub-modules. /// Sub-modules.
modules: HashMap<String, Shared<Module>>, modules: HashMap<ImmutableString, Shared<Module>>,
/// Module variables. /// Module variables.
variables: HashMap<String, Dynamic>, variables: HashMap<ImmutableString, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules. /// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>, all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions. /// External Rust functions.
@ -160,7 +160,7 @@ impl fmt::Debug for Module {
"Module(\n modules: {}\n vars: {}\n functions: {}\n)", "Module(\n modules: {}\n vars: {}\n functions: {}\n)",
self.modules self.modules
.keys() .keys()
.map(String::as_str) .map(|m| m.as_str())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "), .join(", "),
self.variables self.variables
@ -331,7 +331,11 @@ impl Module {
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn set_var(&mut self, name: impl Into<String>, value: impl Variant + Clone) -> &mut Self { pub fn set_var(
&mut self,
name: impl Into<ImmutableString>,
value: impl Variant + Clone,
) -> &mut Self {
self.variables.insert(name.into(), Dynamic::from(value)); self.variables.insert(name.into(), Dynamic::from(value));
self.indexed = false; self.indexed = false;
self self
@ -458,7 +462,7 @@ impl Module {
#[inline(always)] #[inline(always)]
pub fn set_sub_module( pub fn set_sub_module(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<ImmutableString>,
sub_module: impl Into<Shared<Module>>, sub_module: impl Into<Shared<Module>>,
) -> &mut Self { ) -> &mut Self {
self.modules.insert(name.into(), sub_module.into()); self.modules.insert(name.into(), sub_module.into());
@ -1423,7 +1427,7 @@ impl Module {
other.modules.iter().for_each(|(k, v)| { other.modules.iter().for_each(|(k, v)| {
let mut m = Self::new(); let mut m = Self::new();
m.merge_filtered(v, _filter); m.merge_filtered(v, _filter);
self.set_sub_module(k, m); self.set_sub_module(k.clone(), m);
}); });
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
self.modules self.modules
@ -1640,7 +1644,7 @@ impl Module {
// Create new module // Create new module
let mut module = Module::new(); let mut module = Module::new();
scope.into_iter().for_each(|(_, _, value, mut aliases)| { scope.into_iter().for_each(|(_, value, mut aliases)| {
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
if aliases.len() > 1 { if aliases.len() > 1 {
aliases.into_iter().for_each(|alias| { aliases.into_iter().for_each(|alias| {

View File

@ -1,6 +1,7 @@
//! Module implementing the [`AST`] optimizer. //! Module implementing the [`AST`] optimizer.
use crate::ast::{Expr, ScriptFnDef, Stmt}; use crate::ast::{Expr, ScriptFnDef, Stmt};
use crate::dynamic::AccessMode;
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::fn_call::run_builtin_binary_op; use crate::fn_call::run_builtin_binary_op;
use crate::parser::map_dynamic_to_expr; use crate::parser::map_dynamic_to_expr;
@ -58,7 +59,7 @@ struct State<'a> {
/// Has the [`AST`] been changed during this pass? /// Has the [`AST`] been changed during this pass?
changed: bool, changed: bool,
/// Collection of constants to use for eager function evaluations. /// Collection of constants to use for eager function evaluations.
constants: Vec<(String, Expr)>, variables: Vec<(String, AccessMode, Expr)>,
/// An [`Engine`] instance for eager function evaluation. /// An [`Engine`] instance for eager function evaluation.
engine: &'a Engine, engine: &'a Engine,
/// [Module] containing script-defined functions. /// [Module] containing script-defined functions.
@ -73,7 +74,7 @@ impl<'a> State<'a> {
pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self { pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self {
Self { Self {
changed: false, changed: false,
constants: vec![], variables: vec![],
engine, engine,
lib, lib,
optimization_level: level, optimization_level: level,
@ -94,27 +95,26 @@ impl<'a> State<'a> {
pub fn is_dirty(&self) -> bool { pub fn is_dirty(&self) -> bool {
self.changed self.changed
} }
/// Does a constant exist?
#[inline(always)]
pub fn contains_constant(&self, name: &str) -> bool {
self.constants.iter().any(|(n, _)| n == name)
}
/// Prune the list of constants back to a specified size. /// Prune the list of constants back to a specified size.
#[inline(always)] #[inline(always)]
pub fn restore_constants(&mut self, len: usize) { pub fn restore_var(&mut self, len: usize) {
self.constants.truncate(len) self.variables.truncate(len)
} }
/// Add a new constant to the list. /// Add a new constant to the list.
#[inline(always)] #[inline(always)]
pub fn push_constant(&mut self, name: &str, value: Expr) { pub fn push_var(&mut self, name: &str, access: AccessMode, value: Expr) {
self.constants.push((name.into(), value)) self.variables.push((name.into(), access, value))
} }
/// Look up a constant from the list. /// Look up a constant from the list.
#[inline] #[inline]
pub fn find_constant(&self, name: &str) -> Option<&Expr> { pub fn find_constant(&self, name: &str) -> Option<&Expr> {
for (n, expr) in self.constants.iter().rev() { for (n, access, expr) in self.variables.iter().rev() {
if n == name { if n == name {
return Some(expr); return if access.is_read_only() {
Some(expr)
} else {
None
};
} }
} }
@ -142,6 +142,7 @@ fn call_fn_with_constant_arguments(
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
true, true,
Position::NONE,
None, None,
) )
.ok() .ok()
@ -157,20 +158,24 @@ fn optimize_stmt_block(
count_promote_as_dirty: bool, count_promote_as_dirty: bool,
) -> Stmt { ) -> Stmt {
let orig_len = statements.len(); // Original number of statements in the block, for change detection let orig_len = statements.len(); // Original number of statements in the block, for change detection
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
// Optimize each statement in the block // Optimize each statement in the block
statements.iter_mut().for_each(|stmt| match stmt { statements.iter_mut().for_each(|stmt| match stmt {
// Add constant literals into the state // Add constant literals into the state
Stmt::Const(var_def, Some(expr), _, pos) if expr.is_constant() => { Stmt::Const(var_def, Some(expr), _, _) if expr.is_constant() => {
state.set_dirty(); state.push_var(&var_def.name, AccessMode::ReadOnly, mem::take(expr));
state.push_constant(&var_def.name, mem::take(expr));
*stmt = Stmt::Noop(*pos); // No need to keep constants
} }
Stmt::Const(var_def, None, _, pos) => { Stmt::Const(var_def, None, _, _) => {
state.set_dirty(); state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); }
*stmt = Stmt::Noop(*pos); // No need to keep constants // Add variables into the state
Stmt::Let(var_def, _, _, _) => {
state.push_var(
&var_def.name,
AccessMode::ReadWrite,
Expr::Unit(var_def.pos),
);
} }
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
@ -196,7 +201,9 @@ fn optimize_stmt_block(
while let Some(expr) = statements.pop() { while let Some(expr) = statements.pop() {
match expr { match expr {
Stmt::Let(_, expr, _, _) => removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true), Stmt::Let(_, expr, _, _) | Stmt::Const(_, expr, _, _) => {
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
}
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(expr, _, _) => removed = expr.is_pure(), Stmt::Import(expr, _, _) => removed = expr.is_pure(),
_ => { _ => {
@ -241,7 +248,7 @@ fn optimize_stmt_block(
} }
// Pop the stack and remove all the local constants // Pop the stack and remove all the local constants
state.restore_constants(orig_constants_len); state.restore_var(orig_constants_len);
match &statements[..] { match &statements[..] {
// No statements in block - change to No-op // No statements in block - change to No-op
@ -251,6 +258,8 @@ fn optimize_stmt_block(
} }
// Only one let statement - leave it alone // Only one let statement - leave it alone
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos), [x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos),
// Only one const statement - leave it alone
[x] if matches!(x, Stmt::Const(_, _, _, _)) => Stmt::Block(statements, pos),
// Only one import statement - leave it alone // Only one import statement - leave it alone
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos), [x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos),
@ -708,7 +717,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)), Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)),
// constant-name // constant-name
Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.3.name) => { Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.3.name).is_some() => {
state.set_dirty(); state.set_dirty();
// Replace constant with value // Replace constant with value
@ -742,22 +751,23 @@ fn optimize_top_level(
// Set up the state // Set up the state
let mut state = State::new(engine, lib, level); let mut state = State::new(engine, lib, level);
// Add constants from the scope that can be made into a literal into the state // Add constants and variables from the scope
scope scope.iter().for_each(|(name, constant, value)| {
.iter() if !constant {
.filter(|(_, typ, _)| *typ) state.push_var(name, AccessMode::ReadWrite, Expr::Unit(Position::NONE));
.for_each(|(name, _, value)| { } else if let Some(val) = map_dynamic_to_expr(value, Position::NONE) {
if let Some(val) = map_dynamic_to_expr(value, Position::NONE) { state.push_var(name, AccessMode::ReadOnly, val);
state.push_constant(name, val); } else {
state.push_var(name, AccessMode::ReadOnly, Expr::Unit(Position::NONE));
} }
}); });
let orig_constants_len = state.constants.len(); let orig_constants_len = state.variables.len();
// Optimization loop // Optimization loop
loop { loop {
state.reset(); state.reset();
state.restore_constants(orig_constants_len); state.restore_var(orig_constants_len);
let num_statements = statements.len(); let num_statements = statements.len();
@ -769,7 +779,7 @@ fn optimize_top_level(
optimize_expr(value_expr, &mut state); optimize_expr(value_expr, &mut state);
if value_expr.is_constant() { if value_expr.is_constant() {
state.push_constant(&var_def.name, value_expr.clone()); state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone());
} }
// Keep it in the global scope // Keep it in the global scope
@ -779,13 +789,20 @@ fn optimize_top_level(
} }
} }
Stmt::Const(var_def, None, _, _) => { Stmt::Const(var_def, None, _, _) => {
state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
}
Stmt::Let(var_def, _, _, _) => {
state.push_var(
&var_def.name,
AccessMode::ReadWrite,
Expr::Unit(var_def.pos),
);
} }
_ => { _ => {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
let keep = match stmt { let keep = match stmt {
Stmt::Let(_, _, _, _) => true, Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(_, _, _) => true, Stmt::Import(_, _, _) => true,
_ => i == num_statements - 1, _ => i == num_statements - 1,
@ -851,6 +868,7 @@ pub fn optimize_into_ast(
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: Default::default(), mods: Default::default(),
fn_comments: Default::default(),
}) })
.for_each(|fn_def| { .for_each(|fn_def| {
lib2.set_script_fn(fn_def); lib2.set_script_fn(fn_def);

View File

@ -172,11 +172,12 @@ mod print_debug_functions {
let len = map.len(); let len = map.len();
map.iter_mut().enumerate().for_each(|(i, (k, v))| { map.iter_mut().enumerate().for_each(|(i, (k, v))| {
result.push_str(&format!("{:?}: ", k)); result.push_str(&format!(
result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, v)); "{:?}: {}{}",
if i < len - 1 { k,
result.push_str(", "); &print_with_func(FUNC_TO_DEBUG, &ctx, v),
} if i < len - 1 { ", " } else { "" }
));
}); });
result.push_str("}"); result.push_str("}");

View File

@ -121,6 +121,10 @@ pub enum ParseErrorType {
Reserved(String), Reserved(String),
/// Missing an expression. Wrapped value is the expression type. /// Missing an expression. Wrapped value is the expression type.
ExprExpected(String), ExprExpected(String),
/// Defining a doc-comment in an appropriate place (e.g. not at global level).
///
/// Never appears under the `no_function` feature.
WrongDocComment,
/// Defining a function `fn` in an appropriate place (e.g. inside another function). /// Defining a function `fn` in an appropriate place (e.g. inside another function).
/// ///
/// Never appears under the `no_function` feature. /// Never appears under the `no_function` feature.
@ -189,6 +193,7 @@ impl ParseErrorType {
Self::FnMissingParams(_) => "Expecting parameters in function declaration", Self::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for function declaration", Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
Self::WrongDocComment => "Doc-comment must be followed immediately by a function definition",
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
Self::WrongExport => "Export statement can only appear at global level", Self::WrongExport => "Export statement can only appear at global level",
Self::AssignmentToConstant(_) => "Cannot assign to a constant value", Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
@ -243,7 +248,9 @@ impl fmt::Display for ParseErrorType {
Self::LiteralTooLarge(typ, max) => { Self::LiteralTooLarge(typ, max) => {
write!(f, "{} exceeds the maximum limit ({})", typ, max) write!(f, "{} exceeds the maximum limit ({})", typ, max)
} }
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s), Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
_ => f.write_str(self.desc()), _ => f.write_str(self.desc()),
} }
} }

View File

@ -3,12 +3,11 @@
use crate::ast::{ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt,
}; };
use crate::dynamic::Union; use crate::dynamic::{AccessMode, Union};
use crate::engine::{KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::engine::{KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
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::scope::EntryType as ScopeEntryType;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
@ -22,7 +21,7 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
use crate::syntax::CustomSyntax; use crate::syntax::CustomSyntax;
use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::token::{is_doc_comment, is_keyword_function, is_valid_identifier, Token, TokenStream};
use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::utils::{get_hasher, StraightHasherBuilder};
use crate::{ use crate::{
calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType,
@ -49,7 +48,7 @@ struct ParseState<'e> {
/// Interned strings. /// Interned strings.
strings: HashMap<String, ImmutableString>, strings: HashMap<String, ImmutableString>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(ImmutableString, ScopeEntryType)>, stack: Vec<(ImmutableString, AccessMode)>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
entry_stack_len: usize, entry_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
@ -827,7 +826,7 @@ fn parse_switch(
} }
} }
let mut table = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut table = HashMap::new();
let mut def_stmt = None; let mut def_stmt = None;
loop { loop {
@ -916,9 +915,12 @@ fn parse_switch(
} }
} }
let mut final_table = HashMap::with_capacity_and_hasher(table.len(), StraightHasherBuilder);
final_table.extend(table.into_iter());
Ok(Stmt::Switch( Ok(Stmt::Switch(
item, item,
Box::new((table, def_stmt)), Box::new((final_table, def_stmt)),
settings.pos, settings.pos,
)) ))
} }
@ -1291,11 +1293,11 @@ fn make_assignment_stmt<'a>(
}, },
) = x.as_ref(); ) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos))
} }
// Constant values cannot be assigned to // Constant values cannot be assigned to
ScopeEntryType::Constant => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos))
} }
} }
@ -1318,11 +1320,11 @@ fn make_assignment_stmt<'a>(
}, },
) = x.as_ref(); ) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos))
} }
// Constant values cannot be assigned to // Constant values cannot be assigned to
ScopeEntryType::Constant => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos))
} }
} }
@ -1811,7 +1813,7 @@ fn parse_custom_syntax(
// Variable searches stop at the first empty variable name. // Variable searches stop at the first empty variable name.
state.stack.resize( state.stack.resize(
state.stack.len() + delta as usize, state.stack.len() + delta as usize,
("".into(), ScopeEntryType::Normal), ("".into(), AccessMode::ReadWrite),
); );
} }
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(), delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(),
@ -2110,7 +2112,7 @@ fn parse_for(
let loop_var = state.get_interned_string(name.clone()); let loop_var = state.get_interned_string(name.clone());
let prev_stack_len = state.stack.len(); let prev_stack_len = state.stack.len();
state.stack.push((loop_var, ScopeEntryType::Normal)); state.stack.push((loop_var, AccessMode::ReadWrite));
settings.is_breakable = true; settings.is_breakable = true;
let body = parse_block(input, state, lib, settings.level_up())?; let body = parse_block(input, state, lib, settings.level_up())?;
@ -2125,7 +2127,7 @@ fn parse_let(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
var_type: ScopeEntryType, var_type: AccessMode,
export: bool, export: bool,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
@ -2156,17 +2158,17 @@ fn parse_let(
match var_type { match var_type {
// let name = expr // let name = expr
ScopeEntryType::Normal => { AccessMode::ReadWrite => {
let var_name = state.get_interned_string(name.clone()); let var_name = state.get_interned_string(name.clone());
state.stack.push((var_name, ScopeEntryType::Normal)); state.stack.push((var_name, AccessMode::ReadWrite));
let var_def = Ident::new(name, pos); let var_def = IdentX::new(name, pos);
Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos)) Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos))
} }
// const name = { expr:constant } // const name = { expr:constant }
ScopeEntryType::Constant => { AccessMode::ReadOnly => {
let var_name = state.get_interned_string(name.clone()); let var_name = state.get_interned_string(name.clone());
state.stack.push((var_name, ScopeEntryType::Constant)); state.stack.push((var_name, AccessMode::ReadOnly));
let var_def = Ident::new(name, pos); let var_def = IdentX::new(name, pos);
Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos)) Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos))
} }
} }
@ -2232,13 +2234,13 @@ fn parse_export(
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::Let, pos) => { (Token::Let, pos) => {
let pos = *pos; let pos = *pos;
let mut stmt = parse_let(input, state, lib, ScopeEntryType::Normal, true, settings)?; let mut stmt = parse_let(input, state, lib, AccessMode::ReadWrite, true, settings)?;
stmt.set_position(pos); stmt.set_position(pos);
return Ok(stmt); return Ok(stmt);
} }
(Token::Const, pos) => { (Token::Const, pos) => {
let pos = *pos; let pos = *pos;
let mut stmt = parse_let(input, state, lib, ScopeEntryType::Constant, true, settings)?; let mut stmt = parse_let(input, state, lib, AccessMode::ReadOnly, true, settings)?;
stmt.set_position(pos); stmt.set_position(pos);
return Ok(stmt); return Ok(stmt);
} }
@ -2393,7 +2395,38 @@ fn parse_stmt(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Option<Stmt>, ParseError> { ) -> Result<Option<Stmt>, ParseError> {
use ScopeEntryType::{Constant, Normal}; use AccessMode::{ReadOnly, ReadWrite};
let mut fn_comments: Vec<String> = Default::default();
let mut fn_comments_pos = Position::NONE;
// Handle doc-comment.
#[cfg(not(feature = "no_function"))]
while let (Token::Comment(ref comment), comment_pos) = input.peek().unwrap() {
if fn_comments_pos.is_none() {
fn_comments_pos = *comment_pos;
}
if !is_doc_comment(comment) {
unreachable!();
}
if !settings.is_global {
return Err(PERR::WrongDocComment.into_err(fn_comments_pos));
}
if let Token::Comment(comment) = input.next().unwrap().0 {
fn_comments.push(comment);
match input.peek().unwrap() {
(Token::Fn, _) | (Token::Private, _) => break,
(Token::Comment(_), _) => (),
_ => return Err(PERR::WrongDocComment.into_err(fn_comments_pos)),
}
} else {
unreachable!();
}
}
let (token, token_pos) = match input.peek().unwrap() { let (token, token_pos) = match input.peek().unwrap() {
(Token::EOF, pos) => return Ok(Some(Stmt::Noop(*pos))), (Token::EOF, pos) => return Ok(Some(Stmt::Noop(*pos))),
@ -2447,7 +2480,7 @@ fn parse_stmt(
pos: pos, pos: pos,
}; };
let func = parse_fn(input, &mut new_state, lib, access, settings)?; let func = parse_fn(input, &mut new_state, lib, access, settings, fn_comments)?;
// Qualifiers (none) + function name + number of arguments. // Qualifiers (none) + function name + number of arguments.
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()); let hash = calc_script_fn_hash(empty(), &func.name, func.params.len());
@ -2521,9 +2554,9 @@ fn parse_stmt(
Token::Try => parse_try_catch(input, state, lib, settings.level_up()).map(Some), Token::Try => parse_try_catch(input, state, lib, settings.level_up()).map(Some),
Token::Let => parse_let(input, state, lib, Normal, false, settings.level_up()).map(Some), Token::Let => parse_let(input, state, lib, ReadWrite, false, settings.level_up()).map(Some),
Token::Const => { Token::Const => {
parse_let(input, state, lib, Constant, false, settings.level_up()).map(Some) parse_let(input, state, lib, ReadOnly, false, settings.level_up()).map(Some)
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -2606,6 +2639,7 @@ fn parse_fn(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
access: FnAccess, access: FnAccess,
mut settings: ParseSettings, mut settings: ParseSettings,
fn_comments: Vec<String>,
) -> Result<ScriptFnDef, ParseError> { ) -> Result<ScriptFnDef, ParseError> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2637,7 +2671,7 @@ fn parse_fn(
return Err(PERR::FnDuplicatedParam(name, s).into_err(pos)); return Err(PERR::FnDuplicatedParam(name, s).into_err(pos));
} }
let s = state.get_interned_string(s); let s = state.get_interned_string(s);
state.stack.push((s.clone(), ScopeEntryType::Normal)); state.stack.push((s.clone(), AccessMode::ReadWrite));
params.push((s, pos)) params.push((s, pos))
} }
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
@ -2691,6 +2725,7 @@ fn parse_fn(
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: Default::default(), mods: Default::default(),
fn_comments,
}) })
} }
@ -2770,7 +2805,7 @@ fn parse_anon_fn(
return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos)); return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos));
} }
let s = state.get_interned_string(s); let s = state.get_interned_string(s);
state.stack.push((s.clone(), ScopeEntryType::Normal)); state.stack.push((s.clone(), AccessMode::ReadWrite));
params.push((s, pos)) params.push((s, pos))
} }
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
@ -2847,6 +2882,7 @@ fn parse_anon_fn(
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: Default::default(), mods: Default::default(),
fn_comments: Default::default(),
}; };
let expr = Expr::FnPointer(fn_name, settings.pos); let expr = Expr::FnPointer(fn_name, settings.pos);
@ -3005,15 +3041,15 @@ impl Engine {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
match value.0 { match value.0 {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => Some(Expr::FloatConstant(value, pos)), Union::Float(value, _) => Some(Expr::FloatConstant(value, pos)),
Union::Unit(_) => Some(Expr::Unit(pos)), Union::Unit(_, _) => Some(Expr::Unit(pos)),
Union::Int(value) => Some(Expr::IntegerConstant(value, pos)), Union::Int(value, _) => Some(Expr::IntegerConstant(value, pos)),
Union::Char(value) => Some(Expr::CharConstant(value, pos)), Union::Char(value, _) => Some(Expr::CharConstant(value, pos)),
Union::Str(value) => Some(Expr::StringConstant(value, pos)), Union::Str(value, _) => Some(Expr::StringConstant(value, pos)),
Union::Bool(value) => Some(Expr::BoolConstant(value, pos)), Union::Bool(value, _) => Some(Expr::BoolConstant(value, pos)),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(array) => { Union::Array(array, _) => {
let items: Vec<_> = array let items: Vec<_> = array
.into_iter() .into_iter()
.map(|x| map_dynamic_to_expr(x, pos)) .map(|x| map_dynamic_to_expr(x, pos))
@ -3029,7 +3065,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(map) => { Union::Map(map, _) => {
let items: Vec<_> = map let items: Vec<_> = map
.into_iter() .into_iter()
.map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos))) .map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos)))

View File

@ -1,28 +1,8 @@
//! Module that defines the [`Scope`] type representing a function call-stack scope. //! Module that defines the [`Scope`] type representing a function call-stack scope.
use crate::dynamic::Variant; use crate::dynamic::{AccessMode, Variant};
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec};
use crate::{Dynamic, StaticVec}; use crate::{Dynamic, ImmutableString, StaticVec};
/// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum EntryType {
/// Normal value.
Normal,
/// Immutable constant value.
Constant,
}
impl EntryType {
/// Is this entry constant?
#[inline(always)]
pub fn is_constant(&self) -> bool {
match self {
Self::Normal => false,
Self::Constant => true,
}
}
}
/// Type containing information about the current scope. /// Type containing information about the current scope.
/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. /// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs.
@ -58,28 +38,25 @@ impl EntryType {
// //
// # Implementation Notes // # Implementation Notes
// //
// [`Scope`] is implemented as three [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) // [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.)
// is manually split into three equal-length arrays. That's because variable names take up the most space, // is manually split into three equal-length arrays. That's because variable names take up the most space,
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up // with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up
// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether. // a variable's value. Variable lookup is usually via direct index, by-passing the name altogether.
// //
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed. // Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed.
// The variable type is packed separately into another array because it is even smaller. // The variable type is packed separately into another array because it is even smaller.
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct Scope<'a> { pub struct Scope<'a> {
/// Current value of the entry. /// Current value of the entry.
values: Vec<Dynamic>, values: Vec<Dynamic>,
/// Type of the entry.
types: Vec<EntryType>,
/// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely. /// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely.
names: Vec<(Cow<'a, str>, Box<StaticVec<String>>)>, names: Vec<(Cow<'a, str>, Box<StaticVec<ImmutableString>>)>,
} }
impl Default for Scope<'_> { impl Default for Scope<'_> {
fn default() -> Self { fn default() -> Self {
Self { Self {
values: Vec::with_capacity(16), values: Vec::with_capacity(16),
types: Vec::with_capacity(16),
names: Vec::with_capacity(16), names: Vec::with_capacity(16),
} }
} }
@ -124,7 +101,6 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
pub fn clear(&mut self) -> &mut Self { pub fn clear(&mut self) -> &mut Self {
self.names.clear(); self.names.clear();
self.types.clear();
self.values.clear(); self.values.clear();
self self
} }
@ -180,7 +156,7 @@ impl<'a> Scope<'a> {
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value)) self.push_dynamic_value(name, AccessMode::ReadWrite, Dynamic::from(value))
} }
/// Add (push) a new [`Dynamic`] entry to the [`Scope`]. /// Add (push) a new [`Dynamic`] entry to the [`Scope`].
/// ///
@ -196,7 +172,7 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push_dynamic(&mut self, name: impl Into<Cow<'a, str>>, value: Dynamic) -> &mut Self { pub fn push_dynamic(&mut self, name: impl Into<Cow<'a, str>>, value: Dynamic) -> &mut Self {
self.push_dynamic_value(name, EntryType::Normal, value) self.push_dynamic_value(name, value.access_mode(), value)
} }
/// Add (push) a new constant to the [`Scope`]. /// Add (push) a new constant to the [`Scope`].
/// ///
@ -219,7 +195,7 @@ impl<'a> Scope<'a> {
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
value: impl Variant + Clone, value: impl Variant + Clone,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value)) self.push_dynamic_value(name, AccessMode::ReadOnly, Dynamic::from(value))
} }
/// Add (push) a new constant with a [`Dynamic`] value to the Scope. /// Add (push) a new constant with a [`Dynamic`] value to the Scope.
/// ///
@ -242,18 +218,18 @@ impl<'a> Scope<'a> {
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
value: Dynamic, value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.push_dynamic_value(name, EntryType::Constant, value) self.push_dynamic_value(name, AccessMode::ReadOnly, value)
} }
/// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`]. /// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`].
#[inline] #[inline]
pub(crate) fn push_dynamic_value( pub(crate) fn push_dynamic_value(
&mut self, &mut self,
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
entry_type: EntryType, access: AccessMode,
value: Dynamic, mut value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.names.push((name.into(), Box::new(Default::default()))); self.names.push((name.into(), Box::new(Default::default())));
self.types.push(entry_type); value.set_access_mode(access);
self.values.push(value.into()); self.values.push(value.into());
self self
} }
@ -286,7 +262,6 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
pub fn rewind(&mut self, size: usize) -> &mut Self { pub fn rewind(&mut self, size: usize) -> &mut Self {
self.names.truncate(size); self.names.truncate(size);
self.types.truncate(size);
self.values.truncate(size); self.values.truncate(size);
self self
} }
@ -312,14 +287,14 @@ impl<'a> Scope<'a> {
} }
/// Find an entry in the [`Scope`], starting from the last. /// Find an entry in the [`Scope`], starting from the last.
#[inline(always)] #[inline(always)]
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> {
self.names self.names
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find_map(|(index, (key, _))| { .find_map(|(index, (key, _))| {
if name == key.as_ref() { if name == key.as_ref() {
Some((index, self.types[index])) Some((index, self.values[index].access_mode()))
} else { } else {
None None
} }
@ -374,8 +349,8 @@ impl<'a> Scope<'a> {
None => { None => {
self.push(name, value); self.push(name, value);
} }
Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name),
Some((index, EntryType::Normal)) => { Some((index, AccessMode::ReadWrite)) => {
*self.values.get_mut(index).unwrap() = Dynamic::from(value); *self.values.get_mut(index).unwrap() = Dynamic::from(value);
} }
} }
@ -383,19 +358,20 @@ impl<'a> Scope<'a> {
} }
/// Get a mutable reference to an entry in the [`Scope`]. /// Get a mutable reference to an entry in the [`Scope`].
#[inline(always)] #[inline(always)]
pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic {
( self.values.get_mut(index).expect("invalid index in Scope")
self.values.get_mut(index).expect("invalid index in Scope"),
self.types[index],
)
} }
/// Update the access type of an entry in the [`Scope`]. /// Update the access type of an entry in the [`Scope`].
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: String) -> &mut Self { pub(crate) fn add_entry_alias(
&mut self,
index: usize,
alias: impl Into<ImmutableString> + PartialEq<ImmutableString>,
) -> &mut Self {
let entry = self.names.get_mut(index).expect("invalid index in Scope"); let entry = self.names.get_mut(index).expect("invalid index in Scope");
if !entry.1.contains(&alias) { if !entry.1.iter().any(|a| &alias == a) {
entry.1.push(alias); entry.1.push(alias.into());
} }
self self
} }
@ -412,7 +388,6 @@ impl<'a> Scope<'a> {
.for_each(|(index, (name, alias))| { .for_each(|(index, (name, alias))| {
if !entries.names.iter().any(|(key, _)| key == name) { if !entries.names.iter().any(|(key, _)| key == name) {
entries.names.push((name.clone(), alias.clone())); entries.names.push((name.clone(), alias.clone()));
entries.types.push(self.types[index]);
entries.values.push(self.values[index].clone()); entries.values.push(self.values[index].clone());
} }
}); });
@ -424,11 +399,11 @@ impl<'a> Scope<'a> {
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn into_iter( pub(crate) fn into_iter(
self, self,
) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Vec<String>)> { ) -> impl Iterator<Item = (Cow<'a, str>, Dynamic, Vec<ImmutableString>)> {
self.names self.names
.into_iter() .into_iter()
.zip(self.types.into_iter().zip(self.values.into_iter())) .zip(self.values.into_iter())
.map(|((name, alias), (typ, value))| (name, typ, value, alias.to_vec())) .map(|((name, alias), value)| (name, value, alias.to_vec()))
} }
/// Get an iterator to entries in the [`Scope`]. /// Get an iterator to entries in the [`Scope`].
/// Shared values are flatten-cloned. /// Shared values are flatten-cloned.
@ -466,17 +441,16 @@ impl<'a> Scope<'a> {
pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator<Item = (&'a str, bool, &'x Dynamic)> + 'x { pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator<Item = (&'a str, bool, &'x Dynamic)> + 'x {
self.names self.names
.iter() .iter()
.zip(self.types.iter().zip(self.values.iter())) .zip(self.values.iter())
.map(|((name, _), (typ, value))| (name.as_ref(), typ.is_constant(), value)) .map(|((name, _), value)| (name.as_ref(), value.is_read_only(), value))
} }
} }
impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, Dynamic)> for Scope<'a> {
#[inline(always)] #[inline(always)]
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, typ, value)| { iter.into_iter().for_each(|(name, value)| {
self.names.push((name.into(), Box::new(Default::default()))); self.names.push((name.into(), Box::new(Default::default())));
self.types.push(typ);
self.values.push(value); self.values.push(value);
}); });
} }

View File

@ -132,39 +132,39 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> { fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
match &self.value.0 { match &self.value.0 {
Union::Unit(_) => self.deserialize_unit(visitor), Union::Unit(_, _) => self.deserialize_unit(visitor),
Union::Bool(_) => self.deserialize_bool(visitor), Union::Bool(_, _) => self.deserialize_bool(visitor),
Union::Str(_) => self.deserialize_str(visitor), Union::Str(_, _) => self.deserialize_str(visitor),
Union::Char(_) => self.deserialize_char(visitor), Union::Char(_, _) => self.deserialize_char(visitor),
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
Union::Int(_) => self.deserialize_i64(visitor), Union::Int(_, _) => self.deserialize_i64(visitor),
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
Union::Int(_) => self.deserialize_i32(visitor), Union::Int(_, _) => self.deserialize_i32(visitor),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_) => self.deserialize_f64(visitor), Union::Float(_, _) => self.deserialize_f64(visitor),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_) => self.deserialize_seq(visitor), Union::Array(_, _) => self.deserialize_seq(visitor),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => self.deserialize_map(visitor), Union::Map(_, _) => self.deserialize_map(visitor),
Union::FnPtr(_) => self.type_error(), Union::FnPtr(_, _) => self.type_error(),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => self.type_error(), Union::TimeStamp(_, _) => self.type_error(),
Union::Variant(value) if value.is::<i8>() => self.deserialize_i8(visitor), Union::Variant(value, _) if value.is::<i8>() => self.deserialize_i8(visitor),
Union::Variant(value) if value.is::<i16>() => self.deserialize_i16(visitor), Union::Variant(value, _) if value.is::<i16>() => self.deserialize_i16(visitor),
Union::Variant(value) if value.is::<i32>() => self.deserialize_i32(visitor), Union::Variant(value, _) if value.is::<i32>() => self.deserialize_i32(visitor),
Union::Variant(value) if value.is::<i64>() => self.deserialize_i64(visitor), Union::Variant(value, _) if value.is::<i64>() => self.deserialize_i64(visitor),
Union::Variant(value) if value.is::<i128>() => self.deserialize_i128(visitor), Union::Variant(value, _) if value.is::<i128>() => self.deserialize_i128(visitor),
Union::Variant(value) if value.is::<u8>() => self.deserialize_u8(visitor), Union::Variant(value, _) if value.is::<u8>() => self.deserialize_u8(visitor),
Union::Variant(value) if value.is::<u16>() => self.deserialize_u16(visitor), Union::Variant(value, _) if value.is::<u16>() => self.deserialize_u16(visitor),
Union::Variant(value) if value.is::<u32>() => self.deserialize_u32(visitor), Union::Variant(value, _) if value.is::<u32>() => self.deserialize_u32(visitor),
Union::Variant(value) if value.is::<u64>() => self.deserialize_u64(visitor), Union::Variant(value, _) if value.is::<u64>() => self.deserialize_u64(visitor),
Union::Variant(value) if value.is::<u128>() => self.deserialize_u128(visitor), Union::Variant(value, _) if value.is::<u128>() => self.deserialize_u128(visitor),
Union::Variant(_) => self.type_error(), Union::Variant(_, _) => self.type_error(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.type_error(), Union::Shared(_, _) => self.type_error(),
} }
} }

View File

@ -355,6 +355,9 @@ impl Token {
Custom(s) => s.clone().into(), Custom(s) => s.clone().into(),
LexError(err) => err.to_string().into(), LexError(err) => err.to_string().into(),
Comment(s) if is_doc_comment(s) => s[..3].to_string().into(),
Comment(s) => s[..2].to_string().into(),
token => match token { token => match token {
LeftBrace => "{", LeftBrace => "{",
RightBrace => "}", RightBrace => "}",
@ -917,36 +920,36 @@ fn eat_next(stream: &mut impl InputStream, pos: &mut Position) -> Option<char> {
/// Scan for a block comment until the end. /// Scan for a block comment until the end.
fn scan_comment( fn scan_comment(
stream: &mut impl InputStream, stream: &mut impl InputStream,
state: &mut TokenizeState, mut level: usize,
pos: &mut Position, pos: &mut Position,
comment: &mut String, comment: &mut String,
) { ) -> usize {
while let Some(c) = stream.get_next() { while let Some(c) = stream.get_next() {
pos.advance(); pos.advance();
if state.include_comments { if !comment.is_empty() {
comment.push(c); comment.push(c);
} }
match c { match c {
'/' => { '/' => {
if let Some(c2) = stream.get_next() { if let Some(c2) = stream.get_next() {
if state.include_comments { if !comment.is_empty() {
comment.push(c2); comment.push(c2);
} }
if c2 == '*' { if c2 == '*' {
state.comment_level += 1; level += 1;
} }
} }
pos.advance(); pos.advance();
} }
'*' => { '*' => {
if let Some(c2) = stream.get_next() { if let Some(c2) = stream.get_next() {
if state.include_comments { if !comment.is_empty() {
comment.push(c2); comment.push(c2);
} }
if c2 == '/' { if c2 == '/' {
state.comment_level -= 1; level -= 1;
} }
} }
pos.advance(); pos.advance();
@ -955,10 +958,12 @@ fn scan_comment(
_ => (), _ => (),
} }
if state.comment_level == 0 { if level == 0 {
break; break;
} }
} }
level
} }
/// _(INTERNALS)_ Get the next token from the `stream`. /// _(INTERNALS)_ Get the next token from the `stream`.
@ -1012,6 +1017,12 @@ fn is_binary_char(c: char) -> bool {
} }
} }
/// Test if the comment block is a doc-comment.
#[inline(always)]
pub fn is_doc_comment(comment: &str) -> bool {
comment.starts_with("///") || comment.starts_with("/**")
}
/// Get the next token. /// Get the next token.
fn get_next_token_inner( fn get_next_token_inner(
stream: &mut impl InputStream, stream: &mut impl InputStream,
@ -1022,9 +1033,9 @@ fn get_next_token_inner(
if state.comment_level > 0 { if state.comment_level > 0 {
let start_pos = *pos; let start_pos = *pos;
let mut comment = String::new(); let mut comment = String::new();
scan_comment(stream, state, pos, &mut comment); state.comment_level = scan_comment(stream, state.comment_level, pos, &mut comment);
if state.include_comments { if state.include_comments || is_doc_comment(&comment) {
return Some((Token::Comment(comment), start_pos)); return Some((Token::Comment(comment), start_pos));
} }
} }
@ -1271,10 +1282,10 @@ fn get_next_token_inner(
('/', '/') => { ('/', '/') => {
eat_next(stream, pos); eat_next(stream, pos);
let mut comment = if state.include_comments { let mut comment = match stream.peek_next().unwrap() {
"//".to_string() '/' => "///".to_string(),
} else { _ if state.include_comments => "//".to_string(),
String::new() _ => String::new(),
}; };
while let Some(c) = stream.get_next() { while let Some(c) = stream.get_next() {
@ -1283,13 +1294,13 @@ fn get_next_token_inner(
break; break;
} }
if state.include_comments { if !comment.is_empty() {
comment.push(c); comment.push(c);
} }
pos.advance(); pos.advance();
} }
if state.include_comments { if state.include_comments || is_doc_comment(&comment) {
return Some((Token::Comment(comment), start_pos)); return Some((Token::Comment(comment), start_pos));
} }
} }
@ -1298,14 +1309,14 @@ fn get_next_token_inner(
eat_next(stream, pos); eat_next(stream, pos);
let mut comment = if state.include_comments { let mut comment = match stream.peek_next().unwrap() {
"/*".to_string() '*' => "/**".to_string(),
} else { _ if state.include_comments => "/*".to_string(),
String::new() _ => String::new(),
}; };
scan_comment(stream, state, pos, &mut comment); state.comment_level = scan_comment(stream, state.comment_level, pos, &mut comment);
if state.include_comments { if state.include_comments || is_doc_comment(&comment) {
return Some((Token::Comment(comment), start_pos)); return Some((Token::Comment(comment), start_pos));
} }
} }

View File

@ -178,7 +178,10 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
r#" r#"
let x = [1, 2, 3]; let x = [1, 2, 3];
x.reduce(|sum, v, i| { if i == 0 { sum = 10 } sum + v * v }) x.reduce(|sum, v, i| {
if i == 0 { sum = 10 }
sum + v * v
})
"# "#
)?, )?,
24 24

View File

@ -6,7 +6,7 @@ fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|&count| { engine.on_progress(|count| {
if count % 100 == 0 { if count % 100 == 0 {
println!("{}", count); println!("{}", count);
} }
@ -34,7 +34,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|&count| { engine.on_progress(|count| {
if count % 100 == 0 { if count % 100 == 0 {
println!("{}", count); println!("{}", count);
} }
@ -90,7 +90,7 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|&count| { engine.on_progress(|count| {
if count % 100 == 0 { if count % 100 == 0 {
println!("{}", count); println!("{}", count);
} }
@ -117,7 +117,7 @@ fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_operations(500); engine.set_max_operations(500);
engine.on_progress(|&count| { engine.on_progress(|count| {
if count < 100 { if count < 100 {
None None
} else { } else {

View File

@ -53,15 +53,19 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Simple); engine.set_optimization_level(OptimizationLevel::Simple);
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } }")?; let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
assert!(format!("{:?}", ast).starts_with("AST([], Module(")); assert!(format!("{:?}", ast).starts_with(r#"AST { statements: [Block([Const(IdentX { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("if 1 == 2 { 42 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert!(format!("{:?}", ast).starts_with("AST([], Module(")); assert!(format!("{:?}", ast).starts_with("AST { statements: [], functions: Module("));
engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("abs(-42)")?;
assert!(format!("{:?}", ast).starts_with(r"AST { statements: [Expr(IntegerConstant(42, 1:1))]"));
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
#[test] #[test]
fn test_print() -> Result<(), Box<EvalAltResult>> { fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
let logbook = Arc::new(RwLock::new(Vec::<String>::new())); let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
// Redirect print/debug output to 'log' // Redirect print/debug output to 'log'
@ -13,16 +13,20 @@ fn test_print() -> Result<(), Box<EvalAltResult>> {
engine engine
.on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s))) .on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s)))
.on_debug(move |s| log2.write().unwrap().push(format!("DEBUG: {}", s))); .on_debug(move |s, pos| {
log2.write()
.unwrap()
.push(format!("DEBUG at {:?}: {}", pos, s))
});
// Evaluate script // Evaluate script
engine.eval::<()>("print(40 + 2)")?; engine.eval::<()>("print(40 + 2)")?;
engine.eval::<()>(r#"debug("hello!")"#)?; engine.eval::<()>(r#"let x = "hello!"; debug(x)"#)?;
// 'logbook' captures all the 'print' and 'debug' output // 'logbook' captures all the 'print' and 'debug' output
assert_eq!(logbook.read().unwrap().len(), 2); assert_eq!(logbook.read().unwrap().len(), 2);
assert_eq!(logbook.read().unwrap()[0], "entry: 42"); assert_eq!(logbook.read().unwrap()[0], "entry: 42");
assert_eq!(logbook.read().unwrap()[1], r#"DEBUG: "hello!""#); assert_eq!(logbook.read().unwrap()[1], r#"DEBUG at 1:19: "hello!""#);
for entry in logbook.read().unwrap().iter() { for entry in logbook.read().unwrap().iter() {
println!("{}", entry); println!("{}", entry);