Merge pull request #484 from schungx/master
Bug fixes and new features.
This commit is contained in:
commit
1633def76c
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- bug-fixes
|
- v1.1-fixes
|
||||||
pull_request: {}
|
pull_request: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -19,22 +19,22 @@ jobs:
|
|||||||
flags:
|
flags:
|
||||||
- ""
|
- ""
|
||||||
- "--features metadata,serde,internals"
|
- "--features metadata,serde,internals"
|
||||||
- "--features unchecked"
|
- "--features unchecked,serde,metadata,internals"
|
||||||
- "--features sync"
|
- "--features sync,serde,metadata,internals"
|
||||||
- "--features no_position"
|
- "--features no_position,serde,metadata,internals"
|
||||||
- "--features no_optimize"
|
- "--features no_optimize,serde,metadata,internals"
|
||||||
- "--features no_float"
|
- "--features no_float,serde,metadata,internals"
|
||||||
- "--features f32_float,serde,metadata,internals"
|
- "--features f32_float,serde,metadata,internals"
|
||||||
- "--features decimal"
|
- "--features decimal,serde,metadata,internals"
|
||||||
- "--features no_float,decimal"
|
- "--features no_float,decimal"
|
||||||
- "--tests --features only_i32,serde,metadata,internals"
|
- "--tests --features only_i32,serde,metadata,internals"
|
||||||
- "--features only_i64"
|
- "--features only_i64,serde,metadata,internals"
|
||||||
- "--features no_index"
|
- "--features no_index,serde,metadata,internals"
|
||||||
- "--features no_object"
|
- "--features no_object,serde,metadata,internals"
|
||||||
- "--features no_function"
|
- "--features no_function,serde,metadata,internals"
|
||||||
- "--features no_module"
|
- "--features no_module,serde,metadata,internals"
|
||||||
- "--features no_closure"
|
- "--features no_closure,serde,metadata,internals"
|
||||||
- "--features unicode-xid-ident"
|
- "--features unicode-xid-ident,serde,metadata,internals"
|
||||||
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked"
|
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked"
|
||||||
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
|
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
|
||||||
toolchain: [stable]
|
toolchain: [stable]
|
||||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@ -8,6 +8,7 @@ New features
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module.
|
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module.
|
||||||
|
* A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
@ -18,6 +19,7 @@ Enhancements
|
|||||||
* Array adds a `sort` method with no parameters which sorts homogeneous arrays of built-in comparable types (e.g. `INT`).
|
* Array adds a `sort` method with no parameters which sorts homogeneous arrays of built-in comparable types (e.g. `INT`).
|
||||||
* Inlining is disabled for error-path functions because errors are exceptional and scripts usually fail completely when an error is encountered.
|
* Inlining is disabled for error-path functions because errors are exceptional and scripts usually fail completely when an error is encountered.
|
||||||
* The `optimize` module is completely eliminated under `no_optimize`, which should yield smaller code size.
|
* The `optimize` module is completely eliminated under `no_optimize`, which should yield smaller code size.
|
||||||
|
* Add `NativeCallContext::position` to return the position of the function call.
|
||||||
|
|
||||||
Deprecated API's
|
Deprecated API's
|
||||||
----------------
|
----------------
|
||||||
@ -26,6 +28,37 @@ Deprecated API's
|
|||||||
* `From<EvalAltResult>` for `Result<T, Box<EvalAltResult>>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`.
|
* `From<EvalAltResult>` for `Result<T, Box<EvalAltResult>>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.1.3
|
||||||
|
=============
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Reverses a regression on string `+` operations.
|
||||||
|
* The global namespace is now searched before packages, which is the correct behavior.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.1.2
|
||||||
|
=============
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* `0.0` now prints correctly (used to print `0e0`).
|
||||||
|
* Unary operators are now properly recognized as an expression statement.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.1.1
|
||||||
|
=============
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Assignment to indexing expression with dot expressions inside no longer cause a compilation error.
|
||||||
|
* The `no_module` and `internals` features now work together without a compilation error.
|
||||||
|
* String literal operations (such as `"hello" + ", world"`) now optimizes correctly.
|
||||||
|
|
||||||
|
|
||||||
Version 1.1.0
|
Version 1.1.0
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ help: a struct with a similar name exists
|
|||||||
| ~~~~~
|
| ~~~~~
|
||||||
help: consider importing one of these items
|
help: consider importing one of these items
|
||||||
|
|
|
|
||||||
|
11 | use core::fmt::Pointer;
|
||||||
|
|
|
||||||
11 | use std::fmt::Pointer;
|
11 | use std::fmt::Pointer;
|
||||||
|
|
|
|
||||||
11 | use syn::__private::fmt::Pointer;
|
11 | use syn::__private::fmt::Pointer;
|
||||||
|
|
|
|
||||||
11 | use core::fmt::Pointer;
|
|
||||||
|
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
#[cfg(not(feature = "serde"))]
|
#[cfg(any(not(feature = "serde"), feature = "no_object"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("This example requires the 'serde' feature to run.");
|
println!("This example requires the 'serde' feature to run.");
|
||||||
println!("Try: cargo run --features serde --example serde");
|
println!("Try: cargo run --features serde --example serde");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
example::ser();
|
example::ser();
|
||||||
println!();
|
println!();
|
||||||
@ -12,6 +13,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
mod example {
|
mod example {
|
||||||
use rhai::serde::{from_dynamic, to_dynamic};
|
use rhai::serde::{from_dynamic, to_dynamic};
|
||||||
use rhai::{Dynamic, Engine, Map};
|
use rhai::{Dynamic, Engine, Map};
|
||||||
|
42
src/ast.rs
42
src/ast.rs
@ -227,11 +227,11 @@ impl AST {
|
|||||||
/// Create an empty [`AST`].
|
/// Create an empty [`AST`].
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
body: Default::default(),
|
body: StmtBlock::empty(),
|
||||||
functions: Default::default(),
|
functions: Module::new().into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
}
|
}
|
||||||
@ -399,11 +399,11 @@ impl AST {
|
|||||||
&self,
|
&self,
|
||||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut functions: Module = Default::default();
|
let mut functions = Module::new();
|
||||||
functions.merge_filtered(&self.functions, &filter);
|
functions.merge_filtered(&self.functions, &filter);
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
body: Default::default(),
|
body: StmtBlock::empty(),
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
@ -417,7 +417,7 @@ impl AST {
|
|||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
body: self.body.clone(),
|
body: self.body.clone(),
|
||||||
functions: Default::default(),
|
functions: Module::new().into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
}
|
}
|
||||||
@ -599,7 +599,7 @@ impl AST {
|
|||||||
}
|
}
|
||||||
(false, true) => body.clone(),
|
(false, true) => body.clone(),
|
||||||
(true, false) => other.body.clone(),
|
(true, false) => other.body.clone(),
|
||||||
(true, true) => Default::default(),
|
(true, true) => StmtBlock::empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = other.source.clone().or_else(|| self.source.clone());
|
let source = other.source.clone().or_else(|| self.source.clone());
|
||||||
@ -717,7 +717,7 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// Not available under `no_function`.
|
/// Not available under `no_function`.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[allow(dead_code)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &ScriptFnDef> {
|
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &ScriptFnDef> {
|
||||||
self.functions
|
self.functions
|
||||||
@ -740,13 +740,13 @@ impl AST {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_functions(&mut self) -> &mut Self {
|
pub fn clear_functions(&mut self) -> &mut Self {
|
||||||
self.functions = Default::default();
|
self.functions = Module::new().into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// 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) -> &mut Self {
|
pub fn clear_statements(&mut self) -> &mut Self {
|
||||||
self.body = Default::default();
|
self.body = StmtBlock::empty();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Recursively walk the [`AST`], including function bodies (if any).
|
/// Recursively walk the [`AST`], including function bodies (if any).
|
||||||
@ -755,7 +755,7 @@ impl AST {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||||
let path = &mut Default::default();
|
let path = &mut Vec::new();
|
||||||
|
|
||||||
for stmt in self.statements() {
|
for stmt in self.statements() {
|
||||||
if !stmt.walk(path, on_node) {
|
if !stmt.walk(path, on_node) {
|
||||||
@ -777,7 +777,7 @@ impl AST {
|
|||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||||
let path = &mut Default::default();
|
let path = &mut Vec::new();
|
||||||
|
|
||||||
for stmt in self.statements() {
|
for stmt in self.statements() {
|
||||||
if !stmt.walk(path, on_node) {
|
if !stmt.walk(path, on_node) {
|
||||||
@ -899,6 +899,12 @@ impl StmtBlock {
|
|||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
Self(statements, pos)
|
Self(statements, pos)
|
||||||
}
|
}
|
||||||
|
/// Create an empty [`StmtBlock`].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
/// Is this statements block empty?
|
/// Is this statements block empty?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -1205,7 +1211,7 @@ impl From<Stmt> for StmtBlock {
|
|||||||
fn from(stmt: Stmt) -> Self {
|
fn from(stmt: Stmt) -> Self {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
||||||
Stmt::Noop(pos) => Self(Default::default(), pos),
|
Stmt::Noop(pos) => Self(StaticVec::new(), pos),
|
||||||
_ => {
|
_ => {
|
||||||
let pos = stmt.position();
|
let pos = stmt.position();
|
||||||
Self(vec![stmt].into(), pos)
|
Self(vec![stmt].into(), pos)
|
||||||
@ -1565,7 +1571,7 @@ impl Stmt {
|
|||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct CustomExpr {
|
pub struct CustomExpr {
|
||||||
/// List of keywords.
|
/// List of keywords.
|
||||||
pub keywords: StaticVec<Expr>,
|
pub inputs: StaticVec<Expr>,
|
||||||
/// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement
|
/// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement
|
||||||
/// (e.g. introducing a new variable)?
|
/// (e.g. introducing a new variable)?
|
||||||
pub scope_may_be_changed: bool,
|
pub scope_may_be_changed: bool,
|
||||||
@ -1826,7 +1832,9 @@ impl<F: Float + fmt::Debug> fmt::Debug for FloatWrapper<F> {
|
|||||||
impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> {
|
impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let abs = self.0.abs();
|
let abs = self.0.abs();
|
||||||
if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|
if abs.fract().is_zero() {
|
||||||
|
f.write_str("0.0")
|
||||||
|
} else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|
||||||
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
|
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
|
||||||
{
|
{
|
||||||
write!(f, "{:e}", self.0)
|
write!(f, "{:e}", self.0)
|
||||||
@ -2004,7 +2012,7 @@ impl fmt::Debug for Expr {
|
|||||||
Self::Variable(i, _, x) => {
|
Self::Variable(i, _, x) => {
|
||||||
f.write_str("Variable(")?;
|
f.write_str("Variable(")?;
|
||||||
if let Some((_, ref namespace)) = x.1 {
|
if let Some((_, ref namespace)) = x.1 {
|
||||||
write!(f, "{}", namespace)?
|
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?
|
||||||
}
|
}
|
||||||
f.write_str(&x.2)?;
|
f.write_str(&x.2)?;
|
||||||
if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
||||||
@ -2367,7 +2375,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Custom(x, _) => {
|
Self::Custom(x, _) => {
|
||||||
for e in &x.keywords {
|
for e in &x.inputs {
|
||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ fn main() {
|
|||||||
.map_err(|err| err.into())
|
.map_err(|err| err.into())
|
||||||
.and_then(|mut ast| {
|
.and_then(|mut ast| {
|
||||||
ast.set_source(filename.to_string_lossy().to_string());
|
ast.set_source(filename.to_string_lossy().to_string());
|
||||||
Module::eval_ast_as_new(Default::default(), &ast, &engine)
|
Module::eval_ast_as_new(Scope::new(), &ast, &engine)
|
||||||
}) {
|
}) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let filename = filename.to_string_lossy();
|
let filename = filename.to_string_lossy();
|
||||||
@ -163,9 +163,9 @@ fn main() {
|
|||||||
|
|
||||||
// REPL loop
|
// REPL loop
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
let mut main_ast: AST = Default::default();
|
let mut main_ast = AST::empty();
|
||||||
let mut ast_u: AST = Default::default();
|
let mut ast_u = AST::empty();
|
||||||
let mut ast: AST = Default::default();
|
let mut ast = AST::empty();
|
||||||
|
|
||||||
'main_loop: loop {
|
'main_loop: loop {
|
||||||
print!("rhai-repl> ");
|
print!("rhai-repl> ");
|
||||||
|
@ -33,6 +33,8 @@ pub mod markers {
|
|||||||
pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$";
|
pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$";
|
||||||
/// Special marker for matching a boolean value.
|
/// Special marker for matching a boolean value.
|
||||||
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$";
|
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$";
|
||||||
|
/// Special marker for identifying the custom syntax variant.
|
||||||
|
pub const CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT: &str = "$$";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A general expression evaluation trait object.
|
/// A general expression evaluation trait object.
|
||||||
|
@ -283,14 +283,11 @@ enum DynamicWriteLockInner<'d, T: Clone> {
|
|||||||
/// A simple mutable reference to a non-shared value.
|
/// A simple mutable reference to a non-shared value.
|
||||||
Reference(&'d mut T),
|
Reference(&'d mut T),
|
||||||
|
|
||||||
/// A write guard to a shared [`RefCell`][std::cell::RefCell].
|
/// A write guard to a shared value.
|
||||||
|
///
|
||||||
|
/// Not available under `no_closure`.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
Guard(crate::fn_native::LockGuard<'d, Dynamic>),
|
||||||
Guard(std::cell::RefMut<'d, Dynamic>),
|
|
||||||
/// A write guard to a shared [`RwLock`][std::sync::RwLock].
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
Guard(std::sync::RwLockWriteGuard<'d, Dynamic>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {
|
impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {
|
||||||
@ -1593,10 +1590,7 @@ impl Dynamic {
|
|||||||
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"))]
|
let value = crate::fn_native::shared_write_lock(cell);
|
||||||
let value = cell.borrow_mut();
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
let value = cell.write().unwrap();
|
|
||||||
|
|
||||||
if (*value).type_id() != TypeId::of::<T>()
|
if (*value).type_id() != TypeId::of::<T>()
|
||||||
&& TypeId::of::<Dynamic>() != TypeId::of::<T>()
|
&& TypeId::of::<Dynamic>() != TypeId::of::<T>()
|
||||||
|
326
src/engine.rs
326
src/engine.rs
@ -40,7 +40,7 @@ use crate::ast::FnCallHashes;
|
|||||||
|
|
||||||
pub type Precedence = NonZeroU8;
|
pub type Precedence = NonZeroU8;
|
||||||
|
|
||||||
/// _(internals)_ A stack of imported [modules][Module].
|
/// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// # Volatile Data Structure
|
/// # Volatile Data Structure
|
||||||
@ -49,17 +49,38 @@ pub type Precedence = NonZeroU8;
|
|||||||
//
|
//
|
||||||
// # Implementation Notes
|
// # Implementation Notes
|
||||||
//
|
//
|
||||||
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
|
|
||||||
// the module name will live beyond the AST of the eval script text.
|
|
||||||
// The best we can do is a shared reference.
|
|
||||||
//
|
|
||||||
// This implementation splits the module names from the shared modules to improve data locality.
|
// This implementation splits the module names from the shared modules to improve data locality.
|
||||||
// Most usage will be looking up a particular key from the list and then getting the module that
|
// Most usage will be looking up a particular key from the list and then getting the module that
|
||||||
// corresponds to that key.
|
// corresponds to that key.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct Imports {
|
pub struct Imports {
|
||||||
|
/// Stack of module names.
|
||||||
|
//
|
||||||
|
// # Implementation Notes
|
||||||
|
//
|
||||||
|
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
|
||||||
|
// the module name will live beyond the AST of the eval script text.
|
||||||
keys: StaticVec<Identifier>,
|
keys: StaticVec<Identifier>,
|
||||||
|
/// Stack of imported modules.
|
||||||
modules: StaticVec<Shared<Module>>,
|
modules: StaticVec<Shared<Module>>,
|
||||||
|
/// Source of the current context.
|
||||||
|
pub source: Option<Identifier>,
|
||||||
|
/// Number of operations performed.
|
||||||
|
pub num_operations: u64,
|
||||||
|
/// Number of modules loaded.
|
||||||
|
pub num_modules: usize,
|
||||||
|
/// Function call hashes to FN_IDX_GET and FN_IDX_SET.
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
fn_hash_indexing: (u64, u64),
|
||||||
|
/// Embedded module resolver.
|
||||||
|
///
|
||||||
|
/// Not available under `no_module`.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||||
|
/// Cache of globally-defined constants.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
global_constants: Option<Shared<crate::Locked<BTreeMap<Identifier, Dynamic>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Imports {
|
impl Imports {
|
||||||
@ -70,6 +91,16 @@ impl Imports {
|
|||||||
Self {
|
Self {
|
||||||
keys: StaticVec::new(),
|
keys: StaticVec::new(),
|
||||||
modules: StaticVec::new(),
|
modules: StaticVec::new(),
|
||||||
|
source: None,
|
||||||
|
num_operations: 0,
|
||||||
|
num_modules: 0,
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
embedded_module_resolver: None,
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
fn_hash_indexing: (0, 0),
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
global_constants: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the length of this stack of imported [modules][Module].
|
/// Get the length of this stack of imported [modules][Module].
|
||||||
@ -175,6 +206,61 @@ impl Imports {
|
|||||||
.rev()
|
.rev()
|
||||||
.find_map(|m| m.get_qualified_iter(id))
|
.find_map(|m| m.get_qualified_iter(id))
|
||||||
}
|
}
|
||||||
|
/// Get a mutable reference to the cache of globally-defined constants.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn global_constants_mut<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
) -> Option<impl DerefMut<Target = BTreeMap<Identifier, Dynamic>> + 'a> {
|
||||||
|
if let Some(ref global_constants) = self.global_constants {
|
||||||
|
Some(crate::fn_native::shared_write_lock(global_constants))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Set a constant into the cache of globally-defined constants.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub(crate) fn set_global_constant(&mut self, name: &str, value: Dynamic) {
|
||||||
|
if self.global_constants.is_none() {
|
||||||
|
let dict: crate::Locked<_> = BTreeMap::new().into();
|
||||||
|
self.global_constants = Some(dict.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::fn_native::shared_write_lock(
|
||||||
|
self.global_constants
|
||||||
|
.as_mut()
|
||||||
|
.expect("`global_constants` is `Some`"),
|
||||||
|
)
|
||||||
|
.insert(name.into(), value);
|
||||||
|
}
|
||||||
|
/// Get the pre-calculated index getter hash.
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn fn_hash_idx_get(&mut self) -> u64 {
|
||||||
|
if self.fn_hash_indexing != (0, 0) {
|
||||||
|
self.fn_hash_indexing.0
|
||||||
|
} else {
|
||||||
|
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
|
||||||
|
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
|
||||||
|
self.fn_hash_indexing = (n1, n2);
|
||||||
|
n1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Get the pre-calculated index setter hash.
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn fn_hash_idx_set(&mut self) -> u64 {
|
||||||
|
if self.fn_hash_indexing != (0, 0) {
|
||||||
|
self.fn_hash_indexing.1
|
||||||
|
} else {
|
||||||
|
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
|
||||||
|
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
|
||||||
|
self.fn_hash_indexing = (n1, n2);
|
||||||
|
n2
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Imports {
|
impl IntoIterator for Imports {
|
||||||
@ -262,6 +348,7 @@ pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var";
|
|||||||
pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn";
|
pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn";
|
||||||
pub const KEYWORD_THIS: &str = "this";
|
pub const KEYWORD_THIS: &str = "this";
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub const KEYWORD_GLOBAL: &str = "global";
|
pub const KEYWORD_GLOBAL: &str = "global";
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub const FN_GET: &str = "get$";
|
pub const FN_GET: &str = "get$";
|
||||||
@ -660,10 +747,8 @@ pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
|
|||||||
/// # Volatile Data Structure
|
/// # Volatile Data Structure
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EvalState {
|
pub struct EvalState {
|
||||||
/// Source of the current context.
|
|
||||||
pub source: Option<Identifier>,
|
|
||||||
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup.
|
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup.
|
||||||
/// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement,
|
/// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement,
|
||||||
/// subsequent offsets may become mis-aligned.
|
/// subsequent offsets may become mis-aligned.
|
||||||
@ -672,18 +757,8 @@ pub struct EvalState {
|
|||||||
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
||||||
/// is one level higher, and so on.
|
/// is one level higher, and so on.
|
||||||
pub scope_level: usize,
|
pub scope_level: usize,
|
||||||
/// Number of operations performed.
|
|
||||||
pub num_operations: u64,
|
|
||||||
/// Number of modules loaded.
|
|
||||||
pub num_modules: usize,
|
|
||||||
/// Stack of function resolution caches.
|
/// Stack of function resolution caches.
|
||||||
fn_resolution_caches: StaticVec<FnResolutionCache>,
|
fn_resolution_caches: StaticVec<FnResolutionCache>,
|
||||||
/// Embedded module resolver.
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
|
||||||
/// Function call hashes to FN_IDX_GET and FN_IDX_SET
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
|
||||||
fn_hash_indexing: (u64, u64),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvalState {
|
impl EvalState {
|
||||||
@ -692,16 +767,9 @@ impl EvalState {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
|
||||||
always_search_scope: false,
|
always_search_scope: false,
|
||||||
scope_level: 0,
|
scope_level: 0,
|
||||||
num_operations: 0,
|
|
||||||
num_modules: 0,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
embedded_module_resolver: None,
|
|
||||||
fn_resolution_caches: StaticVec::new(),
|
fn_resolution_caches: StaticVec::new(),
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
|
||||||
fn_hash_indexing: (0, 0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the state currently at global (root) level?
|
/// Is the state currently at global (root) level?
|
||||||
@ -726,7 +794,7 @@ impl EvalState {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn push_fn_resolution_cache(&mut self) {
|
pub fn push_fn_resolution_cache(&mut self) {
|
||||||
self.fn_resolution_caches.push(Default::default());
|
self.fn_resolution_caches.push(BTreeMap::new());
|
||||||
}
|
}
|
||||||
/// Remove the current function resolution cache from the stack and make the last one current.
|
/// Remove the current function resolution cache from the stack and make the last one current.
|
||||||
///
|
///
|
||||||
@ -739,32 +807,6 @@ impl EvalState {
|
|||||||
.pop()
|
.pop()
|
||||||
.expect("at least one function resolution cache");
|
.expect("at least one function resolution cache");
|
||||||
}
|
}
|
||||||
/// Get the pre-calculated index getter hash.
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
|
||||||
#[must_use]
|
|
||||||
pub fn fn_hash_idx_get(&mut self) -> u64 {
|
|
||||||
if self.fn_hash_indexing != (0, 0) {
|
|
||||||
self.fn_hash_indexing.0
|
|
||||||
} else {
|
|
||||||
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
|
|
||||||
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
|
|
||||||
self.fn_hash_indexing = (n1, n2);
|
|
||||||
n1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Get the pre-calculated index setter hash.
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
|
||||||
#[must_use]
|
|
||||||
pub fn fn_hash_idx_set(&mut self) -> u64 {
|
|
||||||
if self.fn_hash_indexing != (0, 0) {
|
|
||||||
self.fn_hash_indexing.1
|
|
||||||
} else {
|
|
||||||
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
|
|
||||||
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
|
|
||||||
self.fn_hash_indexing = (n1, n2);
|
|
||||||
n2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ A type containing all the limits imposed by the [`Engine`].
|
/// _(internals)_ A type containing all the limits imposed by the [`Engine`].
|
||||||
@ -814,8 +856,8 @@ pub struct Limits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
impl Default for Limits {
|
impl Limits {
|
||||||
fn default() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
@ -857,7 +899,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn source(&self) -> Option<&str> {
|
pub fn source(&self) -> Option<&str> {
|
||||||
self.state.source.as_ref().map(|s| s.as_str())
|
self.mods.source.as_ref().map(|s| s.as_str())
|
||||||
}
|
}
|
||||||
/// The current [`Scope`].
|
/// The current [`Scope`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -1080,17 +1122,17 @@ impl Engine {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new_raw() -> Self {
|
pub fn new_raw() -> Self {
|
||||||
let mut engine = Self {
|
let mut engine = Self {
|
||||||
global_modules: Default::default(),
|
global_modules: StaticVec::new(),
|
||||||
global_sub_modules: Default::default(),
|
global_sub_modules: BTreeMap::new(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: Default::default(),
|
type_names: BTreeMap::new(),
|
||||||
empty_string: Default::default(),
|
empty_string: ImmutableString::new(),
|
||||||
disabled_symbols: Default::default(),
|
disabled_symbols: BTreeSet::new(),
|
||||||
custom_keywords: Default::default(),
|
custom_keywords: BTreeMap::new(),
|
||||||
custom_syntax: Default::default(),
|
custom_syntax: BTreeMap::new(),
|
||||||
|
|
||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
token_mapper: None,
|
token_mapper: None,
|
||||||
@ -1105,7 +1147,7 @@ impl Engine {
|
|||||||
optimization_level: Default::default(),
|
optimization_level: Default::default(),
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
limits: Default::default(),
|
limits: Limits::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the global namespace module
|
// Add the global namespace module
|
||||||
@ -1169,32 +1211,74 @@ impl Engine {
|
|||||||
Expr::Variable(Some(_), _, _) => {
|
Expr::Variable(Some(_), _, _) => {
|
||||||
self.search_scope_only(scope, mods, state, lib, this_ptr, expr)
|
self.search_scope_only(scope, mods, state, lib, this_ptr, expr)
|
||||||
}
|
}
|
||||||
Expr::Variable(None, var_pos, v) => match v.as_ref() {
|
Expr::Variable(None, _var_pos, v) => match v.as_ref() {
|
||||||
// Normal variable access
|
// Normal variable access
|
||||||
(_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
|
(_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
|
||||||
// Qualified variable
|
|
||||||
|
// Qualified variable access
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
(_, Some((namespace, hash_var)), var_name) => {
|
(_, Some((namespace, hash_var)), var_name) => {
|
||||||
let module = self.search_imports(mods, state, namespace).ok_or_else(|| {
|
if let Some(module) = self.search_imports(mods, state, namespace) {
|
||||||
EvalAltResult::ErrorModuleNotFound(
|
// foo:bar::baz::VARIABLE
|
||||||
namespace[0].name.to_string(),
|
return match module.get_qualified_var(*hash_var) {
|
||||||
|
Ok(target) => {
|
||||||
|
let mut target = target.clone();
|
||||||
|
// Module variables are constant
|
||||||
|
target.set_access_mode(AccessMode::ReadOnly);
|
||||||
|
Ok((target.into(), *_var_pos))
|
||||||
|
}
|
||||||
|
Err(err) => Err(match *err {
|
||||||
|
EvalAltResult::ErrorVariableNotFound(_, _) => {
|
||||||
|
EvalAltResult::ErrorVariableNotFound(
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
namespace,
|
||||||
|
Token::DoubleColon.literal_syntax(),
|
||||||
|
var_name
|
||||||
|
),
|
||||||
|
namespace[0].pos,
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
_ => err.fill_position(*_var_pos),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
if namespace.len() == 1 && namespace[0].name == KEYWORD_GLOBAL {
|
||||||
|
// global::VARIABLE
|
||||||
|
let global_constants = mods.global_constants_mut();
|
||||||
|
|
||||||
|
if let Some(mut guard) = global_constants {
|
||||||
|
if let Some(value) = guard.get_mut(var_name) {
|
||||||
|
let mut target: Target = value.clone().into();
|
||||||
|
// Module variables are constant
|
||||||
|
target.set_access_mode(AccessMode::ReadOnly);
|
||||||
|
return Ok((target.into(), *_var_pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(EvalAltResult::ErrorVariableNotFound(
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
namespace,
|
||||||
|
Token::DoubleColon.literal_syntax(),
|
||||||
|
var_name
|
||||||
|
),
|
||||||
namespace[0].pos,
|
namespace[0].pos,
|
||||||
)
|
)
|
||||||
})?;
|
.into());
|
||||||
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
|
}
|
||||||
match *err {
|
|
||||||
EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => {
|
|
||||||
*err_name = format!("{}{}", namespace, var_name);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
err.fill_position(*var_pos)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Module variables are constant
|
Err(
|
||||||
let mut target = target.clone();
|
EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos)
|
||||||
target.set_access_mode(AccessMode::ReadOnly);
|
.into(),
|
||||||
Ok((target.into(), *var_pos))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
(_, Some((_, _)), _) => unreachable!("qualified access under no_module"),
|
||||||
},
|
},
|
||||||
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
||||||
}
|
}
|
||||||
@ -1335,7 +1419,7 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(mut new_val) = try_setter {
|
if let Some(mut new_val) = try_setter {
|
||||||
// Try to call index setter if value is changed
|
// Try to call index setter if value is changed
|
||||||
let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set());
|
let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set());
|
||||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
||||||
|
|
||||||
if let Err(err) = self.exec_fn_call(
|
if let Err(err) = self.exec_fn_call(
|
||||||
@ -1382,7 +1466,7 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(mut new_val) = try_setter {
|
if let Some(mut new_val) = try_setter {
|
||||||
// Try to call index setter
|
// Try to call index setter
|
||||||
let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set());
|
let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set());
|
||||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -1513,7 +1597,7 @@ impl Engine {
|
|||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
EvalAltResult::ErrorDotExpr(_, _) => {
|
EvalAltResult::ErrorDotExpr(_, _) => {
|
||||||
let args = &mut [target, &mut name.into(), &mut new_val];
|
let args = &mut [target, &mut name.into(), &mut new_val];
|
||||||
let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set());
|
let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set());
|
||||||
let pos = Position::NONE;
|
let pos = Position::NONE;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -1609,7 +1693,7 @@ impl Engine {
|
|||||||
let rhs_chain = match_chaining_type(rhs);
|
let rhs_chain = match_chaining_type(rhs);
|
||||||
let hash_get = FnCallHashes::from_native(*hash_get);
|
let hash_get = FnCallHashes::from_native(*hash_get);
|
||||||
let hash_set = FnCallHashes::from_native(*hash_set);
|
let hash_set = FnCallHashes::from_native(*hash_set);
|
||||||
let mut arg_values = [target.as_mut(), &mut Default::default()];
|
let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()];
|
||||||
let args = &mut arg_values[..1];
|
let args = &mut arg_values[..1];
|
||||||
|
|
||||||
// Assume getters are always pure
|
// Assume getters are always pure
|
||||||
@ -1672,7 +1756,7 @@ impl Engine {
|
|||||||
let args =
|
let args =
|
||||||
&mut [target.as_mut(), &mut name.into(), val];
|
&mut [target.as_mut(), &mut name.into(), val];
|
||||||
let hash_set = FnCallHashes::from_native(
|
let hash_set = FnCallHashes::from_native(
|
||||||
state.fn_hash_idx_set(),
|
mods.fn_hash_idx_set(),
|
||||||
);
|
);
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, FN_IDX_SET, hash_set, args,
|
mods, state, lib, FN_IDX_SET, hash_set, args,
|
||||||
@ -1751,7 +1835,7 @@ impl Engine {
|
|||||||
_ => unreachable!("index or dot chain expected, but gets {:?}", expr),
|
_ => unreachable!("index or dot chain expected, but gets {:?}", expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
let idx_values = &mut Default::default();
|
let idx_values = &mut StaticVec::new();
|
||||||
|
|
||||||
self.eval_dot_index_chain_arguments(
|
self.eval_dot_index_chain_arguments(
|
||||||
scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level,
|
scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level,
|
||||||
@ -1761,7 +1845,7 @@ impl Engine {
|
|||||||
// id.??? or id[???]
|
// id.??? or id[???]
|
||||||
Expr::Variable(_, var_pos, x) => {
|
Expr::Variable(_, var_pos, x) => {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, *var_pos)?;
|
self.inc_operations(&mut mods.num_operations, *var_pos)?;
|
||||||
|
|
||||||
let (mut target, _) =
|
let (mut target, _) =
|
||||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||||
@ -1812,7 +1896,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, expr.position())?;
|
self.inc_operations(&mut mods.num_operations, expr.position())?;
|
||||||
|
|
||||||
let _parent_chain_type = parent_chain_type;
|
let _parent_chain_type = parent_chain_type;
|
||||||
|
|
||||||
@ -1938,7 +2022,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Target<'t>, Box<EvalAltResult>> {
|
) -> Result<Target<'t>, Box<EvalAltResult>> {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, Position::NONE)?;
|
self.inc_operations(&mut mods.num_operations, Position::NONE)?;
|
||||||
|
|
||||||
let mut idx = idx;
|
let mut idx = idx;
|
||||||
let _add_if_not_found = add_if_not_found;
|
let _add_if_not_found = add_if_not_found;
|
||||||
@ -1992,7 +2076,7 @@ impl Engine {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
if _add_if_not_found && !map.contains_key(index.as_str()) {
|
if _add_if_not_found && !map.contains_key(index.as_str()) {
|
||||||
map.insert(index.clone().into(), Default::default());
|
map.insert(index.clone().into(), Dynamic::UNIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(map
|
Ok(map
|
||||||
@ -2078,7 +2162,7 @@ impl Engine {
|
|||||||
|
|
||||||
_ if use_indexers => {
|
_ if use_indexers => {
|
||||||
let args = &mut [target, &mut idx];
|
let args = &mut [target, &mut idx];
|
||||||
let hash_get = FnCallHashes::from_native(state.fn_hash_idx_get());
|
let hash_get = FnCallHashes::from_native(mods.fn_hash_idx_get());
|
||||||
let idx_pos = Position::NONE;
|
let idx_pos = Position::NONE;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -2111,7 +2195,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, expr.position())?;
|
self.inc_operations(&mut mods.num_operations, expr.position())?;
|
||||||
|
|
||||||
let result = match expr {
|
let result = match expr {
|
||||||
Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()),
|
Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()),
|
||||||
@ -2271,7 +2355,7 @@ impl Engine {
|
|||||||
Expr::Unit(_) => Ok(Dynamic::UNIT),
|
Expr::Unit(_) => Ok(Dynamic::UNIT),
|
||||||
|
|
||||||
Expr::Custom(custom, _) => {
|
Expr::Custom(custom, _) => {
|
||||||
let expressions: StaticVec<_> = custom.keywords.iter().map(Into::into).collect();
|
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
||||||
let key_token = custom
|
let key_token = custom
|
||||||
.tokens
|
.tokens
|
||||||
.first()
|
.first()
|
||||||
@ -2463,7 +2547,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, stmt.position())?;
|
self.inc_operations(&mut mods.num_operations, stmt.position())?;
|
||||||
|
|
||||||
let result = match stmt {
|
let result = match stmt {
|
||||||
// No-op
|
// No-op
|
||||||
@ -2496,7 +2580,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, pos)?;
|
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||||
|
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
mods,
|
mods,
|
||||||
@ -2649,7 +2733,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, body.position())?;
|
self.inc_operations(&mut mods.num_operations, body.position())?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2737,7 +2821,6 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
scope.push(unsafe_cast_var_name_to_lifetime(name), ());
|
scope.push(unsafe_cast_var_name_to_lifetime(name), ());
|
||||||
let index = scope.len() - 1;
|
let index = scope.len() - 1;
|
||||||
state.scope_level += 1;
|
|
||||||
|
|
||||||
for (x, iter_value) in func(iter_obj).enumerate() {
|
for (x, iter_value) in func(iter_obj).enumerate() {
|
||||||
// Increment counter
|
// Increment counter
|
||||||
@ -2774,7 +2857,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, statements.position())?;
|
self.inc_operations(&mut mods.num_operations, statements.position())?;
|
||||||
|
|
||||||
if statements.is_empty() {
|
if statements.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@ -2794,7 +2877,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.scope_level -= 1;
|
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
@ -2869,7 +2951,7 @@ impl Engine {
|
|||||||
|
|
||||||
err_map.insert("message".into(), err.to_string().into());
|
err_map.insert("message".into(), err.to_string().into());
|
||||||
|
|
||||||
if let Some(ref source) = state.source {
|
if let Some(ref source) = mods.source {
|
||||||
err_map.insert("source".into(), source.as_str().into());
|
err_map.insert("source".into(), source.as_str().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2897,7 +2979,6 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let orig_scope_len = scope.len();
|
let orig_scope_len = scope.len();
|
||||||
state.scope_level += 1;
|
|
||||||
|
|
||||||
err_var.as_ref().map(|Ident { name, .. }| {
|
err_var.as_ref().map(|Ident { name, .. }| {
|
||||||
scope.push(unsafe_cast_var_name_to_lifetime(name), err_value)
|
scope.push(unsafe_cast_var_name_to_lifetime(name), err_value)
|
||||||
@ -2907,7 +2988,6 @@ impl Engine {
|
|||||||
scope, mods, state, lib, this_ptr, catch_stmt, true, level,
|
scope, mods, state, lib, this_ptr, catch_stmt, true, level,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.scope_level -= 1;
|
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@ -2967,25 +3047,9 @@ impl Engine {
|
|||||||
|
|
||||||
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
|
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
|
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
|
||||||
let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) {
|
mods.set_global_constant(name, value.clone());
|
||||||
match mods.get_mut(index).expect("index is valid") {
|
|
||||||
m if m.internal => Some(m),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Create automatic global module
|
|
||||||
let mut global = Module::new();
|
|
||||||
global.internal = true;
|
|
||||||
mods.push(KEYWORD_GLOBAL, global);
|
|
||||||
Some(mods.get_mut(mods.len() - 1).expect("global module exists"))
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(global) = global {
|
|
||||||
Shared::get_mut(global)
|
|
||||||
.expect("global module is not shared")
|
|
||||||
.set_var(name.clone(), value.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -3011,7 +3075,7 @@ impl Engine {
|
|||||||
Stmt::Import(expr, export, _pos) => {
|
Stmt::Import(expr, export, _pos) => {
|
||||||
// Guard against too many modules
|
// Guard against too many modules
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if state.num_modules >= self.max_modules() {
|
if mods.num_modules >= self.max_modules() {
|
||||||
return Err(EvalAltResult::ErrorTooManyModules(*_pos).into());
|
return Err(EvalAltResult::ErrorTooManyModules(*_pos).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3021,10 +3085,10 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
use crate::ModuleResolver;
|
use crate::ModuleResolver;
|
||||||
|
|
||||||
let source = state.source.as_ref().map(|s| s.as_str());
|
let source = mods.source.as_ref().map(|s| s.as_str());
|
||||||
let path_pos = expr.position();
|
let path_pos = expr.position();
|
||||||
|
|
||||||
let module = state
|
let module = mods
|
||||||
.embedded_module_resolver
|
.embedded_module_resolver
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|r| match r.resolve(self, source, &path, path_pos) {
|
.and_then(|r| match r.resolve(self, source, &path, path_pos) {
|
||||||
@ -3058,7 +3122,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.num_modules += 1;
|
mods.num_modules += 1;
|
||||||
|
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
@ -3238,19 +3302,19 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub(crate) fn inc_operations(
|
pub(crate) fn inc_operations(
|
||||||
&self,
|
&self,
|
||||||
state: &mut EvalState,
|
num_operations: &mut u64,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
state.num_operations += 1;
|
*num_operations += 1;
|
||||||
|
|
||||||
// Guard against too many operations
|
// Guard against too many operations
|
||||||
if self.max_operations() > 0 && state.num_operations > self.max_operations() {
|
if self.max_operations() > 0 && *num_operations > self.max_operations() {
|
||||||
return Err(EvalAltResult::ErrorTooManyOperations(pos).into());
|
return Err(EvalAltResult::ErrorTooManyOperations(pos).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report progress - only in steps
|
// Report progress - only in steps
|
||||||
if let Some(ref progress) = self.progress {
|
if let Some(ref progress) = self.progress {
|
||||||
if let Some(token) = progress(state.num_operations) {
|
if let Some(token) = progress(*num_operations) {
|
||||||
// Terminate script if progress returns a termination token
|
// Terminate script if progress returns a termination token
|
||||||
return Err(EvalAltResult::ErrorTerminated(token, pos).into());
|
return Err(EvalAltResult::ErrorTerminated(token, pos).into());
|
||||||
}
|
}
|
||||||
|
@ -26,16 +26,16 @@ impl Engine {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn global_namespace(&self) -> &Module {
|
pub(crate) fn global_namespace(&self) -> &Module {
|
||||||
self.global_modules
|
self.global_modules
|
||||||
.last()
|
.first()
|
||||||
.expect("global_modules contains at least one module")
|
.expect("global_modules contains at least one module")
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the global namespace module
|
/// Get a mutable reference to the global namespace module
|
||||||
/// (which is the last module in `global_modules`).
|
/// (which is the first module in `global_modules`).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
|
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
|
||||||
Shared::get_mut(
|
Shared::get_mut(
|
||||||
self.global_modules
|
self.global_modules
|
||||||
.last_mut()
|
.first_mut()
|
||||||
.expect("global_modules contains at least one module"),
|
.expect("global_modules contains at least one module"),
|
||||||
)
|
)
|
||||||
.expect("global namespace module is never shared")
|
.expect("global namespace module is never shared")
|
||||||
@ -894,8 +894,9 @@ impl Engine {
|
|||||||
/// modules are searched in reverse order.
|
/// modules are searched in reverse order.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn register_global_module(&mut self, module: Shared<Module>) -> &mut Self {
|
pub fn register_global_module(&mut self, module: Shared<Module>) -> &mut Self {
|
||||||
// Insert the module into the front
|
// Insert the module into the front.
|
||||||
self.global_modules.insert(0, module);
|
// The first module is always the global namespace.
|
||||||
|
self.global_modules.insert(1, module);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Register a shared [`Module`] as a static module namespace with the [`Engine`].
|
/// Register a shared [`Module`] as a static module namespace with the [`Engine`].
|
||||||
@ -1001,7 +1002,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
|
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
|
||||||
self.compile_with_scope(&Default::default(), script)
|
self.compile_with_scope(&Scope::new(), script)
|
||||||
}
|
}
|
||||||
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
|
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
|
||||||
///
|
///
|
||||||
@ -1090,7 +1091,7 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(ref module_resolver) = self.module_resolver {
|
if let Some(ref module_resolver) = self.module_resolver {
|
||||||
let mut resolver = StaticModuleResolver::new();
|
let mut resolver = StaticModuleResolver::new();
|
||||||
let mut imports = Default::default();
|
let mut imports = BTreeSet::new();
|
||||||
|
|
||||||
collect_imports(&ast, &resolver, &mut imports);
|
collect_imports(&ast, &resolver, &mut imports);
|
||||||
|
|
||||||
@ -1255,7 +1256,7 @@ impl Engine {
|
|||||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn compile_file(&self, path: std::path::PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
pub fn compile_file(&self, path: std::path::PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
||||||
self.compile_file_with_scope(&Default::default(), path)
|
self.compile_file_with_scope(&Scope::new(), path)
|
||||||
}
|
}
|
||||||
/// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
|
/// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
|
||||||
///
|
///
|
||||||
@ -1423,7 +1424,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
|
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
|
||||||
self.compile_expression_with_scope(&Default::default(), script)
|
self.compile_expression_with_scope(&Scope::new(), script)
|
||||||
}
|
}
|
||||||
/// Compile a string containing an expression into an [`AST`] using own scope,
|
/// Compile a string containing an expression into an [`AST`] using own scope,
|
||||||
/// which can be used later for evaluation.
|
/// which can be used later for evaluation.
|
||||||
@ -1557,7 +1558,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
|
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
|
||||||
self.eval_with_scope(&mut Default::default(), script)
|
self.eval_with_scope(&mut Scope::new(), script)
|
||||||
}
|
}
|
||||||
/// Evaluate a string with own scope.
|
/// Evaluate a string with own scope.
|
||||||
///
|
///
|
||||||
@ -1614,7 +1615,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
self.eval_expression_with_scope(&mut Default::default(), script)
|
self.eval_expression_with_scope(&mut Scope::new(), script)
|
||||||
}
|
}
|
||||||
/// Evaluate a string containing an expression with own scope.
|
/// Evaluate a string containing an expression with own scope.
|
||||||
///
|
///
|
||||||
@ -1676,7 +1677,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
|
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
|
||||||
self.eval_ast_with_scope(&mut Default::default(), ast)
|
self.eval_ast_with_scope(&mut Scope::new(), ast)
|
||||||
}
|
}
|
||||||
/// Evaluate an [`AST`] with own scope.
|
/// Evaluate an [`AST`] with own scope.
|
||||||
///
|
///
|
||||||
@ -1713,7 +1714,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mods = &mut Default::default();
|
let mods = &mut Imports::new();
|
||||||
|
|
||||||
let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?;
|
let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?;
|
||||||
|
|
||||||
@ -1738,10 +1739,12 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let mut state = EvalState::new();
|
let mut state = EvalState::new();
|
||||||
state.source = ast.source_raw().cloned();
|
if ast.source_raw().is_some() {
|
||||||
|
mods.source = ast.source_raw().cloned();
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
state.embedded_module_resolver = ast.resolver();
|
mods.embedded_module_resolver = ast.resolver();
|
||||||
}
|
}
|
||||||
|
|
||||||
let statements = ast.statements();
|
let statements = ast.statements();
|
||||||
@ -1778,7 +1781,7 @@ impl Engine {
|
|||||||
/// Evaluate a script, returning any error (if any).
|
/// Evaluate a script, returning any error (if any).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
|
pub fn run(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
|
||||||
self.run_with_scope(&mut Default::default(), script)
|
self.run_with_scope(&mut Scope::new(), script)
|
||||||
}
|
}
|
||||||
/// Evaluate a script with own scope, returning any error (if any).
|
/// Evaluate a script with own scope, returning any error (if any).
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1805,7 +1808,7 @@ impl Engine {
|
|||||||
/// Evaluate an AST, returning any error (if any).
|
/// Evaluate an AST, returning any error (if any).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
|
pub fn run_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
|
||||||
self.run_ast_with_scope(&mut Default::default(), ast)
|
self.run_ast_with_scope(&mut Scope::new(), ast)
|
||||||
}
|
}
|
||||||
/// Evaluate an [`AST`] with own scope, returning any error (if any).
|
/// Evaluate an [`AST`] with own scope, returning any error (if any).
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1814,12 +1817,14 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let mods = &mut Default::default();
|
let mods = &mut Imports::new();
|
||||||
let mut state = EvalState::new();
|
let mut state = EvalState::new();
|
||||||
state.source = ast.source_raw().cloned();
|
if ast.source_raw().is_some() {
|
||||||
|
mods.source = ast.source_raw().cloned();
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
state.embedded_module_resolver = ast.resolver();
|
mods.embedded_module_resolver = ast.resolver();
|
||||||
}
|
}
|
||||||
|
|
||||||
let statements = ast.statements();
|
let statements = ast.statements();
|
||||||
@ -1988,7 +1993,7 @@ impl Engine {
|
|||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let state = &mut EvalState::new();
|
let state = &mut EvalState::new();
|
||||||
let mods = &mut Default::default();
|
let mods = &mut Imports::new();
|
||||||
let lib = &[ast.lib()];
|
let lib = &[ast.lib()];
|
||||||
let statements = ast.statements();
|
let statements = ast.statements();
|
||||||
|
|
||||||
@ -2058,7 +2063,7 @@ impl Engine {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
let lib = Default::default();
|
let lib = crate::StaticVec::new();
|
||||||
|
|
||||||
let stmt = std::mem::take(ast.statements_mut());
|
let stmt = std::mem::take(ast.statements_mut());
|
||||||
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
||||||
@ -2206,8 +2211,6 @@ impl Engine {
|
|||||||
+ SendSync
|
+ SendSync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
use std::string::ParseError;
|
|
||||||
|
|
||||||
self.token_mapper = Some(Box::new(callback));
|
self.token_mapper = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -122,11 +122,11 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorInModule(s, err, _) if s.is_empty() => {
|
Self::ErrorInModule(s, err, _) if s.is_empty() => {
|
||||||
write!(f, "Error in module: {}", err)?
|
write!(f, "Error in module: {}", err)?
|
||||||
}
|
}
|
||||||
Self::ErrorInModule(s, err, _) => write!(f, "Error in module '{}': {}", s, err)?,
|
Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?,
|
||||||
|
|
||||||
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
||||||
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
|
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
|
||||||
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: '{}'", s)?,
|
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
|
||||||
Self::ErrorDataRace(s, _) => {
|
Self::ErrorDataRace(s, _) => {
|
||||||
write!(f, "Data race detected when accessing variable: {}", s)?
|
write!(f, "Data race detected when accessing variable: {}", s)?
|
||||||
}
|
}
|
||||||
@ -134,7 +134,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
"" => f.write_str("Malformed dot expression"),
|
"" => f.write_str("Malformed dot expression"),
|
||||||
s => f.write_str(s),
|
s => f.write_str(s),
|
||||||
}?,
|
}?,
|
||||||
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?,
|
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for {}", s)?,
|
||||||
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
|
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
|
||||||
Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?,
|
Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?,
|
||||||
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
|
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
|
||||||
|
@ -266,6 +266,24 @@ pub fn get_builtin_binary_op_fn(
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// () op string
|
||||||
|
if types_pair == (TypeId::of::<()>(), TypeId::of::<ImmutableString>()) {
|
||||||
|
return match op {
|
||||||
|
"+" => Some(|_, args| Ok(args[1].clone())),
|
||||||
|
"==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
|
||||||
|
"!=" => Some(|_, _| Ok(Dynamic::TRUE)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// string op ()
|
||||||
|
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<()>()) {
|
||||||
|
return match op {
|
||||||
|
"+" => Some(|_, args| Ok(args[0].clone())),
|
||||||
|
"==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
|
||||||
|
"!=" => Some(|_, _| Ok(Dynamic::TRUE)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// map op string
|
// map op string
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
148
src/fn_call.rs
148
src/fn_call.rs
@ -9,14 +9,13 @@ use crate::engine::{
|
|||||||
use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
||||||
use crate::fn_native::FnAny;
|
use crate::fn_native::FnAny;
|
||||||
use crate::module::NamespaceRef;
|
use crate::module::NamespaceRef;
|
||||||
|
use crate::token::Token;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Expr, Stmt},
|
ast::{Expr, Stmt},
|
||||||
|
calc_fn_hash, calc_fn_params_hash, combine_hashes,
|
||||||
fn_native::CallableFunction,
|
fn_native::CallableFunction,
|
||||||
RhaiResult,
|
Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, ParseErrorType,
|
||||||
};
|
Position, RhaiResult, Scope, StaticVec,
|
||||||
use crate::{
|
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
|
|
||||||
Identifier, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec,
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -34,13 +33,20 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
|||||||
|
|
||||||
/// A type that temporarily stores a mutable reference to a `Dynamic`,
|
/// A type that temporarily stores a mutable reference to a `Dynamic`,
|
||||||
/// replacing it with a cloned copy.
|
/// replacing it with a cloned copy.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
struct ArgBackup<'a> {
|
struct ArgBackup<'a> {
|
||||||
orig_mut: Option<&'a mut Dynamic>,
|
orig_mut: Option<&'a mut Dynamic>,
|
||||||
value_copy: Dynamic,
|
value_copy: Dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ArgBackup<'a> {
|
impl<'a> ArgBackup<'a> {
|
||||||
|
/// Create a new `ArgBackup`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
orig_mut: None,
|
||||||
|
value_copy: Dynamic::UNIT,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// This function replaces the first argument of a method call with a clone copy.
|
/// This function replaces the first argument of a method call with a clone copy.
|
||||||
/// This is to prevent a pure function unintentionally consuming the first argument.
|
/// This is to prevent a pure function unintentionally consuming the first argument.
|
||||||
///
|
///
|
||||||
@ -135,8 +141,13 @@ impl Engine {
|
|||||||
args: &[&mut Dynamic],
|
args: &[&mut Dynamic],
|
||||||
) -> String {
|
) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}{} ({})",
|
"{}{}{} ({})",
|
||||||
namespace.map_or(String::new(), |ns| ns.to_string()),
|
namespace.map_or(String::new(), |ns| ns.to_string()),
|
||||||
|
if namespace.is_some() {
|
||||||
|
Token::DoubleColon.literal_syntax()
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
fn_name,
|
fn_name,
|
||||||
args.iter()
|
args.iter()
|
||||||
.map(|a| if a.is::<ImmutableString>() {
|
.map(|a| if a.is::<ImmutableString>() {
|
||||||
@ -168,7 +179,7 @@ impl Engine {
|
|||||||
args: Option<&mut FnCallArgs>,
|
args: Option<&mut FnCallArgs>,
|
||||||
allow_dynamic: bool,
|
allow_dynamic: bool,
|
||||||
is_op_assignment: bool,
|
is_op_assignment: bool,
|
||||||
) -> &'s Option<Box<FnResolutionCacheEntry>> {
|
) -> Option<&'s FnResolutionCacheEntry> {
|
||||||
let mut hash = args.as_ref().map_or(hash_script, |args| {
|
let mut hash = args.as_ref().map_or(hash_script, |args| {
|
||||||
combine_hashes(
|
combine_hashes(
|
||||||
hash_script,
|
hash_script,
|
||||||
@ -176,7 +187,7 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
&*state
|
let result = state
|
||||||
.fn_resolution_cache_mut()
|
.fn_resolution_cache_mut()
|
||||||
.entry(hash)
|
.entry(hash)
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
@ -284,7 +295,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
result.as_ref().map(Box::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a native Rust function registered with the [`Engine`].
|
/// Call a native Rust function registered with the [`Engine`].
|
||||||
@ -296,7 +309,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_native_fn(
|
pub(crate) fn call_native_fn(
|
||||||
&self,
|
&self,
|
||||||
mods: &Imports,
|
mods: &mut Imports,
|
||||||
state: &mut EvalState,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
name: &str,
|
name: &str,
|
||||||
@ -307,21 +320,20 @@ impl Engine {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, pos)?;
|
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||||
|
|
||||||
let state_source = state.source.clone();
|
let parent_source = mods.source.clone();
|
||||||
|
|
||||||
// Check if function access already in the cache
|
// Check if function access already in the cache
|
||||||
let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign);
|
let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign);
|
||||||
|
|
||||||
if let Some(f) = func {
|
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||||
let FnResolutionCacheEntry { func, source } = f.as_ref();
|
|
||||||
assert!(func.is_native());
|
assert!(func.is_native());
|
||||||
|
|
||||||
// Calling pure function but the first argument is a reference?
|
// Calling pure function but the first argument is a reference?
|
||||||
let mut backup: Option<ArgBackup> = None;
|
let mut backup: Option<ArgBackup> = None;
|
||||||
if is_method_call && func.is_pure() && !args.is_empty() {
|
if is_method_call && func.is_pure() && !args.is_empty() {
|
||||||
backup = Some(Default::default());
|
backup = Some(ArgBackup::new());
|
||||||
backup
|
backup
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("`backup` is `Some`")
|
.expect("`backup` is `Some`")
|
||||||
@ -331,16 +343,17 @@ impl Engine {
|
|||||||
// Run external function
|
// Run external function
|
||||||
let source = source
|
let source = source
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or_else(|| state_source.as_ref())
|
.or_else(|| parent_source.as_ref())
|
||||||
.map(|s| s.as_str());
|
.map(|s| s.as_str());
|
||||||
|
|
||||||
|
let context = (self, name, source, &*mods, lib, pos).into();
|
||||||
|
|
||||||
let result = if func.is_plugin_fn() {
|
let result = if func.is_plugin_fn() {
|
||||||
func.get_plugin_fn()
|
func.get_plugin_fn()
|
||||||
.expect("plugin function")
|
.expect("plugin function")
|
||||||
.call((self, name, source, mods, lib).into(), args)
|
.call(context, args)
|
||||||
} else {
|
} else {
|
||||||
let func = func.get_native_fn().expect("native function");
|
func.get_native_fn().expect("native function")(context, args)
|
||||||
func((self, name, source, mods, lib).into(), args)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
@ -375,7 +388,7 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let source = state.source.as_ref().map(|s| s.as_str());
|
let source = mods.source.as_ref().map(|s| s.as_str());
|
||||||
(debug(&text, source, pos).into(), false)
|
(debug(&text, source, pos).into(), false)
|
||||||
} else {
|
} else {
|
||||||
(Dynamic::UNIT, false)
|
(Dynamic::UNIT, false)
|
||||||
@ -485,7 +498,7 @@ impl Engine {
|
|||||||
fn make_error(
|
fn make_error(
|
||||||
name: String,
|
name: String,
|
||||||
fn_def: &crate::ast::ScriptFnDef,
|
fn_def: &crate::ast::ScriptFnDef,
|
||||||
state: &EvalState,
|
mods: &Imports,
|
||||||
err: Box<EvalAltResult>,
|
err: Box<EvalAltResult>,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
@ -495,7 +508,7 @@ impl Engine {
|
|||||||
.lib
|
.lib
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|m| m.id().map(|id| id.to_string()))
|
.and_then(|m| m.id().map(|id| id.to_string()))
|
||||||
.or_else(|| state.source.as_ref().map(|s| s.to_string()))
|
.or_else(|| mods.source.as_ref().map(|s| s.to_string()))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
err,
|
err,
|
||||||
pos,
|
pos,
|
||||||
@ -504,7 +517,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, pos)?;
|
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||||
|
|
||||||
if fn_def.body.is_empty() {
|
if fn_def.body.is_empty() {
|
||||||
return Ok(Dynamic::UNIT);
|
return Ok(Dynamic::UNIT);
|
||||||
@ -517,9 +530,6 @@ impl Engine {
|
|||||||
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
|
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let orig_scope_level = state.scope_level;
|
|
||||||
state.scope_level += 1;
|
|
||||||
|
|
||||||
let prev_scope_len = scope.len();
|
let prev_scope_len = scope.len();
|
||||||
let prev_mods_len = mods.len();
|
let prev_mods_len = mods.len();
|
||||||
|
|
||||||
@ -559,7 +569,6 @@ impl Engine {
|
|||||||
|
|
||||||
// Evaluate the function
|
// Evaluate the function
|
||||||
let body = &fn_def.body;
|
let body = &fn_def.body;
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
|
.eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
@ -573,7 +582,7 @@ impl Engine {
|
|||||||
format!("{} @ '{}' < {}", name, src, fn_def.name)
|
format!("{} @ '{}' < {}", name, src, fn_def.name)
|
||||||
};
|
};
|
||||||
|
|
||||||
make_error(fn_name, fn_def, state, err, pos)
|
make_error(fn_name, fn_def, mods, err, pos)
|
||||||
}
|
}
|
||||||
// System errors are passed straight-through
|
// System errors are passed straight-through
|
||||||
mut err if err.is_system_exception() => {
|
mut err if err.is_system_exception() => {
|
||||||
@ -581,13 +590,12 @@ impl Engine {
|
|||||||
Err(err.into())
|
Err(err.into())
|
||||||
}
|
}
|
||||||
// Other errors are wrapped in `ErrorInFunctionCall`
|
// Other errors are wrapped in `ErrorInFunctionCall`
|
||||||
_ => make_error(fn_def.name.to_string(), fn_def, state, err, pos),
|
_ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove all local variables
|
// Remove all local variables
|
||||||
scope.rewind(prev_scope_len);
|
scope.rewind(prev_scope_len);
|
||||||
mods.truncate(prev_mods_len);
|
mods.truncate(prev_mods_len);
|
||||||
state.scope_level = orig_scope_level;
|
|
||||||
|
|
||||||
if unified {
|
if unified {
|
||||||
state.pop_fn_resolution_cache();
|
state.pop_fn_resolution_cache();
|
||||||
@ -614,7 +622,7 @@ impl Engine {
|
|||||||
|
|
||||||
// First check script-defined functions
|
// First check script-defined functions
|
||||||
let result = lib.iter().any(|&m| m.contains_fn(hash_script))
|
let result = lib.iter().any(|&m| m.contains_fn(hash_script))
|
||||||
// Then check packages
|
// Then check the global namespace and packages
|
||||||
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script))
|
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script))
|
||||||
// Then check imported modules
|
// Then check imported modules
|
||||||
|| mods.map_or(false, |m| m.contains_fn(hash_script))
|
|| mods.map_or(false, |m| m.contains_fn(hash_script))
|
||||||
@ -714,11 +722,10 @@ impl Engine {
|
|||||||
let hash_script = hashes.script;
|
let hash_script = hashes.script;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if let Some(f) = hash_script.and_then(|hash| {
|
if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| {
|
||||||
self.resolve_fn(mods, state, lib, fn_name, hash, None, false, false)
|
self.resolve_fn(mods, state, lib, fn_name, hash, None, false, false)
|
||||||
.clone()
|
.cloned()
|
||||||
}) {
|
}) {
|
||||||
let FnResolutionCacheEntry { func, source } = *f;
|
|
||||||
// Script function call
|
// Script function call
|
||||||
assert!(func.is_script());
|
assert!(func.is_script());
|
||||||
|
|
||||||
@ -728,7 +735,7 @@ impl Engine {
|
|||||||
return Ok((Dynamic::UNIT, false));
|
return Ok((Dynamic::UNIT, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope: &mut Scope = &mut Default::default();
|
let scope = &mut Scope::new();
|
||||||
|
|
||||||
// Move captured variables into scope
|
// Move captured variables into scope
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -750,8 +757,8 @@ impl Engine {
|
|||||||
.split_first_mut()
|
.split_first_mut()
|
||||||
.expect("method call has first parameter");
|
.expect("method call has first parameter");
|
||||||
|
|
||||||
let orig_source = state.source.take();
|
let orig_source = mods.source.take();
|
||||||
state.source = source;
|
mods.source = source;
|
||||||
|
|
||||||
let level = _level + 1;
|
let level = _level + 1;
|
||||||
|
|
||||||
@ -768,7 +775,7 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Restore the original source
|
// Restore the original source
|
||||||
state.source = orig_source;
|
mods.source = orig_source;
|
||||||
|
|
||||||
result?
|
result?
|
||||||
} else {
|
} else {
|
||||||
@ -776,15 +783,15 @@ impl Engine {
|
|||||||
// The first argument is a reference?
|
// The first argument is a reference?
|
||||||
let mut backup: Option<ArgBackup> = None;
|
let mut backup: Option<ArgBackup> = None;
|
||||||
if is_ref_mut && !args.is_empty() {
|
if is_ref_mut && !args.is_empty() {
|
||||||
backup = Some(Default::default());
|
backup = Some(ArgBackup::new());
|
||||||
backup
|
backup
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("`backup` is `Some`")
|
.expect("`backup` is `Some`")
|
||||||
.change_first_arg_to_copy(args);
|
.change_first_arg_to_copy(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
let orig_source = state.source.take();
|
let orig_source = mods.source.take();
|
||||||
state.source = source;
|
mods.source = source;
|
||||||
|
|
||||||
let level = _level + 1;
|
let level = _level + 1;
|
||||||
|
|
||||||
@ -792,7 +799,7 @@ impl Engine {
|
|||||||
self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
|
self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
|
||||||
|
|
||||||
// Restore the original source
|
// Restore the original source
|
||||||
state.source = orig_source;
|
mods.source = orig_source;
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
if let Some(bk) = backup {
|
if let Some(bk) = backup {
|
||||||
@ -839,14 +846,13 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut EvalState,
|
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
script: &str,
|
script: &str,
|
||||||
_pos: Position,
|
_pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, _pos)?;
|
self.inc_operations(&mut mods.num_operations, _pos)?;
|
||||||
|
|
||||||
let script = script.trim();
|
let script = script.trim();
|
||||||
if script.is_empty() {
|
if script.is_empty() {
|
||||||
@ -856,7 +862,7 @@ impl Engine {
|
|||||||
// Compile the script text
|
// Compile the script text
|
||||||
// No optimizations because we only run it once
|
// No optimizations because we only run it once
|
||||||
let ast = self.compile_with_scope_and_optimization_level(
|
let ast = self.compile_with_scope_and_optimization_level(
|
||||||
&Default::default(),
|
&Scope::new(),
|
||||||
&[script],
|
&[script],
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
crate::OptimizationLevel::None,
|
crate::OptimizationLevel::None,
|
||||||
@ -873,16 +879,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the AST
|
// Evaluate the AST
|
||||||
let mut new_state = EvalState::new();
|
self.eval_global_statements(scope, mods, &mut EvalState::new(), statements, lib, level)
|
||||||
new_state.source = state.source.clone();
|
|
||||||
new_state.num_operations = state.num_operations;
|
|
||||||
|
|
||||||
let result =
|
|
||||||
self.eval_global_statements(scope, mods, &mut new_state, statements, lib, level);
|
|
||||||
|
|
||||||
state.num_operations = new_state.num_operations;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a dot method.
|
/// Call a dot method.
|
||||||
@ -1221,7 +1218,7 @@ impl Engine {
|
|||||||
.into_immutable_string()
|
.into_immutable_string()
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
||||||
let result =
|
let result =
|
||||||
self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1);
|
self.eval_script_expr_in_place(scope, mods, lib, script, pos, level + 1);
|
||||||
|
|
||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
// all variable offsets from this point on will be mis-aligned.
|
// all variable offsets from this point on will be mis-aligned.
|
||||||
@ -1232,8 +1229,7 @@ impl Engine {
|
|||||||
return result.map_err(|err| {
|
return result.map_err(|err| {
|
||||||
EvalAltResult::ErrorInFunctionCall(
|
EvalAltResult::ErrorInFunctionCall(
|
||||||
KEYWORD_EVAL.to_string(),
|
KEYWORD_EVAL.to_string(),
|
||||||
state
|
mods.source
|
||||||
.source
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(Identifier::to_string)
|
.map(Identifier::to_string)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@ -1280,7 +1276,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, _pos)?;
|
self.inc_operations(&mut mods.num_operations, _pos)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
let target_is_shared = target.is_shared();
|
let target_is_shared = target.is_shared();
|
||||||
@ -1346,7 +1342,7 @@ impl Engine {
|
|||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
for index in 0..args_expr.len() {
|
for index in 0..args_expr.len() {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
arg_values.push(Default::default());
|
arg_values.push(Dynamic::UNIT);
|
||||||
} else {
|
} else {
|
||||||
let (value, _) = self.get_arg_value(
|
let (value, _) = self.get_arg_value(
|
||||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||||
@ -1360,7 +1356,7 @@ impl Engine {
|
|||||||
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])?;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, _pos)?;
|
self.inc_operations(&mut mods.num_operations, _pos)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
let target_is_shared = target.is_shared();
|
let target_is_shared = target.is_shared();
|
||||||
@ -1393,7 +1389,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let module = self.search_imports(mods, state, namespace).ok_or_else(|| {
|
let module = self.search_imports(mods, state, namespace).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorModuleNotFound(namespace[0].name.to_string(), namespace[0].pos)
|
EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
@ -1401,7 +1397,7 @@ impl Engine {
|
|||||||
// Then search in Rust functions
|
// Then search in Rust functions
|
||||||
None => {
|
None => {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(state, pos)?;
|
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||||
|
|
||||||
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
|
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
|
||||||
let hash_qualified_fn = combine_hashes(hash, hash_params);
|
let hash_qualified_fn = combine_hashes(hash, hash_params);
|
||||||
@ -1427,10 +1423,10 @@ impl Engine {
|
|||||||
if fn_def.body.is_empty() {
|
if fn_def.body.is_empty() {
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
let new_scope = &mut Default::default();
|
let new_scope = &mut Scope::new();
|
||||||
|
|
||||||
let mut source = module.id_raw().cloned();
|
let mut source = module.id_raw().cloned();
|
||||||
mem::swap(&mut state.source, &mut source);
|
mem::swap(&mut mods.source, &mut source);
|
||||||
|
|
||||||
let level = level + 1;
|
let level = level + 1;
|
||||||
|
|
||||||
@ -1438,23 +1434,25 @@ impl Engine {
|
|||||||
new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level,
|
new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.source = source;
|
mods.source = source;
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(f) if f.is_plugin_fn() => f
|
Some(f) if f.is_plugin_fn() => {
|
||||||
.get_plugin_fn()
|
let context = (self, fn_name, module.id(), &*mods, lib, pos).into();
|
||||||
.expect("plugin function")
|
f.get_plugin_fn()
|
||||||
.clone()
|
.expect("plugin function")
|
||||||
.call((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
|
.clone()
|
||||||
.map_err(|err| err.fill_position(pos)),
|
.call(context, &mut args)
|
||||||
|
.map_err(|err| err.fill_position(pos))
|
||||||
|
}
|
||||||
|
|
||||||
Some(f) if f.is_native() => {
|
Some(f) if f.is_native() => {
|
||||||
let func = f.get_native_fn().expect("native function");
|
let func = f.get_native_fn().expect("native function");
|
||||||
func((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
|
let context = (self, fn_name, module.id(), &*mods, lib, pos).into();
|
||||||
.map_err(|err| err.fill_position(pos))
|
func(context, &mut args).map_err(|err| err.fill_position(pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(f) => unreachable!("unknown function type: {:?}", f),
|
Some(f) => unreachable!("unknown function type: {:?}", f),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Module defining interfaces to native-Rust functions.
|
//! Module defining interfaces to native-Rust functions.
|
||||||
|
|
||||||
use crate::ast::{FnAccess, FnCallHashes};
|
use crate::ast::{FnAccess, FnCallHashes};
|
||||||
use crate::engine::Imports;
|
use crate::engine::{EvalState, Imports};
|
||||||
use crate::fn_call::FnCallArgs;
|
use crate::fn_call::FnCallArgs;
|
||||||
use crate::plugin::PluginFunction;
|
use crate::plugin::PluginFunction;
|
||||||
use crate::token::{Token, TokenizeState};
|
use crate::token::{Token, TokenizeState};
|
||||||
@ -34,18 +34,25 @@ pub use std::rc::Rc as Shared;
|
|||||||
pub use std::sync::Arc as Shared;
|
pub use std::sync::Arc as Shared;
|
||||||
|
|
||||||
/// Synchronized shared object.
|
/// Synchronized shared object.
|
||||||
///
|
|
||||||
/// Not available under `no_closure`.
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub use std::cell::RefCell as Locked;
|
pub use std::cell::RefCell as Locked;
|
||||||
|
|
||||||
|
/// Lock guard for synchronized shared object.
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub type LockGuard<'a, T> = std::cell::RefMut<'a, T>;
|
||||||
|
|
||||||
/// Synchronized shared object.
|
/// Synchronized shared object.
|
||||||
///
|
|
||||||
/// Not available under `no_closure`.
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub use std::sync::RwLock as Locked;
|
pub use std::sync::RwLock as Locked;
|
||||||
|
|
||||||
|
/// Lock guard for synchronized shared object.
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub type LockGuard<'a, T> = std::sync::RwLockWriteGuard<'a, T>;
|
||||||
|
|
||||||
/// Context of a native Rust function call.
|
/// Context of a native Rust function call.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NativeCallContext<'a> {
|
pub struct NativeCallContext<'a> {
|
||||||
@ -54,19 +61,37 @@ pub struct NativeCallContext<'a> {
|
|||||||
source: Option<&'a str>,
|
source: Option<&'a str>,
|
||||||
mods: Option<&'a Imports>,
|
mods: Option<&'a Imports>,
|
||||||
lib: &'a [&'a Module],
|
lib: &'a [&'a Module],
|
||||||
|
pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized>
|
impl<'a, M: AsRef<[&'a Module]> + ?Sized>
|
||||||
From<(&'a Engine, &'a str, Option<&'a str>, &'a Imports, &'a M)> for NativeCallContext<'a>
|
From<(
|
||||||
|
&'a Engine,
|
||||||
|
&'a str,
|
||||||
|
Option<&'a str>,
|
||||||
|
&'a Imports,
|
||||||
|
&'a M,
|
||||||
|
Position,
|
||||||
|
)> for NativeCallContext<'a>
|
||||||
{
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: (&'a Engine, &'a str, Option<&'a str>, &'a Imports, &'a M)) -> Self {
|
fn from(
|
||||||
|
value: (
|
||||||
|
&'a Engine,
|
||||||
|
&'a str,
|
||||||
|
Option<&'a str>,
|
||||||
|
&'a Imports,
|
||||||
|
&'a M,
|
||||||
|
Position,
|
||||||
|
),
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine: value.0,
|
engine: value.0,
|
||||||
fn_name: value.1,
|
fn_name: value.1,
|
||||||
source: value.2,
|
source: value.2,
|
||||||
mods: Some(value.3),
|
mods: Some(value.3),
|
||||||
lib: value.4.as_ref(),
|
lib: value.4.as_ref(),
|
||||||
|
pos: value.5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,6 +107,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)>
|
|||||||
source: None,
|
source: None,
|
||||||
mods: None,
|
mods: None,
|
||||||
lib: value.2.as_ref(),
|
lib: value.2.as_ref(),
|
||||||
|
pos: Position::NONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,6 +123,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
source: None,
|
source: None,
|
||||||
mods: None,
|
mods: None,
|
||||||
lib,
|
lib,
|
||||||
|
pos: Position::NONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// _(internals)_ Create a new [`NativeCallContext`].
|
/// _(internals)_ Create a new [`NativeCallContext`].
|
||||||
@ -113,6 +140,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
source: Option<&'a str>,
|
source: Option<&'a str>,
|
||||||
imports: &'a Imports,
|
imports: &'a Imports,
|
||||||
lib: &'a [&Module],
|
lib: &'a [&Module],
|
||||||
|
pos: Position,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
@ -120,6 +148,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
source,
|
source,
|
||||||
mods: Some(imports),
|
mods: Some(imports),
|
||||||
lib,
|
lib,
|
||||||
|
pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The current [`Engine`].
|
/// The current [`Engine`].
|
||||||
@ -134,6 +163,12 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
pub const fn fn_name(&self) -> &str {
|
pub const fn fn_name(&self) -> &str {
|
||||||
self.fn_name
|
self.fn_name
|
||||||
}
|
}
|
||||||
|
/// [Position][`Position`] of the function call.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn position(&self) -> Position {
|
||||||
|
self.pos
|
||||||
|
}
|
||||||
/// The current source.
|
/// The current source.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -214,8 +249,8 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
|
|
||||||
self.engine()
|
self.engine()
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
&mut self.mods.cloned().unwrap_or_default(),
|
&mut self.mods.cloned().unwrap_or_else(|| Imports::new()),
|
||||||
&mut Default::default(),
|
&mut EvalState::new(),
|
||||||
self.lib,
|
self.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
hash,
|
hash,
|
||||||
@ -234,6 +269,7 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
|
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
||||||
Shared::make_mut(value)
|
Shared::make_mut(value)
|
||||||
}
|
}
|
||||||
@ -241,12 +277,14 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
|
|||||||
/// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise.
|
/// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn shared_take_or_clone<T: Clone>(value: Shared<T>) -> T {
|
pub fn shared_take_or_clone<T: Clone>(value: Shared<T>) -> T {
|
||||||
shared_try_take(value).unwrap_or_else(|v| v.as_ref().clone())
|
shared_try_take(value).unwrap_or_else(|v| v.as_ref().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume a [`Shared`] resource if is unique (i.e. not shared).
|
/// Consume a [`Shared`] resource if is unique (i.e. not shared).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
|
pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
|
||||||
Shared::try_unwrap(value)
|
Shared::try_unwrap(value)
|
||||||
}
|
}
|
||||||
@ -258,12 +296,25 @@ pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
|
|||||||
/// Panics if the resource is shared (i.e. has other outstanding references).
|
/// Panics if the resource is shared (i.e. has other outstanding references).
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn shared_take<T>(value: Shared<T>) -> T {
|
pub fn shared_take<T>(value: Shared<T>) -> T {
|
||||||
shared_try_take(value)
|
shared_try_take(value)
|
||||||
.ok()
|
.ok()
|
||||||
.expect("no outstanding references")
|
.expect("no outstanding references")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lock a [`Shared`] resource.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn shared_write_lock<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
return value.borrow_mut();
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
return value.write().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
/// A general function trail object.
|
/// A general function trail object.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
|
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
|
||||||
|
@ -146,7 +146,7 @@ impl TryFrom<Identifier> for FnPtr {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
|
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
|
||||||
if is_valid_identifier(value.chars()) {
|
if is_valid_identifier(value.chars()) {
|
||||||
Ok(Self(value, Default::default()))
|
Ok(Self(value, StaticVec::new()))
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
|
Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ macro_rules! def_register {
|
|||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||||
return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into());
|
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
@ -151,7 +151,7 @@ macro_rules! def_register {
|
|||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||||
return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into());
|
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
@ -179,7 +179,7 @@ macro_rules! def_register {
|
|||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||||
return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into());
|
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
@ -204,7 +204,7 @@ macro_rules! def_register {
|
|||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||||
return Err(EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into());
|
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
|
@ -171,8 +171,8 @@ pub type Identifier = ImmutableString;
|
|||||||
/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag.
|
/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag.
|
||||||
pub use fn_native::Shared;
|
pub use fn_native::Shared;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
//// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag.
|
||||||
use fn_native::Locked;
|
pub use fn_native::Locked;
|
||||||
|
|
||||||
pub(crate) use fn_hash::{
|
pub(crate) use fn_hash::{
|
||||||
calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash,
|
calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash,
|
||||||
|
@ -9,7 +9,7 @@ use crate::parse::IdentifierBuilder;
|
|||||||
use crate::token::Token;
|
use crate::token::Token;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult,
|
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult,
|
||||||
Identifier, ImmutableString, NativeCallContext, Position, Shared, StaticVec,
|
Identifier, ImmutableString, NativeCallContext, Shared, StaticVec,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -37,13 +37,6 @@ pub enum FnNamespace {
|
|||||||
Internal,
|
Internal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FnNamespace {
|
|
||||||
#[inline(always)]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Internal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data structure containing a single registered function.
|
/// Data structure containing a single registered function.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FuncInfo {
|
pub struct FuncInfo {
|
||||||
@ -131,6 +124,8 @@ pub struct Module {
|
|||||||
id: Option<Identifier>,
|
id: Option<Identifier>,
|
||||||
/// Is this module internal?
|
/// Is this module internal?
|
||||||
pub(crate) internal: bool,
|
pub(crate) internal: bool,
|
||||||
|
/// Is this module part of a standard library?
|
||||||
|
pub(crate) standard: bool,
|
||||||
/// Sub-modules.
|
/// Sub-modules.
|
||||||
modules: BTreeMap<Identifier, Shared<Module>>,
|
modules: BTreeMap<Identifier, Shared<Module>>,
|
||||||
/// [`Module`] variables.
|
/// [`Module`] variables.
|
||||||
@ -246,16 +241,17 @@ impl Module {
|
|||||||
Self {
|
Self {
|
||||||
id: None,
|
id: None,
|
||||||
internal: false,
|
internal: false,
|
||||||
modules: Default::default(),
|
standard: false,
|
||||||
variables: Default::default(),
|
modules: BTreeMap::new(),
|
||||||
all_variables: Default::default(),
|
variables: BTreeMap::new(),
|
||||||
functions: Default::default(),
|
all_variables: BTreeMap::new(),
|
||||||
all_functions: Default::default(),
|
functions: BTreeMap::new(),
|
||||||
type_iterators: Default::default(),
|
all_functions: BTreeMap::new(),
|
||||||
all_type_iterators: Default::default(),
|
type_iterators: BTreeMap::new(),
|
||||||
|
all_type_iterators: BTreeMap::new(),
|
||||||
indexed: true,
|
indexed: true,
|
||||||
contains_indexed_global_functions: false,
|
contains_indexed_global_functions: false,
|
||||||
identifiers: Default::default(),
|
identifiers: IdentifierBuilder::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,10 +451,11 @@ impl Module {
|
|||||||
|
|
||||||
/// Get a reference to a namespace-qualified variable.
|
/// Get a reference to a namespace-qualified variable.
|
||||||
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
|
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box<EvalAltResult>> {
|
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box<EvalAltResult>> {
|
||||||
self.all_variables.get(&hash_var).ok_or_else(|| {
|
self.all_variables.get(&hash_var).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()
|
EvalAltResult::ErrorVariableNotFound(String::new(), crate::Position::NONE).into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +479,7 @@ impl Module {
|
|||||||
namespace: FnNamespace::Internal,
|
namespace: FnNamespace::Internal,
|
||||||
access: fn_def.access,
|
access: fn_def.access,
|
||||||
params: num_params,
|
params: num_params,
|
||||||
param_types: Default::default(),
|
param_types: StaticVec::new(),
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
param_names,
|
param_names,
|
||||||
func: Into::<CallableFunction>::into(fn_def).into(),
|
func: Into::<CallableFunction>::into(fn_def).into(),
|
||||||
@ -1561,9 +1558,9 @@ impl Module {
|
|||||||
|
|
||||||
if !self.indexed {
|
if !self.indexed {
|
||||||
let mut path = Vec::with_capacity(4);
|
let mut path = Vec::with_capacity(4);
|
||||||
let mut variables = Default::default();
|
let mut variables = BTreeMap::new();
|
||||||
let mut functions = Default::default();
|
let mut functions = BTreeMap::new();
|
||||||
let mut type_iterators = Default::default();
|
let mut type_iterators = BTreeMap::new();
|
||||||
|
|
||||||
path.push("");
|
path.push("");
|
||||||
|
|
||||||
@ -1677,17 +1674,21 @@ impl fmt::Debug for NamespaceRef {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|Ident { name, .. }| name.as_str())
|
.map(|Ident { name, .. }| name.as_str())
|
||||||
.collect::<StaticVec<_>>()
|
.collect::<StaticVec<_>>()
|
||||||
.join("::"),
|
.join(Token::DoubleColon.literal_syntax()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for NamespaceRef {
|
impl fmt::Display for NamespaceRef {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
for Ident { name, .. } in self.path.iter() {
|
f.write_str(
|
||||||
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
&self
|
||||||
}
|
.path
|
||||||
Ok(())
|
.iter()
|
||||||
|
.map(|Ident { name, .. }| name.as_str())
|
||||||
|
.collect::<StaticVec<_>>()
|
||||||
|
.join(Token::DoubleColon.literal_syntax()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ use std::prelude::v1::*;
|
|||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
/// engine.set_module_resolver(collection);
|
/// engine.set_module_resolver(collection);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
|
pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
|
||||||
|
|
||||||
impl ModuleResolversCollection {
|
impl ModuleResolversCollection {
|
||||||
@ -43,7 +42,7 @@ impl ModuleResolversCollection {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Self(Vec::new())
|
||||||
}
|
}
|
||||||
/// Append a [module resolver][ModuleResolver] to the end.
|
/// Append a [module resolver][ModuleResolver] to the end.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared};
|
use crate::fn_native::shared_write_lock;
|
||||||
|
use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared};
|
||||||
|
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -51,13 +53,6 @@ pub struct FileModuleResolver {
|
|||||||
cache: std::sync::RwLock<BTreeMap<PathBuf, Shared<Module>>>,
|
cache: std::sync::RwLock<BTreeMap<PathBuf, Shared<Module>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FileModuleResolver {
|
|
||||||
#[inline(always)]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileModuleResolver {
|
impl FileModuleResolver {
|
||||||
/// Create a new [`FileModuleResolver`] with the current directory as base path.
|
/// Create a new [`FileModuleResolver`] with the current directory as base path.
|
||||||
///
|
///
|
||||||
@ -209,19 +204,12 @@ impl FileModuleResolver {
|
|||||||
|
|
||||||
let file_path = self.get_file_path(path, source_path);
|
let file_path = self.get_file_path(path, source_path);
|
||||||
|
|
||||||
#[cfg(not(feature = "sync"))]
|
shared_write_lock(&self.cache).contains_key(&file_path)
|
||||||
return self.cache.borrow_mut().contains_key(&file_path);
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
return self.cache.write().unwrap().contains_key(&file_path);
|
|
||||||
}
|
}
|
||||||
/// Empty the internal cache.
|
/// Empty the internal cache.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn clear_cache(&mut self) -> &mut Self {
|
pub fn clear_cache(&mut self) -> &mut Self {
|
||||||
#[cfg(not(feature = "sync"))]
|
shared_write_lock(&self.cache).clear();
|
||||||
self.cache.borrow_mut().clear();
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
self.cache.write().unwrap().clear();
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Remove the specified path from internal cache.
|
/// Remove the specified path from internal cache.
|
||||||
@ -236,19 +224,9 @@ impl FileModuleResolver {
|
|||||||
) -> Option<Shared<Module>> {
|
) -> Option<Shared<Module>> {
|
||||||
let file_path = self.get_file_path(path, source_path);
|
let file_path = self.get_file_path(path, source_path);
|
||||||
|
|
||||||
#[cfg(not(feature = "sync"))]
|
shared_write_lock(&self.cache)
|
||||||
return self
|
|
||||||
.cache
|
|
||||||
.borrow_mut()
|
|
||||||
.remove_entry(&file_path)
|
.remove_entry(&file_path)
|
||||||
.map(|(_, v)| v);
|
.map(|(_, v)| v)
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
return self
|
|
||||||
.cache
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.remove_entry(&file_path)
|
|
||||||
.map(|(_, v)| v);
|
|
||||||
}
|
}
|
||||||
/// Construct a full file path.
|
/// Construct a full file path.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -301,7 +279,7 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the script file and compile it
|
// Load the script file and compile it
|
||||||
let scope = Default::default();
|
let scope = Scope::new();
|
||||||
|
|
||||||
let mut ast = engine
|
let mut ast = engine
|
||||||
.compile_file(file_path.clone())
|
.compile_file(file_path.clone())
|
||||||
@ -321,10 +299,7 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
|
|
||||||
// Put it into the cache
|
// Put it into the cache
|
||||||
if self.is_cache_enabled() {
|
if self.is_cache_enabled() {
|
||||||
#[cfg(not(feature = "sync"))]
|
shared_write_lock(&self.cache).insert(file_path, m.clone());
|
||||||
self.cache.borrow_mut().insert(file_path, m.clone());
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
self.cache.write().unwrap().insert(file_path, m.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(m)
|
Ok(m)
|
||||||
|
@ -22,7 +22,7 @@ use std::{collections::BTreeMap, ops::AddAssign};
|
|||||||
///
|
///
|
||||||
/// engine.set_module_resolver(resolver);
|
/// engine.set_module_resolver(resolver);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StaticModuleResolver(BTreeMap<Identifier, Shared<Module>>);
|
pub struct StaticModuleResolver(BTreeMap<Identifier, Shared<Module>>);
|
||||||
|
|
||||||
impl StaticModuleResolver {
|
impl StaticModuleResolver {
|
||||||
@ -45,7 +45,7 @@ impl StaticModuleResolver {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Self(BTreeMap::new())
|
||||||
}
|
}
|
||||||
/// Add a [module][Module] keyed by its path.
|
/// Add a [module][Module] keyed by its path.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
||||||
use crate::dynamic::AccessMode;
|
use crate::dynamic::AccessMode;
|
||||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{
|
||||||
|
EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||||
|
};
|
||||||
use crate::fn_builtin::get_builtin_binary_op_fn;
|
use crate::fn_builtin::get_builtin_binary_op_fn;
|
||||||
use crate::fn_hash::get_hasher;
|
use crate::fn_hash::get_hasher;
|
||||||
use crate::token::Token;
|
use crate::token::Token;
|
||||||
@ -131,8 +133,8 @@ impl<'a> OptimizerState<'a> {
|
|||||||
) -> Option<Dynamic> {
|
) -> Option<Dynamic> {
|
||||||
self.engine
|
self.engine
|
||||||
.call_native_fn(
|
.call_native_fn(
|
||||||
&Default::default(),
|
&mut Imports::new(),
|
||||||
&mut Default::default(),
|
&mut EvalState::new(),
|
||||||
self.lib,
|
self.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
calc_fn_hash(fn_name, arg_values.len()),
|
calc_fn_hash(fn_name, arg_values.len()),
|
||||||
@ -145,12 +147,13 @@ impl<'a> OptimizerState<'a> {
|
|||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
// Has a system function a Rust-native override?
|
// Has a system function a Rust-native override?
|
||||||
pub fn has_native_fn(&self, hash_script: u64, arg_types: &[TypeId]) -> bool {
|
pub fn has_native_fn_override(&self, hash_script: u64, arg_types: &[TypeId]) -> bool {
|
||||||
let hash_params = calc_fn_params_hash(arg_types.iter().cloned());
|
let hash_params = calc_fn_params_hash(arg_types.iter().cloned());
|
||||||
let hash = combine_hashes(hash_script, hash_params);
|
let hash = combine_hashes(hash_script, hash_params);
|
||||||
|
|
||||||
// First check packages
|
// First check the global namespace and packages, but skip modules that are standard because
|
||||||
self.engine.global_modules.iter().any(|m| m.contains_fn(hash))
|
// they should never conflict with system functions.
|
||||||
|
self.engine.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash))
|
||||||
// Then check sub-modules
|
// Then check sub-modules
|
||||||
|| self.engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash))
|
|| self.engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash))
|
||||||
}
|
}
|
||||||
@ -482,9 +485,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
// switch const { ... }
|
// switch const { ... }
|
||||||
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
||||||
let value = match_expr
|
let value = match_expr.get_literal_value().expect("constant");
|
||||||
.get_literal_value()
|
|
||||||
.expect("`match_expr` is constant");
|
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
@ -878,7 +879,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(_, _) if expr.is_constant() => {
|
Expr::Array(_, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::DynamicConstant(expr.get_literal_value().expect("`expr` is constant").into(), expr.position());
|
*expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position());
|
||||||
}
|
}
|
||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -887,7 +888,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(_, _) if expr.is_constant() => {
|
Expr::Map(_, _) if expr.is_constant() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::DynamicConstant(expr.get_literal_value().expect("`expr` is constant").into(), expr.position());
|
*expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position());
|
||||||
}
|
}
|
||||||
// #{ key:value, .. }
|
// #{ key:value, .. }
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -960,7 +961,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let fn_ptr = FnPtr::new_unchecked(
|
let fn_ptr = FnPtr::new_unchecked(
|
||||||
fn_name.as_str_ref().expect("`fn_name` is `ImmutableString`").into(),
|
fn_name.as_str_ref().expect("`fn_name` is `ImmutableString`").into(),
|
||||||
Default::default()
|
StaticVec::new()
|
||||||
);
|
);
|
||||||
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
||||||
}
|
}
|
||||||
@ -981,31 +982,37 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
=> {
|
=> {
|
||||||
let arg_values = &mut x.args.iter().map(|e| match e {
|
let arg_values = &mut x.args.iter().map(|e| match e {
|
||||||
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
||||||
_ => e.get_literal_value().expect("`e` is constant")
|
_ => e.get_literal_value().expect("constant")
|
||||||
}).collect::<StaticVec<_>>();
|
}).collect::<StaticVec<_>>();
|
||||||
|
|
||||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||||
|
|
||||||
let result = match x.name.as_str() {
|
match x.name.as_str() {
|
||||||
KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
|
KEYWORD_TYPE_OF if arg_values.len() == 1 => {
|
||||||
#[cfg(not(feature = "no_closure"))]
|
state.set_dirty();
|
||||||
KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
|
*expr = Expr::from_dynamic(state.engine.map_type_name(arg_values[0].type_name()).into(), *pos);
|
||||||
// Overloaded operators can override built-in.
|
return;
|
||||||
_ if x.args.len() == 2 && !state.has_native_fn(x.hashes.native, arg_types.as_ref()) => {
|
|
||||||
get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
|
|
||||||
.and_then(|f| {
|
|
||||||
let ctx = (state.engine, x.name.as_ref(), state.lib).into();
|
|
||||||
let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty");
|
|
||||||
(f)(ctx, &mut [ first, &mut second[0] ]).ok()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
_ => None
|
#[cfg(not(feature = "no_closure"))]
|
||||||
};
|
KEYWORD_IS_SHARED if arg_values.len() == 1 => {
|
||||||
|
state.set_dirty();
|
||||||
if let Some(result) = result {
|
*expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
|
||||||
state.set_dirty();
|
return;
|
||||||
*expr = Expr::from_dynamic(result, *pos);
|
}
|
||||||
return;
|
// Overloaded operators can override built-in.
|
||||||
|
_ if x.args.len() == 2 && !state.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => {
|
||||||
|
if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
|
||||||
|
.and_then(|f| {
|
||||||
|
let context = (state.engine, x.name.as_ref(), state.lib).into();
|
||||||
|
let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty");
|
||||||
|
(f)(context, &mut [ first, &mut second[0] ]).ok()
|
||||||
|
}) {
|
||||||
|
state.set_dirty();
|
||||||
|
*expr = Expr::from_dynamic(result, *pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
||||||
@ -1035,7 +1042,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
if !has_script_fn {
|
if !has_script_fn {
|
||||||
let arg_values = &mut x.args.iter().map(|e| match e {
|
let arg_values = &mut x.args.iter().map(|e| match e {
|
||||||
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
Expr::Stack(slot, _) => x.constants[*slot].clone(),
|
||||||
_ => e.get_literal_value().expect("`e` is constant")
|
_ => e.get_literal_value().expect("constant")
|
||||||
}).collect::<StaticVec<_>>();
|
}).collect::<StaticVec<_>>();
|
||||||
|
|
||||||
let result = match x.name.as_str() {
|
let result = match x.name.as_str() {
|
||||||
@ -1079,7 +1086,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
if x.scope_may_be_changed {
|
if x.scope_may_be_changed {
|
||||||
state.propagate_constants = false;
|
state.propagate_constants = false;
|
||||||
}
|
}
|
||||||
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
|
x.inputs.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other expressions - skip
|
// All other expressions - skip
|
||||||
@ -1149,16 +1156,16 @@ pub fn optimize_into_ast(
|
|||||||
.map(|fn_def| crate::ast::ScriptFnDef {
|
.map(|fn_def| crate::ast::ScriptFnDef {
|
||||||
name: fn_def.name.clone(),
|
name: fn_def.name.clone(),
|
||||||
access: fn_def.access,
|
access: fn_def.access,
|
||||||
body: Default::default(),
|
body: crate::ast::StmtBlock::empty(),
|
||||||
params: fn_def.params.clone(),
|
params: fn_def.params.clone(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
externals: fn_def.externals.clone(),
|
externals: fn_def.externals.clone(),
|
||||||
lib: None,
|
lib: None,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
mods: Default::default(),
|
mods: crate::engine::Imports::new(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: Default::default(),
|
comments: StaticVec::new(),
|
||||||
})
|
})
|
||||||
.for_each(|fn_def| {
|
.for_each(|fn_def| {
|
||||||
lib2.set_script_fn(fn_def);
|
lib2.set_script_fn(fn_def);
|
||||||
@ -1193,7 +1200,7 @@ pub fn optimize_into_ast(
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
let lib = Default::default();
|
let lib = Module::new();
|
||||||
|
|
||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
|
|
||||||
|
@ -177,6 +177,8 @@ macro_rules! reg_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "int", int_functions);
|
combine_with_exported_module!(lib, "int", int_functions);
|
||||||
reg_functions!(lib += signed_basic; INT);
|
reg_functions!(lib += signed_basic; INT);
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ use std::prelude::v1::*;
|
|||||||
use std::{any::TypeId, cmp::Ordering, mem};
|
use std::{any::TypeId, cmp::Ordering, mem};
|
||||||
|
|
||||||
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "array", array_functions);
|
combine_with_exported_module!(lib, "array", array_functions);
|
||||||
|
|
||||||
// Register array iterator
|
// Register array iterator
|
||||||
|
@ -4,6 +4,8 @@ use crate::{def_package, FnPtr, ImmutableString, NativeCallContext};
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -107,7 +109,12 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
|
|||||||
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
|
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
|
||||||
});
|
});
|
||||||
module.iter_sub_modules().for_each(|(ns, m)| {
|
module.iter_sub_modules().for_each(|(ns, m)| {
|
||||||
let ns = format!("{}::{}", namespace, ns);
|
let ns = format!(
|
||||||
|
"{}{}{}",
|
||||||
|
namespace,
|
||||||
|
crate::token::Token::DoubleColon.literal_syntax(),
|
||||||
|
ns
|
||||||
|
);
|
||||||
scan_module(list, dict, ns.into(), m.as_ref())
|
scan_module(list, dict, ns.into(), m.as_ref())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ where
|
|||||||
if r == from {
|
if r == from {
|
||||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||||
"range".to_string(),
|
"range".to_string(),
|
||||||
Default::default(),
|
String::new(),
|
||||||
EvalAltResult::ErrorArithmetic(
|
EvalAltResult::ErrorArithmetic(
|
||||||
"step value cannot be zero".to_string(),
|
"step value cannot be zero".to_string(),
|
||||||
crate::Position::NONE,
|
crate::Position::NONE,
|
||||||
@ -201,7 +201,7 @@ struct CharsStream(Vec<char>, usize);
|
|||||||
impl CharsStream {
|
impl CharsStream {
|
||||||
pub fn new(string: &str, from: INT, len: INT) -> Self {
|
pub fn new(string: &str, from: INT, len: INT) -> Self {
|
||||||
if len <= 0 {
|
if len <= 0 {
|
||||||
return Self(Default::default(), 0);
|
return Self(Vec::new(), 0);
|
||||||
}
|
}
|
||||||
if from >= 0 {
|
if from >= 0 {
|
||||||
return Self(
|
return Self(
|
||||||
@ -298,6 +298,8 @@ macro_rules! reg_range {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
reg_range!(lib | "range" => INT);
|
reg_range!(lib | "range" => INT);
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
@ -43,5 +43,7 @@ mod core_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:LanguageCorePackage:"Language core functions.", lib, {
|
def_package!(crate:LanguageCorePackage:"Language core functions.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "language_core", core_functions);
|
combine_with_exported_module!(lib, "language_core", core_functions);
|
||||||
});
|
});
|
||||||
|
@ -38,6 +38,8 @@ macro_rules! reg_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:LogicPackage:"Logical operators.", lib, {
|
def_package!(crate:LogicPackage:"Logical operators.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
#[cfg(not(feature = "only_i64"))]
|
#[cfg(not(feature = "only_i64"))]
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,8 @@ use std::prelude::v1::*;
|
|||||||
use crate::Array;
|
use crate::Array;
|
||||||
|
|
||||||
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "map", map_functions);
|
combine_with_exported_module!(lib, "map", map_functions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,6 +62,8 @@ macro_rules! reg_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
// Integer functions
|
// Integer functions
|
||||||
combine_with_exported_module!(lib, "int", int_functions);
|
combine_with_exported_module!(lib, "int", int_functions);
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ use std::prelude::v1::*;
|
|||||||
use crate::def_package;
|
use crate::def_package;
|
||||||
|
|
||||||
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
|
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
LanguageCorePackage::init(lib);
|
LanguageCorePackage::init(lib);
|
||||||
ArithmeticPackage::init(lib);
|
ArithmeticPackage::init(lib);
|
||||||
LogicPackage::init(lib);
|
LogicPackage::init(lib);
|
||||||
|
@ -13,6 +13,8 @@ use std::prelude::v1::*;
|
|||||||
use crate::def_package;
|
use crate::def_package;
|
||||||
|
|
||||||
def_package!(crate:StandardPackage:"_Standard_ package containing all built-in features.", lib, {
|
def_package!(crate:StandardPackage:"_Standard_ package containing all built-in features.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
CorePackage::init(lib);
|
CorePackage::init(lib);
|
||||||
BasicMathPackage::init(lib);
|
BasicMathPackage::init(lib);
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
@ -16,6 +16,8 @@ pub const FUNC_TO_STRING: &str = "to_string";
|
|||||||
pub const FUNC_TO_DEBUG: &str = "to_debug";
|
pub const FUNC_TO_DEBUG: &str = "to_debug";
|
||||||
|
|
||||||
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
|
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
|
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
|
||||||
combine_with_exported_module!(lib, "number_formatting", number_formatting);
|
combine_with_exported_module!(lib, "number_formatting", number_formatting);
|
||||||
});
|
});
|
||||||
@ -58,8 +60,8 @@ mod print_debug_functions {
|
|||||||
ctx.engine().map_type_name(&format!("{:?}", item)).into()
|
ctx.engine().map_type_name(&format!("{:?}", item)).into()
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "print", name = "debug")]
|
#[rhai_fn(name = "print", name = "debug")]
|
||||||
pub fn print_empty_string() -> ImmutableString {
|
pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString {
|
||||||
Default::default()
|
ctx.engine().const_empty_string()
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_string(s: ImmutableString) -> ImmutableString {
|
pub fn print_string(s: ImmutableString) -> ImmutableString {
|
||||||
|
@ -9,6 +9,8 @@ use std::{any::TypeId, mem};
|
|||||||
use super::string_basic::{print_with_func, FUNC_TO_STRING};
|
use super::string_basic::{print_with_func, FUNC_TO_STRING};
|
||||||
|
|
||||||
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
|
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
combine_with_exported_module!(lib, "string", string_functions);
|
combine_with_exported_module!(lib, "string", string_functions);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,6 +55,10 @@ mod string_functions {
|
|||||||
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
||||||
string + character
|
string + character
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn add_prepend_char(character: char, string: ImmutableString) -> ImmutableString {
|
||||||
|
format!("{}{}", character, string).into()
|
||||||
|
}
|
||||||
|
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "+", name = "append")]
|
||||||
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
pub fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
|
||||||
|
@ -16,6 +16,8 @@ use std::time::{Duration, Instant};
|
|||||||
use instant::{Duration, Instant};
|
use instant::{Duration, Instant};
|
||||||
|
|
||||||
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||||
|
lib.standard = true;
|
||||||
|
|
||||||
// Register date/time functions
|
// Register date/time functions
|
||||||
combine_with_exported_module!(lib, "time", time_functions);
|
combine_with_exported_module!(lib, "time", time_functions);
|
||||||
});
|
});
|
||||||
|
117
src/parse.rs
117
src/parse.rs
@ -50,12 +50,21 @@ const NEVER_ENDS: &str = "`TokenStream` never ends";
|
|||||||
/// When [`ImmutableString`] is used as [`Identifier`], this type acts as an interner which keeps a
|
/// When [`ImmutableString`] is used as [`Identifier`], this type acts as an interner which keeps a
|
||||||
/// collection of strings and returns shared instances, only creating a new string when it is not
|
/// collection of strings and returns shared instances, only creating a new string when it is not
|
||||||
/// yet interned.
|
/// yet interned.
|
||||||
#[derive(Debug, Clone, Default, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct IdentifierBuilder(
|
pub struct IdentifierBuilder(
|
||||||
#[cfg(feature = "no_smartstring")] std::collections::BTreeSet<Identifier>,
|
#[cfg(feature = "no_smartstring")] std::collections::BTreeSet<Identifier>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl IdentifierBuilder {
|
impl IdentifierBuilder {
|
||||||
|
/// Create a new IdentifierBuilder.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(
|
||||||
|
#[cfg(feature = "no_smartstring")]
|
||||||
|
std::collections::BTreeSet::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
/// Get an identifier from a text string.
|
/// Get an identifier from a text string.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -121,14 +130,14 @@ impl<'e> ParseState<'e> {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
|
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
external_vars: Default::default(),
|
external_vars: BTreeMap::new(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
allow_capture: true,
|
allow_capture: true,
|
||||||
interned_strings: Default::default(),
|
interned_strings: IdentifierBuilder::new(),
|
||||||
stack: Default::default(),
|
stack: StaticVec::new(),
|
||||||
entry_stack_len: 0,
|
entry_stack_len: 0,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
modules: Default::default(),
|
modules: StaticVec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,7 +407,7 @@ fn parse_symbol(input: &mut TokenStream) -> Result<(String, Position), ParseErro
|
|||||||
// Bad identifier
|
// Bad identifier
|
||||||
(Token::LexError(err), pos) => Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => Err(err.into_err(pos)),
|
||||||
// Not a symbol
|
// Not a symbol
|
||||||
(_, pos) => Err(PERR::MissingSymbol(Default::default()).into_err(pos)),
|
(_, pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,7 +911,7 @@ fn parse_map_literal(
|
|||||||
|
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
let name = state.get_identifier(name);
|
let name = state.get_identifier(name);
|
||||||
template.insert(name.clone(), Default::default());
|
template.insert(name.clone(), crate::Dynamic::UNIT);
|
||||||
map.push((Ident { name, pos }, expr));
|
map.push((Ident { name, pos }, expr));
|
||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
@ -1379,6 +1388,7 @@ fn parse_primary(
|
|||||||
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
||||||
}
|
}
|
||||||
// module access
|
// module access
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => {
|
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => {
|
||||||
let (id2, pos2) = parse_var_name(input)?;
|
let (id2, pos2) = parse_var_name(input)?;
|
||||||
let (_, mut namespace, var_name) = *x;
|
let (_, mut namespace, var_name) = *x;
|
||||||
@ -1572,13 +1582,18 @@ fn make_assignment_stmt(
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Index(x, _, _) | Expr::Dot(x, _, _) if parent_is_dot => match x.lhs {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs {
|
||||||
Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
|
Expr::Property(_) if !term => {
|
||||||
|
check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _)))
|
||||||
|
}
|
||||||
|
Expr::Property(_) => None,
|
||||||
|
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
||||||
ref e => Some(e.position()),
|
ref e => Some(e.position()),
|
||||||
},
|
},
|
||||||
Expr::Index(x, _, _) | Expr::Dot(x, _, _) => match x.lhs {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
_ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
|
_ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
|
||||||
|
_ => None,
|
||||||
},
|
},
|
||||||
Expr::Property(_) if parent_is_dot => None,
|
Expr::Property(_) if parent_is_dot => None,
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
@ -1618,19 +1633,30 @@ fn make_assignment_stmt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// xxx[???]... = rhs, xxx.prop... = rhs
|
// xxx[???]... = rhs, xxx.prop... = rhs
|
||||||
Expr::Index(ref x, _, _) | Expr::Dot(ref x, _, _) => {
|
Expr::Index(ref x, term, _) | Expr::Dot(ref x, term, _) => {
|
||||||
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) {
|
let valid_lvalue = if term {
|
||||||
None => match x.lhs {
|
None
|
||||||
// var[???] = rhs, var.??? = rhs
|
} else {
|
||||||
Expr::Variable(_, _, _) => {
|
check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _)))
|
||||||
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
};
|
||||||
|
|
||||||
|
match valid_lvalue {
|
||||||
|
None => {
|
||||||
|
match x.lhs {
|
||||||
|
// var[???] = rhs, var.??? = rhs
|
||||||
|
Expr::Variable(_, _, _) => {
|
||||||
|
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
||||||
|
}
|
||||||
|
// expr[???] = rhs, expr.??? = rhs
|
||||||
|
ref expr => {
|
||||||
|
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||||
|
.into_err(expr.position()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// expr[???] = rhs, expr.??? = rhs
|
}
|
||||||
ref expr => {
|
Some(err_pos) => {
|
||||||
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position()))
|
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(err_pos))
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Some(pos) => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ??? && ??? = rhs, ??? || ??? = rhs
|
// ??? && ??? = rhs, ??? || ??? = rhs
|
||||||
@ -1978,7 +2004,7 @@ fn parse_custom_syntax(
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut settings = settings;
|
let mut settings = settings;
|
||||||
let mut keywords = StaticVec::<Expr>::new();
|
let mut inputs = StaticVec::<Expr>::new();
|
||||||
let mut segments = StaticVec::new();
|
let mut segments = StaticVec::new();
|
||||||
let mut tokens = StaticVec::new();
|
let mut tokens = StaticVec::new();
|
||||||
|
|
||||||
@ -2002,6 +2028,13 @@ fn parse_custom_syntax(
|
|||||||
let settings = settings.level_up();
|
let settings = settings.level_up();
|
||||||
|
|
||||||
required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) {
|
required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) {
|
||||||
|
Ok(Some(seg))
|
||||||
|
if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT)
|
||||||
|
&& seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() =>
|
||||||
|
{
|
||||||
|
inputs.push(Expr::StringConstant(state.get_identifier(seg).into(), pos));
|
||||||
|
break;
|
||||||
|
}
|
||||||
Ok(Some(seg)) => seg,
|
Ok(Some(seg)) => seg,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(err) => return Err(err.0.into_err(settings.pos)),
|
Err(err) => return Err(err.0.into_err(settings.pos)),
|
||||||
@ -2013,24 +2046,24 @@ fn parse_custom_syntax(
|
|||||||
let name = state.get_identifier(name);
|
let name = state.get_identifier(name);
|
||||||
segments.push(name.clone().into());
|
segments.push(name.clone().into());
|
||||||
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT));
|
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT));
|
||||||
keywords.push(Expr::Variable(None, pos, (None, None, name).into()));
|
inputs.push(Expr::Variable(None, pos, (None, None, name).into()));
|
||||||
}
|
}
|
||||||
CUSTOM_SYNTAX_MARKER_SYMBOL => {
|
CUSTOM_SYNTAX_MARKER_SYMBOL => {
|
||||||
let (symbol, pos) = parse_symbol(input)?;
|
let (symbol, pos) = parse_symbol(input)?;
|
||||||
let symbol: ImmutableString = state.get_identifier(symbol).into();
|
let symbol: ImmutableString = state.get_identifier(symbol).into();
|
||||||
segments.push(symbol.clone());
|
segments.push(symbol.clone());
|
||||||
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL));
|
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL));
|
||||||
keywords.push(Expr::StringConstant(symbol, pos));
|
inputs.push(Expr::StringConstant(symbol, pos));
|
||||||
}
|
}
|
||||||
CUSTOM_SYNTAX_MARKER_EXPR => {
|
CUSTOM_SYNTAX_MARKER_EXPR => {
|
||||||
keywords.push(parse_expr(input, state, lib, settings)?);
|
inputs.push(parse_expr(input, state, lib, settings)?);
|
||||||
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR);
|
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR);
|
||||||
segments.push(keyword.clone().into());
|
segments.push(keyword.clone().into());
|
||||||
tokens.push(keyword);
|
tokens.push(keyword);
|
||||||
}
|
}
|
||||||
CUSTOM_SYNTAX_MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
|
CUSTOM_SYNTAX_MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
|
||||||
block @ Stmt::Block(_, _) => {
|
block @ Stmt::Block(_, _) => {
|
||||||
keywords.push(Expr::Stmt(Box::new(block.into())));
|
inputs.push(Expr::Stmt(Box::new(block.into())));
|
||||||
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK);
|
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK);
|
||||||
segments.push(keyword.clone().into());
|
segments.push(keyword.clone().into());
|
||||||
tokens.push(keyword);
|
tokens.push(keyword);
|
||||||
@ -2039,7 +2072,7 @@ fn parse_custom_syntax(
|
|||||||
},
|
},
|
||||||
CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
|
CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
|
||||||
(b @ Token::True, pos) | (b @ Token::False, pos) => {
|
(b @ Token::True, pos) | (b @ Token::False, pos) => {
|
||||||
keywords.push(Expr::BoolConstant(b == Token::True, pos));
|
inputs.push(Expr::BoolConstant(b == Token::True, pos));
|
||||||
segments.push(state.get_identifier(b.literal_syntax()).into());
|
segments.push(state.get_identifier(b.literal_syntax()).into());
|
||||||
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL));
|
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL));
|
||||||
}
|
}
|
||||||
@ -2052,7 +2085,7 @@ fn parse_custom_syntax(
|
|||||||
},
|
},
|
||||||
CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) {
|
CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::IntegerConstant(i), pos) => {
|
(Token::IntegerConstant(i), pos) => {
|
||||||
keywords.push(Expr::IntegerConstant(i, pos));
|
inputs.push(Expr::IntegerConstant(i, pos));
|
||||||
segments.push(i.to_string().into());
|
segments.push(i.to_string().into());
|
||||||
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT));
|
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT));
|
||||||
}
|
}
|
||||||
@ -2066,7 +2099,7 @@ fn parse_custom_syntax(
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
|
CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::FloatConstant(f), pos) => {
|
(Token::FloatConstant(f), pos) => {
|
||||||
keywords.push(Expr::FloatConstant(f, pos));
|
inputs.push(Expr::FloatConstant(f, pos));
|
||||||
segments.push(f.to_string().into());
|
segments.push(f.to_string().into());
|
||||||
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT));
|
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT));
|
||||||
}
|
}
|
||||||
@ -2080,7 +2113,7 @@ fn parse_custom_syntax(
|
|||||||
CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) {
|
CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::StringConstant(s), pos) => {
|
(Token::StringConstant(s), pos) => {
|
||||||
let s: ImmutableString = state.get_identifier(s).into();
|
let s: ImmutableString = state.get_identifier(s).into();
|
||||||
keywords.push(Expr::StringConstant(s.clone(), pos));
|
inputs.push(Expr::StringConstant(s.clone(), pos));
|
||||||
segments.push(s);
|
segments.push(s);
|
||||||
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING));
|
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING));
|
||||||
}
|
}
|
||||||
@ -2105,7 +2138,7 @@ fn parse_custom_syntax(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keywords.shrink_to_fit();
|
inputs.shrink_to_fit();
|
||||||
tokens.shrink_to_fit();
|
tokens.shrink_to_fit();
|
||||||
|
|
||||||
const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax();
|
const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax();
|
||||||
@ -2121,7 +2154,7 @@ fn parse_custom_syntax(
|
|||||||
|
|
||||||
Ok(Expr::Custom(
|
Ok(Expr::Custom(
|
||||||
CustomExpr {
|
CustomExpr {
|
||||||
keywords,
|
inputs,
|
||||||
tokens,
|
tokens,
|
||||||
scope_may_be_changed: syntax.scope_may_be_changed,
|
scope_may_be_changed: syntax.scope_may_be_changed,
|
||||||
self_terminated,
|
self_terminated,
|
||||||
@ -2532,7 +2565,7 @@ fn parse_export(
|
|||||||
}
|
}
|
||||||
(name, pos)
|
(name, pos)
|
||||||
} else {
|
} else {
|
||||||
(Default::default(), Position::NONE)
|
(String::new(), Position::NONE)
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.push((
|
exports.push((
|
||||||
@ -3051,7 +3084,7 @@ fn parse_fn(
|
|||||||
body,
|
body,
|
||||||
lib: None,
|
lib: None,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
mods: Default::default(),
|
mods: crate::engine::Imports::new(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments,
|
comments,
|
||||||
@ -3192,17 +3225,17 @@ fn parse_anon_fn(
|
|||||||
access: FnAccess::Public,
|
access: FnAccess::Public,
|
||||||
params,
|
params,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
externals: Default::default(),
|
externals: std::collections::BTreeSet::new(),
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
lib: None,
|
lib: None,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
mods: Default::default(),
|
mods: crate::engine::Imports::new(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: Default::default(),
|
comments: StaticVec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let fn_ptr = crate::FnPtr::new_unchecked(fn_name, Default::default());
|
let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new());
|
||||||
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos);
|
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -3221,7 +3254,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
|
#[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let _scope = scope;
|
let _scope = scope;
|
||||||
let mut functions = Default::default();
|
let mut functions = BTreeMap::new();
|
||||||
|
|
||||||
let settings = ParseSettings {
|
let settings = ParseSettings {
|
||||||
allow_if_expr: false,
|
allow_if_expr: false,
|
||||||
@ -3254,7 +3287,7 @@ impl Engine {
|
|||||||
self,
|
self,
|
||||||
_scope,
|
_scope,
|
||||||
statements,
|
statements,
|
||||||
Default::default(),
|
StaticVec::new(),
|
||||||
optimization_level,
|
optimization_level,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
23
src/scope.rs
23
src/scope.rs
@ -243,7 +243,7 @@ impl<'a> Scope<'a> {
|
|||||||
access: AccessMode,
|
access: AccessMode,
|
||||||
mut value: Dynamic,
|
mut value: Dynamic,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.names.push((name.into(), Default::default()));
|
self.names.push((name.into(), None));
|
||||||
value.set_access_mode(access);
|
value.set_access_mode(access);
|
||||||
self.values.push(value);
|
self.values.push(value);
|
||||||
self
|
self
|
||||||
@ -307,10 +307,11 @@ impl<'a> Scope<'a> {
|
|||||||
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> {
|
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> {
|
||||||
self.names
|
self.names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
|
.enumerate()
|
||||||
.find_map(|(index, (key, _))| {
|
.find_map(|(index, (key, _))| {
|
||||||
if name == key.as_ref() {
|
if name == key.as_ref() {
|
||||||
|
let index = self.len() - 1 - index;
|
||||||
Some((index, self.values[index].access_mode()))
|
Some((index, self.values[index].access_mode()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -334,10 +335,14 @@ impl<'a> Scope<'a> {
|
|||||||
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
||||||
self.names
|
self.names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev()
|
.rev()
|
||||||
|
.enumerate()
|
||||||
.find(|(_, (key, _))| name == key.as_ref())
|
.find(|(_, (key, _))| name == key.as_ref())
|
||||||
.and_then(|(index, _)| self.values[index].flatten_clone().try_cast())
|
.and_then(|(index, _)| {
|
||||||
|
self.values[self.len() - 1 - index]
|
||||||
|
.flatten_clone()
|
||||||
|
.try_cast()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/// Check if the named entry in the [`Scope`] is constant.
|
/// Check if the named entry in the [`Scope`] is constant.
|
||||||
///
|
///
|
||||||
@ -516,12 +521,14 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
self.names
|
self.names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev()
|
.rev()
|
||||||
.for_each(|(i, (name, alias))| {
|
.enumerate()
|
||||||
|
.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.values.push(self.values[i].clone());
|
entries
|
||||||
|
.values
|
||||||
|
.push(self.values[self.len() - 1 - index].clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -585,7 +592,7 @@ impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
||||||
iter.into_iter().for_each(|(name, value)| {
|
iter.into_iter().for_each(|(name, value)| {
|
||||||
self.names.push((name.into(), Default::default()));
|
self.names.push((name.into(), None));
|
||||||
self.values.push(value);
|
self.values.push(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>(
|
|||||||
|
|
||||||
impl Error for Box<EvalAltResult> {
|
impl Error for Box<EvalAltResult> {
|
||||||
fn custom<T: fmt::Display>(err: T) -> Self {
|
fn custom<T: fmt::Display>(err: T) -> Self {
|
||||||
LexError::ImproperSymbol(Default::default(), err.to_string())
|
LexError::ImproperSymbol(String::new(), err.to_string())
|
||||||
.into_err(Position::NONE)
|
.into_err(Position::NONE)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
|
|||||||
.to_vec()
|
.to_vec()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Vec::new()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,7 +198,7 @@ impl From<crate::ast::ScriptFnMetadata<'_>> for FnMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ModuleMetadata {
|
struct ModuleMetadata {
|
||||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
@ -210,7 +210,10 @@ struct ModuleMetadata {
|
|||||||
impl ModuleMetadata {
|
impl ModuleMetadata {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Self {
|
||||||
|
modules: BTreeMap::new(),
|
||||||
|
functions: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,6 +284,6 @@ impl Engine {
|
|||||||
/// 2) Functions in static modules
|
/// 2) Functions in static modules
|
||||||
/// 3) Functions in global modules (optional)
|
/// 3) Functions in global modules (optional)
|
||||||
pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result<String> {
|
pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result<String> {
|
||||||
self.gen_fn_metadata_with_ast_to_json(&Default::default(), include_global)
|
self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl DynamicSerializer {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(_value: Dynamic) -> Self {
|
pub fn new(_value: Dynamic) -> Self {
|
||||||
Self {
|
Self {
|
||||||
_key: Default::default(),
|
_key: Dynamic::UNIT,
|
||||||
_value,
|
_value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ impl DynamicSerializer {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to_dynamic<T: Serialize>(value: T) -> RhaiResult {
|
pub fn to_dynamic<T: Serialize>(value: T) -> RhaiResult {
|
||||||
let mut s = DynamicSerializer::new(Default::default());
|
let mut s = DynamicSerializer::new(Dynamic::UNIT);
|
||||||
value.serialize(&mut s)
|
value.serialize(&mut s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +397,7 @@ impl Serializer for &mut DynamicSerializer {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
return Ok(StructVariantSerializer {
|
return Ok(StructVariantSerializer {
|
||||||
variant: _variant,
|
variant: _variant,
|
||||||
map: Default::default(),
|
map: Map::new(),
|
||||||
});
|
});
|
||||||
#[cfg(feature = "no_object")]
|
#[cfg(feature = "no_object")]
|
||||||
return Err(EvalAltResult::ErrorMismatchDataType(
|
return Err(EvalAltResult::ErrorMismatchDataType(
|
||||||
|
@ -36,4 +36,12 @@ fn check_struct_sizes() {
|
|||||||
if cfg!(feature = "no_position") { 8 } else { 16 }
|
if cfg!(feature = "no_position") { 8 } else { 16 }
|
||||||
);
|
);
|
||||||
assert_eq!(size_of::<EvalAltResult>(), 72);
|
assert_eq!(size_of::<EvalAltResult>(), 72);
|
||||||
|
assert_eq!(
|
||||||
|
size_of::<NativeCallContext>(),
|
||||||
|
if cfg!(feature = "no_position") {
|
||||||
|
64
|
||||||
|
} else {
|
||||||
|
72
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
73
src/token.rs
73
src/token.rs
@ -33,13 +33,24 @@ use crate::engine::KEYWORD_IS_DEF_FN;
|
|||||||
/// # Volatile Data Structure
|
/// # Volatile Data Structure
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
|
||||||
pub struct TokenizerControlBlock {
|
pub struct TokenizerControlBlock {
|
||||||
/// Is the current tokenizer position within an interpolated text string?
|
/// Is the current tokenizer position within an interpolated text string?
|
||||||
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
|
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
|
||||||
pub is_within_text: bool,
|
pub is_within_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TokenizerControlBlock {
|
||||||
|
/// Create a new `TokenizerControlBlock`.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
is_within_text: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// _(internals)_ A shared object that allows control of the tokenizer from outside.
|
/// _(internals)_ A shared object that allows control of the tokenizer from outside.
|
||||||
pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
|
pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
|
||||||
|
|
||||||
@ -805,55 +816,56 @@ impl Token {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
LexError(_) |
|
LexError(_) |
|
||||||
LeftBrace | // {+expr} - is unary
|
SemiColon | // ; - is unary
|
||||||
// RightBrace | {expr} - expr not unary & is closing
|
Comma | // ( ... , -expr ) - is unary
|
||||||
LeftParen | // (-expr) - is unary
|
//Period |
|
||||||
// RightParen | (expr) - expr not unary & is closing
|
LeftBrace | // { -expr } - is unary
|
||||||
LeftBracket | // [-expr] - is unary
|
// RightBrace | { expr } - expr not unary & is closing
|
||||||
// RightBracket | [expr] - expr not unary & is closing
|
LeftParen | // ( -expr ) - is unary
|
||||||
|
// RightParen | // ( expr ) - expr not unary & is closing
|
||||||
|
LeftBracket | // [ -expr ] - is unary
|
||||||
|
// RightBracket | // [ expr ] - expr not unary & is closing
|
||||||
Plus |
|
Plus |
|
||||||
|
PlusAssign |
|
||||||
UnaryPlus |
|
UnaryPlus |
|
||||||
Minus |
|
Minus |
|
||||||
|
MinusAssign |
|
||||||
UnaryMinus |
|
UnaryMinus |
|
||||||
Multiply |
|
Multiply |
|
||||||
|
MultiplyAssign |
|
||||||
Divide |
|
Divide |
|
||||||
Comma |
|
DivideAssign |
|
||||||
Period |
|
Modulo |
|
||||||
|
ModuloAssign |
|
||||||
|
PowerOf |
|
||||||
|
PowerOfAssign |
|
||||||
|
LeftShift |
|
||||||
|
LeftShiftAssign |
|
||||||
|
RightShift |
|
||||||
|
RightShiftAssign |
|
||||||
Equals |
|
Equals |
|
||||||
|
EqualsTo |
|
||||||
|
NotEqualsTo |
|
||||||
LessThan |
|
LessThan |
|
||||||
GreaterThan |
|
GreaterThan |
|
||||||
Bang |
|
Bang |
|
||||||
LessThanEqualsTo |
|
LessThanEqualsTo |
|
||||||
GreaterThanEqualsTo |
|
GreaterThanEqualsTo |
|
||||||
EqualsTo |
|
|
||||||
NotEqualsTo |
|
|
||||||
Pipe |
|
Pipe |
|
||||||
Or |
|
|
||||||
Ampersand |
|
Ampersand |
|
||||||
And |
|
|
||||||
If |
|
If |
|
||||||
Do |
|
//Do |
|
||||||
While |
|
While |
|
||||||
Until |
|
Until |
|
||||||
PlusAssign |
|
In |
|
||||||
MinusAssign |
|
And |
|
||||||
MultiplyAssign |
|
|
||||||
DivideAssign |
|
|
||||||
LeftShiftAssign |
|
|
||||||
RightShiftAssign |
|
|
||||||
PowerOf |
|
|
||||||
PowerOfAssign |
|
|
||||||
AndAssign |
|
AndAssign |
|
||||||
|
Or |
|
||||||
OrAssign |
|
OrAssign |
|
||||||
XOrAssign |
|
|
||||||
LeftShift |
|
|
||||||
RightShift |
|
|
||||||
XOr |
|
XOr |
|
||||||
Modulo |
|
XOrAssign |
|
||||||
ModuloAssign |
|
|
||||||
Return |
|
Return |
|
||||||
Throw |
|
Throw => true,
|
||||||
In => true,
|
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -1829,6 +1841,7 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
('=', _) => return Some((Token::Equals, start_pos)),
|
('=', _) => return Some((Token::Equals, start_pos)),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
(':', ':') => {
|
(':', ':') => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
|
|
||||||
@ -2279,7 +2292,7 @@ impl Engine {
|
|||||||
input: impl IntoIterator<Item = &'a &'a str>,
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
token_mapper: Option<&'a OnParseTokenCallback>,
|
token_mapper: Option<&'a OnParseTokenCallback>,
|
||||||
) -> (TokenIterator<'a>, TokenizerControl) {
|
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||||
let buffer: TokenizerControl = Default::default();
|
let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into();
|
||||||
let buffer2 = buffer.clone();
|
let buffer2 = buffer.clone();
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -194,8 +194,9 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
0 => unreachable!(),
|
0 => unreachable!(),
|
||||||
1 => Ok(Some("$ident$".into())),
|
1 => Ok(Some("$ident$".into())),
|
||||||
2 => match stream[1].as_str() {
|
2 => match stream[1].as_str() {
|
||||||
"world" | "kitty" => Ok(None),
|
"world" => Ok(Some("$$hello".into())),
|
||||||
s => Err(LexError::ImproperSymbol(s.to_string(), Default::default())
|
"kitty" => Ok(None),
|
||||||
|
s => Err(LexError::ImproperSymbol(s.to_string(), String::new())
|
||||||
.into_err(Position::NONE)
|
.into_err(Position::NONE)
|
||||||
.into()),
|
.into()),
|
||||||
},
|
},
|
||||||
@ -206,7 +207,17 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
context.scope_mut().push("foo", 999 as INT);
|
context.scope_mut().push("foo", 999 as INT);
|
||||||
|
|
||||||
Ok(match inputs[0].get_variable_name().unwrap() {
|
Ok(match inputs[0].get_variable_name().unwrap() {
|
||||||
|
"world"
|
||||||
|
if inputs
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.get_literal_value::<ImmutableString>()
|
||||||
|
.map_or(false, |s| s == "$$hello") =>
|
||||||
|
{
|
||||||
|
0 as INT
|
||||||
|
}
|
||||||
"world" => 123 as INT,
|
"world" => 123 as INT,
|
||||||
|
"kitty" if inputs.len() > 1 => 999 as INT,
|
||||||
"kitty" => 42 as INT,
|
"kitty" => 42 as INT,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
@ -214,7 +225,7 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("hello world")?, 123);
|
assert_eq!(engine.eval::<INT>("hello world")?, 0);
|
||||||
assert_eq!(engine.eval::<INT>("hello kitty")?, 42);
|
assert_eq!(engine.eval::<INT>("hello kitty")?, 42);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("let foo = 0; (hello kitty) + foo")?,
|
engine.eval::<INT>("let foo = 0; (hello kitty) + foo")?,
|
||||||
|
@ -59,7 +59,7 @@ fn test_expressions_eval() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.register_get("gender", AGENT::get_gender);
|
engine.register_get("gender", AGENT::get_gender);
|
||||||
engine.register_get("age", AGENT::get_age);
|
engine.register_get("age", AGENT::get_age);
|
||||||
|
|
||||||
// Create your context, add the agent as a constant
|
// Create your scope, add the agent as a constant
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
scope.push_constant("agent", my_agent);
|
scope.push_constant("agent", my_agent);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, FnNamespace, Module, Shared, INT};
|
use rhai::{Engine, EvalAltResult, FnNamespace, Module, NativeCallContext, Shared, INT};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[test]
|
#[test]
|
||||||
@ -51,33 +51,95 @@ fn test_functions_namespaces() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
engine.register_static_module("hello", m.into());
|
engine.register_static_module("hello", m.into());
|
||||||
|
|
||||||
|
let mut m = Module::new();
|
||||||
|
m.set_var("ANSWER", 123 as INT);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("test()")?, 999);
|
assert_eq!(engine.eval::<INT>("test()")?, 999);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
assert_eq!(engine.eval::<INT>("fn test() { 123 } test()")?, 123);
|
assert_eq!(engine.eval::<INT>("fn test() { 123 } test()")?, 123);
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.register_fn("test", || 42 as INT);
|
engine.register_fn("test", || 42 as INT);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("fn test() { 123 } test()")?, 123);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("test()")?, 42);
|
assert_eq!(engine.eval::<INT>("test()")?, 42);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
Ok(())
|
||||||
{
|
}
|
||||||
assert_eq!(engine.eval::<INT>("fn test() { 123 } test()")?, 123);
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
assert_eq!(
|
#[test]
|
||||||
engine.eval::<INT>(
|
fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
|
||||||
"
|
let mut engine = Engine::new();
|
||||||
const ANSWER = 42;
|
|
||||||
|
assert_eq!(
|
||||||
fn foo() { global::ANSWER }
|
engine.eval::<INT>(
|
||||||
|
"
|
||||||
foo()
|
const ANSWER = 42;
|
||||||
"
|
fn foo() { global::ANSWER }
|
||||||
)?,
|
foo()
|
||||||
42
|
"
|
||||||
);
|
)?,
|
||||||
}
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(*engine.run("
|
||||||
|
fn foo() { global::ANSWER }
|
||||||
|
|
||||||
|
{
|
||||||
|
const ANSWER = 42;
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
").expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorInFunctionCall(_, _, err, _)
|
||||||
|
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::ANSWER")
|
||||||
|
));
|
||||||
|
|
||||||
|
engine.register_result_fn(
|
||||||
|
"do_stuff",
|
||||||
|
|context: NativeCallContext, callback: rhai::FnPtr| {
|
||||||
|
callback.call_dynamic(&context, None, [])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
assert!(matches!(*engine.run("
|
||||||
|
do_stuff(|| {
|
||||||
|
const LOCAL_VALUE = 42;
|
||||||
|
global::LOCAL_VALUE
|
||||||
|
});
|
||||||
|
").expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorInFunctionCall(_, _, err, _)
|
||||||
|
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::LOCAL_VALUE")
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
"
|
||||||
|
const GLOBAL_VALUE = 42;
|
||||||
|
do_stuff(|| global::GLOBAL_VALUE);
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
// Override global
|
||||||
|
let mut module = Module::new();
|
||||||
|
module.set_var("ANSWER", 123 as INT);
|
||||||
|
engine.register_static_module("global", module.into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
"
|
||||||
|
const ANSWER = 42;
|
||||||
|
fn foo() { global::ANSWER }
|
||||||
|
foo()
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
123
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -444,7 +444,7 @@ fn test_module_ast_namespace() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile(script)?;
|
let ast = engine.compile(script)?;
|
||||||
|
|
||||||
let module = Module::eval_ast_as_new(Default::default(), &ast, &engine)?;
|
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||||
|
|
||||||
let mut resolver = StaticModuleResolver::new();
|
let mut resolver = StaticModuleResolver::new();
|
||||||
resolver.insert("testing", module);
|
resolver.insert("testing", module);
|
||||||
@ -512,6 +512,6 @@ fn test_module_file() -> Result<(), Box<EvalAltResult>> {
|
|||||||
print("top");
|
print("top");
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
Module::eval_ast_as_new(Default::default(), &ast, &engine)?;
|
Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, INT};
|
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, NativeCallContext, INT};
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -49,3 +49,41 @@ fn test_native_context_fn_name() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"let x = "hello, "; let y = "world"; x + y"#)?,
|
||||||
|
"hello, world"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"let x = "hello"; let y = (); x + y"#)?,
|
||||||
|
"hello"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Overload the `+` operator for strings
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_fn(
|
||||||
|
"+",
|
||||||
|
|s1: ImmutableString, s2: ImmutableString| -> ImmutableString {
|
||||||
|
format!("{}***{}", s1, s2).into()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.register_fn("+", |s1: ImmutableString, _: ()| -> ImmutableString {
|
||||||
|
format!("{} Foo!", s1).into()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
|
||||||
|
"hello***world"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"let x = "hello"; let y = (); x + y"#)?,
|
||||||
|
"hello Foo!"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user