commit
cd8a93acd4
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -27,6 +27,7 @@ jobs:
|
||||
- "--features no_float,serde,metadata,internals,debugging"
|
||||
- "--features f32_float,serde,metadata,internals,debugging"
|
||||
- "--features decimal,serde,metadata,internals,debugging"
|
||||
- "--features no_custom_syntax,serde,metadata,internals,debugging"
|
||||
- "--features no_float,decimal"
|
||||
- "--tests --features only_i32,serde,metadata,internals,debugging"
|
||||
- "--features only_i64,serde,metadata,internals,debugging"
|
||||
@ -36,8 +37,8 @@ jobs:
|
||||
- "--features no_module,serde,metadata,internals,debugging"
|
||||
- "--features no_closure,serde,metadata,internals,debugging"
|
||||
- "--features unicode-xid-ident,serde,metadata,internals,debugging"
|
||||
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked,debugging"
|
||||
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
|
||||
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging"
|
||||
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked"
|
||||
toolchain: [stable]
|
||||
experimental: [false]
|
||||
include:
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,6 +1,21 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 1.9.0
|
||||
=============
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most).
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* `switch` cases can now include multiple values separated by `|`.
|
||||
* `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block.
|
||||
|
||||
|
||||
Version 1.8.0
|
||||
=============
|
||||
|
||||
|
@ -53,6 +53,7 @@ no_object = [] # no custom objects
|
||||
no_function = ["no_closure"] # no script-defined functions (meaning no closures)
|
||||
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
|
||||
no_module = [] # no modules
|
||||
no_custom_syntax = [] # no custom syntax or custom operators
|
||||
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
|
||||
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
|
||||
internals = [] # expose internal data structures
|
||||
|
@ -137,7 +137,7 @@ impl Parse for Module {
|
||||
Ok(vec)
|
||||
})?;
|
||||
// Gather and parse constants definitions.
|
||||
for item in content.iter() {
|
||||
for item in &*content {
|
||||
match item {
|
||||
syn::Item::Const(syn::ItemConst {
|
||||
vis: syn::Visibility::Public(..),
|
||||
@ -156,7 +156,7 @@ impl Parse for Module {
|
||||
}
|
||||
}
|
||||
// Gather and parse type definitions.
|
||||
for item in content.iter() {
|
||||
for item in &*content {
|
||||
match item {
|
||||
syn::Item::Type(syn::ItemType {
|
||||
vis: syn::Visibility::Public(..),
|
||||
|
@ -300,7 +300,7 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> {
|
||||
let mut renames = BTreeMap::new();
|
||||
let mut fn_defs = BTreeMap::new();
|
||||
|
||||
for item_fn in fns.iter() {
|
||||
for item_fn in fns {
|
||||
if !item_fn.params().name.is_empty() || item_fn.params().special != FnSpecialAccess::None {
|
||||
let mut names: Vec<_> = item_fn
|
||||
.params()
|
||||
|
@ -185,6 +185,10 @@ impl Engine {
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// # WARNING - Unstable API
|
||||
///
|
||||
/// This API is volatile and may change in the future.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is _extremely_ low level.
|
||||
@ -202,6 +206,7 @@ impl Engine {
|
||||
/// Do not use the arguments after this call. If they are needed afterwards, clone them _before_
|
||||
/// calling this function.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
|
||||
#[inline(always)]
|
||||
pub fn call_fn_raw_raw(
|
||||
&self,
|
||||
@ -259,7 +264,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
let mut this_ptr = this_ptr;
|
||||
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||
|
||||
// Check for data race.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
|
@ -219,7 +219,7 @@ impl Engine {
|
||||
) -> ParseResult<AST> {
|
||||
let (stream, tokenizer_control) = self.lex_raw(
|
||||
scripts.as_ref(),
|
||||
self.token_mapper.as_ref().map(Box::as_ref),
|
||||
self.token_mapper.as_ref().map(<_>::as_ref),
|
||||
);
|
||||
let mut state = ParseState::new(self, scope, tokenizer_control);
|
||||
self.parse(&mut stream.peekable(), &mut state, optimization_level)
|
||||
@ -288,7 +288,7 @@ impl Engine {
|
||||
) -> ParseResult<AST> {
|
||||
let scripts = [script];
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
|
||||
|
||||
let mut peekable = stream.peekable();
|
||||
let mut state = ParseState::new(self, scope, tokenizer_control);
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Module implementing custom syntax for [`Engine`].
|
||||
#![cfg(not(feature = "no_custom_syntax"))]
|
||||
|
||||
use crate::ast::Expr;
|
||||
use crate::func::SendSync;
|
||||
@ -73,6 +74,28 @@ impl Expression<'_> {
|
||||
pub fn eval_with_context(&self, context: &mut EvalContext) -> RhaiResult {
|
||||
context.eval_expression_tree(self)
|
||||
}
|
||||
/// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`].
|
||||
///
|
||||
/// The following option is available:
|
||||
///
|
||||
/// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock]
|
||||
///
|
||||
/// # WARNING - Unstable API
|
||||
///
|
||||
/// This API is volatile and may change in the future.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST].
|
||||
#[inline(always)]
|
||||
pub fn eval_with_context_raw(
|
||||
&self,
|
||||
context: &mut EvalContext,
|
||||
rewind_scope: bool,
|
||||
) -> RhaiResult {
|
||||
#[allow(deprecated)]
|
||||
context.eval_expression_tree_raw(self, rewind_scope)
|
||||
}
|
||||
/// Get the value of this expression if it is a variable name or a string constant.
|
||||
///
|
||||
/// Returns [`None`] also if the constant is not of the specified type.
|
||||
@ -153,6 +176,8 @@ pub struct CustomSyntax {
|
||||
impl Engine {
|
||||
/// Register a custom syntax with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_custom_syntax`.
|
||||
///
|
||||
/// * `symbols` holds a slice of strings that define the custom syntax.
|
||||
/// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax.
|
||||
/// * `func` is the implementation function.
|
||||
@ -296,6 +321,8 @@ impl Engine {
|
||||
}
|
||||
/// Register a custom syntax with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_custom_syntax`.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is very low level.
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Module containing all deprecated API that will be removed in the next major version.
|
||||
|
||||
use crate::{
|
||||
Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext,
|
||||
Position, RhaiResult, RhaiResultOf, Scope, AST,
|
||||
Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, Position,
|
||||
RhaiResult, RhaiResultOf, Scope, AST,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -312,7 +312,8 @@ impl FnPtr {
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression<'_> {
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
impl crate::Expression<'_> {
|
||||
/// If this expression is a variable name, return it. Otherwise [`None`].
|
||||
///
|
||||
/// # Deprecated
|
||||
|
@ -115,7 +115,7 @@ impl Engine {
|
||||
) -> RhaiResultOf<T> {
|
||||
let scripts = [script];
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
|
||||
let mut state = ParseState::new(self, scope, tokenizer_control);
|
||||
|
||||
// No need to optimize a lone expression
|
||||
|
@ -28,10 +28,11 @@ pub mod custom_syntax;
|
||||
|
||||
pub mod deprecated;
|
||||
|
||||
use crate::engine::Precedence;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::{Dynamic, Engine, Identifier};
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
use crate::{engine::Precedence, tokenizer::Token};
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
@ -121,6 +122,8 @@ impl Engine {
|
||||
|
||||
/// Register a custom operator with a precedence into the language.
|
||||
///
|
||||
/// Not available under `no_custom_syntax`.
|
||||
///
|
||||
/// The operator can be a valid identifier, a reserved symbol, a disabled operator or a disabled keyword.
|
||||
///
|
||||
/// The precedence cannot be zero.
|
||||
@ -147,6 +150,7 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
pub fn register_custom_operator(
|
||||
&mut self,
|
||||
keyword: impl AsRef<str>,
|
||||
@ -158,16 +162,21 @@ impl Engine {
|
||||
return Err("precedence cannot be zero".into());
|
||||
}
|
||||
|
||||
match Token::lookup_from_syntax(keyword.as_ref()) {
|
||||
// Standard identifiers, reserved keywords and custom keywords are OK
|
||||
None | Some(Token::Reserved(..)) | Some(Token::Custom(..)) => (),
|
||||
let keyword = keyword.as_ref();
|
||||
|
||||
match Token::lookup_from_syntax(keyword) {
|
||||
// Standard identifiers and reserved keywords are OK
|
||||
None | Some(Token::Reserved(..)) => (),
|
||||
// custom keywords are OK
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Some(Token::Custom(..)) => (),
|
||||
// Active standard keywords cannot be made custom
|
||||
// Disabled keywords are OK
|
||||
Some(token) if token.is_standard_keyword() => {
|
||||
if self.disabled_symbols.is_empty()
|
||||
|| !self.disabled_symbols.contains(&*token.syntax())
|
||||
{
|
||||
return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
|
||||
return Err(format!("'{}' is a reserved keyword", keyword));
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
@ -175,7 +184,7 @@ impl Engine {
|
||||
if self.disabled_symbols.is_empty()
|
||||
|| !self.disabled_symbols.contains(&*token.syntax())
|
||||
{
|
||||
return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
|
||||
return Err(format!("'{}' is a reserved operator", keyword));
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
@ -183,15 +192,14 @@ impl Engine {
|
||||
if self.disabled_symbols.is_empty()
|
||||
|| !self.disabled_symbols.contains(&*token.syntax()) =>
|
||||
{
|
||||
return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
|
||||
return Err(format!("'{}' is a reserved symbol", keyword))
|
||||
}
|
||||
// Disabled symbols are OK
|
||||
Some(_) => (),
|
||||
}
|
||||
|
||||
// Add to custom keywords
|
||||
self.custom_keywords
|
||||
.insert(keyword.as_ref().into(), precedence);
|
||||
self.custom_keywords.insert(keyword.into(), precedence);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -987,8 +987,9 @@ impl Engine {
|
||||
module: Shared<Module>,
|
||||
) {
|
||||
let separator = crate::tokenizer::Token::DoubleColon.syntax();
|
||||
let separator = separator.as_ref();
|
||||
|
||||
if !name.contains(separator.as_ref()) {
|
||||
if !name.contains(separator) {
|
||||
if !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::func::shared_take_or_clone(module);
|
||||
@ -998,7 +999,7 @@ impl Engine {
|
||||
root.insert(name.into(), module);
|
||||
}
|
||||
} else {
|
||||
let mut iter = name.splitn(2, separator.as_ref());
|
||||
let mut iter = name.splitn(2, separator);
|
||||
let sub_module = iter.next().expect("contains separator").trim();
|
||||
let remainder = iter.next().expect("contains separator").trim();
|
||||
|
||||
|
@ -23,7 +23,7 @@ impl Engine {
|
||||
pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> {
|
||||
let scripts = [script];
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
|
||||
let mut state = ParseState::new(self, scope, tokenizer_control);
|
||||
|
||||
let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?;
|
||||
|
@ -54,7 +54,7 @@ impl fmt::Debug for AST {
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !self.lib.is_empty() {
|
||||
for (.., ref fn_def) in self.lib.iter_script_fn() {
|
||||
for (.., fn_def) in self.lib.iter_script_fn() {
|
||||
let sig = fn_def.to_string();
|
||||
fp.field(&sig, &fn_def.body.as_slice());
|
||||
}
|
||||
@ -523,19 +523,19 @@ impl AST {
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
match (
|
||||
self.resolver().map_or(0, |r| r.len()),
|
||||
other.resolver().map_or(0, |r| r.len()),
|
||||
self.resolver().map_or(true, |r| r.is_empty()),
|
||||
other.resolver().map_or(true, |r| r.is_empty()),
|
||||
) {
|
||||
(0, 0) => (),
|
||||
(_, 0) => {
|
||||
(true, true) => (),
|
||||
(false, true) => {
|
||||
_ast.set_resolver(self.resolver().unwrap().clone());
|
||||
}
|
||||
(0, _) => {
|
||||
(true, false) => {
|
||||
_ast.set_resolver(other.resolver().unwrap().clone());
|
||||
}
|
||||
(_, _) => {
|
||||
let mut resolver = (**self.resolver().unwrap()).clone();
|
||||
let other_resolver = (**other.resolver().unwrap()).clone();
|
||||
(false, false) => {
|
||||
let mut resolver = self.resolver().unwrap().as_ref().clone();
|
||||
let other_resolver = other.resolver().unwrap().as_ref().clone();
|
||||
for (k, v) in other_resolver {
|
||||
resolver.insert(k, crate::func::shared_take_or_clone(v));
|
||||
}
|
||||
@ -611,23 +611,16 @@ impl AST {
|
||||
other: Self,
|
||||
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
self.body.extend(other.body.into_iter());
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !other.lib.is_empty() {
|
||||
crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
match (
|
||||
self.resolver.as_ref().map_or(0, |r| r.len()),
|
||||
other.resolver.as_ref().map_or(0, |r| r.len()),
|
||||
self.resolver().map_or(true, |r| r.is_empty()),
|
||||
other.resolver().map_or(true, |r| r.is_empty()),
|
||||
) {
|
||||
(_, 0) => (),
|
||||
(0, _) => {
|
||||
(_, true) => (),
|
||||
(true, false) => {
|
||||
self.set_resolver(other.resolver.unwrap());
|
||||
}
|
||||
(_, _) => {
|
||||
(false, false) => {
|
||||
let resolver = crate::func::shared_make_mut(self.resolver.as_mut().unwrap());
|
||||
let other_resolver = crate::func::shared_take_or_clone(other.resolver.unwrap());
|
||||
for (k, v) in other_resolver {
|
||||
@ -636,6 +629,13 @@ impl AST {
|
||||
}
|
||||
}
|
||||
|
||||
self.body.extend(other.body.into_iter());
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !other.lib.is_empty() {
|
||||
crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
/// Filter out the functions, retaining only some based on a filter predicate.
|
||||
@ -792,7 +792,7 @@ impl AST {
|
||||
if options.contains(ASTFlags::CONSTANT) && include_constants
|
||||
|| !options.contains(ASTFlags::CONSTANT) && include_variables =>
|
||||
{
|
||||
let (name, expr, ..) = x.as_ref();
|
||||
let (name, expr, ..) = &**x;
|
||||
if let Some(value) = expr.get_literal_value() {
|
||||
Some((name.as_str(), options.contains(ASTFlags::CONSTANT), value))
|
||||
} else {
|
||||
|
@ -48,6 +48,9 @@ impl From<(Expr, Expr)> for BinaryExpr {
|
||||
|
||||
/// _(internals)_ A custom syntax expression.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_custom_syntax`.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct CustomExpr {
|
||||
/// List of keywords.
|
||||
@ -61,6 +64,7 @@ pub struct CustomExpr {
|
||||
pub self_terminated: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
impl CustomExpr {
|
||||
/// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)?
|
||||
///
|
||||
@ -421,6 +425,7 @@ pub enum Expr {
|
||||
/// lhs `??` rhs
|
||||
Coalesce(Box<BinaryExpr>, Position),
|
||||
/// Custom syntax
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Custom(Box<CustomExpr>, Position),
|
||||
}
|
||||
|
||||
@ -530,6 +535,7 @@ impl fmt::Debug for Expr {
|
||||
.field("rhs", &x.rhs)
|
||||
.finish()
|
||||
}
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(),
|
||||
}?;
|
||||
|
||||
@ -703,10 +709,12 @@ impl Expr {
|
||||
| Self::Coalesce(.., pos)
|
||||
| Self::Index(.., pos)
|
||||
| Self::Dot(.., pos)
|
||||
| Self::Custom(.., pos)
|
||||
| Self::InterpolatedString(.., pos)
|
||||
| Self::Property(.., pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Self::Custom(.., pos) => *pos,
|
||||
|
||||
Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos,
|
||||
|
||||
Self::Stmt(x) => x.position(),
|
||||
@ -761,10 +769,12 @@ impl Expr {
|
||||
| Self::Variable(.., pos)
|
||||
| Self::FnCall(.., pos)
|
||||
| Self::MethodCall(.., pos)
|
||||
| Self::Custom(.., pos)
|
||||
| Self::InterpolatedString(.., pos)
|
||||
| Self::Property(.., pos) => *pos = new_pos,
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Self::Custom(.., pos) => *pos = new_pos,
|
||||
|
||||
Self::Stmt(x) => x.set_position(new_pos, Position::NONE),
|
||||
}
|
||||
|
||||
@ -853,8 +863,10 @@ impl Expr {
|
||||
| Self::Dot(..)
|
||||
| Self::Index(..)
|
||||
| Self::Array(..)
|
||||
| Self::Map(..)
|
||||
| Self::Custom(..) => false,
|
||||
| Self::Map(..) => false,
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Self::Custom(..) => false,
|
||||
|
||||
Self::Variable(..) => match token {
|
||||
Token::LeftParen => true,
|
||||
@ -886,14 +898,14 @@ impl Expr {
|
||||
|
||||
match self {
|
||||
Self::Stmt(x) => {
|
||||
for s in x.iter() {
|
||||
for s in &**x {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::InterpolatedString(x, ..) | Self::Array(x, ..) => {
|
||||
for e in x.as_ref() {
|
||||
for e in &**x {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -925,6 +937,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Self::Custom(x, ..) => {
|
||||
for e in &x.inputs {
|
||||
if !e.walk(path, on_node) {
|
||||
|
@ -9,7 +9,9 @@ pub mod script_fn;
|
||||
pub mod stmt;
|
||||
|
||||
pub use ast::{ASTNode, AST};
|
||||
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
pub use expr::CustomExpr;
|
||||
pub use expr::{BinaryExpr, Expr, FnCallExpr, FnCallHashes};
|
||||
pub use flags::{ASTFlags, FnAccess};
|
||||
pub use ident::Ident;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -20,8 +22,8 @@ pub use script_fn::EncapsulatedEnviron;
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||
pub use stmt::{
|
||||
ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases,
|
||||
TryCatchBlock,
|
||||
ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer,
|
||||
SwitchCases, TryCatchBlock,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
|
@ -123,7 +123,7 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
|
||||
params: value.params.iter().map(|s| s.as_str()).collect(),
|
||||
access: value.access,
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: value.comments.iter().map(Box::as_ref).collect(),
|
||||
comments: value.comments.iter().map(<_>::as_ref).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
165
src/ast/stmt.rs
165
src/ast/stmt.rs
@ -12,7 +12,7 @@ use std::{
|
||||
hash::Hash,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
ops::{Deref, DerefMut, Range, RangeInclusive},
|
||||
};
|
||||
|
||||
/// _(internals)_ An op-assignment operator.
|
||||
@ -125,7 +125,7 @@ impl fmt::Debug for OpAssignment {
|
||||
/// A statements block with a condition.
|
||||
///
|
||||
/// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Default, Hash)]
|
||||
pub struct ConditionalStmtBlock {
|
||||
/// Condition.
|
||||
pub condition: Expr,
|
||||
@ -153,16 +153,112 @@ impl<B: Into<StmtBlock>> From<(Expr, B)> for ConditionalStmtBlock {
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ A type containing a range case for a `switch` statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum RangeCase {
|
||||
/// Exclusive range.
|
||||
ExclusiveInt(Range<INT>, usize),
|
||||
/// Inclusive range.
|
||||
InclusiveInt(RangeInclusive<INT>, usize),
|
||||
}
|
||||
|
||||
impl fmt::Debug for RangeCase {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {}", r.start, r.end, n),
|
||||
Self::InclusiveInt(r, n) => write!(f, "{}..={} => {}", *r.start(), *r.end(), n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<INT>> for RangeCase {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn from(value: Range<INT>) -> Self {
|
||||
Self::ExclusiveInt(value, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<INT>> for RangeCase {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn from(value: RangeInclusive<INT>) -> Self {
|
||||
Self::InclusiveInt(value, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl RangeCase {
|
||||
/// Is the range empty?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) => r.is_empty(),
|
||||
Self::InclusiveInt(r, ..) => r.is_empty(),
|
||||
}
|
||||
}
|
||||
/// Is the specified number within this range?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains(&self, n: INT) -> bool {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) => r.contains(&n),
|
||||
Self::InclusiveInt(r, ..) => r.contains(&n),
|
||||
}
|
||||
}
|
||||
/// If the range contains only of a single [`INT`], return it;
|
||||
/// otherwise return [`None`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn single_int(&self) -> Option<INT> {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) if r.end.checked_sub(r.start) == Some(1) => Some(r.start),
|
||||
Self::InclusiveInt(r, ..) if r.end().checked_sub(*r.start()) == Some(0) => {
|
||||
Some(*r.start())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Is the specified range inclusive?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn is_inclusive(&self) -> bool {
|
||||
match self {
|
||||
Self::ExclusiveInt(..) => false,
|
||||
Self::InclusiveInt(..) => true,
|
||||
}
|
||||
}
|
||||
/// Get the index to the [`ConditionalStmtBlock`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn index(&self) -> usize {
|
||||
match self {
|
||||
Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n,
|
||||
}
|
||||
}
|
||||
/// Set the index to the [`ConditionalStmtBlock`].
|
||||
#[inline(always)]
|
||||
pub fn set_index(&mut self, index: usize) {
|
||||
match self {
|
||||
Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n = index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ A type containing all cases for a `switch` statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct SwitchCases {
|
||||
/// List of [`ConditionalStmtBlock`]'s.
|
||||
pub blocks: StaticVec<ConditionalStmtBlock>,
|
||||
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
||||
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
|
||||
pub cases: BTreeMap<u64, usize>,
|
||||
/// Statements block for the default case (there can be no condition for the default case).
|
||||
pub def_case: Box<StmtBlock>,
|
||||
pub def_case: usize,
|
||||
/// List of range cases.
|
||||
pub ranges: StaticVec<(INT, INT, bool, Box<ConditionalStmtBlock>)>,
|
||||
pub ranges: StaticVec<RangeCase>,
|
||||
}
|
||||
|
||||
/// _(internals)_ A `try-catch` block.
|
||||
@ -195,7 +291,9 @@ pub type StmtBlockContainer = StaticVec<Stmt>;
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub struct StmtBlock {
|
||||
/// List of [statements][Stmt].
|
||||
block: StmtBlockContainer,
|
||||
/// [Position] of the statements block.
|
||||
span: Span,
|
||||
}
|
||||
|
||||
@ -371,6 +469,17 @@ impl IntoIterator for StmtBlock {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a StmtBlock {
|
||||
type Item = &'a Stmt;
|
||||
type IntoIter = std::slice::Iter<'a, Stmt>;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let x = self.block.iter();
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<Stmt> for StmtBlock {
|
||||
#[inline(always)]
|
||||
fn extend<T: IntoIterator<Item = Stmt>>(&mut self, iter: T) {
|
||||
@ -604,6 +713,7 @@ impl Stmt {
|
||||
Self::Noop(..) => false,
|
||||
|
||||
Self::Expr(e) => match &**e {
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Expr::Custom(x, ..) if x.is_self_terminated() => true,
|
||||
_ => false,
|
||||
},
|
||||
@ -634,14 +744,17 @@ impl Stmt {
|
||||
x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::Switch(x, ..) => {
|
||||
x.0.is_pure()
|
||||
&& x.1.cases.values().all(|block| {
|
||||
let (expr, sw) = &**x;
|
||||
expr.is_pure()
|
||||
&& sw.cases.values().all(|&c| {
|
||||
let block = &sw.blocks[c];
|
||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
||||
})
|
||||
&& x.1.ranges.iter().all(|(.., block)| {
|
||||
&& sw.ranges.iter().all(|r| {
|
||||
let block = &sw.blocks[r.index()];
|
||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
||||
})
|
||||
&& x.1.def_case.iter().all(Stmt::is_pure)
|
||||
&& sw.blocks[sw.def_case].statements.iter().all(Stmt::is_pure)
|
||||
}
|
||||
|
||||
// Loops that exit can be pure because it can never be infinite.
|
||||
@ -715,7 +828,7 @@ impl Stmt {
|
||||
match self {
|
||||
Self::Var(x, ..) => x.1.is_pure(),
|
||||
|
||||
Self::Expr(e) => match e.as_ref() {
|
||||
Self::Expr(e) => match &**e {
|
||||
Expr::Stmt(s) => s.iter().all(Stmt::is_internally_pure),
|
||||
_ => self.is_pure(),
|
||||
},
|
||||
@ -765,42 +878,48 @@ impl Stmt {
|
||||
if !x.0.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in x.1.iter() {
|
||||
for s in &x.1 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in x.2.iter() {
|
||||
for s in &x.2 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Switch(x, ..) => {
|
||||
if !x.0.walk(path, on_node) {
|
||||
let (expr, sw) = &**x;
|
||||
|
||||
if !expr.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for b in x.1.cases.values() {
|
||||
if !b.condition.walk(path, on_node) {
|
||||
for (.., &b) in &sw.cases {
|
||||
let block = &sw.blocks[b];
|
||||
|
||||
if !block.condition.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in b.statements.iter() {
|
||||
for s in &block.statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (.., b) in &x.1.ranges {
|
||||
if !b.condition.walk(path, on_node) {
|
||||
for r in &sw.ranges {
|
||||
let block = &sw.blocks[r.index()];
|
||||
|
||||
if !block.condition.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in b.statements.iter() {
|
||||
for s in &block.statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for s in x.1.def_case.iter() {
|
||||
for s in &sw.blocks[sw.def_case].statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -820,7 +939,7 @@ impl Stmt {
|
||||
if !x.2.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in x.3.iter() {
|
||||
for s in &x.3 {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -849,12 +968,12 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
Self::TryCatch(x, ..) => {
|
||||
for s in x.try_block.iter() {
|
||||
for s in &x.try_block {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in x.catch_block.iter() {
|
||||
for s in &x.catch_block {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Main module defining the script evaluation [`Engine`].
|
||||
|
||||
use crate::api::custom_syntax::CustomSyntax;
|
||||
use crate::api::options::LangOptions;
|
||||
use crate::func::native::{
|
||||
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback,
|
||||
@ -111,9 +110,11 @@ pub struct Engine {
|
||||
/// A set of symbols to disable.
|
||||
pub(crate) disabled_symbols: BTreeSet<Identifier>,
|
||||
/// A map containing custom keywords and precedence to recognize.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
|
||||
/// Custom syntax.
|
||||
pub(crate) custom_syntax: BTreeMap<Identifier, CustomSyntax>,
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
pub(crate) custom_syntax: BTreeMap<Identifier, crate::api::custom_syntax::CustomSyntax>,
|
||||
/// Callback closure for filtering variable definition.
|
||||
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
|
||||
/// Callback closure for resolving variable access.
|
||||
@ -160,17 +161,19 @@ impl fmt::Debug for Engine {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
f.field("global_sub_modules", &self.global_sub_modules);
|
||||
|
||||
f.field("disabled_symbols", &self.disabled_symbols)
|
||||
.field("custom_keywords", &self.custom_keywords)
|
||||
.field(
|
||||
f.field("disabled_symbols", &self.disabled_symbols);
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
f.field("custom_keywords", &self.custom_keywords).field(
|
||||
"custom_syntax",
|
||||
&self
|
||||
.custom_syntax
|
||||
.keys()
|
||||
.map(|s| s.as_str())
|
||||
.collect::<String>(),
|
||||
)
|
||||
.field("def_var_filter", &self.def_var_filter.is_some())
|
||||
);
|
||||
|
||||
f.field("def_var_filter", &self.def_var_filter.is_some())
|
||||
.field("resolve_var", &self.resolve_var.is_some())
|
||||
.field("token_mapper", &self.token_mapper.is_some());
|
||||
|
||||
@ -268,7 +271,9 @@ impl Engine {
|
||||
|
||||
empty_string: ImmutableString::new(),
|
||||
disabled_symbols: BTreeSet::new(),
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
custom_keywords: BTreeMap::new(),
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
custom_syntax: BTreeMap::new(),
|
||||
|
||||
def_var_filter: None,
|
||||
|
@ -205,7 +205,7 @@ impl Engine {
|
||||
|
||||
let crate::ast::FnCallExpr {
|
||||
name, hashes, args, ..
|
||||
} = x.as_ref();
|
||||
} = &**x;
|
||||
|
||||
let offset = idx_values.len() - args.len();
|
||||
let call_args = &mut idx_values[offset..];
|
||||
@ -266,7 +266,7 @@ impl Engine {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, lib, this_ptr, rhs, level)?;
|
||||
|
||||
let ((getter, hash_get), (setter, hash_set), name) = x.as_ref();
|
||||
let ((getter, hash_get), (setter, hash_set), name) = &**x;
|
||||
let (mut new_val, op_info) = new_val.expect("`Some`");
|
||||
|
||||
if op_info.is_op_assignment() {
|
||||
@ -331,7 +331,7 @@ impl Engine {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, lib, this_ptr, rhs, level)?;
|
||||
|
||||
let ((getter, hash_get), _, name) = x.as_ref();
|
||||
let ((getter, hash_get), _, name) = &**x;
|
||||
let args = &mut [target.as_mut()];
|
||||
self.call_native_fn(
|
||||
global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos,
|
||||
@ -382,7 +382,7 @@ impl Engine {
|
||||
|
||||
let crate::ast::FnCallExpr {
|
||||
name, hashes, args, ..
|
||||
} = x.as_ref();
|
||||
} = &**x;
|
||||
|
||||
let offset = idx_values.len() - args.len();
|
||||
let call_args = &mut idx_values[offset..];
|
||||
@ -425,7 +425,7 @@ impl Engine {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, lib, this_ptr, _node, level)?;
|
||||
|
||||
let ((getter, hash_get), (setter, hash_set), name) = p.as_ref();
|
||||
let ((getter, hash_get), (setter, hash_set), name) = &**p;
|
||||
let rhs_chain = rhs.into();
|
||||
let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()];
|
||||
let args = &mut arg_values[..1];
|
||||
@ -507,7 +507,7 @@ impl Engine {
|
||||
|
||||
let crate::ast::FnCallExpr {
|
||||
name, hashes, args, ..
|
||||
} = f.as_ref();
|
||||
} = &**f;
|
||||
let rhs_chain = rhs.into();
|
||||
|
||||
let offset = idx_values.len() - args.len();
|
||||
@ -563,9 +563,9 @@ impl Engine {
|
||||
let chain_type = ChainType::from(expr);
|
||||
let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x, options, pos) => (x.as_ref(), *options, *pos),
|
||||
Expr::Index(x, options, pos) => (&**x, *options, *pos),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x, options, pos) => (x.as_ref(), *options, *pos),
|
||||
Expr::Dot(x, options, pos) => (&**x, *options, *pos),
|
||||
expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr),
|
||||
};
|
||||
|
||||
@ -666,7 +666,7 @@ impl Engine {
|
||||
Expr::MethodCall(x, ..)
|
||||
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
||||
{
|
||||
for arg_expr in x.args.as_ref() {
|
||||
for arg_expr in &x.args {
|
||||
idx_values.push(
|
||||
self.get_arg_value(scope, global, caches, lib, this_ptr, arg_expr, level)?
|
||||
.0
|
||||
@ -686,7 +686,7 @@ impl Engine {
|
||||
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..)
|
||||
if !parent_options.contains(ASTFlags::BREAK) =>
|
||||
{
|
||||
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
||||
let crate::ast::BinaryExpr { lhs, rhs, .. } = &**x;
|
||||
|
||||
let mut _arg_values = FnArgsVec::new_const();
|
||||
|
||||
@ -700,7 +700,7 @@ impl Engine {
|
||||
Expr::MethodCall(x, ..)
|
||||
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
||||
{
|
||||
for arg_expr in x.args.as_ref() {
|
||||
for arg_expr in &x.args {
|
||||
_arg_values.push(
|
||||
self.get_arg_value(
|
||||
scope, global, caches, lib, this_ptr, arg_expr, level,
|
||||
|
@ -357,7 +357,7 @@ impl Debugger {
|
||||
ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
|
||||
x.name == *name
|
||||
}
|
||||
ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() {
|
||||
ASTNode::Stmt(Stmt::Expr(e)) => match &**e {
|
||||
Expr::FnCall(x, ..) => x.name == *name,
|
||||
_ => false,
|
||||
},
|
||||
@ -367,7 +367,7 @@ impl Debugger {
|
||||
ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
|
||||
x.args.len() == *args && x.name == *name
|
||||
}
|
||||
ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() {
|
||||
ASTNode::Stmt(Stmt::Expr(e)) => match &**e {
|
||||
Expr::FnCall(x, ..) => x.args.len() == *args && x.name == *name,
|
||||
_ => false,
|
||||
},
|
||||
@ -560,7 +560,7 @@ impl Engine {
|
||||
ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => {
|
||||
level + 1
|
||||
}
|
||||
ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => {
|
||||
ASTNode::Stmt(Stmt::Expr(e)) if matches!(**e, Expr::FnCall(..)) => {
|
||||
level + 1
|
||||
}
|
||||
_ => level,
|
||||
|
@ -1,12 +1,12 @@
|
||||
//! Evaluation context.
|
||||
|
||||
use super::{Caches, GlobalRuntimeState};
|
||||
use crate::{Dynamic, Engine, Expression, Module, RhaiResult, Scope};
|
||||
use crate::{Dynamic, Engine, Module, Scope};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
/// Context of a script evaluation process.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> {
|
||||
/// The current [`Engine`].
|
||||
engine: &'a Engine,
|
||||
@ -142,13 +142,40 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, '
|
||||
self.level
|
||||
}
|
||||
|
||||
/// Evaluate an [expression tree][Expression] within this [evaluation context][`EvalContext`].
|
||||
/// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`].
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is very low level. It evaluates an expression from an [`AST`][crate::AST].
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[inline(always)]
|
||||
pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult {
|
||||
pub fn eval_expression_tree(&mut self, expr: &crate::Expression) -> crate::RhaiResult {
|
||||
#[allow(deprecated)]
|
||||
self.eval_expression_tree_raw(expr, true)
|
||||
}
|
||||
/// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`].
|
||||
///
|
||||
/// The following option is available:
|
||||
///
|
||||
/// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock]
|
||||
///
|
||||
/// # WARNING - Unstable API
|
||||
///
|
||||
/// This API is volatile and may change in the future.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST].
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
|
||||
#[inline]
|
||||
pub fn eval_expression_tree_raw(
|
||||
&mut self,
|
||||
expr: &crate::Expression,
|
||||
rewind_scope: bool,
|
||||
) -> crate::RhaiResult {
|
||||
let expr: &crate::ast::Expr = expr;
|
||||
|
||||
let mut new_caches = Caches::new();
|
||||
|
||||
let caches = match self.caches.as_mut() {
|
||||
@ -156,7 +183,18 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, '
|
||||
None => &mut new_caches,
|
||||
};
|
||||
|
||||
self.engine.eval_expr(
|
||||
match expr {
|
||||
crate::ast::Expr::Stmt(statements) => self.engine.eval_stmt_block(
|
||||
self.scope,
|
||||
self.global,
|
||||
caches,
|
||||
self.lib,
|
||||
self.this_ptr,
|
||||
&statements,
|
||||
rewind_scope,
|
||||
self.level,
|
||||
),
|
||||
_ => self.engine.eval_expr(
|
||||
self.scope,
|
||||
self.global,
|
||||
caches,
|
||||
@ -164,6 +202,7 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, '
|
||||
self.this_ptr,
|
||||
expr,
|
||||
self.level,
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||
use crate::ast::{Expr, FnCallExpr, OpAssignment};
|
||||
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
|
||||
use std::num::NonZeroUsize;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -60,7 +60,7 @@ impl Engine {
|
||||
Expr::Variable(_, Some(_), _) => {
|
||||
self.search_scope_only(scope, global, lib, this_ptr, expr, level)
|
||||
}
|
||||
Expr::Variable(v, None, _var_pos) => match v.as_ref() {
|
||||
Expr::Variable(v, None, _var_pos) => match &**v {
|
||||
// Normal variable access
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(_, ns, ..) if ns.is_empty() => {
|
||||
@ -323,7 +323,7 @@ impl Engine {
|
||||
let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE);
|
||||
let root = ("", Position::NONE);
|
||||
|
||||
for expr in x.iter() {
|
||||
for expr in &**x {
|
||||
let item =
|
||||
match self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) {
|
||||
Ok(r) => r,
|
||||
@ -354,7 +354,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
let mut sizes = (0, 0, 0);
|
||||
|
||||
for item_expr in x.iter() {
|
||||
for item_expr in &**x {
|
||||
let value = match self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, item_expr, level)
|
||||
{
|
||||
@ -392,7 +392,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
let mut sizes = (0, 0, 0);
|
||||
|
||||
for (key, value_expr) in x.0.iter() {
|
||||
for (key, value_expr) in &x.0 {
|
||||
let value = match self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, value_expr, level)
|
||||
{
|
||||
@ -475,8 +475,10 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Expr::Custom(custom, pos) => {
|
||||
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
||||
let expressions: crate::StaticVec<_> =
|
||||
custom.inputs.iter().map(Into::into).collect();
|
||||
// The first token acts as the custom syntax's key
|
||||
let key_token = custom.tokens.first().unwrap();
|
||||
// The key should exist, unless the AST is compiled in a different Engine
|
||||
|
@ -197,7 +197,7 @@ impl GlobalRuntimeState<'_> {
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(self.modules.iter().rev())
|
||||
.map(|(name, module)| (name.as_str(), module.as_ref()))
|
||||
.map(|(name, module)| (name.as_str(), &**module))
|
||||
}
|
||||
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
|
||||
///
|
||||
@ -327,6 +327,21 @@ impl IntoIterator for GlobalRuntimeState<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> {
|
||||
type Item = (&'a Identifier, &'a crate::Shared<crate::Module>);
|
||||
type IntoIter = std::iter::Zip<
|
||||
std::iter::Rev<std::slice::Iter<'a, Identifier>>,
|
||||
std::iter::Rev<std::slice::Iter<'a, crate::Shared<crate::Module>>>,
|
||||
>;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let x = self.keys.iter().rev().zip(self.modules.iter().rev());
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
impl<K: Into<Identifier>, M: Into<crate::Shared<crate::Module>>> Extend<(K, M)>
|
||||
for GlobalRuntimeState<'_>
|
||||
|
@ -248,7 +248,7 @@ impl Engine {
|
||||
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
||||
|
||||
let result = if x.1.lhs.is_variable_access(false) {
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||
|
||||
let rhs_result = self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, rhs, level)
|
||||
@ -294,7 +294,7 @@ impl Engine {
|
||||
rhs_result
|
||||
}
|
||||
} else {
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = &**x;
|
||||
|
||||
let rhs_result = self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, rhs, level)
|
||||
@ -356,7 +356,7 @@ impl Engine {
|
||||
|
||||
// If statement
|
||||
Stmt::If(x, ..) => {
|
||||
let (expr, if_block, else_block) = x.as_ref();
|
||||
let (expr, if_block, else_block) = &**x;
|
||||
|
||||
let guard_val = self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, expr, level)
|
||||
@ -394,11 +394,12 @@ impl Engine {
|
||||
let (
|
||||
expr,
|
||||
SwitchCases {
|
||||
blocks,
|
||||
cases,
|
||||
def_case,
|
||||
ranges,
|
||||
},
|
||||
) = x.as_ref();
|
||||
) = &**x;
|
||||
|
||||
let value_result =
|
||||
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level);
|
||||
@ -410,7 +411,9 @@ impl Engine {
|
||||
let hash = hasher.finish();
|
||||
|
||||
// First check hashes
|
||||
if let Some(case_block) = cases.get(&hash) {
|
||||
if let Some(&case_block) = cases.get(&hash) {
|
||||
let case_block = &blocks[case_block];
|
||||
|
||||
let cond_result = match case_block.condition {
|
||||
Expr::BoolConstant(b, ..) => Ok(b),
|
||||
ref c => self
|
||||
@ -432,12 +435,9 @@ impl Engine {
|
||||
let value = value.as_int().expect("`INT`");
|
||||
let mut result = Ok(None);
|
||||
|
||||
for (.., block) in
|
||||
ranges.iter().filter(|&&(start, end, inclusive, ..)| {
|
||||
(!inclusive && (start..end).contains(&value))
|
||||
|| (inclusive && (start..=end).contains(&value))
|
||||
})
|
||||
{
|
||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||
let block = &blocks[r.index()];
|
||||
|
||||
let cond_result = match block.condition {
|
||||
Expr::BoolConstant(b, ..) => Ok(b),
|
||||
ref c => self
|
||||
@ -481,6 +481,8 @@ impl Engine {
|
||||
}
|
||||
} else if let Ok(None) = stmt_block_result {
|
||||
// Default match clause
|
||||
let def_case = &blocks[*def_case].statements;
|
||||
|
||||
if !def_case.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, global, caches, lib, this_ptr, def_case, true, level,
|
||||
@ -498,7 +500,7 @@ impl Engine {
|
||||
|
||||
// Loop
|
||||
Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..)) => loop {
|
||||
let (.., body) = x.as_ref();
|
||||
let (.., body) = &**x;
|
||||
|
||||
if !body.is_empty() {
|
||||
match self
|
||||
@ -519,7 +521,7 @@ impl Engine {
|
||||
|
||||
// While loop
|
||||
Stmt::While(x, ..) => loop {
|
||||
let (expr, body) = x.as_ref();
|
||||
let (expr, body) = &**x;
|
||||
|
||||
let condition = self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, expr, level)
|
||||
@ -550,7 +552,7 @@ impl Engine {
|
||||
|
||||
// Do loop
|
||||
Stmt::Do(x, options, ..) => loop {
|
||||
let (expr, body) = x.as_ref();
|
||||
let (expr, body) = &**x;
|
||||
let is_while = !options.contains(ASTFlags::NEGATED);
|
||||
|
||||
if !body.is_empty() {
|
||||
@ -583,7 +585,7 @@ impl Engine {
|
||||
|
||||
// For loop
|
||||
Stmt::For(x, ..) => {
|
||||
let (var_name, counter, expr, statements) = x.as_ref();
|
||||
let (var_name, counter, expr, statements) = &**x;
|
||||
|
||||
let iter_result = self
|
||||
.eval_expr(scope, global, caches, lib, this_ptr, expr, level)
|
||||
@ -726,7 +728,7 @@ impl Engine {
|
||||
name: catch_var, ..
|
||||
},
|
||||
catch_block,
|
||||
} = x.as_ref();
|
||||
} = &**x;
|
||||
|
||||
let result = self
|
||||
.eval_stmt_block(scope, global, caches, lib, this_ptr, try_block, true, level)
|
||||
@ -830,7 +832,7 @@ impl Engine {
|
||||
}
|
||||
// Let/const statement
|
||||
Stmt::Var(x, options, pos) => {
|
||||
let (var_name, expr, index) = x.as_ref();
|
||||
let (var_name, expr, index) = &**x;
|
||||
|
||||
let access = if options.contains(ASTFlags::CONSTANT) {
|
||||
AccessMode::ReadOnly
|
||||
@ -924,7 +926,7 @@ impl Engine {
|
||||
// Import statement
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(x, _pos) => {
|
||||
let (expr, export) = x.as_ref();
|
||||
let (expr, export) = &**x;
|
||||
|
||||
// Guard against too many modules
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -993,7 +995,7 @@ impl Engine {
|
||||
// Export statement
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Export(x, ..) => {
|
||||
let (Ident { name, pos, .. }, alias) = x.as_ref();
|
||||
let (Ident { name, pos, .. }, alias) = &**x;
|
||||
// Mark scope variables as public
|
||||
if let Some((index, ..)) = scope.get_index(name) {
|
||||
let alias = if alias.is_empty() { name } else { alias }.clone();
|
||||
|
@ -178,7 +178,7 @@ impl CallableFunction {
|
||||
#[must_use]
|
||||
pub fn get_iter_fn(&self) -> Option<&IteratorFn> {
|
||||
match self {
|
||||
Self::Iterator(f) => Some(f.as_ref()),
|
||||
Self::Iterator(f) => Some(&**f),
|
||||
Self::Pure(..) | Self::Method(..) | Self::Plugin(..) => None,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
|
@ -110,7 +110,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||
Self {
|
||||
engine: value.0,
|
||||
fn_name: value.1.as_ref(),
|
||||
source: value.2.map(S::as_ref),
|
||||
source: value.2.map(<_>::as_ref),
|
||||
global: Some(value.3),
|
||||
lib: value.4.as_ref(),
|
||||
pos: value.5,
|
||||
|
@ -131,7 +131,7 @@ impl Engine {
|
||||
lib
|
||||
} else {
|
||||
caches.push_fn_resolution_cache();
|
||||
lib_merged.push(fn_lib.as_ref());
|
||||
lib_merged.push(&**fn_lib);
|
||||
lib_merged.extend(lib.iter().cloned());
|
||||
&lib_merged
|
||||
},
|
||||
|
12
src/lib.rs
12
src/lib.rs
@ -165,7 +165,6 @@ type ExclusiveRange = std::ops::Range<INT>;
|
||||
/// An inclusive integer range.
|
||||
type InclusiveRange = std::ops::RangeInclusive<INT>;
|
||||
|
||||
pub use api::custom_syntax::Expression;
|
||||
pub use api::events::VarDefInfo;
|
||||
pub use ast::{FnAccess, AST};
|
||||
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
|
||||
@ -179,6 +178,9 @@ pub use types::{
|
||||
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
pub use api::custom_syntax::Expression;
|
||||
|
||||
/// _(debugging)_ Module containing types for debugging.
|
||||
/// Exported under the `debugging` feature only.
|
||||
#[cfg(feature = "debugging")]
|
||||
@ -282,10 +284,14 @@ pub use parser::ParseState;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
pub use ast::{
|
||||
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr,
|
||||
FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
pub use ast::CustomExpr;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub use ast::Namespace;
|
||||
|
@ -781,7 +781,7 @@ impl Module {
|
||||
#[must_use]
|
||||
pub fn get_sub_module(&self, name: &str) -> Option<&Module> {
|
||||
if !self.modules.is_empty() {
|
||||
self.modules.get(name).map(|m| m.as_ref())
|
||||
self.modules.get(name).map(|m| &**m)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -1006,18 +1006,19 @@ impl Module {
|
||||
(names, return_type)
|
||||
};
|
||||
|
||||
let hash_fn = calc_native_fn_hash(None, name.as_ref(), ¶m_types);
|
||||
let name = name.as_ref();
|
||||
let hash_fn = calc_native_fn_hash(None, name, ¶m_types);
|
||||
|
||||
if is_dynamic {
|
||||
self.dynamic_functions
|
||||
.insert(calc_fn_hash(name.as_ref(), param_types.len()));
|
||||
.insert(calc_fn_hash(name, param_types.len()));
|
||||
}
|
||||
|
||||
self.functions.insert(
|
||||
hash_fn,
|
||||
FuncInfo {
|
||||
metadata: FnMetadata {
|
||||
name: name.as_ref().into(),
|
||||
name: name.into(),
|
||||
namespace,
|
||||
access,
|
||||
params: param_types.len(),
|
||||
@ -1549,7 +1550,7 @@ impl Module {
|
||||
/// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level.
|
||||
#[inline]
|
||||
pub fn combine_flatten(&mut self, other: Self) -> &mut Self {
|
||||
for (.., m) in other.modules.into_iter() {
|
||||
for (.., m) in other.modules {
|
||||
self.combine_flatten(shared_take_or_clone(m));
|
||||
}
|
||||
self.variables.extend(other.variables.into_iter());
|
||||
@ -1707,7 +1708,7 @@ impl Module {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn iter_fn(&self) -> impl Iterator<Item = &FuncInfo> {
|
||||
self.functions.values().map(Box::as_ref)
|
||||
self.functions.values().map(<_>::as_ref)
|
||||
}
|
||||
|
||||
/// Get an iterator over all script-defined functions in the [`Module`].
|
||||
@ -2154,7 +2155,7 @@ impl Module {
|
||||
#[must_use]
|
||||
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> {
|
||||
if !self.all_type_iterators.is_empty() {
|
||||
self.all_type_iterators.get(&id).map(|f| f.as_ref())
|
||||
self.all_type_iterators.get(&id).map(|f| &**f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -2165,7 +2166,7 @@ impl Module {
|
||||
#[must_use]
|
||||
pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> {
|
||||
if !self.type_iterators.is_empty() {
|
||||
self.type_iterators.get(&id).map(|f| f.as_ref())
|
||||
self.type_iterators.get(&id).map(|f| &**f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{ops::AddAssign, vec::IntoIter};
|
||||
use std::{ops::AddAssign, slice::Iter, vec::IntoIter};
|
||||
|
||||
/// [Module] resolution service that holds a collection of module resolvers,
|
||||
/// to be searched in sequential order.
|
||||
@ -116,6 +116,16 @@ impl IntoIterator for ModuleResolversCollection {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a ModuleResolversCollection {
|
||||
type Item = &'a Box<dyn ModuleResolver>;
|
||||
type IntoIter = Iter<'a, Box<dyn ModuleResolver>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for ModuleResolversCollection {
|
||||
fn resolve(
|
||||
&self,
|
||||
|
@ -172,7 +172,7 @@ impl FileModuleResolver {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn base_path(&self) -> Option<&Path> {
|
||||
self.base_path.as_ref().map(PathBuf::as_ref)
|
||||
self.base_path.as_ref().map(<_>::as_ref)
|
||||
}
|
||||
/// Set the base path for script files.
|
||||
#[inline(always)]
|
||||
|
@ -3,7 +3,11 @@ use crate::{
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{collections::btree_map::IntoIter, collections::BTreeMap, ops::AddAssign};
|
||||
use std::{
|
||||
collections::btree_map::{IntoIter, Iter},
|
||||
collections::BTreeMap,
|
||||
ops::AddAssign,
|
||||
};
|
||||
|
||||
/// A static [module][Module] resolution service that serves [modules][Module] added into it.
|
||||
///
|
||||
@ -122,11 +126,22 @@ impl IntoIterator for StaticModuleResolver {
|
||||
type Item = (Identifier, Shared<Module>);
|
||||
type IntoIter = IntoIter<SmartString, Shared<Module>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a StaticModuleResolver {
|
||||
type Item = (&'a Identifier, &'a Shared<Module>);
|
||||
type IntoIter = Iter<'a, SmartString, Shared<Module>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for StaticModuleResolver {
|
||||
#[inline]
|
||||
fn resolve(
|
||||
|
148
src/optimizer.rs
148
src/optimizer.rs
@ -512,7 +512,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
}
|
||||
// if expr { if_block } else { else_block }
|
||||
Stmt::If(x, ..) => {
|
||||
let (condition, body, other) = x.as_mut();
|
||||
let (condition, body, other) = &mut **x;
|
||||
optimize_expr(condition, state, false);
|
||||
**body =
|
||||
optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false);
|
||||
@ -525,11 +525,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
let (
|
||||
match_expr,
|
||||
SwitchCases {
|
||||
blocks: blocks_list,
|
||||
cases,
|
||||
ranges,
|
||||
def_case,
|
||||
},
|
||||
) = x.as_mut();
|
||||
) = &mut **x;
|
||||
|
||||
let value = match_expr.get_literal_value().unwrap();
|
||||
let hasher = &mut get_hasher();
|
||||
@ -537,34 +538,30 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
let hash = hasher.finish();
|
||||
|
||||
// First check hashes
|
||||
if let Some(block) = cases.get_mut(&hash) {
|
||||
match mem::take(&mut block.condition) {
|
||||
if let Some(block) = cases.remove(&hash) {
|
||||
let mut block = mem::take(&mut blocks_list[block]);
|
||||
cases.clear();
|
||||
|
||||
match block.condition {
|
||||
Expr::BoolConstant(true, ..) => {
|
||||
// Promote the matched case
|
||||
let statements = optimize_stmt_block(
|
||||
mem::take(&mut block.statements),
|
||||
state,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
let statements: StmtBlockContainer = mem::take(&mut block.statements);
|
||||
let statements = optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt = (statements, block.statements.span()).into();
|
||||
}
|
||||
mut condition => {
|
||||
ref mut condition => {
|
||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||
optimize_expr(&mut condition, state, false);
|
||||
|
||||
let def_stmt =
|
||||
optimize_stmt_block(mem::take(def_case), state, true, true, false);
|
||||
optimize_expr(condition, state, false);
|
||||
|
||||
let def_case = &mut blocks_list[*def_case].statements;
|
||||
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
||||
let def_case: StmtBlockContainer = mem::take(def_case);
|
||||
let def_stmt = optimize_stmt_block(def_case, state, true, true, false);
|
||||
*stmt = Stmt::If(
|
||||
(
|
||||
condition,
|
||||
mem::take(condition),
|
||||
mem::take(&mut block.statements),
|
||||
StmtBlock::new_with_span(
|
||||
def_stmt,
|
||||
def_case.span_or_else(*pos, Position::NONE),
|
||||
),
|
||||
StmtBlock::new_with_span(def_stmt, def_span),
|
||||
)
|
||||
.into(),
|
||||
match_expr.start_position(),
|
||||
@ -582,21 +579,20 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
|
||||
// Only one range or all ranges without conditions
|
||||
if ranges.len() == 1
|
||||
|| ranges
|
||||
.iter()
|
||||
.all(|(.., c)| matches!(c.condition, Expr::BoolConstant(true, ..)))
|
||||
{
|
||||
for (.., block) in
|
||||
ranges
|
||||
.iter_mut()
|
||||
.filter(|&&mut (start, end, inclusive, ..)| {
|
||||
(!inclusive && (start..end).contains(&value))
|
||||
|| (inclusive && (start..=end).contains(&value))
|
||||
|| ranges.iter().all(|r| {
|
||||
matches!(
|
||||
blocks_list[r.index()].condition,
|
||||
Expr::BoolConstant(true, ..)
|
||||
)
|
||||
})
|
||||
{
|
||||
match mem::take(&mut block.condition) {
|
||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||
let condition = mem::take(&mut blocks_list[r.index()].condition);
|
||||
|
||||
match condition {
|
||||
Expr::BoolConstant(true, ..) => {
|
||||
// Promote the matched case
|
||||
let block = &mut blocks_list[r.index()];
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
let statements =
|
||||
optimize_stmt_block(statements, state, true, true, false);
|
||||
@ -606,21 +602,19 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||
optimize_expr(&mut condition, state, false);
|
||||
|
||||
let def_stmt = optimize_stmt_block(
|
||||
mem::take(def_case),
|
||||
state,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
let def_case = &mut blocks_list[*def_case].statements;
|
||||
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
||||
let def_case: StmtBlockContainer = mem::take(def_case);
|
||||
let def_stmt =
|
||||
optimize_stmt_block(def_case, state, true, true, false);
|
||||
|
||||
let statements = mem::take(&mut blocks_list[r.index()].statements);
|
||||
|
||||
*stmt = Stmt::If(
|
||||
(
|
||||
condition,
|
||||
mem::take(&mut block.statements),
|
||||
StmtBlock::new_with_span(
|
||||
def_stmt,
|
||||
def_case.span_or_else(*pos, Position::NONE),
|
||||
),
|
||||
statements,
|
||||
StmtBlock::new_with_span(def_stmt, def_span),
|
||||
)
|
||||
.into(),
|
||||
match_expr.start_position(),
|
||||
@ -640,16 +634,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
|
||||
let old_ranges_len = ranges.len();
|
||||
|
||||
ranges.retain(|&mut (start, end, inclusive, ..)| {
|
||||
(!inclusive && (start..end).contains(&value))
|
||||
|| (inclusive && (start..=end).contains(&value))
|
||||
});
|
||||
ranges.retain(|r| r.contains(value));
|
||||
|
||||
if ranges.len() != old_ranges_len {
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
for (.., block) in ranges.iter_mut() {
|
||||
for r in &*ranges {
|
||||
let block = &mut blocks_list[r.index()];
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
@ -670,25 +662,29 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
|
||||
// Promote the default case
|
||||
state.set_dirty();
|
||||
let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false);
|
||||
*stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into();
|
||||
let def_case = &mut blocks_list[*def_case].statements;
|
||||
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
||||
let def_case: StmtBlockContainer = mem::take(def_case);
|
||||
let def_stmt = optimize_stmt_block(def_case, state, true, true, false);
|
||||
*stmt = (def_stmt, def_span).into();
|
||||
}
|
||||
// switch
|
||||
Stmt::Switch(x, ..) => {
|
||||
let (
|
||||
match_expr,
|
||||
SwitchCases {
|
||||
blocks: blocks_list,
|
||||
cases,
|
||||
ranges,
|
||||
def_case,
|
||||
..
|
||||
},
|
||||
) = x.as_mut();
|
||||
) = &mut **x;
|
||||
|
||||
optimize_expr(match_expr, state, false);
|
||||
|
||||
// Optimize cases
|
||||
for block in cases.values_mut() {
|
||||
// Optimize blocks
|
||||
for block in blocks_list.iter_mut() {
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
@ -700,38 +696,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
block.condition = Expr::BoolConstant(true, pos);
|
||||
state.set_dirty();
|
||||
}
|
||||
Expr::BoolConstant(false, ..) => {
|
||||
if !block.statements.is_empty() {
|
||||
block.statements = StmtBlock::NONE;
|
||||
state.set_dirty();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Remove false cases
|
||||
cases.retain(|_, block| match block.condition {
|
||||
cases.retain(|_, &mut block| match blocks_list[block].condition {
|
||||
Expr::BoolConstant(false, ..) => {
|
||||
state.set_dirty();
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
|
||||
// Optimize ranges
|
||||
for (.., block) in ranges.iter_mut() {
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
optimize_expr(&mut block.condition, state, false);
|
||||
|
||||
match block.condition {
|
||||
Expr::Unit(pos) => {
|
||||
block.condition = Expr::BoolConstant(true, pos);
|
||||
state.set_dirty();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Remove false ranges
|
||||
ranges.retain(|(.., block)| match block.condition {
|
||||
ranges.retain(|r| match blocks_list[r.index()].condition {
|
||||
Expr::BoolConstant(false, ..) => {
|
||||
state.set_dirty();
|
||||
false
|
||||
@ -739,8 +723,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
_ => true,
|
||||
});
|
||||
|
||||
let def_block = mem::take(&mut ***def_case);
|
||||
***def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||
let def_case = &mut blocks_list[*def_case].statements;
|
||||
let def_block = mem::take(&mut **def_case);
|
||||
**def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||
}
|
||||
|
||||
// while false { block } -> Noop
|
||||
@ -753,7 +738,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
},
|
||||
// while expr { block }
|
||||
Stmt::While(x, ..) => {
|
||||
let (condition, body) = x.as_mut();
|
||||
let (condition, body) = &mut **x;
|
||||
optimize_expr(condition, state, false);
|
||||
if let Expr::BoolConstant(true, pos) = condition {
|
||||
*condition = Expr::Unit(*pos);
|
||||
@ -861,7 +846,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
Stmt::Expr(expr) => {
|
||||
optimize_expr(expr, state, false);
|
||||
|
||||
match expr.as_mut() {
|
||||
match &mut **expr {
|
||||
// func(...)
|
||||
Expr::FnCall(x, pos) => {
|
||||
state.set_dirty();
|
||||
@ -907,7 +892,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false);
|
||||
|
||||
// { Stmt(Expr) } - promote
|
||||
match x.as_mut().as_mut() {
|
||||
match &mut ****x {
|
||||
[ Stmt::Expr(e) ] => { state.set_dirty(); *expr = mem::take(e); }
|
||||
_ => ()
|
||||
}
|
||||
@ -1144,7 +1129,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
return;
|
||||
}
|
||||
// Overloaded operators can override built-in.
|
||||
_ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, arg_types.as_ref()) => {
|
||||
_ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, &arg_types) => {
|
||||
if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1])
|
||||
.and_then(|f| {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -1246,11 +1231,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
}
|
||||
|
||||
// Custom syntax
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Expr::Custom(x, ..) => {
|
||||
if x.scope_may_be_changed {
|
||||
state.propagate_constants = false;
|
||||
}
|
||||
x.inputs.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
|
||||
// Do not optimize custom syntax expressions as you won't know how they would be called
|
||||
}
|
||||
|
||||
// All other expressions - skip
|
||||
|
@ -273,12 +273,12 @@ fn collect_fn_metadata(
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax(),
|
||||
ns
|
||||
);
|
||||
scan_module(list, dict, ns.into(), m.as_ref(), filter)
|
||||
scan_module(list, dict, ns.into(), &**m, filter)
|
||||
}
|
||||
}
|
||||
|
||||
for (ns, m) in ctx.iter_imports_raw() {
|
||||
scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)
|
||||
scan_module(&mut list, &dict, ns.clone(), &**m, filter)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ mod map_functions {
|
||||
if !map1.is_empty() {
|
||||
let mut map2 = map2;
|
||||
|
||||
for (m1, v1) in map1.iter_mut() {
|
||||
for (m1, v1) in map1 {
|
||||
if let Some(v2) = map2.get_mut(m1) {
|
||||
let equals = ctx
|
||||
.call_fn_raw(OP_EQUALS, true, false, &mut [v1, v2])?
|
||||
|
208
src/parser.rs
208
src/parser.rs
@ -1,11 +1,10 @@
|
||||
//! Main module defining the lexer and parser.
|
||||
|
||||
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::api::options::LangOptions;
|
||||
use crate::ast::{
|
||||
ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock,
|
||||
ASTFlags, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock,
|
||||
};
|
||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
@ -25,6 +24,7 @@ use crate::{
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
num::{NonZeroU8, NonZeroUsize},
|
||||
};
|
||||
@ -41,7 +41,6 @@ const NEVER_ENDS: &str = "`Token`";
|
||||
|
||||
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug)]
|
||||
pub struct ParseState<'e> {
|
||||
/// Input stream buffer containing the next character to read.
|
||||
pub tokenizer_control: TokenizerControl,
|
||||
@ -55,6 +54,8 @@ pub struct ParseState<'e> {
|
||||
pub stack: Scope<'e>,
|
||||
/// Size of the local variables stack upon entry of the current block scope.
|
||||
pub block_stack_len: usize,
|
||||
/// Controls whether parsing of an expression should stop given the next token.
|
||||
pub expr_filter: fn(&Token) -> bool,
|
||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub external_vars: Vec<crate::ast::Ident>,
|
||||
@ -72,6 +73,27 @@ pub struct ParseState<'e> {
|
||||
pub max_expr_depth: usize,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ParseState<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut f = f.debug_struct("ParseState");
|
||||
|
||||
f.field("tokenizer_control", &self.tokenizer_control)
|
||||
.field("interned_strings", &self.interned_strings)
|
||||
.field("scope", &self.scope)
|
||||
.field("global", &self.global)
|
||||
.field("stack", &self.stack)
|
||||
.field("block_stack_len", &self.block_stack_len);
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
f.field("external_vars", &self.external_vars)
|
||||
.field("allow_capture", &self.allow_capture);
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
f.field("imports", &self.imports);
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
f.field("max_expr_depth", &self.max_expr_depth);
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e> ParseState<'e> {
|
||||
/// Create a new [`ParseState`].
|
||||
#[inline(always)]
|
||||
@ -79,6 +101,7 @@ impl<'e> ParseState<'e> {
|
||||
pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self {
|
||||
Self {
|
||||
tokenizer_control,
|
||||
expr_filter: |_| true,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
external_vars: Vec::new(),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -404,6 +427,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position
|
||||
}
|
||||
|
||||
/// Parse a symbol.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[inline]
|
||||
fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> {
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
@ -1027,15 +1051,16 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
|
||||
let mut ranges = StaticVec::<(INT, INT, bool, Box<ConditionalStmtBlock>)>::new();
|
||||
let mut blocks = StaticVec::<ConditionalStmtBlock>::new();
|
||||
let mut cases = BTreeMap::<u64, usize>::new();
|
||||
let mut ranges = StaticVec::<RangeCase>::new();
|
||||
let mut def_pos = Position::NONE;
|
||||
let mut def_stmt = None;
|
||||
|
||||
loop {
|
||||
const MISSING_RBRACE: &str = "to end this switch block";
|
||||
|
||||
let (expr, condition) = match input.peek().expect(NEVER_ENDS) {
|
||||
let (case_expr_list, condition) = match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::RightBrace, ..) => {
|
||||
eat_token(input, Token::RightBrace);
|
||||
break;
|
||||
@ -1056,7 +1081,7 @@ impl Engine {
|
||||
return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos));
|
||||
}
|
||||
|
||||
(None, Expr::BoolConstant(true, Position::NONE))
|
||||
(Default::default(), Expr::BoolConstant(true, Position::NONE))
|
||||
}
|
||||
(Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)),
|
||||
|
||||
@ -1065,8 +1090,25 @@ impl Engine {
|
||||
}
|
||||
|
||||
_ => {
|
||||
let case_expr =
|
||||
Some(self.parse_expr(input, state, lib, settings.level_up())?);
|
||||
let mut case_expr_list = StaticVec::new();
|
||||
|
||||
loop {
|
||||
let filter = state.expr_filter;
|
||||
state.expr_filter = |t| t != &Token::Pipe;
|
||||
let expr = self.parse_expr(input, state, lib, settings.level_up());
|
||||
state.expr_filter = filter;
|
||||
|
||||
match expr {
|
||||
Ok(expr) => case_expr_list.push(expr),
|
||||
Err(err) => {
|
||||
return Err(PERR::ExprExpected("literal".into()).into_err(err.1))
|
||||
}
|
||||
}
|
||||
|
||||
if !match_token(input, Token::Pipe).0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let condition = if match_token(input, Token::If).0 {
|
||||
ensure_not_statement_expr(input, "a boolean")?;
|
||||
@ -1078,37 +1120,10 @@ impl Engine {
|
||||
} else {
|
||||
Expr::BoolConstant(true, Position::NONE)
|
||||
};
|
||||
(case_expr, condition)
|
||||
(case_expr_list, condition)
|
||||
}
|
||||
};
|
||||
|
||||
let (hash, range) = if let Some(expr) = expr {
|
||||
let value = expr.get_literal_value().ok_or_else(|| {
|
||||
PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position())
|
||||
})?;
|
||||
|
||||
let guard = value.read_lock::<ExclusiveRange>();
|
||||
|
||||
if let Some(range) = guard {
|
||||
(None, Some((range.start, range.end, false)))
|
||||
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
||||
(None, Some((*range.start(), *range.end(), true)))
|
||||
} else if value.is::<INT>() && !ranges.is_empty() {
|
||||
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
|
||||
} else {
|
||||
let hasher = &mut get_hasher();
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
if !cases.is_empty() && cases.contains_key(&hash) {
|
||||
return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position()));
|
||||
}
|
||||
(Some(hash), None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
(Token::DoubleArrow, ..) => (),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
@ -1122,48 +1137,61 @@ impl Engine {
|
||||
};
|
||||
|
||||
let stmt = self.parse_stmt(input, state, lib, settings.level_up())?;
|
||||
|
||||
let need_comma = !stmt.is_self_terminated();
|
||||
|
||||
def_stmt = match (hash, range) {
|
||||
(None, Some(range)) => {
|
||||
let is_empty = if range.2 {
|
||||
(range.0..=range.1).is_empty()
|
||||
} else {
|
||||
(range.0..range.1).is_empty()
|
||||
};
|
||||
blocks.push((condition, stmt).into());
|
||||
let index = blocks.len() - 1;
|
||||
|
||||
if !is_empty {
|
||||
match (range.1.checked_sub(range.0), range.2) {
|
||||
if !case_expr_list.is_empty() {
|
||||
for expr in case_expr_list {
|
||||
let value = expr.get_literal_value().ok_or_else(|| {
|
||||
PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position())
|
||||
})?;
|
||||
|
||||
let mut range_value: Option<RangeCase> = None;
|
||||
|
||||
let guard = value.read_lock::<ExclusiveRange>();
|
||||
if let Some(range) = guard {
|
||||
range_value = Some(range.clone().into());
|
||||
} else if let Some(range) = value.read_lock::<InclusiveRange>() {
|
||||
range_value = Some(range.clone().into());
|
||||
}
|
||||
|
||||
if let Some(mut r) = range_value {
|
||||
if !r.is_empty() {
|
||||
if let Some(n) = r.single_int() {
|
||||
// Unroll single range
|
||||
(Some(1), false) | (Some(0), true) => {
|
||||
let value = Dynamic::from_int(range.0);
|
||||
let value = Dynamic::from_int(n);
|
||||
let hasher = &mut get_hasher();
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
cases.entry(hash).or_insert_with(|| {
|
||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||
block.into()
|
||||
});
|
||||
}
|
||||
cases.entry(hash).or_insert(index);
|
||||
} else {
|
||||
// Other range
|
||||
_ => {
|
||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||
ranges.push((range.0, range.1, range.2, block.into()))
|
||||
r.set_index(index);
|
||||
ranges.push(r);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
None
|
||||
|
||||
if value.is::<INT>() && !ranges.is_empty() {
|
||||
return Err(PERR::WrongSwitchIntegerCase.into_err(expr.start_position()));
|
||||
}
|
||||
(Some(hash), None) => {
|
||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||
cases.insert(hash, block.into());
|
||||
None
|
||||
|
||||
let hasher = &mut get_hasher();
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
if cases.contains_key(&hash) {
|
||||
return Err(PERR::DuplicatedSwitchCase.into_err(expr.start_position()));
|
||||
}
|
||||
cases.insert(hash, index);
|
||||
}
|
||||
} else {
|
||||
def_stmt = Some(index);
|
||||
}
|
||||
(None, None) => Some(Box::new(stmt.into())),
|
||||
_ => unreachable!("both hash and range in switch statement case"),
|
||||
};
|
||||
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::Comma, ..) => {
|
||||
@ -1188,9 +1216,15 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let def_case = def_stmt.unwrap_or_else(|| {
|
||||
blocks.push(Default::default());
|
||||
blocks.len() - 1
|
||||
});
|
||||
|
||||
let cases = SwitchCases {
|
||||
blocks,
|
||||
cases,
|
||||
def_case: def_stmt.unwrap_or_else(|| StmtBlock::NONE.into()),
|
||||
def_case,
|
||||
ranges,
|
||||
};
|
||||
|
||||
@ -1214,6 +1248,12 @@ impl Engine {
|
||||
settings.pos = *token_pos;
|
||||
|
||||
let root_expr = match token {
|
||||
_ if !(state.expr_filter)(token) => {
|
||||
return Err(
|
||||
LexError::UnexpectedInput(token.syntax().to_string()).into_err(settings.pos)
|
||||
)
|
||||
}
|
||||
|
||||
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||
|
||||
Token::Unit => {
|
||||
@ -1409,6 +1449,7 @@ impl Engine {
|
||||
Token::MapStart => self.parse_map_literal(input, state, lib, settings.level_up())?,
|
||||
|
||||
// Custom syntax.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key)
|
||||
if !self.custom_syntax.is_empty() && self.custom_syntax.contains_key(&**key) =>
|
||||
{
|
||||
@ -1538,6 +1579,10 @@ impl Engine {
|
||||
}
|
||||
};
|
||||
|
||||
if !(state.expr_filter)(&input.peek().expect(NEVER_ENDS).0) {
|
||||
return Ok(root_expr);
|
||||
}
|
||||
|
||||
self.parse_postfix(input, state, lib, root_expr, settings)
|
||||
}
|
||||
|
||||
@ -1685,9 +1730,9 @@ impl Engine {
|
||||
// Cache the hash key for namespace-qualified variables
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let namespaced_variable = match lhs {
|
||||
Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(x.as_mut()),
|
||||
Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x),
|
||||
Expr::Index(ref mut x, ..) | Expr::Dot(ref mut x, ..) => match x.lhs {
|
||||
Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(x.as_mut()),
|
||||
Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
@ -1738,6 +1783,10 @@ impl Engine {
|
||||
|
||||
let (token, token_pos) = input.peek().expect(NEVER_ENDS);
|
||||
|
||||
if !(state.expr_filter)(token) {
|
||||
return Err(LexError::UnexpectedInput(token.syntax().to_string()).into_err(*token_pos));
|
||||
}
|
||||
|
||||
let mut settings = settings;
|
||||
settings.pos = *token_pos;
|
||||
|
||||
@ -1885,7 +1934,7 @@ impl Engine {
|
||||
}
|
||||
// var (indexed) = rhs
|
||||
Expr::Variable(ref x, i, var_pos) => {
|
||||
let (index, .., name) = x.as_ref();
|
||||
let (index, .., name) = &**x;
|
||||
let index = i.map_or_else(
|
||||
|| index.expect("either long or short index is `None`").get(),
|
||||
|n| n.get() as usize,
|
||||
@ -2019,7 +2068,7 @@ impl Engine {
|
||||
(.., Expr::FnCall(func, func_pos))
|
||||
if func.args.is_empty()
|
||||
&& [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL]
|
||||
.contains(&func.name.as_ref()) =>
|
||||
.contains(&func.name.as_str()) =>
|
||||
{
|
||||
let err_msg = format!(
|
||||
"'{}' should not be called in method style. Try {}(...);",
|
||||
@ -2130,7 +2179,13 @@ impl Engine {
|
||||
|
||||
loop {
|
||||
let (current_op, current_pos) = input.peek().expect(NEVER_ENDS);
|
||||
|
||||
if !(state.expr_filter)(current_op) {
|
||||
return Ok(root);
|
||||
}
|
||||
|
||||
let precedence = match current_op {
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Token::Custom(c) => self
|
||||
.custom_keywords
|
||||
.get(c)
|
||||
@ -2155,6 +2210,7 @@ impl Engine {
|
||||
|
||||
let (next_op, next_pos) = input.peek().expect(NEVER_ENDS);
|
||||
let next_precedence = match next_op {
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Token::Custom(c) => self
|
||||
.custom_keywords
|
||||
.get(c)
|
||||
@ -2264,6 +2320,7 @@ impl Engine {
|
||||
.into_fn_call_expr(pos)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Token::Custom(s)
|
||||
if self
|
||||
.custom_keywords
|
||||
@ -2294,6 +2351,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Parse a custom syntax.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
fn parse_custom_syntax(
|
||||
&self,
|
||||
input: &mut TokenStream,
|
||||
@ -2301,9 +2359,11 @@ impl Engine {
|
||||
lib: &mut FnLib,
|
||||
settings: ParseSettings,
|
||||
key: impl Into<ImmutableString>,
|
||||
syntax: &CustomSyntax,
|
||||
syntax: &crate::api::custom_syntax::CustomSyntax,
|
||||
pos: Position,
|
||||
) -> ParseResult<Expr> {
|
||||
use crate::api::custom_syntax::markers::*;
|
||||
|
||||
let mut settings = settings;
|
||||
let mut inputs = StaticVec::<Expr>::new();
|
||||
let mut segments = StaticVec::new_const();
|
||||
@ -2317,7 +2377,7 @@ impl Engine {
|
||||
state.stack.push(marker, ());
|
||||
}
|
||||
|
||||
let parse_func = syntax.parse.as_ref();
|
||||
let parse_func = &*syntax.parse;
|
||||
let mut required_token: ImmutableString = key.into();
|
||||
|
||||
tokens.push(required_token.clone().into());
|
||||
@ -2467,7 +2527,7 @@ impl Engine {
|
||||
};
|
||||
|
||||
Ok(Expr::Custom(
|
||||
CustomExpr {
|
||||
crate::ast::CustomExpr {
|
||||
inputs,
|
||||
tokens,
|
||||
scope_may_be_changed: syntax.scope_may_be_changed,
|
||||
|
@ -146,10 +146,10 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
|
||||
.expect("script-defined function")
|
||||
.comments
|
||||
.iter()
|
||||
.map(Box::as_ref)
|
||||
.map(<_>::as_ref)
|
||||
.collect()
|
||||
} else {
|
||||
info.metadata.comments.iter().map(Box::as_ref).collect()
|
||||
info.metadata.comments.iter().map(<_>::as_ref).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +283,7 @@ impl AddAssign for Position {
|
||||
|
||||
/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
pub struct Span {
|
||||
/// Starting [position][Position].
|
||||
start: Position,
|
||||
@ -291,6 +291,12 @@ pub struct Span {
|
||||
end: Position,
|
||||
}
|
||||
|
||||
impl Default for Span {
|
||||
fn default() -> Self {
|
||||
Self::NONE
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// Empty [`Span`].
|
||||
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
|
||||
@ -323,20 +329,20 @@ impl Span {
|
||||
|
||||
impl fmt::Display for Span {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match (self.start().is_none(), self.end().is_none()) {
|
||||
(false, false) if self.start().line() != self.end().line() => {
|
||||
write!(f, "{:?}-{:?}", self.start(), self.end())
|
||||
match (self.start(), self.end()) {
|
||||
(Position::NONE, Position::NONE) => write!(f, "{:?}", Position::NONE),
|
||||
(Position::NONE, end) => write!(f, "..{:?}", end),
|
||||
(start, Position::NONE) => write!(f, "{:?}", start),
|
||||
(start, end) if start.line() != end.line() => {
|
||||
write!(f, "{:?}-{:?}", start, end)
|
||||
}
|
||||
(false, false) => write!(
|
||||
(start, end) => write!(
|
||||
f,
|
||||
"{}:{}-{}",
|
||||
self.start().line().unwrap(),
|
||||
self.start().position().unwrap_or(0),
|
||||
self.end().position().unwrap_or(0)
|
||||
start.line().unwrap(),
|
||||
start.position().unwrap_or(0),
|
||||
end.position().unwrap_or(0)
|
||||
),
|
||||
(true, false) => write!(f, "..{:?}", self.end()),
|
||||
(false, true) => write!(f, "{:?}", self.start()),
|
||||
(true, true) => write!(f, "{:?}", Position::NONE),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -556,6 +562,9 @@ pub enum Token {
|
||||
/// A reserved symbol.
|
||||
Reserved(SmartString),
|
||||
/// A custom keyword.
|
||||
///
|
||||
/// Not available under the `no_custom_syntax` feature.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Custom(SmartString),
|
||||
/// End of the input stream.
|
||||
EOF,
|
||||
@ -676,6 +685,7 @@ impl Token {
|
||||
CharConstant(c) => c.to_string().into(),
|
||||
Identifier(s) => s.to_string().into(),
|
||||
Reserved(s) => s.to_string().into(),
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Custom(s) => s.to_string().into(),
|
||||
LexError(err) => err.to_string().into(),
|
||||
Comment(s) => s.to_string().into(),
|
||||
@ -1063,12 +1073,15 @@ impl Token {
|
||||
#[inline]
|
||||
pub(crate) fn into_function_name_for_override(self) -> Result<SmartString, Self> {
|
||||
match self {
|
||||
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Self::Custom(s) if is_valid_function_name(&s) => Ok(s),
|
||||
Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this token a custom keyword?
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn is_custom(&self) -> bool {
|
||||
@ -2323,7 +2336,12 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
}
|
||||
// Reserved keyword/symbol
|
||||
Some((Token::Reserved(s), pos)) => (match
|
||||
(&*s, !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s))
|
||||
(&*s,
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
(!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)),
|
||||
#[cfg(feature = "no_custom_syntax")]
|
||||
false
|
||||
)
|
||||
{
|
||||
("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
|
||||
@ -2352,7 +2370,10 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
"'#' is not a valid symbol. Should it be '#{'?".to_string(),
|
||||
).into()),
|
||||
// Reserved keyword/operator that is custom.
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
(.., true) => Token::Custom(s),
|
||||
#[cfg(feature = "no_custom_syntax")]
|
||||
(.., true) => unreachable!("no custom operators"),
|
||||
// Reserved keyword that is not custom and disabled.
|
||||
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => {
|
||||
let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token);
|
||||
@ -2362,10 +2383,12 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
(.., false) => Token::Reserved(s),
|
||||
}, pos),
|
||||
// Custom keyword
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Some((Token::Identifier(s), pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s) => {
|
||||
(Token::Custom(s), pos)
|
||||
}
|
||||
// Custom keyword/symbol - must be disabled
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => {
|
||||
if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) {
|
||||
// Disabled standard keyword/symbol
|
||||
|
@ -56,7 +56,7 @@ impl FnPtr {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn fn_name(&self) -> &str {
|
||||
self.fn_name_raw().as_ref()
|
||||
self.fn_name_raw().as_str()
|
||||
}
|
||||
/// Get the name of the function.
|
||||
#[inline(always)]
|
||||
@ -223,7 +223,7 @@ impl FnPtr {
|
||||
args_data = StaticVec::with_capacity(self.curry().len() + arg_values.len());
|
||||
args_data.extend(self.curry().iter().cloned());
|
||||
args_data.extend(arg_values.iter_mut().map(mem::take));
|
||||
arg_values = args_data.as_mut();
|
||||
arg_values = &mut *args_data;
|
||||
};
|
||||
|
||||
let is_method = this_ptr.is_some();
|
||||
|
@ -51,7 +51,10 @@ impl StringsInterner<'_> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get(&mut self, prefix: impl AsRef<str>, text: impl AsRef<str>) -> ImmutableString {
|
||||
let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix.as_ref() {
|
||||
let prefix = prefix.as_ref();
|
||||
let text = text.as_ref();
|
||||
|
||||
let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix {
|
||||
"" => (&mut self.strings, |s| s.into()),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -59,14 +62,14 @@ impl StringsInterner<'_> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
crate::engine::FN_SET => (&mut self.setters, crate::engine::make_setter),
|
||||
|
||||
_ => unreachable!("unsupported prefix {}", prefix.as_ref()),
|
||||
_ => unreachable!("unsupported prefix {}", prefix),
|
||||
};
|
||||
|
||||
if !dict.is_empty() && dict.contains_key(text.as_ref()) {
|
||||
dict.get(text.as_ref()).unwrap().clone()
|
||||
if !dict.is_empty() && dict.contains_key(text) {
|
||||
dict.get(text).unwrap().clone()
|
||||
} else {
|
||||
let value: ImmutableString = mapper(text.as_ref()).into();
|
||||
dict.insert(text.as_ref().into(), value.clone());
|
||||
let value: ImmutableString = mapper(text).into();
|
||||
dict.insert(text.into(), value.clone());
|
||||
value
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +132,21 @@ impl IntoIterator for Scope<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Scope<'_> {
|
||||
type Item = (&'a Identifier, &'a Dynamic, &'a Vec<Identifier>);
|
||||
type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Box::new(
|
||||
self.values
|
||||
.iter()
|
||||
.zip(self.names.iter().zip(self.aliases.iter()))
|
||||
.map(|(value, (name, alias))| (name, value, alias)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Scope<'_> {
|
||||
/// Create a new [`Scope`].
|
||||
///
|
||||
@ -686,7 +701,7 @@ impl Scope<'_> {
|
||||
self.names
|
||||
.iter()
|
||||
.zip(self.values.iter())
|
||||
.map(|(name, value)| (name.as_ref(), value.is_read_only(), value))
|
||||
.map(|(name, value)| (name.as_str(), value.is_read_only(), value))
|
||||
}
|
||||
/// Get a reverse iterator to entries in the [`Scope`].
|
||||
/// Shared values are not expanded.
|
||||
@ -696,7 +711,7 @@ impl Scope<'_> {
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(self.values.iter().rev())
|
||||
.map(|(name, value)| (name.as_ref(), value.is_read_only(), value))
|
||||
.map(|(name, value)| (name.as_str(), value.is_read_only(), value))
|
||||
}
|
||||
/// Remove a range of entries within the [`Scope`].
|
||||
///
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![cfg(not(feature = "no_custom_syntax"))]
|
||||
|
||||
use rhai::{
|
||||
Dynamic, Engine, EvalAltResult, ImmutableString, LexError, ParseErrorType, Position, Scope, INT,
|
||||
};
|
||||
@ -50,7 +52,13 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
break;
|
||||
}
|
||||
|
||||
// Do not rewind if the variable is upper-case
|
||||
if var_name.to_uppercase() == var_name {
|
||||
context.eval_expression_tree_raw(stmt, false)?;
|
||||
} else {
|
||||
context.eval_expression_tree(stmt)?;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
|
||||
context
|
||||
@ -123,6 +131,26 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
)?,
|
||||
144
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let foo = 123;
|
||||
exec [x<15] -> { let foo = x; x += 1; } while x < 42;
|
||||
foo
|
||||
"
|
||||
)?,
|
||||
123
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let foo = 123;
|
||||
exec [ABC<15] -> { let foo = ABC; ABC += 1; } while ABC < 42;
|
||||
foo
|
||||
"
|
||||
)?,
|
||||
14
|
||||
);
|
||||
|
||||
// The first symbol must be an identifier
|
||||
assert_eq!(
|
||||
|
@ -45,6 +45,13 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?,
|
||||
()
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"switch x { 1 | 2 | 3 | 5..50 | 'x' | true => 123, 'z' => 'a' }"
|
||||
)?,
|
||||
123
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?,
|
||||
42
|
||||
|
@ -30,6 +30,7 @@ fn test_tokens_disabled() {
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[test]
|
||||
fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
@ -60,6 +61,7 @@ fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
#[test]
|
||||
fn test_tokens_custom_operator_symbol() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
Loading…
Reference in New Issue
Block a user