Merge pull request #576 from schungx/master
Elvis and coalescing operators.
This commit is contained in:
commit
a7098fb074
15
CHANGELOG.md
15
CHANGELOG.md
@ -12,15 +12,30 @@ Bug fixes
|
|||||||
* Parsing of index expressions is relaxed and many cases no longer result in an index-type error to allow for custom indexers.
|
* Parsing of index expressions is relaxed and many cases no longer result in an index-type error to allow for custom indexers.
|
||||||
* Merging or combining a self-contained `AST` into another `AST` now works properly.
|
* Merging or combining a self-contained `AST` into another `AST` now works properly.
|
||||||
* Plugin modules/functions no longer generate errors under `#![deny(missing_docs)]`.
|
* Plugin modules/functions no longer generate errors under `#![deny(missing_docs)]`.
|
||||||
|
* Calling a property on a function call that returns a shared value no longer causes an error.
|
||||||
|
* _Strict Variables Mode_ now checks for module namespaces within functions as well.
|
||||||
|
* Module defined via `Engine::register_static_module` are now checked in _Strict Variables Mode_.
|
||||||
|
|
||||||
|
Reserved Symbols
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* `?`, `??`, `?.` and `!.` are now reserved symbols.
|
||||||
|
|
||||||
Deprecated API's
|
Deprecated API's
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
* `FnPtr::num_curried` is deprecated in favor of `FnPtr::curry().len()`.
|
* `FnPtr::num_curried` is deprecated in favor of `FnPtr::curry().len()`.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* The _Elvis operator_ (`?.`) is now supported for property access and method calls.
|
||||||
|
* The _null-coalescing operator_ (`??`) is now supported to short-circuit `()` values.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* Indexing and property access are now faster.
|
||||||
* `EvalAltResult::IndexNotFound` is added to aid in raising errors for indexers.
|
* `EvalAltResult::IndexNotFound` is added to aid in raising errors for indexers.
|
||||||
* `Engine::def_tag`, `Engine::def_tag_mut` and `Engine::set_tag` are added to manage a default value for the custom evaluation state, accessible via `EvalState::tag()` (which is the same as `NativeCallContext::tag()`).
|
* `Engine::def_tag`, `Engine::def_tag_mut` and `Engine::set_tag` are added to manage a default value for the custom evaluation state, accessible via `EvalState::tag()` (which is the same as `NativeCallContext::tag()`).
|
||||||
* Originally, the debugger's custom state uses the same state as `EvalState::tag()` (which is the same as `NativeCallContext::tag()`). It is now split into its own variable accessible under `Debugger::state()`.
|
* Originally, the debugger's custom state uses the same state as `EvalState::tag()` (which is the same as `NativeCallContext::tag()`). It is now split into its own variable accessible under `Debugger::state()`.
|
||||||
|
@ -162,7 +162,7 @@ impl Engine {
|
|||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let mut arg_values = arg_values;
|
let mut arg_values = arg_values;
|
||||||
|
|
||||||
self.call_fn_internal(
|
self._call_fn(
|
||||||
scope,
|
scope,
|
||||||
&mut GlobalRuntimeState::new(self),
|
&mut GlobalRuntimeState::new(self),
|
||||||
&mut Caches::new(),
|
&mut Caches::new(),
|
||||||
@ -215,7 +215,7 @@ impl Engine {
|
|||||||
this_ptr: Option<&mut Dynamic>,
|
this_ptr: Option<&mut Dynamic>,
|
||||||
arg_values: &mut [Dynamic],
|
arg_values: &mut [Dynamic],
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
self.call_fn_internal(
|
self._call_fn(
|
||||||
scope,
|
scope,
|
||||||
global,
|
global,
|
||||||
caches,
|
caches,
|
||||||
@ -228,7 +228,7 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
|
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
|
||||||
fn call_fn_internal(
|
fn _call_fn(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
|
@ -6,9 +6,9 @@ use crate::types::dynamic::Variant;
|
|||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
|
||||||
};
|
};
|
||||||
|
use std::any::type_name;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{any::type_name, mem};
|
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Evaluate a string.
|
/// Evaluate a string.
|
||||||
@ -222,7 +222,7 @@ impl Engine {
|
|||||||
global.source = ast.source_raw().clone();
|
global.source = ast.source_raw().clone();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_embedded_module_resolver = mem::replace(
|
let orig_embedded_module_resolver = std::mem::replace(
|
||||||
&mut global.embedded_module_resolver,
|
&mut global.embedded_module_resolver,
|
||||||
ast.resolver().cloned(),
|
ast.resolver().cloned(),
|
||||||
);
|
);
|
||||||
|
@ -68,7 +68,7 @@ impl Engine {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[deprecated = "This API is volatile and may change in the future."]
|
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_var(
|
pub fn on_var(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -130,7 +130,7 @@ impl Engine {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[deprecated = "This API is volatile and may change in the future."]
|
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_def_var(
|
pub fn on_def_var(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -188,7 +188,7 @@ impl Engine {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[deprecated = "This API is volatile and may change in the future."]
|
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_parse_token(
|
pub fn on_parse_token(
|
||||||
@ -343,7 +343,7 @@ impl Engine {
|
|||||||
/// # WARNING - Unstable API
|
/// # WARNING - Unstable API
|
||||||
///
|
///
|
||||||
/// This API is volatile and may change in the future.
|
/// This API is volatile and may change in the future.
|
||||||
#[deprecated = "This API is volatile and may change in the future."]
|
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn register_debugger(
|
pub fn register_debugger(
|
||||||
|
@ -809,30 +809,21 @@ impl AST {
|
|||||||
/// Return `false` from the callback to terminate the walk.
|
/// Return `false` from the callback to terminate the walk.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
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 Vec::new();
|
self._walk(on_node)
|
||||||
|
|
||||||
for stmt in self.statements() {
|
|
||||||
if !stmt.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) {
|
|
||||||
if !stmt.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
/// _(internals)_ Recursively walk the [`AST`], including function bodies (if any).
|
/// _(internals)_ Recursively walk the [`AST`], including function bodies (if any).
|
||||||
/// Return `false` from the callback to terminate the walk.
|
/// Return `false` from the callback to terminate the walk.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||||
|
self._walk(on_node)
|
||||||
|
}
|
||||||
|
/// Recursively walk the [`AST`], including function bodies (if any).
|
||||||
|
/// Return `false` from the callback to terminate the walk.
|
||||||
|
fn _walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||||
let path = &mut Vec::new();
|
let path = &mut Vec::new();
|
||||||
|
|
||||||
for stmt in self.statements() {
|
for stmt in self.statements() {
|
||||||
|
@ -400,19 +400,25 @@ pub enum Expr {
|
|||||||
Stmt(Box<StmtBlock>),
|
Stmt(Box<StmtBlock>),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
FnCall(Box<FnCallExpr>, Position),
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
/// lhs `.` rhs
|
/// lhs `.` rhs | lhs `?.` rhs
|
||||||
|
///
|
||||||
|
/// ### Flags
|
||||||
|
///
|
||||||
|
/// [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset)
|
||||||
|
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||||
Dot(Box<BinaryExpr>, ASTFlags, Position),
|
Dot(Box<BinaryExpr>, ASTFlags, Position),
|
||||||
/// lhs `[` rhs `]`
|
/// lhs `[` rhs `]`
|
||||||
///
|
///
|
||||||
/// ### Flags
|
/// ### Flags
|
||||||
///
|
///
|
||||||
/// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain
|
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain
|
|
||||||
Index(Box<BinaryExpr>, ASTFlags, Position),
|
Index(Box<BinaryExpr>, ASTFlags, Position),
|
||||||
/// lhs `&&` rhs
|
/// lhs `&&` rhs
|
||||||
And(Box<BinaryExpr>, Position),
|
And(Box<BinaryExpr>, Position),
|
||||||
/// lhs `||` rhs
|
/// lhs `||` rhs
|
||||||
Or(Box<BinaryExpr>, Position),
|
Or(Box<BinaryExpr>, Position),
|
||||||
|
/// lhs `??` rhs
|
||||||
|
Coalesce(Box<BinaryExpr>, Position),
|
||||||
/// Custom syntax
|
/// Custom syntax
|
||||||
Custom(Box<CustomExpr>, Position),
|
Custom(Box<CustomExpr>, Position),
|
||||||
}
|
}
|
||||||
@ -480,26 +486,38 @@ impl fmt::Debug for Expr {
|
|||||||
f.debug_list().entries(x.iter()).finish()
|
f.debug_list().entries(x.iter()).finish()
|
||||||
}
|
}
|
||||||
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
||||||
Self::Index(x, term, pos) => {
|
Self::Index(x, options, pos) => {
|
||||||
if !pos.is_none() {
|
if !pos.is_none() {
|
||||||
display_pos = format!(" @ {:?}", pos);
|
display_pos = format!(" @ {:?}", pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.debug_struct("Index")
|
let mut f = f.debug_struct("Index");
|
||||||
.field("lhs", &x.lhs)
|
|
||||||
.field("rhs", &x.rhs)
|
f.field("lhs", &x.lhs).field("rhs", &x.rhs);
|
||||||
.field("terminate", term)
|
if !options.is_empty() {
|
||||||
.finish()
|
f.field("options", options);
|
||||||
|
}
|
||||||
|
f.finish()
|
||||||
}
|
}
|
||||||
Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
|
Self::Dot(x, options, pos) => {
|
||||||
|
if !pos.is_none() {
|
||||||
|
display_pos = format!(" @ {:?}", pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut f = f.debug_struct("Dot");
|
||||||
|
|
||||||
|
f.field("lhs", &x.lhs).field("rhs", &x.rhs);
|
||||||
|
if !options.is_empty() {
|
||||||
|
f.field("options", options);
|
||||||
|
}
|
||||||
|
f.finish()
|
||||||
|
}
|
||||||
|
Self::And(x, pos) | Self::Or(x, pos) | Self::Coalesce(x, pos) => {
|
||||||
let op_name = match self {
|
let op_name = match self {
|
||||||
Self::Dot(..) => "Dot",
|
|
||||||
Self::And(..) => "And",
|
Self::And(..) => "And",
|
||||||
Self::Or(..) => "Or",
|
Self::Or(..) => "Or",
|
||||||
expr => unreachable!(
|
Self::Coalesce(..) => "Coalesce",
|
||||||
"Self::Dot or Self::And or Self::Or expected but gets {:?}",
|
expr => unreachable!("`And`, `Or` or `Coalesce` expected but gets {:?}", expr),
|
||||||
expr
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !pos.is_none() {
|
if !pos.is_none() {
|
||||||
@ -681,6 +699,7 @@ impl Expr {
|
|||||||
| Self::Variable(.., pos)
|
| Self::Variable(.., pos)
|
||||||
| Self::And(.., pos)
|
| Self::And(.., pos)
|
||||||
| Self::Or(.., pos)
|
| Self::Or(.., pos)
|
||||||
|
| Self::Coalesce(.., pos)
|
||||||
| Self::Index(.., pos)
|
| Self::Index(.., pos)
|
||||||
| Self::Dot(.., pos)
|
| Self::Dot(.., pos)
|
||||||
| Self::Custom(.., pos)
|
| Self::Custom(.., pos)
|
||||||
@ -706,10 +725,15 @@ impl Expr {
|
|||||||
self.position()
|
self.position()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::And(x, ..) | Self::Or(x, ..) | Self::Index(x, ..) | Self::Dot(x, ..) => {
|
|
||||||
x.lhs.start_position()
|
Self::And(x, ..)
|
||||||
}
|
| Self::Or(x, ..)
|
||||||
|
| Self::Coalesce(x, ..)
|
||||||
|
| Self::Index(x, ..)
|
||||||
|
| Self::Dot(x, ..) => x.lhs.start_position(),
|
||||||
|
|
||||||
Self::FnCall(.., pos) => *pos,
|
Self::FnCall(.., pos) => *pos,
|
||||||
|
|
||||||
_ => self.position(),
|
_ => self.position(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -730,6 +754,7 @@ impl Expr {
|
|||||||
| Self::Map(.., pos)
|
| Self::Map(.., pos)
|
||||||
| Self::And(.., pos)
|
| Self::And(.., pos)
|
||||||
| Self::Or(.., pos)
|
| Self::Or(.., pos)
|
||||||
|
| Self::Coalesce(.., pos)
|
||||||
| Self::Dot(.., pos)
|
| Self::Dot(.., pos)
|
||||||
| Self::Index(.., pos)
|
| Self::Index(.., pos)
|
||||||
| Self::Variable(.., pos)
|
| Self::Variable(.., pos)
|
||||||
@ -755,7 +780,9 @@ impl Expr {
|
|||||||
|
|
||||||
Self::Map(x, ..) => x.0.iter().map(|(.., v)| v).all(Self::is_pure),
|
Self::Map(x, ..) => x.0.iter().map(|(.., v)| v).all(Self::is_pure),
|
||||||
|
|
||||||
Self::And(x, ..) | Self::Or(x, ..) => x.lhs.is_pure() && x.rhs.is_pure(),
|
Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => {
|
||||||
|
x.lhs.is_pure() && x.rhs.is_pure()
|
||||||
|
}
|
||||||
|
|
||||||
Self::Stmt(x) => x.iter().all(Stmt::is_pure),
|
Self::Stmt(x) => x.iter().all(Stmt::is_pure),
|
||||||
|
|
||||||
@ -798,7 +825,7 @@ impl Expr {
|
|||||||
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
|
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||||
match token {
|
match token {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => return true,
|
Token::Period | Token::Elvis => return true,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => return true,
|
Token::LeftBracket => return true,
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -813,6 +840,7 @@ impl Expr {
|
|||||||
| Self::CharConstant(..)
|
| Self::CharConstant(..)
|
||||||
| Self::And(..)
|
| Self::And(..)
|
||||||
| Self::Or(..)
|
| Self::Or(..)
|
||||||
|
| Self::Coalesce(..)
|
||||||
| Self::Unit(..) => false,
|
| Self::Unit(..) => false,
|
||||||
|
|
||||||
Self::IntegerConstant(..)
|
Self::IntegerConstant(..)
|
||||||
@ -877,7 +905,11 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Index(x, ..) | Self::Dot(x, ..) | Expr::And(x, ..) | Expr::Or(x, ..) => {
|
Self::Index(x, ..)
|
||||||
|
| Self::Dot(x, ..)
|
||||||
|
| Expr::And(x, ..)
|
||||||
|
| Expr::Or(x, ..)
|
||||||
|
| Expr::Coalesce(x, ..) => {
|
||||||
if !x.lhs.walk(path, on_node) {
|
if !x.lhs.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
use super::{Caches, GlobalRuntimeState, Target};
|
use super::{Caches, GlobalRuntimeState, Target};
|
||||||
use crate::ast::{ASTFlags, Expr, OpAssignment};
|
use crate::ast::{ASTFlags, Expr, OpAssignment};
|
||||||
use crate::types::dynamic::Union;
|
use crate::types::dynamic::Union;
|
||||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
|
use crate::{Dynamic, Engine, FnArgsVec, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -33,92 +33,9 @@ impl From<&Expr> for ChainType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Value of a chaining argument.
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
|
||||||
pub enum ChainArgument {
|
|
||||||
/// Dot-property access.
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
Property(Position),
|
|
||||||
/// Arguments to a dot method call.
|
|
||||||
/// Wrapped values are the arguments plus the [position][Position] of the first argument.
|
|
||||||
///
|
|
||||||
/// Since many dotted function calls have no arguments (e.g. `string.len()`), it is better to
|
|
||||||
/// reduce the size of [`ChainArgument`] by using a boxed slice.
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
MethodCallArgs(Box<[Dynamic]>, Position),
|
|
||||||
/// Index value and [position][Position].
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
IndexValue(Dynamic, Position),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChainArgument {
|
|
||||||
/// Return the index value.
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_index_value(self) -> Option<Dynamic> {
|
|
||||||
match self {
|
|
||||||
Self::IndexValue(value, ..) => Some(value),
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Return the list of method call arguments.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the [`ChainArgument`] is not [`MethodCallArgs`][ChainArgument::MethodCallArgs].
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_fn_call_args(self) -> (crate::FnArgsVec<Dynamic>, Position) {
|
|
||||||
match self {
|
|
||||||
Self::MethodCallArgs(values, pos) if values.is_empty() => {
|
|
||||||
(crate::FnArgsVec::new_const(), pos)
|
|
||||||
}
|
|
||||||
Self::MethodCallArgs(mut values, pos) => {
|
|
||||||
(values.iter_mut().map(std::mem::take).collect(), pos)
|
|
||||||
}
|
|
||||||
x => unreachable!("ChainArgument::MethodCallArgs expected but gets {:?}", x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Return the [position][Position].
|
|
||||||
#[inline(always)]
|
|
||||||
#[must_use]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const fn position(&self) -> Position {
|
|
||||||
match self {
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
ChainArgument::Property(pos) => *pos,
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
ChainArgument::MethodCallArgs(.., pos) => *pos,
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
ChainArgument::IndexValue(.., pos) => *pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Create n [`MethodCallArgs`][ChainArgument::MethodCallArgs].
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
#[must_use]
|
|
||||||
pub fn from_fn_call_args(values: crate::FnArgsVec<Dynamic>, pos: Position) -> Self {
|
|
||||||
if values.is_empty() {
|
|
||||||
Self::MethodCallArgs(Box::default(), pos)
|
|
||||||
} else {
|
|
||||||
Self::MethodCallArgs(values.into_boxed_slice(), pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Create an [`IndexValue`][ChainArgument::IndexValue].
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
#[must_use]
|
|
||||||
pub const fn from_index_value(value: Dynamic, pos: Position) -> Self {
|
|
||||||
Self::IndexValue(value, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Chain-evaluate a dot/index chain.
|
/// Chain-evaluate a dot/index chain.
|
||||||
/// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and must be set afterwards.
|
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
||||||
fn eval_dot_index_chain_helper(
|
fn eval_dot_index_chain_helper(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
@ -130,16 +47,13 @@ impl Engine {
|
|||||||
_parent: &Expr,
|
_parent: &Expr,
|
||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
_parent_options: ASTFlags,
|
_parent_options: ASTFlags,
|
||||||
idx_values: &mut StaticVec<ChainArgument>,
|
idx_values: &mut FnArgsVec<Dynamic>,
|
||||||
chain_type: ChainType,
|
chain_type: ChainType,
|
||||||
level: usize,
|
level: usize,
|
||||||
new_val: Option<(Dynamic, OpAssignment)>,
|
new_val: Option<(Dynamic, OpAssignment)>,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
let is_ref_mut = target.is_ref();
|
let is_ref_mut = target.is_ref();
|
||||||
|
|
||||||
// Pop the last index value
|
|
||||||
let idx_val = idx_values.pop().unwrap();
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let scope = &mut Scope::new();
|
let scope = &mut Scope::new();
|
||||||
|
|
||||||
@ -147,7 +61,6 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ChainType::Indexing => {
|
ChainType::Indexing => {
|
||||||
let pos = rhs.start_position();
|
let pos = rhs.start_position();
|
||||||
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
|
||||||
|
|
||||||
match rhs {
|
match rhs {
|
||||||
// xxx[idx].expr... | xxx[idx][expr]...
|
// xxx[idx].expr... | xxx[idx][expr]...
|
||||||
@ -157,6 +70,7 @@ impl Engine {
|
|||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, lib, this_ptr, _parent, level)?;
|
||||||
|
|
||||||
|
let idx_val = idx_values.pop().unwrap();
|
||||||
let mut idx_val_for_setter = idx_val.clone();
|
let mut idx_val_for_setter = idx_val.clone();
|
||||||
let idx_pos = x.lhs.start_position();
|
let idx_pos = x.lhs.start_position();
|
||||||
let rhs_chain = rhs.into();
|
let rhs_chain = rhs.into();
|
||||||
@ -201,6 +115,7 @@ impl Engine {
|
|||||||
self.run_debugger(scope, global, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, lib, this_ptr, _parent, level)?;
|
||||||
|
|
||||||
let (new_val, op_info) = new_val.expect("`Some`");
|
let (new_val, op_info) = new_val.expect("`Some`");
|
||||||
|
let idx_val = idx_values.pop().unwrap();
|
||||||
let mut idx_val2 = idx_val.clone();
|
let mut idx_val2 = idx_val.clone();
|
||||||
|
|
||||||
let try_setter = match self.get_indexed_mut(
|
let try_setter = match self.get_indexed_mut(
|
||||||
@ -259,6 +174,8 @@ impl Engine {
|
|||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, lib, this_ptr, _parent, level)?;
|
||||||
|
|
||||||
|
let idx_val = idx_values.pop().unwrap();
|
||||||
|
|
||||||
self.get_indexed_mut(
|
self.get_indexed_mut(
|
||||||
global, caches, lib, target, idx_val, pos, false, true, level,
|
global, caches, lib, target, idx_val, pos, false, true, level,
|
||||||
)
|
)
|
||||||
@ -269,20 +186,33 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
ChainType::Dotting => {
|
ChainType::Dotting => {
|
||||||
|
// Check for existence with the Elvis operator
|
||||||
|
if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() {
|
||||||
|
return Ok((Dynamic::UNIT, false));
|
||||||
|
}
|
||||||
|
|
||||||
match rhs {
|
match rhs {
|
||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
||||||
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
|
|
||||||
let call_args = &mut idx_val.into_fn_call_args();
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset_debugger =
|
||||||
self.run_debugger_with_reset(scope, global, lib, this_ptr, rhs, level)?;
|
self.run_debugger_with_reset(scope, global, lib, this_ptr, rhs, level)?;
|
||||||
|
|
||||||
|
let crate::ast::FnCallExpr {
|
||||||
|
name, hashes, args, ..
|
||||||
|
} = x.as_ref();
|
||||||
|
|
||||||
|
let offset = idx_values.len() - args.len();
|
||||||
|
let call_args = &mut idx_values[offset..];
|
||||||
|
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let result = self.make_method_call(
|
||||||
global, caches, lib, name, *hashes, target, call_args, *pos, level,
|
global, caches, lib, name, *hashes, target, call_args, pos1, *pos,
|
||||||
|
level,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
idx_values.truncate(offset);
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
global.debugger.reset_status(reset_debugger);
|
global.debugger.reset_status(reset_debugger);
|
||||||
|
|
||||||
@ -440,19 +370,26 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
||||||
Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
|
Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
|
||||||
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
|
|
||||||
let call_args = &mut idx_val.into_fn_call_args();
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger = self.run_debugger_with_reset(
|
let reset_debugger = self.run_debugger_with_reset(
|
||||||
scope, global, lib, this_ptr, _node, level,
|
scope, global, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let crate::ast::FnCallExpr {
|
||||||
|
name, hashes, args, ..
|
||||||
|
} = x.as_ref();
|
||||||
|
|
||||||
|
let offset = idx_values.len() - args.len();
|
||||||
|
let call_args = &mut idx_values[offset..];
|
||||||
|
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let result = self.make_method_call(
|
||||||
global, caches, lib, name, *hashes, target, call_args, pos,
|
global, caches, lib, name, *hashes, target, call_args, pos1,
|
||||||
level,
|
pos, level,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
idx_values.truncate(offset);
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
global.debugger.reset_status(reset_debugger);
|
global.debugger.reset_status(reset_debugger);
|
||||||
|
|
||||||
@ -558,19 +495,27 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||||
Expr::MethodCall(ref f, pos) if !f.is_qualified() => {
|
Expr::MethodCall(ref f, pos) if !f.is_qualified() => {
|
||||||
let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref();
|
|
||||||
let rhs_chain = rhs.into();
|
|
||||||
let args = &mut idx_val.into_fn_call_args();
|
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger = self.run_debugger_with_reset(
|
let reset_debugger = self.run_debugger_with_reset(
|
||||||
scope, global, lib, this_ptr, _node, level,
|
scope, global, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let crate::ast::FnCallExpr {
|
||||||
|
name, hashes, args, ..
|
||||||
|
} = f.as_ref();
|
||||||
|
let rhs_chain = rhs.into();
|
||||||
|
|
||||||
|
let offset = idx_values.len() - args.len();
|
||||||
|
let call_args = &mut idx_values[offset..];
|
||||||
|
let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let result = self.make_method_call(
|
||||||
global, caches, lib, name, *hashes, target, args, pos, level,
|
global, caches, lib, name, *hashes, target, call_args, pos1,
|
||||||
|
pos, level,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
idx_values.truncate(offset);
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
global.debugger.reset_status(reset_debugger);
|
global.debugger.reset_status(reset_debugger);
|
||||||
|
|
||||||
@ -610,21 +555,46 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
new_val: Option<(Dynamic, OpAssignment)>,
|
new_val: Option<(Dynamic, OpAssignment)>,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, options, op_pos) = match expr {
|
let chain_type = ChainType::from(expr);
|
||||||
|
let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(x, options, pos) => (x.as_ref(), ChainType::Indexing, *options, *pos),
|
Expr::Index(x, options, pos) => (x.as_ref(), *options, *pos),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x, options, pos) => (x.as_ref(), ChainType::Dotting, *options, *pos),
|
Expr::Dot(x, options, pos) => (x.as_ref(), *options, *pos),
|
||||||
expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr),
|
expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
let idx_values = &mut StaticVec::new_const();
|
let idx_values = &mut FnArgsVec::new_const();
|
||||||
|
|
||||||
self.eval_dot_index_chain_arguments(
|
match rhs {
|
||||||
scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, 0, level,
|
// Short-circuit for simple property access: {expr}.prop
|
||||||
)?;
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Property(..) if chain_type == ChainType::Dotting => (),
|
||||||
let is_assignment = new_val.is_some();
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
|
// Short-circuit for simple method call: {expr}.func()
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::FnCall(x, ..) if chain_type == ChainType::Dotting && x.args.is_empty() => (),
|
||||||
|
// Short-circuit for method call with all literal arguments: {expr}.func(1, 2, 3)
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::FnCall(x, ..)
|
||||||
|
if chain_type == ChainType::Dotting && x.args.iter().all(Expr::is_constant) =>
|
||||||
|
{
|
||||||
|
idx_values.extend(x.args.iter().map(|expr| expr.get_literal_value().unwrap()))
|
||||||
|
}
|
||||||
|
// Short-circuit for indexing with literal: {expr}[1]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
_ if chain_type == ChainType::Indexing && rhs.is_constant() => {
|
||||||
|
idx_values.push(rhs.get_literal_value().unwrap())
|
||||||
|
}
|
||||||
|
// All other patterns - evaluate the arguments chain
|
||||||
|
_ => {
|
||||||
|
self.eval_dot_index_chain_arguments(
|
||||||
|
scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, 0,
|
||||||
|
level,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match lhs {
|
match lhs {
|
||||||
// id.??? or id[???]
|
// id.??? or id[???]
|
||||||
@ -645,24 +615,25 @@ impl Engine {
|
|||||||
global, caches, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values,
|
global, caches, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values,
|
||||||
chain_type, level, new_val,
|
chain_type, level, new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, ..)| v)
|
|
||||||
.map_err(|err| err.fill_position(op_pos))
|
|
||||||
}
|
}
|
||||||
// {expr}.??? = ??? or {expr}[???] = ???
|
// {expr}.??? = ??? or {expr}[???] = ???
|
||||||
_ if is_assignment => unreachable!("cannot assign to an expression"),
|
_ if new_val.is_some() => unreachable!("cannot assign to an expression"),
|
||||||
// {expr}.??? or {expr}[???]
|
// {expr}.??? or {expr}[???]
|
||||||
expr => {
|
expr => {
|
||||||
let value = self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)?;
|
let value = self
|
||||||
|
.eval_expr(scope, global, caches, lib, this_ptr, expr, level)?
|
||||||
|
.flatten();
|
||||||
let obj_ptr = &mut value.into();
|
let obj_ptr = &mut value.into();
|
||||||
let root = ("", expr.start_position());
|
let root = ("", expr.start_position());
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
global, caches, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values,
|
global, caches, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values,
|
||||||
chain_type, level, new_val,
|
chain_type, level, new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, ..)| if is_assignment { Dynamic::UNIT } else { v })
|
|
||||||
.map_err(|err| err.fill_position(op_pos))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.map(|(v, ..)| v)
|
||||||
|
.map_err(|err| err.fill_position(op_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a chain of indexes and store the results in a [`StaticVec`].
|
/// Evaluate a chain of indexes and store the results in a [`StaticVec`].
|
||||||
@ -678,7 +649,7 @@ impl Engine {
|
|||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
parent_options: ASTFlags,
|
parent_options: ASTFlags,
|
||||||
_parent_chain_type: ChainType,
|
_parent_chain_type: ChainType,
|
||||||
idx_values: &mut StaticVec<ChainArgument>,
|
idx_values: &mut FnArgsVec<Dynamic>,
|
||||||
size: usize,
|
size: usize,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResultOf<()> {
|
) -> RhaiResultOf<()> {
|
||||||
@ -690,22 +661,13 @@ impl Engine {
|
|||||||
Expr::MethodCall(x, ..)
|
Expr::MethodCall(x, ..)
|
||||||
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
||||||
{
|
{
|
||||||
let crate::ast::FnCallExpr { args, .. } = x.as_ref();
|
for arg_expr in x.args.as_ref() {
|
||||||
|
idx_values.push(
|
||||||
let (values, pos) = args.iter().try_fold(
|
self.get_arg_value(scope, global, caches, lib, this_ptr, arg_expr, level)?
|
||||||
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
.0
|
||||||
|(mut values, mut pos), expr| {
|
.flatten(),
|
||||||
let (value, arg_pos) =
|
);
|
||||||
self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level)?;
|
}
|
||||||
if values.is_empty() {
|
|
||||||
pos = arg_pos;
|
|
||||||
}
|
|
||||||
values.push(value.flatten());
|
|
||||||
Ok::<_, crate::RhaiError>((values, pos))
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
idx_values.push(ChainArgument::from_fn_call_args(values, pos));
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
|
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
|
||||||
@ -713,9 +675,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Property(.., pos) if _parent_chain_type == ChainType::Dotting => {
|
Expr::Property(..) if _parent_chain_type == ChainType::Dotting => (),
|
||||||
idx_values.push(ChainArgument::Property(*pos))
|
|
||||||
}
|
|
||||||
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
|
|
||||||
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..)
|
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..)
|
||||||
@ -723,34 +683,27 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
||||||
|
|
||||||
|
let mut _arg_values = FnArgsVec::new_const();
|
||||||
|
|
||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_arg_val = match lhs {
|
match lhs {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Property(.., pos) if _parent_chain_type == ChainType::Dotting => {
|
Expr::Property(..) if _parent_chain_type == ChainType::Dotting => (),
|
||||||
ChainArgument::Property(*pos)
|
|
||||||
}
|
|
||||||
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::MethodCall(x, ..)
|
Expr::MethodCall(x, ..)
|
||||||
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
||||||
{
|
{
|
||||||
let crate::ast::FnCallExpr { args, .. } = x.as_ref();
|
for arg_expr in x.args.as_ref() {
|
||||||
|
_arg_values.push(
|
||||||
let (values, pos) = args.iter().try_fold(
|
self.get_arg_value(
|
||||||
(crate::FnArgsVec::with_capacity(args.len()), Position::NONE),
|
scope, global, caches, lib, this_ptr, arg_expr, level,
|
||||||
|(mut values, mut pos), expr| {
|
)?
|
||||||
let (value, arg_pos) = self.get_arg_value(
|
.0
|
||||||
scope, global, caches, lib, this_ptr, expr, level,
|
.flatten(),
|
||||||
)?;
|
);
|
||||||
if values.is_empty() {
|
}
|
||||||
pos = arg_pos
|
|
||||||
}
|
|
||||||
values.push(value.flatten());
|
|
||||||
Ok::<_, crate::RhaiError>((values, pos))
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
ChainArgument::from_fn_call_args(values, pos)
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
|
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
|
||||||
@ -761,13 +714,14 @@ impl Engine {
|
|||||||
unreachable!("invalid dot expression: {:?}", expr);
|
unreachable!("invalid dot expression: {:?}", expr);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
_ if _parent_chain_type == ChainType::Indexing => self
|
_ if _parent_chain_type == ChainType::Indexing => {
|
||||||
.eval_expr(scope, global, caches, lib, this_ptr, lhs, level)
|
_arg_values.push(
|
||||||
.map(|v| {
|
self.eval_expr(scope, global, caches, lib, this_ptr, lhs, level)?
|
||||||
ChainArgument::from_index_value(v.flatten(), lhs.start_position())
|
.flatten(),
|
||||||
})?,
|
);
|
||||||
|
}
|
||||||
expr => unreachable!("unknown chained expression: {:?}", expr),
|
expr => unreachable!("unknown chained expression: {:?}", expr),
|
||||||
};
|
}
|
||||||
|
|
||||||
// Push in reverse order
|
// Push in reverse order
|
||||||
let chain_type = expr.into();
|
let chain_type = expr.into();
|
||||||
@ -777,7 +731,9 @@ impl Engine {
|
|||||||
size, level,
|
size, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
idx_values.push(lhs_arg_val);
|
if !_arg_values.is_empty() {
|
||||||
|
idx_values.extend(_arg_values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -786,8 +742,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
_ if _parent_chain_type == ChainType::Indexing => idx_values.push(
|
_ if _parent_chain_type == ChainType::Indexing => idx_values.push(
|
||||||
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)
|
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)?
|
||||||
.map(|v| ChainArgument::from_index_value(v.flatten(), expr.start_position()))?,
|
.flatten(),
|
||||||
),
|
),
|
||||||
_ => unreachable!("unknown chained expression: {:?}", expr),
|
_ => unreachable!("unknown chained expression: {:?}", expr),
|
||||||
}
|
}
|
||||||
@ -796,7 +752,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call a get indexer.
|
/// Call a get indexer.
|
||||||
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn call_indexer_get(
|
fn call_indexer_get(
|
||||||
&self,
|
&self,
|
||||||
@ -820,7 +775,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call a set indexer.
|
/// Call a set indexer.
|
||||||
/// [`Position`] in [`EvalAltResult`] may be [`NONE`][Position::NONE] and should be set afterwards.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn call_indexer_set(
|
fn call_indexer_set(
|
||||||
&self,
|
&self,
|
||||||
@ -1043,6 +997,11 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
Dynamic(Union::Shared(..)) => {
|
||||||
|
unreachable!("`get_indexed_mut` cannot handle shared values")
|
||||||
|
}
|
||||||
|
|
||||||
_ if use_indexers => self
|
_ if use_indexers => self
|
||||||
.call_indexer_get(global, caches, lib, target, &mut idx, level)
|
.call_indexer_get(global, caches, lib, target, &mut idx, level)
|
||||||
.map(Into::into),
|
.map(Into::into),
|
||||||
|
@ -315,7 +315,7 @@ impl Debugger {
|
|||||||
/// Change the current status to [`CONTINUE`][DebuggerStatus::CONTINUE] and return the previous status.
|
/// Change the current status to [`CONTINUE`][DebuggerStatus::CONTINUE] and return the previous status.
|
||||||
pub(crate) fn clear_status_if(
|
pub(crate) fn clear_status_if(
|
||||||
&mut self,
|
&mut self,
|
||||||
filter: impl Fn(&DebuggerStatus) -> bool,
|
filter: impl FnOnce(&DebuggerStatus) -> bool,
|
||||||
) -> Option<DebuggerStatus> {
|
) -> Option<DebuggerStatus> {
|
||||||
if filter(&self.status) {
|
if filter(&self.status) {
|
||||||
Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE))
|
Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE))
|
||||||
|
@ -32,13 +32,17 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
let offset = global.num_imports() - index.get();
|
let offset = global.num_imports() - index.get();
|
||||||
Some(global.get_shared_import(offset).unwrap())
|
|
||||||
} else {
|
if let m @ Some(_) = global.get_shared_import(offset) {
|
||||||
global
|
return m;
|
||||||
.find_import(root)
|
}
|
||||||
.map(|n| global.get_shared_import(n).unwrap())
|
|
||||||
.or_else(|| self.global_sub_modules.get(root).cloned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do a text-match search if the index doesn't work
|
||||||
|
global.find_import(root).map_or_else(
|
||||||
|
|| self.global_sub_modules.get(root).cloned(),
|
||||||
|
|offset| global.get_shared_import(offset),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope or within imports,
|
/// Search for a variable within the scope or within imports,
|
||||||
@ -460,6 +464,17 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Coalesce(x, ..) => {
|
||||||
|
let lhs = self.eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level);
|
||||||
|
|
||||||
|
match lhs {
|
||||||
|
Ok(value) if value.is::<()>() => {
|
||||||
|
self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level)
|
||||||
|
}
|
||||||
|
Ok(_) | Err(_) => lhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Expr::Custom(custom, pos) => {
|
Expr::Custom(custom, pos) => {
|
||||||
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
||||||
// The first token acts as the custom syntax's key
|
// The first token acts as the custom syntax's key
|
||||||
|
@ -10,7 +10,7 @@ mod target;
|
|||||||
|
|
||||||
pub use cache::{Caches, FnResolutionCache, FnResolutionCacheEntry};
|
pub use cache::{Caches, FnResolutionCache, FnResolutionCacheEntry};
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
pub use chaining::{ChainArgument, ChainType};
|
pub use chaining::ChainType;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
pub use debugger::{
|
pub use debugger::{
|
||||||
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
BreakPoint, CallStackFrame, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus,
|
||||||
|
@ -184,9 +184,7 @@ impl Engine {
|
|||||||
*target.as_mut() = new_val;
|
*target.as_mut() = new_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
target
|
target.propagate_changed_value(op_info.pos)
|
||||||
.propagate_changed_value()
|
|
||||||
.map_err(|err| err.fill_position(op_info.pos))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statement.
|
/// Evaluate a statement.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Type to hold a mutable reference to the target of an evaluation.
|
//! Type to hold a mutable reference to the target of an evaluation.
|
||||||
|
|
||||||
use crate::types::dynamic::Variant;
|
use crate::types::dynamic::Variant;
|
||||||
use crate::{Dynamic, RhaiResultOf};
|
use crate::{Dynamic, Position, RhaiResultOf};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -46,7 +46,7 @@ pub fn calc_index<E>(
|
|||||||
length: usize,
|
length: usize,
|
||||||
start: crate::INT,
|
start: crate::INT,
|
||||||
negative_count_from_end: bool,
|
negative_count_from_end: bool,
|
||||||
err: impl Fn() -> Result<usize, E>,
|
err: impl FnOnce() -> Result<usize, E>,
|
||||||
) -> Result<usize, E> {
|
) -> Result<usize, E> {
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
if negative_count_from_end {
|
if negative_count_from_end {
|
||||||
@ -272,10 +272,8 @@ impl<'a> Target<'a> {
|
|||||||
}
|
}
|
||||||
/// Propagate a changed value back to the original source.
|
/// Propagate a changed value back to the original source.
|
||||||
/// This has no effect for direct references.
|
/// This has no effect for direct references.
|
||||||
///
|
|
||||||
/// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and should be set afterwards.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn propagate_changed_value(&mut self) -> RhaiResultOf<()> {
|
pub fn propagate_changed_value(&mut self, _pos: Position) -> RhaiResultOf<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::RefMut(..) | Self::TempValue(..) => (),
|
Self::RefMut(..) | Self::TempValue(..) => (),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -287,7 +285,7 @@ impl<'a> Target<'a> {
|
|||||||
Box::new(crate::ERR::ErrorMismatchDataType(
|
Box::new(crate::ERR::ErrorMismatchDataType(
|
||||||
"bool".to_string(),
|
"bool".to_string(),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
crate::Position::NONE,
|
_pos,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -317,7 +315,7 @@ impl<'a> Target<'a> {
|
|||||||
Box::new(crate::ERR::ErrorMismatchDataType(
|
Box::new(crate::ERR::ErrorMismatchDataType(
|
||||||
"integer".to_string(),
|
"integer".to_string(),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
crate::Position::NONE,
|
_pos,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -338,7 +336,7 @@ impl<'a> Target<'a> {
|
|||||||
Box::new(crate::ERR::ErrorMismatchDataType(
|
Box::new(crate::ERR::ErrorMismatchDataType(
|
||||||
"INT".to_string(),
|
"INT".to_string(),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
crate::Position::NONE,
|
_pos,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -363,7 +361,7 @@ impl<'a> Target<'a> {
|
|||||||
Box::new(crate::ERR::ErrorMismatchDataType(
|
Box::new(crate::ERR::ErrorMismatchDataType(
|
||||||
"char".to_string(),
|
"char".to_string(),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
crate::Position::NONE,
|
_pos,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -782,8 +782,9 @@ impl Engine {
|
|||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
mut hash: FnCallHashes,
|
mut hash: FnCallHashes,
|
||||||
target: &mut crate::eval::Target,
|
target: &mut crate::eval::Target,
|
||||||
(call_args, call_arg_pos): &mut (FnArgsVec<Dynamic>, Position),
|
mut call_args: &mut [Dynamic],
|
||||||
pos: Position,
|
first_arg_pos: Position,
|
||||||
|
fn_call_pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
let is_ref_mut = target.is_ref();
|
let is_ref_mut = target.is_ref();
|
||||||
@ -806,7 +807,16 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
None, global, caches, lib, fn_name, new_hash, &mut args, false, false, pos,
|
None,
|
||||||
|
global,
|
||||||
|
caches,
|
||||||
|
lib,
|
||||||
|
fn_name,
|
||||||
|
new_hash,
|
||||||
|
&mut args,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
fn_call_pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -814,15 +824,17 @@ impl Engine {
|
|||||||
if !call_args.is_empty() {
|
if !call_args.is_empty() {
|
||||||
if !call_args[0].is::<FnPtr>() {
|
if !call_args[0].is::<FnPtr>() {
|
||||||
let typ = self.map_type_name(call_args[0].type_name());
|
let typ = self.map_type_name(call_args[0].type_name());
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, *call_arg_pos));
|
return Err(self.make_type_mismatch_err::<FnPtr>(typ, first_arg_pos));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let typ = self.map_type_name(target.type_name());
|
let typ = self.map_type_name(target.type_name());
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, pos));
|
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FnPtr call on object
|
// FnPtr call on object
|
||||||
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
|
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>();
|
||||||
|
call_args = &mut call_args[1..];
|
||||||
|
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
let fn_name = fn_ptr.fn_name();
|
let fn_name = fn_ptr.fn_name();
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||||
@ -842,14 +854,23 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
None, global, caches, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos,
|
None,
|
||||||
|
global,
|
||||||
|
caches,
|
||||||
|
lib,
|
||||||
|
fn_name,
|
||||||
|
new_hash,
|
||||||
|
&mut args,
|
||||||
|
is_ref_mut,
|
||||||
|
true,
|
||||||
|
fn_call_pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KEYWORD_FN_PTR_CURRY => {
|
KEYWORD_FN_PTR_CURRY => {
|
||||||
if !target.is::<FnPtr>() {
|
if !target.is::<FnPtr>() {
|
||||||
let typ = self.map_type_name(target.type_name());
|
let typ = self.map_type_name(target.type_name());
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, pos));
|
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
|
let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
|
||||||
@ -883,6 +904,8 @@ impl Engine {
|
|||||||
_ => {
|
_ => {
|
||||||
let mut fn_name = fn_name;
|
let mut fn_name = fn_name;
|
||||||
let _redirected;
|
let _redirected;
|
||||||
|
let mut _arg_values: FnArgsVec<_>;
|
||||||
|
let mut call_args = call_args;
|
||||||
|
|
||||||
// Check if it is a map method call in OOP style
|
// Check if it is a map method call in OOP style
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -894,7 +917,13 @@ impl Engine {
|
|||||||
fn_name = &_redirected;
|
fn_name = &_redirected;
|
||||||
// Add curried arguments
|
// Add curried arguments
|
||||||
if fn_ptr.is_curried() {
|
if fn_ptr.is_curried() {
|
||||||
call_args.insert_many(0, fn_ptr.curry().iter().cloned());
|
_arg_values = fn_ptr
|
||||||
|
.curry()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(call_args.iter_mut().map(mem::take))
|
||||||
|
.collect();
|
||||||
|
call_args = &mut _arg_values;
|
||||||
}
|
}
|
||||||
// Recalculate the hash based on the new function name and new arguments
|
// Recalculate the hash based on the new function name and new arguments
|
||||||
hash = FnCallHashes::from_all(
|
hash = FnCallHashes::from_all(
|
||||||
@ -912,7 +941,16 @@ impl Engine {
|
|||||||
args.extend(call_args.iter_mut());
|
args.extend(call_args.iter_mut());
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
None, global, caches, lib, fn_name, hash, &mut args, is_ref_mut, true, pos,
|
None,
|
||||||
|
global,
|
||||||
|
caches,
|
||||||
|
lib,
|
||||||
|
fn_name,
|
||||||
|
hash,
|
||||||
|
&mut args,
|
||||||
|
is_ref_mut,
|
||||||
|
true,
|
||||||
|
fn_call_pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -920,9 +958,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Propagate the changed value back to the source if necessary
|
// Propagate the changed value back to the source if necessary
|
||||||
if updated {
|
if updated {
|
||||||
target
|
target.propagate_changed_value(fn_call_pos)?;
|
||||||
.propagate_changed_value()
|
|
||||||
.map_err(|err| err.fill_position(pos))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((result, updated))
|
Ok((result, updated))
|
||||||
|
@ -134,9 +134,9 @@ macro_rules! def_register {
|
|||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() }
|
||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
||||||
#[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| {
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
check_constant!(ctx, args);
|
check_constant!(_ctx, args);
|
||||||
|
|
||||||
let mut _drain = args.iter_mut();
|
let mut _drain = args.iter_mut();
|
||||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||||
@ -186,9 +186,9 @@ macro_rules! def_register {
|
|||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() }
|
||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
|
||||||
#[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| {
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
check_constant!(ctx, args);
|
check_constant!(_ctx, args);
|
||||||
|
|
||||||
let mut _drain = args.iter_mut();
|
let mut _drain = args.iter_mut();
|
||||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||||
|
@ -130,6 +130,7 @@ const INT_BITS: usize = std::mem::size_of::<INT>() * 8;
|
|||||||
/// Number of bytes that make up an [`INT`].
|
/// Number of bytes that make up an [`INT`].
|
||||||
///
|
///
|
||||||
/// It is 8 unless the `only_i32` feature is enabled when it will be 4.
|
/// It is 8 unless the `only_i32` feature is enabled when it will be 4.
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
const INT_BYTES: usize = std::mem::size_of::<INT>();
|
const INT_BYTES: usize = std::mem::size_of::<INT>();
|
||||||
|
|
||||||
/// The system floating-point type. It is defined as [`f64`].
|
/// The system floating-point type. It is defined as [`f64`].
|
||||||
@ -155,6 +156,7 @@ pub type FLOAT = f32;
|
|||||||
///
|
///
|
||||||
/// It is 8 unless the `f32_float` feature is enabled when it will be 4.
|
/// It is 8 unless the `f32_float` feature is enabled when it will be 4.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
const FLOAT_BYTES: usize = std::mem::size_of::<FLOAT>();
|
const FLOAT_BYTES: usize = std::mem::size_of::<FLOAT>();
|
||||||
|
|
||||||
/// An exclusive integer range.
|
/// An exclusive integer range.
|
||||||
|
@ -10,7 +10,7 @@ use crate::tokenizer::{Span, Token};
|
|||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Identifier,
|
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Identifier,
|
||||||
Position, Scope, StaticVec, AST, INT, INT_BITS,
|
Position, Scope, StaticVec, AST, INT,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -912,9 +912,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ()?.rhs
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Dot(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
*expr = mem::take(&mut x.lhs);
|
||||||
|
}
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x,_, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// map.string
|
// map.string
|
||||||
(Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
|
(Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
|
||||||
let prop = p.2.as_str();
|
let prop = p.2.as_str();
|
||||||
@ -932,7 +938,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
}
|
}
|
||||||
// ....lhs.rhs
|
// ....lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x,_, ..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
|
Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
|
||||||
|
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -966,16 +972,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
.unwrap_or_else(|| Expr::Unit(*pos));
|
.unwrap_or_else(|| Expr::Unit(*pos));
|
||||||
}
|
}
|
||||||
// int[int]
|
// int[int]
|
||||||
(Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && (*i as usize) < INT_BITS => {
|
(Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && (*i as usize) < crate::INT_BITS => {
|
||||||
// Bit-field literal indexing - get the bit
|
// Bit-field literal indexing - get the bit
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::BoolConstant((*n & (1 << (*i as usize))) != 0, *pos);
|
*expr = Expr::BoolConstant((*n & (1 << (*i as usize))) != 0, *pos);
|
||||||
}
|
}
|
||||||
// int[-int]
|
// int[-int]
|
||||||
(Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.checked_abs().map(|i| i as usize <= INT_BITS).unwrap_or(false) => {
|
(Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.checked_abs().map(|i| i as usize <= crate::INT_BITS).unwrap_or(false) => {
|
||||||
// Bit-field literal indexing - get the bit
|
// Bit-field literal indexing - get the bit
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::BoolConstant((*n & (1 << (INT_BITS - i.abs() as usize))) != 0, *pos);
|
*expr = Expr::BoolConstant((*n & (1 << (crate::INT_BITS - i.abs() as usize))) != 0, *pos);
|
||||||
}
|
}
|
||||||
// string[int]
|
// string[int]
|
||||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && (*i as usize) < s.chars().count() => {
|
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && (*i as usize) < s.chars().count() => {
|
||||||
@ -1067,6 +1073,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
// lhs || rhs
|
// lhs || rhs
|
||||||
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
|
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
|
||||||
},
|
},
|
||||||
|
// () ?? rhs -> rhs
|
||||||
|
Expr::Coalesce(x, ..) if matches!(x.lhs, Expr::Unit(..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
*expr = mem::take(&mut x.rhs);
|
||||||
|
},
|
||||||
|
// lhs:constant ?? rhs -> lhs
|
||||||
|
Expr::Coalesce(x, ..) if x.lhs.is_constant() => {
|
||||||
|
state.set_dirty();
|
||||||
|
*expr = mem::take(&mut x.lhs);
|
||||||
|
},
|
||||||
|
|
||||||
// eval!
|
// eval!
|
||||||
Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
|
Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
|
||||||
|
150
src/parser.rs
150
src/parser.rs
@ -494,19 +494,20 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let hash = if !namespace.is_empty() {
|
let hash = if !namespace.is_empty() {
|
||||||
let index = state.find_module(namespace.root());
|
let root = namespace.root();
|
||||||
|
let index = state.find_module(root);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let relax = settings.is_function_scope;
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(feature = "no_function")]
|
let is_global = root == crate::engine::KEYWORD_GLOBAL;
|
||||||
let relax = false;
|
#[cfg(any(feature = "no_function", feature = "no_module"))]
|
||||||
|
let is_global = false;
|
||||||
|
|
||||||
if !relax
|
if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() {
|
||||||
&& settings.options.contains(LangOptions::STRICT_VAR)
|
if !is_global && !self.global_sub_modules.contains_key(root) {
|
||||||
&& index.is_none()
|
return Err(PERR::ModuleUndefined(root.to_string())
|
||||||
{
|
.into_err(namespace.position()));
|
||||||
return Err(PERR::ModuleUndefined(namespace.root().to_string())
|
}
|
||||||
.into_err(namespace.position()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace.set_index(index);
|
namespace.set_index(index);
|
||||||
@ -557,19 +558,20 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let hash = if !namespace.is_empty() {
|
let hash = if !namespace.is_empty() {
|
||||||
let index = state.find_module(namespace.root());
|
let root = namespace.root();
|
||||||
|
let index = state.find_module(root);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let relax = settings.is_function_scope;
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(feature = "no_function")]
|
let is_global = root == crate::engine::KEYWORD_GLOBAL;
|
||||||
let relax = false;
|
#[cfg(any(feature = "no_function", feature = "no_module"))]
|
||||||
|
let is_global = false;
|
||||||
|
|
||||||
if !relax
|
if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() {
|
||||||
&& settings.options.contains(LangOptions::STRICT_VAR)
|
if !is_global && !self.global_sub_modules.contains_key(root) {
|
||||||
&& index.is_none()
|
return Err(PERR::ModuleUndefined(root.to_string())
|
||||||
{
|
.into_err(namespace.position()));
|
||||||
return Err(PERR::ModuleUndefined(namespace.root().to_string())
|
}
|
||||||
.into_err(namespace.position()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace.set_index(index);
|
namespace.set_index(index);
|
||||||
@ -1269,6 +1271,9 @@ impl Engine {
|
|||||||
let mut new_state =
|
let mut new_state =
|
||||||
ParseState::new(self, state.scope, state.tokenizer_control.clone());
|
ParseState::new(self, state.scope, state.tokenizer_control.clone());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
new_state.imports.clone_from(&state.imports);
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
{
|
||||||
new_state.max_expr_depth = self.max_function_expr_depth();
|
new_state.max_expr_depth = self.max_function_expr_depth();
|
||||||
@ -1634,7 +1639,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// Property access
|
// Property access
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
(expr, Token::Period) => {
|
(expr, op @ Token::Period) | (expr, op @ Token::Elvis) => {
|
||||||
// Expression after dot must start with an identifier
|
// Expression after dot must start with an identifier
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
(Token::Identifier(..), ..) => {
|
(Token::Identifier(..), ..) => {
|
||||||
@ -1649,7 +1654,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rhs = self.parse_primary(input, state, lib, settings.level_up())?;
|
let rhs = self.parse_primary(input, state, lib, settings.level_up())?;
|
||||||
Self::make_dot_expr(state, expr, ASTFlags::NONE, rhs, tail_pos)?
|
let op_flags = match op {
|
||||||
|
Token::Period => ASTFlags::NONE,
|
||||||
|
Token::Elvis => ASTFlags::NEGATED,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)?
|
||||||
}
|
}
|
||||||
// Unknown postfix operator
|
// Unknown postfix operator
|
||||||
(expr, token) => unreachable!(
|
(expr, token) => unreachable!(
|
||||||
@ -1678,19 +1688,20 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
let index = state.find_module(namespace.root());
|
let root = namespace.root();
|
||||||
|
let index = state.find_module(root);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let relax = settings.is_function_scope;
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(feature = "no_function")]
|
let is_global = root == crate::engine::KEYWORD_GLOBAL;
|
||||||
let relax = false;
|
#[cfg(any(feature = "no_function", feature = "no_module"))]
|
||||||
|
let is_global = false;
|
||||||
|
|
||||||
if !relax
|
if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() {
|
||||||
&& settings.options.contains(LangOptions::STRICT_VAR)
|
if !is_global && !self.global_sub_modules.contains_key(root) {
|
||||||
&& index.is_none()
|
return Err(PERR::ModuleUndefined(root.to_string())
|
||||||
{
|
.into_err(namespace.position()));
|
||||||
return Err(PERR::ModuleUndefined(namespace.root().to_string())
|
}
|
||||||
.into_err(namespace.position()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace.set_index(index);
|
namespace.set_index(index);
|
||||||
@ -1906,8 +1917,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ??? && ??? = rhs, ??? || ??? = rhs
|
// ??? && ??? = rhs, ??? || ??? = rhs, xxx ?? xxx = rhs
|
||||||
Expr::And(..) | Expr::Or(..) => Err(LexError::ImproperSymbol(
|
Expr::And(..) | Expr::Or(..) | Expr::Coalesce(..) => Err(LexError::ImproperSymbol(
|
||||||
"=".to_string(),
|
"=".to_string(),
|
||||||
"Possibly a typo of '=='?".to_string(),
|
"Possibly a typo of '=='?".to_string(),
|
||||||
)
|
)
|
||||||
@ -1953,14 +1964,22 @@ impl Engine {
|
|||||||
fn make_dot_expr(
|
fn make_dot_expr(
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
lhs: Expr,
|
lhs: Expr,
|
||||||
parent_options: ASTFlags,
|
|
||||||
rhs: Expr,
|
rhs: Expr,
|
||||||
|
parent_options: ASTFlags,
|
||||||
|
op_flags: ASTFlags,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
) -> ParseResult<Expr> {
|
) -> ParseResult<Expr> {
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
// lhs[idx_expr].rhs
|
// lhs[idx_expr].rhs
|
||||||
(Expr::Index(mut x, options, pos), rhs) => {
|
(Expr::Index(mut x, options, pos), rhs) => {
|
||||||
x.rhs = Self::make_dot_expr(state, x.rhs, options | parent_options, rhs, op_pos)?;
|
x.rhs = Self::make_dot_expr(
|
||||||
|
state,
|
||||||
|
x.rhs,
|
||||||
|
rhs,
|
||||||
|
options | parent_options,
|
||||||
|
op_flags,
|
||||||
|
op_pos,
|
||||||
|
)?;
|
||||||
Ok(Expr::Index(x, ASTFlags::NONE, pos))
|
Ok(Expr::Index(x, ASTFlags::NONE, pos))
|
||||||
}
|
}
|
||||||
// lhs.module::id - syntax error
|
// lhs.module::id - syntax error
|
||||||
@ -1971,16 +1990,12 @@ impl Engine {
|
|||||||
// lhs.id
|
// lhs.id
|
||||||
(lhs, var_expr @ Expr::Variable(..)) => {
|
(lhs, var_expr @ Expr::Variable(..)) => {
|
||||||
let rhs = var_expr.into_property(state);
|
let rhs = var_expr.into_property(state);
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
// lhs.prop
|
// lhs.prop
|
||||||
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
||||||
BinaryExpr { lhs, rhs: prop }.into(),
|
BinaryExpr { lhs, rhs: prop }.into(),
|
||||||
ASTFlags::NONE,
|
op_flags,
|
||||||
op_pos,
|
op_pos,
|
||||||
)),
|
)),
|
||||||
// lhs.nnn::func(...) - syntax error
|
// lhs.nnn::func(...) - syntax error
|
||||||
@ -2017,17 +2032,13 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rhs = Expr::MethodCall(func, func_pos);
|
let rhs = Expr::MethodCall(func, func_pos);
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
||||||
(lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => {
|
(lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => {
|
||||||
let (x, term, pos, is_dot) = match rhs {
|
let (x, options, pos, is_dot) = match rhs {
|
||||||
Expr::Dot(x, term, pos) => (x, term, pos, true),
|
Expr::Dot(x, options, pos) => (x, options, pos, true),
|
||||||
Expr::Index(x, term, pos) => (x, term, pos, false),
|
Expr::Index(x, options, pos) => (x, options, pos, false),
|
||||||
expr => unreachable!("Expr::Dot or Expr::Index expected but gets {:?}", expr),
|
expr => unreachable!("Expr::Dot or Expr::Index expected but gets {:?}", expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2044,22 +2055,18 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// lhs.id.dot_rhs or lhs.id[idx_rhs]
|
// lhs.id.dot_rhs or lhs.id[idx_rhs]
|
||||||
Expr::Variable(..) | Expr::Property(..) => {
|
Expr::Variable(..) | Expr::Property(..) => {
|
||||||
let new_lhs = BinaryExpr {
|
let new_binary = BinaryExpr {
|
||||||
lhs: x.lhs.into_property(state),
|
lhs: x.lhs.into_property(state),
|
||||||
rhs: x.rhs,
|
rhs: x.rhs,
|
||||||
}
|
}
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let rhs = if is_dot {
|
let rhs = if is_dot {
|
||||||
Expr::Dot(new_lhs, term, pos)
|
Expr::Dot(new_binary, options, pos)
|
||||||
} else {
|
} else {
|
||||||
Expr::Index(new_lhs, term, pos)
|
Expr::Index(new_binary, options, pos)
|
||||||
};
|
};
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
||||||
Expr::FnCall(mut func, func_pos) => {
|
Expr::FnCall(mut func, func_pos) => {
|
||||||
@ -2077,15 +2084,11 @@ impl Engine {
|
|||||||
.into();
|
.into();
|
||||||
|
|
||||||
let rhs = if is_dot {
|
let rhs = if is_dot {
|
||||||
Expr::Dot(new_lhs, term, pos)
|
Expr::Dot(new_lhs, options, pos)
|
||||||
} else {
|
} else {
|
||||||
Expr::Index(new_lhs, term, pos)
|
Expr::Index(new_lhs, options, pos)
|
||||||
};
|
};
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
expr => unreachable!("invalid dot expression: {:?}", expr),
|
expr => unreachable!("invalid dot expression: {:?}", expr),
|
||||||
}
|
}
|
||||||
@ -2220,6 +2223,18 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Token::DoubleQuestion => {
|
||||||
|
let rhs = args.pop().unwrap();
|
||||||
|
let current_lhs = args.pop().unwrap();
|
||||||
|
Expr::Coalesce(
|
||||||
|
BinaryExpr {
|
||||||
|
lhs: current_lhs,
|
||||||
|
rhs,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
Token::In => {
|
Token::In => {
|
||||||
// Swap the arguments
|
// Swap the arguments
|
||||||
let current_lhs = args.remove(0);
|
let current_lhs = args.remove(0);
|
||||||
@ -2904,7 +2919,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut statements = StaticVec::new();
|
let mut statements = StaticVec::new_const();
|
||||||
|
|
||||||
let prev_entry_stack_len = state.block_stack_len;
|
let prev_entry_stack_len = state.block_stack_len;
|
||||||
state.block_stack_len = state.stack.len();
|
state.block_stack_len = state.stack.len();
|
||||||
@ -3081,6 +3096,9 @@ impl Engine {
|
|||||||
let mut new_state =
|
let mut new_state =
|
||||||
ParseState::new(self, state.scope, state.tokenizer_control.clone());
|
ParseState::new(self, state.scope, state.tokenizer_control.clone());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
new_state.imports.clone_from(&state.imports);
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
{
|
||||||
new_state.max_expr_depth = self.max_function_expr_depth();
|
new_state.max_expr_depth = self.max_function_expr_depth();
|
||||||
|
@ -420,6 +420,10 @@ pub enum Token {
|
|||||||
Comma,
|
Comma,
|
||||||
/// `.`
|
/// `.`
|
||||||
Period,
|
Period,
|
||||||
|
/// `?.`
|
||||||
|
Elvis,
|
||||||
|
/// `??`
|
||||||
|
DoubleQuestion,
|
||||||
/// `..`
|
/// `..`
|
||||||
ExclusiveRange,
|
ExclusiveRange,
|
||||||
/// `..=`
|
/// `..=`
|
||||||
@ -576,6 +580,8 @@ impl Token {
|
|||||||
Underscore => "_",
|
Underscore => "_",
|
||||||
Comma => ",",
|
Comma => ",",
|
||||||
Period => ".",
|
Period => ".",
|
||||||
|
Elvis => "?.",
|
||||||
|
DoubleQuestion => "??",
|
||||||
ExclusiveRange => "..",
|
ExclusiveRange => "..",
|
||||||
InclusiveRange => "..=",
|
InclusiveRange => "..=",
|
||||||
MapStart => "#{",
|
MapStart => "#{",
|
||||||
@ -771,6 +777,8 @@ impl Token {
|
|||||||
"_" => Underscore,
|
"_" => Underscore,
|
||||||
"," => Comma,
|
"," => Comma,
|
||||||
"." => Period,
|
"." => Period,
|
||||||
|
"?." => Elvis,
|
||||||
|
"??" => DoubleQuestion,
|
||||||
".." => ExclusiveRange,
|
".." => ExclusiveRange,
|
||||||
"..=" => InclusiveRange,
|
"..=" => InclusiveRange,
|
||||||
"#{" => MapStart,
|
"#{" => MapStart,
|
||||||
@ -877,11 +885,13 @@ impl Token {
|
|||||||
use Token::*;
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
LexError(..) |
|
LexError(..) |
|
||||||
SemiColon | // ; - is unary
|
SemiColon | // ; - is unary
|
||||||
Colon | // #{ foo: - is unary
|
Colon | // #{ foo: - is unary
|
||||||
Comma | // ( ... , -expr ) - is unary
|
Comma | // ( ... , -expr ) - is unary
|
||||||
//Period |
|
//Period |
|
||||||
|
//Elvis |
|
||||||
|
//DoubleQuestion |
|
||||||
ExclusiveRange | // .. - is unary
|
ExclusiveRange | // .. - is unary
|
||||||
InclusiveRange | // ..= - is unary
|
InclusiveRange | // ..= - is unary
|
||||||
LeftBrace | // { -expr } - is unary
|
LeftBrace | // { -expr } - is unary
|
||||||
@ -952,6 +962,8 @@ impl Token {
|
|||||||
|
|
||||||
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
|
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
|
||||||
|
|
||||||
|
DoubleQuestion => 135,
|
||||||
|
|
||||||
ExclusiveRange | InclusiveRange => 140,
|
ExclusiveRange | InclusiveRange => 140,
|
||||||
|
|
||||||
Plus | Minus => 150,
|
Plus | Minus => 150,
|
||||||
@ -987,12 +999,12 @@ impl Token {
|
|||||||
match self {
|
match self {
|
||||||
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
||||||
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
||||||
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | ExclusiveRange
|
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | Elvis
|
||||||
| InclusiveRange | MapStart | Equals | LessThan | GreaterThan | LessThanEqualsTo
|
| DoubleQuestion | ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan
|
||||||
| GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand
|
| GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
|
||||||
| And | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
|
| Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign
|
||||||
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
| MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign
|
||||||
| PowerOfAssign => true,
|
| OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -1992,6 +2004,7 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
return Some((Token::NotEqualsTo, start_pos));
|
return Some((Token::NotEqualsTo, start_pos));
|
||||||
}
|
}
|
||||||
|
('!', '.') => return Some((Token::Reserved("!.".into()), start_pos)),
|
||||||
('!', ..) => return Some((Token::Bang, start_pos)),
|
('!', ..) => return Some((Token::Bang, start_pos)),
|
||||||
|
|
||||||
('|', '|') => {
|
('|', '|') => {
|
||||||
@ -2032,6 +2045,16 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
('$', ..) => return Some((Token::Reserved("$".into()), start_pos)),
|
('$', ..) => return Some((Token::Reserved("$".into()), start_pos)),
|
||||||
|
|
||||||
|
('?', '.') => {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
return Some((Token::Elvis, start_pos));
|
||||||
|
}
|
||||||
|
('?', '?') => {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
return Some((Token::DoubleQuestion, start_pos));
|
||||||
|
}
|
||||||
|
('?', ..) => return Some((Token::Reserved("?".into()), start_pos)),
|
||||||
|
|
||||||
(ch, ..) if ch.is_whitespace() => (),
|
(ch, ..) if ch.is_whitespace() => (),
|
||||||
|
|
||||||
(ch, ..) => {
|
(ch, ..) => {
|
||||||
|
@ -52,7 +52,7 @@ pub trait Variant: Any + private::Sealed {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
|
|
||||||
/// Convert this [`Variant`] trait object to [`Box<dyn Any>`].
|
/// Convert this [`Variant`] trait object to [`Box<dyn Any>`][Any].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
|
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ pub trait Variant: Any + Send + Sync + private::Sealed {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
|
|
||||||
/// Convert this [`Variant`] trait object to [`Box<dyn Any>`].
|
/// Convert this [`Variant`] trait object to [`Box<dyn Any>`][Any].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
|
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
|
||||||
|
|
||||||
|
@ -135,3 +135,13 @@ fn test_binary_ops() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binary_ops_null_coalesce() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("let x = 42; x ?? 123")?, 42);
|
||||||
|
assert_eq!(engine.eval::<INT>("let x = (); x ?? 123")?, 123);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, FnPtr, ParseErrorType, Scope, INT};
|
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, ParseErrorType, Scope, INT};
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::mem::take;
|
use std::mem::take;
|
||||||
@ -217,6 +217,34 @@ fn test_closures_sharing() -> Result<(), Box<EvalAltResult>> {
|
|||||||
6
|
6
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
let mut m = Map::new();
|
||||||
|
m.insert("hello".into(), "world".into());
|
||||||
|
let m = Dynamic::from(m).into_shared();
|
||||||
|
|
||||||
|
engine.register_fn("baz", move || m.clone());
|
||||||
|
|
||||||
|
assert!(!engine.eval::<bool>(
|
||||||
|
"
|
||||||
|
let m = baz();
|
||||||
|
m.is_shared()
|
||||||
|
"
|
||||||
|
)?);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(
|
||||||
|
"
|
||||||
|
let m = baz();
|
||||||
|
m.hello
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
"world"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<String>("baz().hello")?, "world");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,3 +390,15 @@ fn test_get_set_indexer() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_set_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<()>("let x = (); x?.foo.bar.baz")?, ());
|
||||||
|
assert_eq!(engine.eval::<()>("let x = (); x?.foo(1,2,3)")?, ());
|
||||||
|
assert_eq!(engine.eval::<()>("let x = #{a:()}; x.a?.foo.bar.baz")?, ());
|
||||||
|
assert_eq!(engine.eval::<String>("let x = 'x'; x?.type_of()")?, "char");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -91,18 +91,17 @@ fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
assert!(engine.compile("fn foo(x) { x + y }").is_err());
|
assert!(engine.compile("fn foo(x) { x + y }").is_err());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
assert!(engine.compile("print(h::y::z);").is_err());
|
assert!(engine.compile("print(h::y::z);").is_err());
|
||||||
engine.compile(r#"import "hello" as h; print(h::y::z);"#)?;
|
assert!(engine.compile("fn foo() { h::y::z }").is_err());
|
||||||
|
assert!(engine.compile("fn foo() { h::y::foo() }").is_err());
|
||||||
|
engine.compile(r#"import "hello" as h; fn foo() { h::a::b::c } print(h::y::z);"#)?;
|
||||||
assert!(engine.compile("let x = h::y::foo();").is_err());
|
assert!(engine.compile("let x = h::y::foo();").is_err());
|
||||||
engine.compile(r#"import "hello" as h; let x = h::y::foo();"#)?;
|
engine.compile(r#"import "hello" as h; fn foo() { h::a::b::c() } let x = h::y::foo();"#)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
engine.compile("fn foo() { h::y::foo() }")?;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
assert!(engine.compile("let f = |y| x * y;").is_err());
|
assert!(engine.compile("let f = |y| x * y;").is_err());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user