diff --git a/Cargo.toml b/Cargo.toml index 4079d85c..14fb9603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 45c2ed40..07422206 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -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 diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 312b561d..a34a5ac5 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -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)?; diff --git a/src/api/json.rs b/src/api/json.rs index adde4bc1..dd6d1f42 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -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(), - ) - .into(), + 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 = { diff --git a/src/api/limits_unchecked.rs b/src/api/limits_unchecked.rs new file mode 100644 index 00000000..02179ecc --- /dev/null +++ b/src/api/limits_unchecked.rs @@ -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 + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index b318e14f..028cbe59 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -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; diff --git a/src/api/optimize.rs b/src/api/optimize.rs index 7e596059..6ac76b22 100644 --- a/src/api/optimize.rs +++ b/src/api/optimize.rs @@ -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, ); diff --git a/src/ast/expr.rs b/src/ast/expr.rs index f6e4e5c9..a8376bd6 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -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, super::Namespace, u64, ImmutableString)>, - #[cfg(feature = "no_module")] Box<(Option, [(); 0], u64, ImmutableString)>, + Box<(Option, Namespace, u64, ImmutableString)>, Option, 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, diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 695f0786..b64ef8a0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -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; diff --git a/src/ast/namespace_none.rs b/src/ast/namespace_none.rs new file mode 100644 index 00000000..eafe1c52 --- /dev/null +++ b/src/ast/namespace_none.rs @@ -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 + } +} diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index 4b8f3e22..e523d7d7 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -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>, /// Function name. pub name: ImmutableString, diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 3a0e0458..8dba2bca 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -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::*; diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 9f130dbf..3b257b57 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -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,17 +72,11 @@ 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, - ) - .map(|(r, ..)| r) + 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) } /// Call a set indexer. @@ -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 diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index cfee4cba..62c6fde7 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -508,11 +508,10 @@ impl Engine { node: ASTNode<'a>, event: DebuggerEvent, ) -> Result, Box> { - 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 src = global.source_raw().cloned(); + let src = src.as_ref().map(|s| s.as_str()); + 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())?; diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index df6a87e3..6243d949 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -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, diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 2ccfcdad..13f53334 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -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::().unwrap() = new_val + } + _ => **target = new_val, } - - *target.write_lock::().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,36 +262,31 @@ 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` - match lhs { - // name op= rhs (handled above) - Expr::Variable(..) => { - unreachable!("Expr::Variable case is already handled") - } - // 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) - } - // 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) - } - _ => unreachable!("cannot assign to expression: {:?}", lhs), - }?; + // 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(..) => { + unreachable!("Expr::Variable case is already handled") + } + // 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), + // 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), + _ => unreachable!("cannot assign to expression: {:?}", lhs), + }?; return Ok(Dynamic::UNIT); } @@ -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 diff --git a/src/eval/target.rs b/src/eval/target.rs index 8ee2024e..1f36a835 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -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 { + #[cfg(not(feature = "no_closure"))] + return match self { Self::RefMut(r) => r.is_shared(), - #[cfg(not(feature = "no_closure"))] 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::().expect("`Dynamic`"); - return Self::SharedValue { source, value: val }; + let shared_value = value.clone(); + let guard = value.write_lock::().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, .. } diff --git a/src/func/call.rs b/src/func/call.rs index 70f52470..fc70a214 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -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 diff --git a/src/func/native.rs b/src/func/native.rs index 20935c06..5d029fb6 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -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; diff --git a/src/lib.rs b/src/lib.rs index fa95cb95..3f6e73ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, }; diff --git a/src/optimizer.rs b/src/optimizer.rs index f5cc2562..67976cb7 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -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, diff --git a/src/parser.rs b/src/parser.rs index a17cae45..2daa3358 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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>>, + pub external_vars: Option>>, /// 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 { 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 { 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, + externals: crate::FnArgsVec, 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::>() + .collect::>() .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); diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 3f43f595..7f392ff1 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -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>; -/// 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 { - #[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 { - #[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, } diff --git a/src/types/mod.rs b/src/types/mod.rs index aad22a3c..2a166886 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -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; diff --git a/src/types/position.rs b/src/types/position.rs new file mode 100644 index 00000000..4284ecb4 --- /dev/null +++ b/src/types/position.rs @@ -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 { + 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 { + 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) + } +} diff --git a/src/types/position_none.rs b/src/types/position_none.rs new file mode 100644 index 00000000..469491b2 --- /dev/null +++ b/src/types/position_none.rs @@ -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 { + 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 { + 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) + } +}