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] [features]
default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng
std = ["ahash/std", "num-traits/std", "smartstring/std"] std = ["ahash/std", "num-traits/std", "smartstring/std"]
unchecked = [] # unchecked arithmetic unchecked = [] # disable safety checks
sync = [] # restrict to only types that implement Send + Sync sync = [] # restrict to only types that implement Send + Sync
no_position = [] # do not track position in the parser no_position = [] # do not track position in the parser
no_optimize = [] # no script optimizer no_optimize = [] # no script optimizer

View File

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

View File

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

View File

@ -64,8 +64,8 @@ impl Engine {
let (stream, tokenizer_control) = self.lex_raw( let (stream, tokenizer_control) = self.lex_raw(
&scripts, &scripts,
if has_null { Some(if has_null {
Some(&|token, _, _| { &|token, _, _| {
match token { match token {
// `null` => `()` // `null` => `()`
Token::Reserved(s) if &*s == "null" => Token::Unit, Token::Reserved(s) if &*s == "null" => Token::Unit,
@ -86,9 +86,9 @@ impl Engine {
// All others // All others
_ => token, _ => token,
} }
}) }
} else { } else {
Some(&|token, _, _| { &|token, _, _| {
match token { match token {
Token::Reserved(s) if &*s == "null" => Token::LexError( Token::Reserved(s) if &*s == "null" => Token::LexError(
LexError::ImproperSymbol("null".to_string(), String::new()).into(), LexError::ImproperSymbol("null".to_string(), String::new()).into(),
@ -97,24 +97,21 @@ impl Engine {
Token::LeftBrace => Token::MapStart, Token::LeftBrace => Token::MapStart,
// Disallowed syntax // Disallowed syntax
t @ (Token::Unit | Token::MapStart) => Token::LexError( t @ (Token::Unit | Token::MapStart) => Token::LexError(
LexError::ImproperSymbol( LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
t.literal_syntax().to_string(),
"Invalid JSON syntax".to_string(),
)
.into(), .into(),
), ),
Token::InterpolatedString(..) => Token::LexError( Token::InterpolatedString(..) => Token::LexError(
LexError::ImproperSymbol( LexError::ImproperSymbol(
"interpolated string".to_string(), "interpolated string".to_string(),
"Invalid JSON syntax".to_string(), String::new(),
) )
.into(), .into(),
), ),
// All others // All others
_ => token, _ => token,
} }
}) }
}, }),
); );
let ast = { 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 optimize;
pub mod limits; pub mod limits;
pub mod limits_unchecked;
pub mod events; 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}; use crate::{Dynamic, Engine, Identifier};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -156,7 +168,7 @@ impl Engine {
Some(token) if token.is_standard_keyword() => { Some(token) if token.is_standard_keyword() => {
if !self if !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) .map_or(false, |m| m.contains(token.literal_syntax()))
{ {
return Err(format!("'{keyword}' is a reserved keyword")); return Err(format!("'{keyword}' is a reserved keyword"));
@ -166,7 +178,7 @@ impl Engine {
Some(token) if token.is_standard_symbol() => { Some(token) if token.is_standard_symbol() => {
if !self if !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) .map_or(false, |m| m.contains(token.literal_syntax()))
{ {
return Err(format!("'{keyword}' is a reserved operator")); return Err(format!("'{keyword}' is a reserved operator"));
@ -176,7 +188,7 @@ impl Engine {
Some(token) Some(token)
if !self if !self
.disabled_symbols .disabled_symbols
.as_ref() .as_deref()
.map_or(false, |m| m.contains(token.literal_syntax())) => .map_or(false, |m| m.contains(token.literal_syntax())) =>
{ {
return Err(format!("'{keyword}' is a reserved symbol")) return Err(format!("'{keyword}' is a reserved symbol"))
@ -212,82 +224,3 @@ impl Engine {
self 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"))] #[cfg(not(feature = "no_function"))]
ast.shared_lib() ast.shared_lib()
.iter_fn() .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(), .collect(),
optimization_level, optimization_level,
); );

View File

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

View File

@ -5,6 +5,7 @@ pub mod expr;
pub mod flags; pub mod flags;
pub mod ident; pub mod ident;
pub mod namespace; pub mod namespace;
pub mod namespace_none;
pub mod script_fn; pub mod script_fn;
pub mod stmt; pub mod stmt;
@ -16,6 +17,8 @@ pub use flags::{ASTFlags, FnAccess};
pub use ident::Ident; pub use ident::Ident;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub use namespace::Namespace; pub use namespace::Namespace;
#[cfg(feature = "no_module")]
pub use namespace_none::Namespace;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use script_fn::EncapsulatedEnviron; 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`. /// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EncapsulatedEnviron { pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST]. /// Functions defined within the same [`AST`][crate::AST].
@ -35,7 +34,6 @@ pub struct ScriptFnDef {
pub body: StmtBlock, pub body: StmtBlock,
/// Encapsulated AST environment, if any. /// Encapsulated AST environment, if any.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub environ: Option<crate::Shared<EncapsulatedEnviron>>, pub environ: Option<crate::Shared<EncapsulatedEnviron>>,
/// Function name. /// Function name.
pub name: ImmutableString, pub name: ImmutableString,

View File

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

View File

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

View File

@ -508,11 +508,10 @@ impl Engine {
node: ASTNode<'a>, node: ASTNode<'a>,
event: DebuggerEvent, event: DebuggerEvent,
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> { ) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
if let Some(ref x) = self.debugger_interface {
let src = global.source_raw().cloned(); let src = global.source_raw().cloned();
let src = src.as_ref().map(|s| s.as_str()); let src = src.as_ref().map(|s| s.as_str());
let context = crate::EvalContext::new(self, global, caches, scope, this_ptr); let context = EvalContext::new(self, global, caches, scope, this_ptr);
if let Some(ref x) = self.debugger_interface {
let (.., ref on_debugger) = **x; let (.., ref on_debugger) = **x;
let command = on_debugger(context, event, node, src, node.position())?; 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. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)]
pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> { pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// The current [`Engine`]. /// The current [`Engine`].
engine: &'a Engine, engine: &'a Engine,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ use crate::api::events::VarDefInfo;
use crate::api::options::LangOptions; use crate::api::options::LangOptions;
use crate::ast::{ use crate::ast::{
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident, ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer,
SwitchCasesCollection, TryCatchBlock, SwitchCasesCollection, TryCatchBlock,
}; };
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; 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::func::{hashing::get_hasher, StraightHashMap};
use crate::tokenizer::{ use crate::tokenizer::{
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl, TokenizerControl, NO_TOKEN,
}; };
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::types::StringsInterner; use crate::types::StringsInterner;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec, calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, Identifier,
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, Scope,
Scope, Shared, SmartString, StaticVec, AST, INT, PERR, Shared, SmartString, StaticVec, AST, INT, PERR,
}; };
use bitflags::bitflags; use bitflags::bitflags;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -63,7 +63,7 @@ pub struct ParseState<'e, 's> {
pub block_stack_len: usize, pub block_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))] #[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 /// An indicator that disables variable capturing into externals one single time
/// up until the nearest consumed Identifier token. /// 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. /// 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, id: ImmutableString,
no_args: bool, no_args: bool,
capture_parent_scope: bool, capture_parent_scope: bool,
#[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace, namespace: Namespace,
settings: ParseSettings, settings: ParseSettings,
) -> ParseResult<Expr> { ) -> ParseResult<Expr> {
let (token, token_pos) = if no_args { let (token, token_pos) = if no_args {
@ -566,8 +566,7 @@ impl Engine {
input.peek().expect(NEVER_ENDS) input.peek().expect(NEVER_ENDS)
}; };
#[cfg(not(feature = "no_module"))] let mut _namespace = namespace;
let mut namespace = namespace;
let mut args = StaticVec::new_const(); let mut args = StaticVec::new_const();
match token { match token {
@ -588,17 +587,16 @@ impl Engine {
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let hash = if namespace.is_empty() { let hash = if _namespace.is_empty() {
calc_fn_hash(None, &id, 0) calc_fn_hash(None, &id, 0)
} else { } else {
let root = namespace.root(); let root = _namespace.root();
let index = state.find_module(root); let index = state.find_module(root);
let is_global = false;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let is_global = root == crate::engine::KEYWORD_GLOBAL; let is_global = is_global || root == crate::engine::KEYWORD_GLOBAL;
#[cfg(any(feature = "no_function", feature = "no_module"))]
let is_global = false;
if settings.has_option(LangOptions::STRICT_VAR) if settings.has_option(LangOptions::STRICT_VAR)
&& index.is_none() && index.is_none()
@ -615,13 +613,13 @@ impl Engine {
.map_or(false, |m| m.contains_key(root)) .map_or(false, |m| m.contains_key(root))
{ {
return Err( 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")] #[cfg(feature = "no_module")]
let hash = calc_fn_hash(None, &id, 0); let hash = calc_fn_hash(None, &id, 0);
@ -637,9 +635,8 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
op_token: Token::NonToken, op_token: NO_TOKEN,
#[cfg(not(feature = "no_module"))] namespace: _namespace,
namespace,
hashes, hashes,
args, args,
} }
@ -664,10 +661,10 @@ impl Engine {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let hash = if namespace.is_empty() { let hash = if _namespace.is_empty() {
calc_fn_hash(None, &id, args.len()) calc_fn_hash(None, &id, args.len())
} else { } else {
let root = namespace.root(); let root = _namespace.root();
let index = state.find_module(root); let index = state.find_module(root);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -691,13 +688,13 @@ impl Engine {
.map_or(false, |m| m.contains_key(root)) .map_or(false, |m| m.contains_key(root))
{ {
return Err( 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")] #[cfg(feature = "no_module")]
let hash = calc_fn_hash(None, &id, args.len()); let hash = calc_fn_hash(None, &id, args.len());
@ -713,9 +710,8 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
op_token: Token::NonToken, op_token: NO_TOKEN,
#[cfg(not(feature = "no_module"))] namespace: _namespace,
namespace,
hashes, hashes,
args, args,
} }
@ -1592,10 +1588,7 @@ impl Engine {
// Identifier // Identifier
Token::Identifier(..) => { Token::Identifier(..) => {
#[cfg(not(feature = "no_module"))] let ns = Namespace::NONE;
let ns = crate::ast::Namespace::NONE;
#[cfg(feature = "no_module")]
let ns = [];
let s = match input.next().expect(NEVER_ENDS) { let s = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), ..) => s, (Token::Identifier(s), ..) => s,
@ -1657,10 +1650,7 @@ impl Engine {
// Reserved keyword or symbol // Reserved keyword or symbol
Token::Reserved(..) => { Token::Reserved(..) => {
#[cfg(not(feature = "no_module"))] let ns = Namespace::NONE;
let ns = crate::ast::Namespace::NONE;
#[cfg(feature = "no_module")]
let ns = [];
let s = match input.next().expect(NEVER_ENDS) { let s = match input.next().expect(NEVER_ENDS) {
(Token::Reserved(s), ..) => s, (Token::Reserved(s), ..) => s,
@ -1763,35 +1753,18 @@ impl Engine {
let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit; let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit;
let (.., _ns, _, name) = *x; let (.., ns, _, name) = *x;
settings.pos = pos; settings.pos = pos;
self.parse_fn_call( let settings = settings.level_up()?;
input, self.parse_fn_call(input, state, lib, name, no_args, true, ns, settings)?
state,
lib,
name,
no_args,
true,
#[cfg(not(feature = "no_module"))]
_ns,
settings.level_up()?,
)?
} }
// Function call // Function call
(Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => { (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; settings.pos = pos;
self.parse_fn_call( let settings = settings.level_up()?;
input, self.parse_fn_call(input, state, lib, name, no_args, false, ns, settings)?
state,
lib,
name,
t == Token::Unit,
false,
#[cfg(not(feature = "no_module"))]
_ns,
settings.level_up()?,
)?
} }
// module access // module access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1959,8 +1932,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
Ok(FnCallExpr { Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))] namespace: Namespace::NONE,
namespace: Default::default(),
name: state.get_interned_string("-"), name: state.get_interned_string("-"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)),
args, args,
@ -1988,8 +1960,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
Ok(FnCallExpr { Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))] namespace: Namespace::NONE,
namespace: Default::default(),
name: state.get_interned_string("+"), name: state.get_interned_string("+"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)),
args, args,
@ -2010,8 +1981,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
Ok(FnCallExpr { Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))] namespace: Namespace::NONE,
namespace: Default::default(),
name: state.get_interned_string("!"), name: state.get_interned_string("!"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)),
args, 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) OpAssignment::new_op_assignment_from_token(op, op_pos)
} else { } else {
OpAssignment::new_assignment(op_pos) OpAssignment::new_assignment(op_pos)
@ -2143,7 +2113,7 @@ impl Engine {
) -> ParseResult<Stmt> { ) -> ParseResult<Stmt> {
let (op, pos) = match input.peek().expect(NEVER_ENDS) { let (op, pos) = match input.peek().expect(NEVER_ENDS) {
// var = ... // var = ...
(Token::Equals, ..) => (Token::NonToken, eat_token(input, Token::Equals)), (Token::Equals, ..) => (NO_TOKEN, eat_token(input, Token::Equals)),
// var op= ... // var op= ...
(token, ..) if token.is_op_assignment() => { (token, ..) if token.is_op_assignment() => {
input.next().map(|(op, pos)| (op, pos)).expect(NEVER_ENDS) 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 hash = calc_fn_hash(None, &op, 2);
let is_valid_script_function = is_valid_function_name(&op); let is_valid_script_function = is_valid_function_name(&op);
let operator_token = if is_valid_script_function { let operator_token = if is_valid_script_function {
Token::NonToken NO_TOKEN
} else { } else {
op_token.clone() op_token.clone()
}; };
@ -2400,8 +2370,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
let mut op_base = FnCallExpr { let mut op_base = FnCallExpr {
#[cfg(not(feature = "no_module"))] namespace: Namespace::NONE,
namespace: Default::default(),
name: state.get_interned_string(&op), name: state.get_interned_string(&op),
hashes: FnCallHashes::from_native(hash), hashes: FnCallHashes::from_native(hash),
args, args,
@ -2539,10 +2508,7 @@ impl Engine {
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
let name = state.get_interned_string(name); let name = state.get_interned_string(name);
#[cfg(not(feature = "no_module"))] let ns = Namespace::NONE;
let ns = crate::ast::Namespace::NONE;
#[cfg(feature = "no_module")]
let ns = [];
segments.push(name.clone()); segments.push(name.clone());
tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT));
@ -3632,7 +3598,7 @@ impl Engine {
} }
.into(); .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(); params.shrink_to_fit();
Ok(ScriptFnDef { Ok(ScriptFnDef {
@ -3660,7 +3626,7 @@ impl Engine {
parent: &mut ParseState, parent: &mut ParseState,
lib: &FnLib, lib: &FnLib,
fn_expr: Expr, fn_expr: Expr,
externals: FnArgsVec<Ident>, externals: crate::FnArgsVec<Ident>,
pos: Position, pos: Position,
) -> Expr { ) -> Expr {
// If there are no captured variables, no need to curry // If there are no captured variables, no need to curry
@ -3684,8 +3650,7 @@ impl Engine {
})); }));
let expr = FnCallExpr { let expr = FnCallExpr {
#[cfg(not(feature = "no_module"))] namespace: Namespace::NONE,
namespace: Default::default(),
name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY),
hashes: FnCallHashes::from_native(calc_fn_hash( hashes: FnCallHashes::from_native(calc_fn_hash(
None, None,
@ -3693,7 +3658,7 @@ impl Engine {
num_externals + 1, num_externals + 1,
)), )),
args, args,
op_token: Token::NonToken, op_token: NO_TOKEN,
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos); .into_fn_call_expr(pos);
@ -3708,7 +3673,7 @@ impl Engine {
let (index, _) = parent.access_var(&name, lib, pos); let (index, _) = parent.access_var(&name, lib, pos);
(name, index, pos) (name, index, pos)
}) })
.collect::<FnArgsVec<_>>() .collect::<crate::FnArgsVec<_>>()
.into(), .into(),
)); ));
statements.push(Stmt::Expr(expr.into())); statements.push(Stmt::Expr(expr.into()));
@ -3779,20 +3744,20 @@ impl Engine {
// so extract them into a list. // so extract them into a list.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let (mut params, externals) = if let Some(ref external_vars) = state.external_vars { 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.extend(externals.iter().map(|Ident { name, .. }| name.clone()));
(params, externals) (params, externals)
} else { } else {
( (
FnArgsVec::with_capacity(params_list.len()), crate::FnArgsVec::with_capacity(params_list.len()),
FnArgsVec::new_const(), crate::FnArgsVec::new_const(),
) )
}; };
#[cfg(feature = "no_closure")] #[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); 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, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
}; };
use crate::func::native::OnParseTokenCallback; 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")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -13,7 +13,6 @@ use std::{
char, fmt, char, fmt,
iter::{FusedIterator, Peekable}, iter::{FusedIterator, Peekable},
num::NonZeroUsize, num::NonZeroUsize,
ops::{Add, AddAssign},
rc::Rc, rc::Rc,
str::{Chars, FromStr}, str::{Chars, FromStr},
}; };
@ -50,326 +49,12 @@ type LERR = LexError;
/// Separator character for numbers. /// Separator character for numbers.
const NUMBER_SEPARATOR: char = '_'; const NUMBER_SEPARATOR: char = '_';
/// No token.
pub const NO_TOKEN: Token = Token::NONE;
/// A stream of tokens. /// A stream of tokens.
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>; 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. /// _(internals)_ A Rhai language token.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, PartialEq, Clone, Hash)] #[derive(Debug, PartialEq, Clone, Hash)]
@ -587,7 +272,7 @@ pub enum Token {
/// Used as a placeholder for the end of input. /// Used as a placeholder for the end of input.
EOF, EOF,
/// Placeholder to indicate the lack of a token. /// Placeholder to indicate the lack of a token.
NonToken, NONE,
} }
impl fmt::Display for Token { impl fmt::Display for Token {
@ -613,7 +298,7 @@ impl fmt::Display for Token {
Comment(s) => f.write_str(s), Comment(s) => f.write_str(s),
EOF => f.write_str("{EOF}"), EOF => f.write_str("{EOF}"),
NonToken => f.write_str("{NONE}"), NONE => f.write_str("{NONE}"),
token => f.write_str(token.literal_syntax()), token => f.write_str(token.literal_syntax()),
} }
@ -642,7 +327,7 @@ impl Token {
Custom(..) => false, Custom(..) => false,
LexError(..) | Comment(..) => false, LexError(..) | Comment(..) => false,
EOF | NonToken => false, EOF | NONE => false,
_ => true, _ => true,
} }

View File

@ -9,6 +9,8 @@ pub mod fn_ptr;
pub mod immutable_string; pub mod immutable_string;
pub mod interner; pub mod interner;
pub mod parse_error; pub mod parse_error;
pub mod position;
pub mod position_none;
pub mod restore; pub mod restore;
pub mod scope; pub mod scope;
pub mod variant; pub mod variant;
@ -25,6 +27,12 @@ pub use fn_ptr::FnPtr;
pub use immutable_string::ImmutableString; pub use immutable_string::ImmutableString;
pub use interner::StringsInterner; pub use interner::StringsInterner;
pub use parse_error::{LexError, ParseError, ParseErrorType}; 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 restore::RestoreOnDrop;
pub use scope::Scope; pub use scope::Scope;
pub use variant::Variant; 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)
}
}