Code cleanup and refactor.

This commit is contained in:
Stephen Chung 2022-11-28 23:24:22 +08:00
parent 29a397b216
commit 0c85f0c796
26 changed files with 762 additions and 704 deletions

View File

@ -43,7 +43,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
[features]
default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng
std = ["ahash/std", "num-traits/std", "smartstring/std"]
unchecked = [] # unchecked arithmetic
unchecked = [] # disable safety checks
sync = [] # restrict to only types that implement Send + Sync
no_position = [] # do not track position in the parser
no_optimize = [] # no script optimizer

View File

@ -4,7 +4,7 @@
use crate::ast::Expr;
use crate::func::SendSync;
use crate::parser::ParseResult;
use crate::tokenizer::{is_valid_identifier, Token};
use crate::tokenizer::{is_valid_identifier, Token, NO_TOKEN};
use crate::types::dynamic::Variant;
use crate::{
reify, Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position,
@ -166,6 +166,7 @@ impl Deref for Expression<'_> {
type Target = Expr;
#[inline(always)]
#[must_use]
fn deref(&self) -> &Self::Target {
self.0
}
@ -230,11 +231,11 @@ impl Engine {
continue;
}
let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
let token = Token::lookup_symbol_from_syntax(s).unwrap_or_else(|| {
if Token::is_reserved_keyword(s) {
Some(Token::Reserved(Box::new(s.into())))
Token::Reserved(Box::new(s.into()))
} else {
None
NO_TOKEN
}
});
@ -255,16 +256,16 @@ impl Engine {
#[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position
_ if !segments.is_empty() && token.is_some() => {
_ if !segments.is_empty() && token != NO_TOKEN => {
// Make it a custom keyword/symbol if it is disabled or reserved
if (self
.disabled_symbols
.as_deref()
.map_or(false, |m| m.contains(s))
|| token.map_or(false, |v| v.is_reserved()))
|| token.is_reserved())
&& !self
.custom_keywords
.as_ref()
.as_deref()
.map_or(false, |m| m.contains_key(s))
{
self.custom_keywords
@ -275,10 +276,10 @@ impl Engine {
}
// Standard keyword in first position but not disabled
_ if segments.is_empty()
&& token.as_ref().map_or(false, Token::is_standard_keyword)
&& token.is_standard_keyword()
&& !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(s)) =>
{
return Err(LexError::ImproperSymbol(
@ -295,12 +296,12 @@ impl Engine {
// Make it a custom keyword/symbol if it is disabled or reserved
if self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(s))
|| (token.map_or(false, |v| v.is_reserved())
|| (token.is_reserved()
&& !self
.custom_keywords
.as_ref()
.as_deref()
.map_or(false, |m| m.contains_key(s)))
{
self.custom_keywords

View File

@ -461,7 +461,7 @@ impl Module {
|| def
.engine
.custom_keywords
.as_ref()
.as_deref()
.map_or(false, |m| m.contains_key(f.metadata.name.as_str()));
f.write_definition(writer, def, operator)?;

View File

@ -64,8 +64,8 @@ impl Engine {
let (stream, tokenizer_control) = self.lex_raw(
&scripts,
if has_null {
Some(&|token, _, _| {
Some(if has_null {
&|token, _, _| {
match token {
// `null` => `()`
Token::Reserved(s) if &*s == "null" => Token::Unit,
@ -86,9 +86,9 @@ impl Engine {
// All others
_ => token,
}
})
}
} else {
Some(&|token, _, _| {
&|token, _, _| {
match token {
Token::Reserved(s) if &*s == "null" => Token::LexError(
LexError::ImproperSymbol("null".to_string(), String::new()).into(),
@ -97,24 +97,21 @@ impl Engine {
Token::LeftBrace => Token::MapStart,
// Disallowed syntax
t @ (Token::Unit | Token::MapStart) => Token::LexError(
LexError::ImproperSymbol(
t.literal_syntax().to_string(),
"Invalid JSON syntax".to_string(),
)
LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
.into(),
),
Token::InterpolatedString(..) => Token::LexError(
LexError::ImproperSymbol(
"interpolated string".to_string(),
"Invalid JSON syntax".to_string(),
String::new(),
)
.into(),
),
// All others
_ => token,
}
})
},
}
}),
);
let ast = {

View File

@ -0,0 +1,70 @@
#![cfg(feature = "unchecked")]
use crate::Engine;
impl Engine {
/// The maximum levels of function calls allowed for a script.
///
/// Always returns [`usize::MAX`] under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_call_levels(&self) -> usize {
usize::MAX
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_operations(&self) -> u64 {
0
}
/// The maximum number of imported [modules][crate::Module] allowed for a script.
///
/// Always returns [`usize::MAX`] under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_modules(&self) -> usize {
usize::MAX
}
/// The depth limit for expressions (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_expr_depth(&self) -> usize {
0
}
/// The depth limit for expressions in functions (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_function_expr_depth(&self) -> usize {
0
}
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_string_size(&self) -> usize {
0
}
/// The maximum length of [arrays][crate::Array] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_array_size(&self) -> usize {
0
}
/// The maximum size of [object maps][crate::Map] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_map_size(&self) -> usize {
0
}
}

View File

@ -19,9 +19,21 @@ pub mod options;
pub mod optimize;
pub mod limits;
pub mod limits_unchecked;
pub mod events;
pub mod type_names;
pub mod custom_syntax;
pub mod build_type;
#[cfg(feature = "metadata")]
pub mod definitions;
pub mod deprecated;
use crate::{Dynamic, Engine, Identifier};
#[cfg(feature = "no_std")]
@ -156,7 +168,7 @@ impl Engine {
Some(token) if token.is_standard_keyword() => {
if !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(token.literal_syntax()))
{
return Err(format!("'{keyword}' is a reserved keyword"));
@ -166,7 +178,7 @@ impl Engine {
Some(token) if token.is_standard_symbol() => {
if !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(token.literal_syntax()))
{
return Err(format!("'{keyword}' is a reserved operator"));
@ -176,7 +188,7 @@ impl Engine {
Some(token)
if !self
.disabled_symbols
.as_ref()
.as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) =>
{
return Err(format!("'{keyword}' is a reserved symbol"))
@ -212,82 +224,3 @@ impl Engine {
self
}
}
#[cfg(feature = "unchecked")]
impl Engine {
/// The maximum levels of function calls allowed for a script.
///
/// Always returns [`usize::MAX`] under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_call_levels(&self) -> usize {
usize::MAX
}
/// The maximum number of operations allowed for a script to run (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_operations(&self) -> u64 {
0
}
/// The maximum number of imported [modules][crate::Module] allowed for a script.
///
/// Always returns [`usize::MAX`] under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_modules(&self) -> usize {
usize::MAX
}
/// The depth limit for expressions (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_expr_depth(&self) -> usize {
0
}
/// The depth limit for expressions in functions (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_function_expr_depth(&self) -> usize {
0
}
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_string_size(&self) -> usize {
0
}
/// The maximum length of [arrays][crate::Array] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_array_size(&self) -> usize {
0
}
/// The maximum size of [object maps][crate::Map] (0 for unlimited).
///
/// Always returns zero under `unchecked`.
#[inline(always)]
#[must_use]
pub const fn max_map_size(&self) -> usize {
0
}
}
pub mod type_names;
pub mod custom_syntax;
pub mod build_type;
#[cfg(feature = "metadata")]
pub mod definitions;
pub mod deprecated;

View File

@ -57,7 +57,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
ast.shared_lib()
.iter_fn()
.map(|f| f.func.get_script_fn_def().expect("`ScriptFnDef").clone())
.map(|f| f.func.get_script_fn_def().cloned().expect("`ScriptFnDef"))
.collect(),
optimization_level,
);

View File

@ -1,9 +1,9 @@
//! Module defining script expressions.
use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock};
use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock};
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
use crate::func::hashing::ALT_ZERO_HASH;
use crate::tokenizer::Token;
use crate::tokenizer::{Token, NO_TOKEN};
use crate::types::dynamic::Union;
use crate::{
calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec,
@ -197,8 +197,7 @@ impl FnCallHashes {
#[derive(Clone, Hash)]
pub struct FnCallExpr {
/// Namespace of the function, if any.
#[cfg(not(feature = "no_module"))]
pub namespace: super::Namespace,
pub namespace: Namespace,
/// Function name.
pub name: ImmutableString,
/// Pre-calculated hashes.
@ -208,7 +207,7 @@ pub struct FnCallExpr {
/// Does this function call capture the parent scope?
pub capture_parent_scope: bool,
/// Is this function call a native operator?
/// Otherwise set to [`Token::NonToken`].
/// Otherwise set to [`Token::NONE`].
pub op_token: Token,
}
@ -217,14 +216,13 @@ impl fmt::Debug for FnCallExpr {
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ff = f.debug_struct("FnCallExpr");
#[cfg(not(feature = "no_module"))]
if !self.namespace.is_empty() {
ff.field("namespace", &self.namespace);
}
ff.field("hash", &self.hashes)
.field("name", &self.name)
.field("args", &self.args);
if self.op_token != Token::NonToken {
if self.op_token != NO_TOKEN {
ff.field("op_token", &self.op_token);
}
if self.capture_parent_scope {
@ -241,10 +239,7 @@ impl FnCallExpr {
#[inline(always)]
#[must_use]
pub fn is_qualified(&self) -> bool {
#[cfg(not(feature = "no_module"))]
return !self.namespace.is_empty();
#[cfg(feature = "no_module")]
return false;
!self.namespace.is_empty()
}
/// Convert this into an [`Expr::FnCall`].
#[inline(always)]
@ -304,9 +299,7 @@ pub enum Expr {
/// majority of cases (unless there are more than 255 variables defined!).
/// This is to avoid reading a pointer redirection during each variable access.
Variable(
#[cfg(not(feature = "no_module"))]
Box<(Option<NonZeroUsize>, super::Namespace, u64, ImmutableString)>,
#[cfg(feature = "no_module")] Box<(Option<NonZeroUsize>, [(); 0], u64, ImmutableString)>,
Box<(Option<NonZeroUsize>, Namespace, u64, ImmutableString)>,
Option<NonZeroU8>,
Position,
),
@ -584,13 +577,12 @@ impl Expr {
Union::FnPtr(f, ..) if !f.is_curried() => Self::FnCall(
FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: super::Namespace::NONE,
namespace: Namespace::NONE,
name: KEYWORD_FN_PTR.into(),
hashes: calc_fn_hash(None, f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false,
op_token: Token::NonToken,
op_token: NO_TOKEN,
}
.into(),
pos,

View File

@ -5,6 +5,7 @@ pub mod expr;
pub mod flags;
pub mod ident;
pub mod namespace;
pub mod namespace_none;
pub mod script_fn;
pub mod stmt;
@ -16,6 +17,8 @@ pub use flags::{ASTFlags, FnAccess};
pub use ident::Ident;
#[cfg(not(feature = "no_module"))]
pub use namespace::Namespace;
#[cfg(feature = "no_module")]
pub use namespace_none::Namespace;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use script_fn::EncapsulatedEnviron;

22
src/ast/namespace_none.rs Normal file
View File

@ -0,0 +1,22 @@
//! Namespace reference type.
#![cfg(feature = "no_module")]
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// _(internals)_ A chain of [module][crate::Module] names to namespace-qualify a variable or function call.
/// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
#[derive(Debug, Clone, Eq, PartialEq, Default, Hash)]
pub struct Namespace;
impl Namespace {
/// Constant for no namespace.
pub const NONE: Self = Self;
#[inline(always)]
pub const fn is_empty(&self) -> bool {
true
}
}

View File

@ -16,7 +16,6 @@ use std::{fmt, hash::Hash};
///
/// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone)]
pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST].
@ -35,7 +34,6 @@ pub struct ScriptFnDef {
pub body: StmtBlock,
/// Encapsulated AST environment, if any.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub environ: Option<crate::Shared<EncapsulatedEnviron>>,
/// Function name.
pub name: ImmutableString,

View File

@ -2,7 +2,8 @@
use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
use crate::engine::KEYWORD_EVAL;
use crate::tokenizer::{Span, Token};
use crate::tokenizer::Token;
use crate::types::Span;
use crate::{calc_fn_hash, Position, StaticVec, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;

View File

@ -5,7 +5,7 @@ use super::{Caches, GlobalRuntimeState, Target};
use crate::ast::{ASTFlags, Expr, OpAssignment};
use crate::config::hashing::SusLock;
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::tokenizer::Token;
use crate::tokenizer::NO_TOKEN;
use crate::types::{dynamic::Union, RestoreOnDrop};
use crate::{
calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR,
@ -72,16 +72,10 @@ impl Engine {
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
self.exec_native_fn_call(
global,
caches,
FN_IDX_GET,
Token::NonToken,
hash_idx().0,
&mut [target, idx],
true,
pos,
)
let hash = hash_idx().0;
let args = &mut [target, idx];
self.exec_native_fn_call(global, caches, FN_IDX_GET, NO_TOKEN, hash, args, true, pos)
.map(|(r, ..)| r)
}
@ -101,15 +95,11 @@ impl Engine {
global.level += 1;
let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level);
let hash = hash_idx().1;
let args = &mut [target, idx, new_val];
self.exec_native_fn_call(
global,
caches,
FN_IDX_SET,
Token::NonToken,
hash_idx().1,
&mut [target, idx, new_val],
is_ref_mut,
pos,
global, caches, FN_IDX_SET, NO_TOKEN, hash, args, is_ref_mut, pos,
)
}
@ -766,15 +756,10 @@ impl Engine {
if op_info.is_op_assignment() {
let args = &mut [target.as_mut()];
let (mut orig_val, ..) = self
.exec_native_fn_call(
global,
caches,
getter,
Token::NonToken,
*hash_get,
args,
is_ref_mut,
global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut,
*pos,
)
.or_else(|err| match *err {
@ -807,15 +792,9 @@ impl Engine {
}
let args = &mut [target.as_mut(), &mut new_val];
self.exec_native_fn_call(
global,
caches,
setter,
Token::NonToken,
*hash_set,
args,
is_ref_mut,
*pos,
global, caches, setter, NO_TOKEN, *hash_set, args, is_ref_mut, *pos,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist
@ -840,15 +819,9 @@ impl Engine {
let ((getter, hash_get), _, name) = &**x;
let args = &mut [target.as_mut()];
self.exec_native_fn_call(
global,
caches,
getter,
Token::NonToken,
*hash_get,
args,
is_ref_mut,
*pos,
global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut, *pos,
)
.map_or_else(
|err| match *err {
@ -938,20 +911,13 @@ impl Engine {
self.run_debugger(global, caches, scope, this_ptr, _node)?;
let ((getter, hash_get), (setter, hash_set), name) = &**p;
let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()];
let args = &mut arg_values[..1];
let args = &mut [target.as_mut()];
// Assume getters are always pure
let (mut val, ..) = self
.exec_native_fn_call(
global,
caches,
getter,
Token::NonToken,
*hash_get,
args,
is_ref_mut,
pos,
global, caches, getter, NO_TOKEN, *hash_get, args,
is_ref_mut, pos,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist
@ -981,19 +947,13 @@ impl Engine {
// Feed the value back via a setter just in case it has been updated
if may_be_changed {
// Re-use args because the first &mut parameter will not be consumed
let mut arg_values = [target.as_mut(), val.as_mut()];
let args = &mut arg_values;
let args = &mut [target.as_mut(), val.as_mut()];
// The return value is thrown away and not used.
let _ = self
.exec_native_fn_call(
global,
caches,
setter,
Token::NonToken,
*hash_set,
args,
is_ref_mut,
pos,
global, caches, setter, NO_TOKEN, *hash_set, args,
is_ref_mut, pos,
)
.or_else(|err| match *err {
// Try an indexer if property does not exist

View File

@ -508,11 +508,10 @@ impl Engine {
node: ASTNode<'a>,
event: DebuggerEvent,
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
if let Some(ref x) = self.debugger_interface {
let src = global.source_raw().cloned();
let src = src.as_ref().map(|s| s.as_str());
let context = crate::EvalContext::new(self, global, caches, scope, this_ptr);
if let Some(ref x) = self.debugger_interface {
let context = EvalContext::new(self, global, caches, scope, this_ptr);
let (.., ref on_debugger) = **x;
let command = on_debugger(context, event, node, src, node.position())?;

View File

@ -7,7 +7,6 @@ use std::prelude::v1::*;
/// Context of a script evaluation process.
#[derive(Debug)]
#[allow(dead_code)]
pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// The current [`Engine`].
engine: &'a Engine,

View File

@ -6,13 +6,26 @@ use crate::ast::{
ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock,
};
use crate::func::{get_builtin_op_assignment_fn, get_hasher};
use crate::types::dynamic::AccessMode;
use crate::types::dynamic::{AccessMode, Union};
use crate::types::RestoreOnDrop;
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Dynamic {
/// If the value is a string, intern it.
#[inline(always)]
fn intern_string(self, engine: &Engine) -> Self {
match self.0 {
Union::Str(..) => engine
.get_interned_string(self.into_immutable_string().expect("`ImmutableString`"))
.into(),
_ => self,
}
}
}
impl Engine {
/// Evaluate a statements block.
pub(crate) fn eval_stmt_block(
@ -169,14 +182,13 @@ impl Engine {
self.check_data_size(&*args[0], root.position())?;
} else {
// Normal assignment
// If value is a string, intern it
if new_val.is_string() {
let value = new_val.into_immutable_string().expect("`ImmutableString`");
new_val = self.get_interned_string(value).into();
match target {
// Lock it again just in case it is shared
Target::RefMut(_) | Target::TempValue(_) => {
*target.write_lock::<Dynamic>().unwrap() = new_val
}
_ => **target = new_val,
}
*target.write_lock::<Dynamic>().unwrap() = new_val;
}
target.propagate_changed_value(op_info.pos)
@ -224,14 +236,13 @@ impl Engine {
let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let is_temp_result = !target.is_ref();
let var_name = x.3.as_str();
#[cfg(not(feature = "no_closure"))]
// Also handle case where target is a `Dynamic` shared value
// (returned by a variable resolver, for example)
let is_temp_result = !target.is_ref() && !target.is_shared();
#[cfg(feature = "no_closure")]
let is_temp_result = !target.is_ref();
let is_temp_result = is_temp_result && !target.is_shared();
// Cannot assign to temp result from expression
if is_temp_result {
@ -251,19 +262,16 @@ impl Engine {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
{
let mut rhs_val = self
let rhs_val = self
.eval_expr(global, caches, scope, this_ptr, rhs)?
.flatten();
// If value is a string, intern it
if rhs_val.is_string() {
let value = rhs_val.into_immutable_string().expect("`ImmutableString`");
rhs_val = self.get_interned_string(value).into();
}
.flatten()
.intern_string(self);
let _new_val = Some((rhs_val, op_info));
// Must be either `var[index] op= val` or `var.prop op= val`
// Must be either `var[index] op= val` or `var.prop op= val`.
// The return value of any op-assignment (should be `()`) is thrown away and not used.
let _ =
match lhs {
// name op= rhs (handled above)
Expr::Variable(..) => {
@ -271,14 +279,12 @@ impl Engine {
}
// idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(..) => {
self.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val)
}
Expr::Index(..) => self
.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val),
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(..) => {
self.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val)
}
Expr::Dot(..) => self
.eval_dot_index_chain(global, caches, scope, this_ptr, lhs, _new_val),
_ => unreachable!("cannot assign to expression: {:?}", lhs),
}?;
@ -723,7 +729,8 @@ impl Engine {
// Evaluate initial value
let mut value = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.flatten();
.flatten()
.intern_string(self);
let _alias = if !rewind_scope {
// Put global constants into global module

View File

@ -89,9 +89,9 @@ pub enum Target<'a> {
#[cfg(not(feature = "no_closure"))]
SharedValue {
/// Lock guard to the shared [`Dynamic`].
source: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>,
/// Copy of the value.
value: Dynamic,
guard: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>,
/// Copy of the shared value.
shared_value: Dynamic,
},
/// The target is a temporary [`Dynamic`] value (i.e. its mutation can cause no side effects).
TempValue(Dynamic),
@ -178,21 +178,21 @@ impl<'a> Target<'a> {
}
}
/// Is the [`Target`] a shared value?
#[cfg(not(feature = "no_closure"))]
#[inline]
#[must_use]
pub fn is_shared(&self) -> bool {
match self {
Self::RefMut(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
return match self {
Self::RefMut(r) => r.is_shared(),
Self::SharedValue { .. } => true,
Self::TempValue(value) => value.is_shared(),
#[cfg(not(feature = "no_index"))]
Self::Bit { .. }
| Self::BitField { .. }
| Self::BlobByte { .. }
| Self::StringChar { .. } => false,
}
};
#[cfg(feature = "no_closure")]
return false;
}
/// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary.
#[inline]
@ -200,7 +200,7 @@ impl<'a> Target<'a> {
match self {
Self::RefMut(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { value, .. } => value, // Original shared value is simply taken
Self::SharedValue { shared_value, .. } => shared_value, // Original shared value is simply taken
Self::TempValue(value) => value, // Owned value is simply taken
#[cfg(not(feature = "no_index"))]
Self::Bit { value, .. } => value, // boolean is taken
@ -227,7 +227,7 @@ impl<'a> Target<'a> {
match self {
Self::RefMut(r) => Self::TempValue(r.clone()),
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { value, .. } => Self::TempValue(value),
Self::SharedValue { shared_value, .. } => Self::TempValue(shared_value),
_ => self,
}
}
@ -239,7 +239,7 @@ impl<'a> Target<'a> {
match self {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source,
Self::SharedValue { guard, .. } => guard,
Self::TempValue(value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { source, .. } => source,
@ -365,9 +365,12 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
#[cfg(not(feature = "no_closure"))]
if value.is_shared() {
// Cloning is cheap for a shared value
let val = value.clone();
let source = value.write_lock::<Dynamic>().expect("`Dynamic`");
return Self::SharedValue { source, value: val };
let shared_value = value.clone();
let guard = value.write_lock::<Dynamic>().expect("`Dynamic`");
return Self::SharedValue {
guard,
shared_value,
};
}
Self::RefMut(value)
@ -382,7 +385,7 @@ impl Deref for Target<'_> {
match self {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => source,
Self::SharedValue { guard, .. } => guard,
Self::TempValue(ref value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { ref value, .. }
@ -415,7 +418,7 @@ impl DerefMut for Target<'_> {
match self {
Self::RefMut(r) => r,
#[cfg(not(feature = "no_closure"))]
Self::SharedValue { source, .. } => &mut *source,
Self::SharedValue { guard, .. } => &mut *guard,
Self::TempValue(ref mut value) => value,
#[cfg(not(feature = "no_index"))]
Self::Bit { ref mut value, .. }

View File

@ -8,7 +8,7 @@ use crate::engine::{
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
use crate::tokenizer::{is_valid_function_name, Token};
use crate::tokenizer::{is_valid_function_name, Token, NO_TOKEN};
use crate::types::RestoreOnDrop;
use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
@ -272,7 +272,7 @@ impl Engine {
// Try to find a built-in version
let builtin = args.and_then(|args| match op_token {
Token::NonToken => None,
Token::NONE => None,
token if token.is_op_assignment() => {
let (first_arg, rest_args) = args.split_first().unwrap();
@ -639,15 +639,7 @@ impl Engine {
let local_entry = &mut None;
if let Some(FnResolutionCacheEntry { func, ref source }) = self
.resolve_fn(
global,
caches,
local_entry,
Token::NonToken,
hash,
None,
false,
)
.resolve_fn(global, caches, local_entry, NO_TOKEN, hash, None, false)
.cloned()
{
// Script function call
@ -791,7 +783,7 @@ impl Engine {
caches,
None,
fn_name,
Token::NonToken,
NO_TOKEN,
new_hash,
&mut args,
false,
@ -845,7 +837,7 @@ impl Engine {
caches,
None,
&fn_name,
Token::NonToken,
NO_TOKEN,
new_hash,
&mut args,
is_ref_mut,
@ -944,7 +936,7 @@ impl Engine {
caches,
None,
fn_name,
Token::NonToken,
NO_TOKEN,
hash,
&mut args,
is_ref_mut,
@ -986,7 +978,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call
match name {
_ if op_token != Token::NonToken => (),
_ if op_token != NO_TOKEN => (),
// Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
@ -1217,12 +1209,7 @@ impl Engine {
self.track_operation(global, first_expr.position())?;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")]
let target_is_shared = false;
if target_is_shared || target.is_temp_value() {
if target.is_shared() || target.is_temp_value() {
arg_values.insert(0, target.take_or_clone().flatten());
} else {
// Turn it into a method call only if the object is not shared and not a simple value
@ -1500,7 +1487,7 @@ impl Engine {
let op_token = op_token.clone();
// Short-circuit native binary operator call if under Fast Operators mode
if op_token != Token::NonToken && self.fast_operators() && args.len() == 2 {
if op_token != NO_TOKEN && self.fast_operators() && args.len() == 2 {
let mut lhs = self
.get_arg_value(global, caches, scope, this_ptr, &args[0])?
.0

View File

@ -4,7 +4,7 @@ use super::call::FnCallArgs;
use crate::ast::FnCallHashes;
use crate::eval::{Caches, GlobalRuntimeState};
use crate::plugin::PluginFunction;
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState};
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState, NO_TOKEN};
use crate::types::dynamic::Variant;
use crate::{
calc_fn_hash, reify, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult,
@ -424,7 +424,7 @@ impl<'a> NativeCallContext<'a> {
let caches = &mut Caches::new();
let fn_name = fn_name.as_ref();
let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(Token::NonToken);
let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(NO_TOKEN);
let args_len = args.len();
global.level += 1;

View File

@ -207,9 +207,9 @@ pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
pub use eval::EvalContext;
pub use func::{NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module};
pub use tokenizer::Position;
#[cfg(not(feature = "no_time"))]
pub use types::Instant;
pub use types::Position;
pub use types::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
};
@ -311,12 +311,12 @@ pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant}
pub use types::FloatWrapper;
#[cfg(feature = "internals")]
pub use types::StringsInterner;
pub use types::{Span, StringsInterner};
#[cfg(feature = "internals")]
pub use tokenizer::{
get_next_token, is_valid_function_name, is_valid_identifier, parse_string_literal, InputStream,
MultiInputsStream, Span, Token, TokenIterator, TokenizeState, TokenizerControl,
MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
TokenizerControlBlock,
};

View File

@ -69,14 +69,15 @@ impl<'a> OptimizerState<'a> {
#[inline(always)]
pub fn new(
engine: &'a Engine,
#[cfg(not(feature = "no_function"))] lib: &'a [crate::SharedModule],
lib: &'a [crate::SharedModule],
optimization_level: OptimizationLevel,
) -> Self {
let mut _global = GlobalRuntimeState::new(engine);
let _lib = lib;
#[cfg(not(feature = "no_function"))]
{
_global.lib = lib.iter().cloned().collect();
_global.lib = _lib.iter().cloned().collect();
}
Self {
@ -986,7 +987,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// ``
Expr::InterpolatedString(x, pos) if x.is_empty() => {
state.set_dirty();
*expr = Expr::StringConstant(state.engine.get_interned_string(""), *pos);
*expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
}
// `... ${const} ...`
Expr::InterpolatedString(..) if expr.is_constant() => {
@ -1117,7 +1118,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return;
}
// Overloaded operators can override built-in.
_ if x.args.len() == 2 && x.op_token != Token::NonToken && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
_ if x.args.len() == 2 && x.op_token != Token::NONE && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone(), &arg_values[0], &arg_values[1])
.and_then(|f| {
let context = (state.engine, x.name.as_str(),None, &state.global, *pos).into();
@ -1265,7 +1266,7 @@ impl Engine {
&self,
statements: StmtBlockContainer,
scope: &Scope,
#[cfg(not(feature = "no_function"))] lib: &[crate::SharedModule],
lib: &[crate::SharedModule],
optimization_level: OptimizationLevel,
) -> StmtBlockContainer {
let mut statements = statements;
@ -1277,12 +1278,7 @@ impl Engine {
}
// Set up the state
let mut state = OptimizerState::new(
self,
#[cfg(not(feature = "no_function"))]
lib,
optimization_level,
);
let mut state = OptimizerState::new(self, lib, optimization_level);
// Add constants from global modules
for (name, value) in self.global_modules.iter().rev().flat_map(|m| m.iter_var()) {
@ -1355,19 +1351,17 @@ impl Engine {
module.into()
};
#[cfg(feature = "no_function")]
let lib: crate::Shared<_> = crate::Module::new().into();
statements.shrink_to_fit();
AST::new(
match optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => self.optimize_top_level(
statements,
scope,
#[cfg(not(feature = "no_function"))]
&[lib.clone()],
optimization_level,
),
OptimizationLevel::Simple | OptimizationLevel::Full => {
self.optimize_top_level(statements, scope, &[lib.clone()], optimization_level)
}
},
#[cfg(not(feature = "no_function"))]
lib,

View File

@ -4,7 +4,7 @@ use crate::api::events::VarDefInfo;
use crate::api::options::LangOptions;
use crate::ast::{
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer,
Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer,
SwitchCasesCollection, TryCatchBlock,
};
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
@ -12,14 +12,14 @@ use crate::eval::{Caches, GlobalRuntimeState};
use crate::func::{hashing::get_hasher, StraightHashMap};
use crate::tokenizer::{
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl,
TokenizerControl, NO_TOKEN,
};
use crate::types::dynamic::AccessMode;
use crate::types::StringsInterner;
use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
Scope, Shared, SmartString, StaticVec, AST, INT, PERR,
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, Identifier,
ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, Scope,
Shared, SmartString, StaticVec, AST, INT, PERR,
};
use bitflags::bitflags;
#[cfg(feature = "no_std")]
@ -63,7 +63,7 @@ pub struct ParseState<'e, 's> {
pub block_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))]
pub external_vars: Option<Box<FnArgsVec<Ident>>>,
pub external_vars: Option<Box<crate::FnArgsVec<Ident>>>,
/// An indicator that disables variable capturing into externals one single time
/// up until the nearest consumed Identifier token.
/// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable.
@ -557,7 +557,7 @@ impl Engine {
id: ImmutableString,
no_args: bool,
capture_parent_scope: bool,
#[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace,
namespace: Namespace,
settings: ParseSettings,
) -> ParseResult<Expr> {
let (token, token_pos) = if no_args {
@ -566,8 +566,7 @@ impl Engine {
input.peek().expect(NEVER_ENDS)
};
#[cfg(not(feature = "no_module"))]
let mut namespace = namespace;
let mut _namespace = namespace;
let mut args = StaticVec::new_const();
match token {
@ -588,17 +587,16 @@ impl Engine {
}
#[cfg(not(feature = "no_module"))]
let hash = if namespace.is_empty() {
let hash = if _namespace.is_empty() {
calc_fn_hash(None, &id, 0)
} else {
let root = namespace.root();
let root = _namespace.root();
let index = state.find_module(root);
let is_global = false;
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
let is_global = root == crate::engine::KEYWORD_GLOBAL;
#[cfg(any(feature = "no_function", feature = "no_module"))]
let is_global = false;
let is_global = is_global || root == crate::engine::KEYWORD_GLOBAL;
if settings.has_option(LangOptions::STRICT_VAR)
&& index.is_none()
@ -615,13 +613,13 @@ impl Engine {
.map_or(false, |m| m.contains_key(root))
{
return Err(
PERR::ModuleUndefined(root.into()).into_err(namespace.position())
PERR::ModuleUndefined(root.into()).into_err(_namespace.position())
);
}
namespace.set_index(index);
_namespace.set_index(index);
crate::calc_fn_hash(namespace.iter().map(Ident::as_str), &id, 0)
calc_fn_hash(_namespace.iter().map(Ident::as_str), &id, 0)
};
#[cfg(feature = "no_module")]
let hash = calc_fn_hash(None, &id, 0);
@ -637,9 +635,8 @@ impl Engine {
return Ok(FnCallExpr {
name: state.get_interned_string(id),
capture_parent_scope,
op_token: Token::NonToken,
#[cfg(not(feature = "no_module"))]
namespace,
op_token: NO_TOKEN,
namespace: _namespace,
hashes,
args,
}
@ -664,10 +661,10 @@ impl Engine {
eat_token(input, Token::RightParen);
#[cfg(not(feature = "no_module"))]
let hash = if namespace.is_empty() {
let hash = if _namespace.is_empty() {
calc_fn_hash(None, &id, args.len())
} else {
let root = namespace.root();
let root = _namespace.root();
let index = state.find_module(root);
#[cfg(not(feature = "no_function"))]
@ -691,13 +688,13 @@ impl Engine {
.map_or(false, |m| m.contains_key(root))
{
return Err(
PERR::ModuleUndefined(root.into()).into_err(namespace.position())
PERR::ModuleUndefined(root.into()).into_err(_namespace.position())
);
}
namespace.set_index(index);
_namespace.set_index(index);
crate::calc_fn_hash(namespace.iter().map(Ident::as_str), &id, args.len())
calc_fn_hash(_namespace.iter().map(Ident::as_str), &id, args.len())
};
#[cfg(feature = "no_module")]
let hash = calc_fn_hash(None, &id, args.len());
@ -713,9 +710,8 @@ impl Engine {
return Ok(FnCallExpr {
name: state.get_interned_string(id),
capture_parent_scope,
op_token: Token::NonToken,
#[cfg(not(feature = "no_module"))]
namespace,
op_token: NO_TOKEN,
namespace: _namespace,
hashes,
args,
}
@ -1592,10 +1588,7 @@ impl Engine {
// Identifier
Token::Identifier(..) => {
#[cfg(not(feature = "no_module"))]
let ns = crate::ast::Namespace::NONE;
#[cfg(feature = "no_module")]
let ns = [];
let ns = Namespace::NONE;
let s = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), ..) => s,
@ -1657,10 +1650,7 @@ impl Engine {
// Reserved keyword or symbol
Token::Reserved(..) => {
#[cfg(not(feature = "no_module"))]
let ns = crate::ast::Namespace::NONE;
#[cfg(feature = "no_module")]
let ns = [];
let ns = Namespace::NONE;
let s = match input.next().expect(NEVER_ENDS) {
(Token::Reserved(s), ..) => s,
@ -1763,35 +1753,18 @@ impl Engine {
let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit;
let (.., _ns, _, name) = *x;
let (.., ns, _, name) = *x;
settings.pos = pos;
self.parse_fn_call(
input,
state,
lib,
name,
no_args,
true,
#[cfg(not(feature = "no_module"))]
_ns,
settings.level_up()?,
)?
let settings = settings.level_up()?;
self.parse_fn_call(input, state, lib, name, no_args, true, ns, settings)?
}
// Function call
(Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => {
let (.., _ns, _, name) = *x;
let (.., ns, _, name) = *x;
let no_args = t == Token::Unit;
settings.pos = pos;
self.parse_fn_call(
input,
state,
lib,
name,
t == Token::Unit,
false,
#[cfg(not(feature = "no_module"))]
_ns,
settings.level_up()?,
)?
let settings = settings.level_up()?;
self.parse_fn_call(input, state, lib, name, no_args, false, ns, settings)?
}
// module access
#[cfg(not(feature = "no_module"))]
@ -1959,8 +1932,7 @@ impl Engine {
args.shrink_to_fit();
Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: Default::default(),
namespace: Namespace::NONE,
name: state.get_interned_string("-"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)),
args,
@ -1988,8 +1960,7 @@ impl Engine {
args.shrink_to_fit();
Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: Default::default(),
namespace: Namespace::NONE,
name: state.get_interned_string("+"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)),
args,
@ -2010,8 +1981,7 @@ impl Engine {
args.shrink_to_fit();
Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: Default::default(),
namespace: Namespace::NONE,
name: state.get_interned_string("!"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)),
args,
@ -2062,7 +2032,7 @@ impl Engine {
}
}
let op_info = if op != Token::NonToken {
let op_info = if op != NO_TOKEN {
OpAssignment::new_op_assignment_from_token(op, op_pos)
} else {
OpAssignment::new_assignment(op_pos)
@ -2143,7 +2113,7 @@ impl Engine {
) -> ParseResult<Stmt> {
let (op, pos) = match input.peek().expect(NEVER_ENDS) {
// var = ...
(Token::Equals, ..) => (Token::NonToken, eat_token(input, Token::Equals)),
(Token::Equals, ..) => (NO_TOKEN, eat_token(input, Token::Equals)),
// var op= ...
(token, ..) if token.is_op_assignment() => {
input.next().map(|(op, pos)| (op, pos)).expect(NEVER_ENDS)
@ -2389,7 +2359,7 @@ impl Engine {
let hash = calc_fn_hash(None, &op, 2);
let is_valid_script_function = is_valid_function_name(&op);
let operator_token = if is_valid_script_function {
Token::NonToken
NO_TOKEN
} else {
op_token.clone()
};
@ -2400,8 +2370,7 @@ impl Engine {
args.shrink_to_fit();
let mut op_base = FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: Default::default(),
namespace: Namespace::NONE,
name: state.get_interned_string(&op),
hashes: FnCallHashes::from_native(hash),
args,
@ -2539,10 +2508,7 @@ impl Engine {
let (name, pos) = parse_var_name(input)?;
let name = state.get_interned_string(name);
#[cfg(not(feature = "no_module"))]
let ns = crate::ast::Namespace::NONE;
#[cfg(feature = "no_module")]
let ns = [];
let ns = Namespace::NONE;
segments.push(name.clone());
tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT));
@ -3632,7 +3598,7 @@ impl Engine {
}
.into();
let mut params: FnArgsVec<_> = params.into_iter().map(|(p, ..)| p).collect();
let mut params: crate::FnArgsVec<_> = params.into_iter().map(|(p, ..)| p).collect();
params.shrink_to_fit();
Ok(ScriptFnDef {
@ -3660,7 +3626,7 @@ impl Engine {
parent: &mut ParseState,
lib: &FnLib,
fn_expr: Expr,
externals: FnArgsVec<Ident>,
externals: crate::FnArgsVec<Ident>,
pos: Position,
) -> Expr {
// If there are no captured variables, no need to curry
@ -3684,8 +3650,7 @@ impl Engine {
}));
let expr = FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: Default::default(),
namespace: Namespace::NONE,
name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY),
hashes: FnCallHashes::from_native(calc_fn_hash(
None,
@ -3693,7 +3658,7 @@ impl Engine {
num_externals + 1,
)),
args,
op_token: Token::NonToken,
op_token: NO_TOKEN,
capture_parent_scope: false,
}
.into_fn_call_expr(pos);
@ -3708,7 +3673,7 @@ impl Engine {
let (index, _) = parent.access_var(&name, lib, pos);
(name, index, pos)
})
.collect::<FnArgsVec<_>>()
.collect::<crate::FnArgsVec<_>>()
.into(),
));
statements.push(Stmt::Expr(expr.into()));
@ -3779,20 +3744,20 @@ impl Engine {
// so extract them into a list.
#[cfg(not(feature = "no_closure"))]
let (mut params, externals) = if let Some(ref external_vars) = state.external_vars {
let externals: FnArgsVec<_> = external_vars.iter().cloned().collect();
let externals: crate::FnArgsVec<_> = external_vars.iter().cloned().collect();
let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len());
let mut params = crate::FnArgsVec::with_capacity(params_list.len() + externals.len());
params.extend(externals.iter().map(|Ident { name, .. }| name.clone()));
(params, externals)
} else {
(
FnArgsVec::with_capacity(params_list.len()),
FnArgsVec::new_const(),
crate::FnArgsVec::with_capacity(params_list.len()),
crate::FnArgsVec::new_const(),
)
};
#[cfg(feature = "no_closure")]
let mut params = FnArgsVec::with_capacity(params_list.len());
let mut params = crate::FnArgsVec::with_capacity(params_list.len());
params.append(&mut params_list);

View File

@ -5,7 +5,7 @@ use crate::engine::{
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
use crate::func::native::OnParseTokenCallback;
use crate::{Engine, Identifier, LexError, SmartString, StaticVec, INT, UNSIGNED_INT};
use crate::{Engine, Identifier, LexError, Position, SmartString, StaticVec, INT, UNSIGNED_INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
@ -13,7 +13,6 @@ use std::{
char, fmt,
iter::{FusedIterator, Peekable},
num::NonZeroUsize,
ops::{Add, AddAssign},
rc::Rc,
str::{Chars, FromStr},
};
@ -50,326 +49,12 @@ type LERR = LexError;
/// Separator character for numbers.
const NUMBER_SEPARATOR: char = '_';
/// No token.
pub const NO_TOKEN: Token = Token::NONE;
/// A stream of tokens.
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
/// A location (line number + character position) in the input script.
///
/// # Limitations
///
/// In order to keep footprint small, both line number and character position have 16-bit resolution,
/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line.
///
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number: 0 = none
#[cfg(not(feature = "no_position"))]
line: u16,
/// Character position: 0 = BOL
#[cfg(not(feature = "no_position"))]
pos: u16,
}
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self {
#[cfg(not(feature = "no_position"))]
line: 0,
#[cfg(not(feature = "no_position"))]
pos: 0,
};
/// A [`Position`] representing the first position.
pub const START: Self = Self {
#[cfg(not(feature = "no_position"))]
line: 1,
#[cfg(not(feature = "no_position"))]
pos: 0,
};
/// Create a new [`Position`].
///
/// `line` must not be zero.
///
/// If `position` is zero, then it is at the beginning of a line.
///
/// # Panics
///
/// Panics if `line` is zero.
#[inline]
#[must_use]
pub const fn new(line: u16, position: u16) -> Self {
assert!(line != 0, "line cannot be zero");
let _pos = position;
Self {
#[cfg(not(feature = "no_position"))]
line,
#[cfg(not(feature = "no_position"))]
pos: _pos,
}
}
/// Get the line number (1-based), or [`None`] if there is no position.
#[inline]
#[must_use]
pub const fn line(self) -> Option<usize> {
#[cfg(not(feature = "no_position"))]
return if self.is_none() {
None
} else {
Some(self.line as usize)
};
#[cfg(feature = "no_position")]
return None;
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
#[inline]
#[must_use]
pub const fn position(self) -> Option<usize> {
#[cfg(not(feature = "no_position"))]
return if self.is_none() || self.pos == 0 {
None
} else {
Some(self.pos as usize)
};
#[cfg(feature = "no_position")]
return None;
}
/// Advance by one character position.
#[inline]
pub(crate) fn advance(&mut self) {
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.pos < u16::MAX {
self.pos += 1;
}
}
}
/// Go backwards by one character position.
///
/// # Panics
///
/// Panics if already at beginning of a line - cannot rewind to a previous line.
#[inline]
pub(crate) fn rewind(&mut self) {
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
}
}
/// Advance to the next line.
#[inline]
pub(crate) fn new_line(&mut self) {
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.line < u16::MAX {
self.line += 1;
self.pos = 0;
}
}
}
/// Is this [`Position`] at the beginning of a line?
#[inline]
#[must_use]
pub const fn is_beginning_of_line(self) -> bool {
#[cfg(not(feature = "no_position"))]
return self.pos == 0 && !self.is_none();
#[cfg(feature = "no_position")]
return false;
}
/// Is there no [`Position`]?
#[inline]
#[must_use]
pub const fn is_none(self) -> bool {
#[cfg(not(feature = "no_position"))]
return self.line == 0 && self.pos == 0;
#[cfg(feature = "no_position")]
return true;
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
#[inline]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
if self.is_none() {
pos
} else {
self
}
}
/// Print this [`Position`] for debug purposes.
#[inline]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_none() {
write!(_f, " @ {:?}", self)?;
}
Ok(())
}
}
impl Default for Position {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::START
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
write!(f, "none")?;
} else {
#[cfg(not(feature = "no_position"))]
write!(f, "line {}, position {}", self.line, self.pos)?;
#[cfg(feature = "no_position")]
unreachable!("no position");
}
Ok(())
}
}
impl fmt::Debug for Position {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
f.write_str("none")
} else {
#[cfg(not(feature = "no_position"))]
if self.is_beginning_of_line() {
write!(f, "{}", self.line)
} else {
write!(f, "{}:{}", self.line, self.pos)
}
#[cfg(feature = "no_position")]
unreachable!("no position");
}
}
}
impl Add for Position {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_none() {
self
} else {
#[cfg(not(feature = "no_position"))]
return Self {
line: self.line + rhs.line - 1,
pos: if rhs.is_beginning_of_line() {
self.pos
} else {
self.pos + rhs.pos - 1
},
};
#[cfg(feature = "no_position")]
unreachable!("no position");
}
}
}
impl AddAssign for Position {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
/// _(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)]
pub struct Span {
/// Starting [position][Position].
start: Position,
/// Ending [position][Position].
end: Position,
}
impl Default for Span {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::NONE
}
}
impl Span {
/// Empty [`Span`].
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
/// Create a new [`Span`].
#[inline(always)]
#[must_use]
pub const fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
/// Is this [`Span`] non-existent?
#[inline]
#[must_use]
pub const fn is_none(&self) -> bool {
self.start.is_none() && self.end.is_none()
}
/// Get the [`Span`]'s starting [position][Position].
#[inline(always)]
#[must_use]
pub const fn start(&self) -> Position {
self.start
}
/// Get the [`Span`]'s ending [position][Position].
#[inline(always)]
#[must_use]
pub const fn end(&self) -> Position {
self.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _f = f;
#[cfg(not(feature = "no_position"))]
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)
}
(start, end) => write!(
_f,
"{}:{}-{}",
start.line().unwrap(),
start.position().unwrap_or(0),
end.position().unwrap_or(0)
),
}
#[cfg(feature = "no_position")]
Ok(())
}
}
impl fmt::Debug for Span {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
/// _(internals)_ A Rhai language token.
/// Exported under the `internals` feature only.
#[derive(Debug, PartialEq, Clone, Hash)]
@ -587,7 +272,7 @@ pub enum Token {
/// Used as a placeholder for the end of input.
EOF,
/// Placeholder to indicate the lack of a token.
NonToken,
NONE,
}
impl fmt::Display for Token {
@ -613,7 +298,7 @@ impl fmt::Display for Token {
Comment(s) => f.write_str(s),
EOF => f.write_str("{EOF}"),
NonToken => f.write_str("{NONE}"),
NONE => f.write_str("{NONE}"),
token => f.write_str(token.literal_syntax()),
}
@ -642,7 +327,7 @@ impl Token {
Custom(..) => false,
LexError(..) | Comment(..) => false,
EOF | NonToken => false,
EOF | NONE => false,
_ => true,
}

View File

@ -9,6 +9,8 @@ pub mod fn_ptr;
pub mod immutable_string;
pub mod interner;
pub mod parse_error;
pub mod position;
pub mod position_none;
pub mod restore;
pub mod scope;
pub mod variant;
@ -25,6 +27,12 @@ pub use fn_ptr::FnPtr;
pub use immutable_string::ImmutableString;
pub use interner::StringsInterner;
pub use parse_error::{LexError, ParseError, ParseErrorType};
#[cfg(not(feature = "no_position"))]
pub use position::{Position, Span};
#[cfg(feature = "no_position")]
pub use position_none::{Position, Span};
pub use restore::RestoreOnDrop;
pub use scope::Scope;
pub use variant::Variant;

269
src/types/position.rs Normal file
View File

@ -0,0 +1,269 @@
#![cfg(not(feature = "no_position"))]
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
fmt,
ops::{Add, AddAssign},
};
/// A location (line number + character position) in the input script.
///
/// # Limitations
///
/// In order to keep footprint small, both line number and character position have 16-bit resolution,
/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line.
///
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number: 0 = none
line: u16,
/// Character position: 0 = BOL
pos: u16,
}
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self { line: 0, pos: 0 };
/// A [`Position`] representing the first position.
pub const START: Self = Self { line: 1, pos: 0 };
/// Create a new [`Position`].
///
/// `line` must not be zero.
///
/// If `position` is zero, then it is at the beginning of a line.
///
/// # Panics
///
/// Panics if `line` is zero.
#[inline]
#[must_use]
pub const fn new(line: u16, position: u16) -> Self {
assert!(line != 0, "line cannot be zero");
let _pos = position;
Self { line, pos: _pos }
}
/// Get the line number (1-based), or [`None`] if there is no position.
#[inline]
#[must_use]
pub const fn line(self) -> Option<usize> {
if self.is_none() {
None
} else {
Some(self.line as usize)
}
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
#[inline]
#[must_use]
pub const fn position(self) -> Option<usize> {
if self.is_none() || self.pos == 0 {
None
} else {
Some(self.pos as usize)
}
}
/// Advance by one character position.
#[inline]
pub(crate) fn advance(&mut self) {
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.pos < u16::MAX {
self.pos += 1;
}
}
/// Go backwards by one character position.
///
/// # Panics
///
/// Panics if already at beginning of a line - cannot rewind to a previous line.
#[inline]
pub(crate) fn rewind(&mut self) {
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
}
/// Advance to the next line.
#[inline]
pub(crate) fn new_line(&mut self) {
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.line < u16::MAX {
self.line += 1;
self.pos = 0;
}
}
/// Is this [`Position`] at the beginning of a line?
#[inline]
#[must_use]
pub const fn is_beginning_of_line(self) -> bool {
self.pos == 0 && !self.is_none()
}
/// Is there no [`Position`]?
#[inline]
#[must_use]
pub const fn is_none(self) -> bool {
self.line == 0 && self.pos == 0
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
#[inline]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
if self.is_none() {
pos
} else {
self
}
}
/// Print this [`Position`] for debug purposes.
#[inline]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_none() {
write!(_f, " @ {:?}", self)?;
}
Ok(())
}
}
impl Default for Position {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::START
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
write!(f, "none")
} else {
write!(f, "line {}, position {}", self.line, self.pos)
}
}
}
impl fmt::Debug for Position {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
f.write_str("none")
} else {
if self.is_beginning_of_line() {
write!(f, "{}", self.line)
} else {
write!(f, "{}:{}", self.line, self.pos)
}
}
}
}
impl Add for Position {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_none() {
self
} else {
Self {
line: self.line + rhs.line - 1,
pos: if rhs.is_beginning_of_line() {
self.pos
} else {
self.pos + rhs.pos - 1
},
}
}
}
}
impl AddAssign for Position {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
/// _(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)]
pub struct Span {
/// Starting [position][Position].
start: Position,
/// Ending [position][Position].
end: Position,
}
impl Default for Span {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self::NONE
}
}
impl Span {
/// Empty [`Span`].
pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
/// Create a new [`Span`].
#[inline(always)]
#[must_use]
pub const fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
/// Is this [`Span`] non-existent?
#[inline]
#[must_use]
pub const fn is_none(&self) -> bool {
self.start.is_none() && self.end.is_none()
}
/// Get the [`Span`]'s starting [position][Position].
#[inline(always)]
#[must_use]
pub const fn start(&self) -> Position {
self.start
}
/// Get the [`Span`]'s ending [position][Position].
#[inline(always)]
#[must_use]
pub const fn end(&self) -> Position {
self.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _f = f;
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)
}
(start, end) => write!(
_f,
"{}:{}-{}",
start.line().unwrap(),
start.position().unwrap_or(0),
end.position().unwrap_or(0)
),
}
}
}
impl fmt::Debug for Span {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

165
src/types/position_none.rs Normal file
View File

@ -0,0 +1,165 @@
#![cfg(feature = "no_position")]
#![allow(unused_variables)]
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
fmt,
ops::{Add, AddAssign},
};
/// A location (line number + character position) in the input script.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position;
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self;
/// A [`Position`] representing the first position.
pub const START: Self = Self;
/// Create a new [`Position`].
#[inline(always)]
#[must_use]
pub const fn new(line: u16, position: u16) -> Self {
Self
}
/// Get the line number (1-based), or [`None`] if there is no position.
#[inline(always)]
#[must_use]
pub const fn line(self) -> Option<usize> {
None
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
#[inline(always)]
#[must_use]
pub const fn position(self) -> Option<usize> {
None
}
/// Advance by one character position.
#[inline(always)]
pub(crate) fn advance(&mut self) {}
/// Go backwards by one character position.
#[inline(always)]
pub(crate) fn rewind(&mut self) {}
/// Advance to the next line.
#[inline(always)]
pub(crate) fn new_line(&mut self) {}
/// Is this [`Position`] at the beginning of a line?
#[inline(always)]
#[must_use]
pub const fn is_beginning_of_line(self) -> bool {
false
}
/// Is there no [`Position`]?
#[inline(always)]
#[must_use]
pub const fn is_none(self) -> bool {
true
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
#[inline(always)]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
pos
}
/// Print this [`Position`] for debug purposes.
#[inline(always)]
pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
impl Default for Position {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "none")
}
}
impl fmt::Debug for Position {
#[cold]
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("none")
}
}
impl Add for Position {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self::Output {
Self
}
}
impl AddAssign for Position {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {}
}
/// _(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)]
pub struct Span;
impl Default for Span {
#[inline(always)]
#[must_use]
fn default() -> Self {
Self
}
}
impl Span {
/// Empty [`Span`].
pub const NONE: Self = Self;
/// Create a new [`Span`].
#[inline(always)]
#[must_use]
pub const fn new(start: Position, end: Position) -> Self {
Self
}
/// Is this [`Span`] non-existent?
#[inline(always)]
#[must_use]
pub const fn is_none(&self) -> bool {
true
}
/// Get the [`Span`]'s starting [position][Position].
#[inline(always)]
#[must_use]
pub const fn start(&self) -> Position {
Position
}
/// Get the [`Span`]'s ending [position][Position].
#[inline(always)]
#[must_use]
pub const fn end(&self) -> Position {
Position
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let f = f;
write!(f, "{:?}", Position)
}
}
impl fmt::Debug for Span {
#[cold]
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}