From f56c3fe9cb85029aee78bbe347014356aaabda79 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 28 Oct 2020 19:11:17 +0800 Subject: [PATCH 1/7] Add Ident/IdentX to AST. --- RELEASES.md | 5 + src/engine.rs | 65 ++++++++----- src/fn_call.rs | 3 +- src/lib.rs | 4 +- src/optimize.rs | 78 ++++++++-------- src/parser.rs | 240 +++++++++++++++++++++++++++--------------------- src/unsafe.rs | 20 +--- 7 files changed, 232 insertions(+), 183 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index ffdca9a9..4fb86c9e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -24,6 +24,11 @@ New features * Low-level API for custom syntax allowing more flexibility in designing the syntax. * `Module::fill_with` to poly-fill a module with another. +Enhancements +------------ + +* AST data structures are optimized to maximize cache friendliness. This may have speed impacts on large, complex scripts (benchmarks wanted!). + Version 0.19.3 ============== diff --git a/src/engine.rs b/src/engine.rs index 2bc062f4..dcd786e4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,7 +6,7 @@ use crate::fn_native::{Callback, FnPtr, OnVarCallback}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{BinaryExpr, Expr, ReturnType, Stmt}; +use crate::parser::{BinaryExpr, Expr, Ident, ReturnType, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -33,6 +33,7 @@ use crate::any::DynamicWriteLock; use crate::stdlib::{ any::type_name, + borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, fmt, format, @@ -393,6 +394,11 @@ impl State { pub fn new() -> Self { Default::default() } + /// Is the state currently at global (root) level? + #[inline(always)] + pub fn is_global(&self) -> bool { + self.scope_level == 0 + } } /// _[INTERNALS]_ A type containing all the limits imposed by the `Engine`. @@ -764,7 +770,7 @@ impl Engine { match expr { Expr::Variable(v) => match v.as_ref() { // Qualified variable - ((name, pos), Some(modules), hash_var, _) => { + (Ident { name, pos }, Some(modules), hash_var, _) => { let module = search_imports_mut(mods, state, modules)?; let target = module.get_qualified_var_mut(*hash_var).map_err(|mut err| { match *err { @@ -796,7 +802,7 @@ impl Engine { this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, ) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box> { - let ((name, pos), _, _, index) = match expr { + let (Ident { name, pos }, _, _, index) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), }; @@ -1187,7 +1193,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] Expr::Variable(x) => { - let (var_name, var_pos) = &x.0; + let Ident { + name: var_name, + pos: var_pos, + } = &x.0; self.inc_operations(state) .map_err(|err| err.fill_position(*var_pos))?; @@ -1491,14 +1500,16 @@ impl Engine { Expr::IntegerConstant(x) => Ok(x.0.into()), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x) => Ok(x.0.into()), - Expr::StringConstant(x) => Ok(x.0.to_string().into()), + Expr::StringConstant(x) => Ok(x.name.to_string().into()), Expr::CharConstant(x) => Ok(x.0.into()), - Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone(), Default::default()).into()), - Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { + Expr::FnPointer(x) => { + Ok(FnPtr::new_unchecked(x.name.clone(), Default::default()).into()) + } + Expr::Variable(x) if (x.0).name == KEYWORD_THIS => { if let Some(val) = this_ptr { Ok(val.clone()) } else { - EvalAltResult::ErrorUnboundThis((x.0).1).into() + EvalAltResult::ErrorUnboundThis((x.0).pos).into() } } Expr::Variable(_) => { @@ -1533,9 +1544,9 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Map(x) => Ok(Dynamic(Union::Map(Box::new( x.0.iter() - .map(|((key, _), expr)| { + .map(|(key, expr)| { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|val| (key.clone(), val)) + .map(|val| (key.name.clone(), val)) }) .collect::, _>>()?, )))), @@ -1885,7 +1896,11 @@ impl Engine { if let Some(func) = func { // Add the loop variable - let var_name = unsafe_cast_var_name_to_lifetime(name, &state); + let var_name: Cow<'_, str> = if state.is_global() { + name.clone().into() + } else { + unsafe_cast_var_name_to_lifetime(name).into() + }; scope.push(var_name, ()); let index = scope.len() - 1; state.scope_level += 1; @@ -1929,7 +1944,7 @@ impl Engine { // Try/Catch statement Stmt::TryCatch(x) => { - let ((try_body, _), var_def, (catch_body, _)) = x.as_ref(); + let (try_body, var_def, catch_body, _) = x.as_ref(); let result = self .eval_stmt(scope, mods, state, lib, this_ptr, try_body, level) @@ -1951,8 +1966,12 @@ impl Engine { let orig_scope_len = scope.len(); state.scope_level += 1; - if let Some((var_name, _)) = var_def { - let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); + if let Some(Ident { name, .. }) = var_def { + let var_name: Cow<'_, str> = if state.is_global() { + name.clone().into() + } else { + unsafe_cast_var_name_to_lifetime(name).into() + }; scope.push(var_name, value); } @@ -2016,7 +2035,11 @@ impl Engine { } else { ().into() }; - let var_name = unsafe_cast_var_name_to_lifetime(&var_def.0, &state); + let var_name: Cow<'_, str> = if state.is_global() { + var_def.name.clone().into() + } else { + unsafe_cast_var_name_to_lifetime(&var_def.name).into() + }; scope.push_dynamic_value(var_name, entry_type, val, false); Ok(Default::default()) } @@ -2039,7 +2062,7 @@ impl Engine { if let Some(name_def) = alias { module.index_all_sub_modules(); - mods.push((name_def.0.clone(), module)); + mods.push((name_def.name.clone(), module)); } state.modules += 1; @@ -2059,13 +2082,13 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(list, _) => { - for ((id, id_pos), rename) in list.iter() { + for (Ident { name, pos: id_pos }, rename) in list.iter() { // Mark scope variables as public - if let Some(index) = scope.get_index(id).map(|(i, _)| i) { - let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); + if let Some(index) = scope.get_index(name).map(|(i, _)| i) { + let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name); scope.set_entry_alias(index, alias.clone()); } else { - return EvalAltResult::ErrorVariableNotFound(id.into(), *id_pos).into(); + return EvalAltResult::ErrorVariableNotFound(name.into(), *id_pos).into(); } } Ok(Default::default()) @@ -2073,7 +2096,7 @@ impl Engine { // Share statement #[cfg(not(feature = "no_closure"))] - Stmt::Share(var_name, _) => { + Stmt::Share(Ident { name: var_name, .. }) => { match scope.get_index(var_name) { Some((index, ScopeEntryType::Normal)) => { let (val, _) = scope.get_mut(index); diff --git a/src/fn_call.rs b/src/fn_call.rs index efd563b6..531a9284 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -41,6 +41,7 @@ use crate::scope::Entry as ScopeEntry; use crate::stdlib::{ any::{type_name, TypeId}, + borrow::Cow, boxed::Box, convert::TryFrom, format, @@ -376,7 +377,7 @@ impl Engine { .iter() .zip(args.iter_mut().map(|v| mem::take(*v))) .map(|(name, value)| { - let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state); + let var_name: Cow<'_, str> = unsafe_cast_var_name_to_lifetime(name).into(); (var_name, ScopeEntryType::Normal, value) }), ); diff --git a/src/lib.rs b/src/lib.rs index e2973ae0..d1896d9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,9 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use parser::{CustomExpr, Expr, FloatWrapper, ReturnType, ScriptFnDef, Stmt}; +pub use parser::{ + BinaryExpr, CustomExpr, Expr, FloatWrapper, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, +}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/optimize.rs b/src/optimize.rs index 8fe5c876..de031742 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -7,7 +7,7 @@ use crate::engine::{ }; use crate::fn_call::run_builtin_binary_op; use crate::module::Module; -use crate::parser::{map_dynamic_to_expr, BinaryExpr, Expr, ScriptFnDef, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, BinaryExpr, CustomExpr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, Scope}; use crate::token::{is_valid_identifier, Position}; use crate::{calc_fn_hash, StaticVec}; @@ -15,9 +15,6 @@ use crate::{calc_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::parser::ReturnType; -#[cfg(feature = "internals")] -use crate::parser::CustomExpr; - use crate::stdlib::{ boxed::Box, iter::empty, @@ -283,18 +280,18 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { .into_iter() .map(|stmt| match stmt { // Add constant literals into the state - Stmt::Const(name, Some(expr), pos) if expr.is_literal() => { + Stmt::Const(var_def, Some(expr), pos) if expr.is_literal() => { state.set_dirty(); - state.push_constant(&name.0, expr); + state.push_constant(&var_def.name, expr); Stmt::Noop(pos) // No need to keep constants } - Stmt::Const(name, Some(expr), pos) if expr.is_literal() => { + Stmt::Const(var_def, Some(expr), pos) if expr.is_literal() => { let expr = optimize_expr(expr, state); - Stmt::Const(name, Some(expr), pos) + Stmt::Const(var_def, Some(expr), pos) } - Stmt::Const(name, None, pos) => { + Stmt::Const(var_def, None, pos) => { state.set_dirty(); - state.push_constant(&name.0, Expr::Unit(name.1)); + state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); Stmt::Noop(pos) // No need to keep constants } // Optimize the statement @@ -389,22 +386,25 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { } } // try { block } catch ( var ) { block } - Stmt::TryCatch(x) if (x.0).0.is_pure() => { + Stmt::TryCatch(x) if x.0.is_pure() => { // If try block is pure, there will never be any exceptions state.set_dirty(); - let pos = (x.0).0.position(); - let mut statements: Vec<_> = Default::default(); - statements.push(optimize_stmt((x.0).0, state, preserve_result)); + let pos = x.0.position(); + let mut statements = match optimize_stmt(x.0, state, preserve_result) { + Stmt::Block(statements, _) => statements, + stmt => vec![stmt], + }; statements.push(Stmt::Noop(pos)); Stmt::Block(statements, pos) } // try { block } catch ( var ) { block } Stmt::TryCatch(x) => { - let ((try_block, try_pos), var_name, (catch_block, catch_pos)) = *x; + let (try_block, var_name, catch_block, pos) = *x; Stmt::TryCatch(Box::new(( - (optimize_stmt(try_block, state, false), try_pos), + optimize_stmt(try_block, state, false), var_name, - (optimize_stmt(catch_block, state, false), catch_pos), + optimize_stmt(catch_block, state, false), + pos, ))) } // expr; @@ -463,7 +463,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name == prop) + m.0.into_iter().find(|(x, _)| &x.name == prop) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -495,15 +495,15 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| *name == s.0) + m.0.into_iter().find(|(x, _)| x.name == s.name) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] - (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => { + (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.name.chars().count() => { // String literal indexing - get the character state.set_dirty(); - Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1))) + Expr::CharConstant(Box::new((s.name.chars().nth(i.0 as usize).unwrap(), s.pos))) } // lhs[rhs] (lhs, rhs) => Expr::Index(Box::new(BinaryExpr { @@ -520,27 +520,27 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // [ items .. ] #[cfg(not(feature = "no_object"))] Expr::Map(m) => Expr::Map(Box::new((m.0 - .into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state))) + .into_iter().map(|(key, expr)| (key, optimize_expr(expr, state))) .collect(), m.1))), // lhs in rhs Expr::In(x) => match (x.lhs, x.rhs) { // "xxx" in "xxxxx" (Expr::StringConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.0.contains(a.0.as_str()) { Expr::True(a.1) } else { Expr::False(a.1) } + if b.name.contains(a.name.as_str()) { Expr::True(a.pos) } else { Expr::False(a.pos) } } // 'x' in "xxxxx" (Expr::CharConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.0.contains(a.0) { Expr::True(a.1) } else { Expr::False(a.1) } + if b.name.contains(a.0) { Expr::True(a.1) } else { Expr::False(a.1) } } // "xxx" in #{...} (Expr::StringConstant(a), Expr::Map(b)) => { state.set_dirty(); - if b.0.iter().find(|((name, _), _)| *name == a.0).is_some() { - Expr::True(a.1) + if b.0.iter().find(|(x, _)| x.name == a.name).is_some() { + Expr::True(a.pos) } else { - Expr::False(a.1) + Expr::False(a.pos) } } // 'x' in #{...} @@ -548,7 +548,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { + if b.0.iter().find(|(x, _)| x.name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -697,24 +697,20 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { } // constant-name - Expr::Variable(x) if x.1.is_none() && state.contains_constant(&(x.0).0) => { - let (name, pos) = x.0; + Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.0.name) => { state.set_dirty(); // Replace constant with value - let mut expr = state.find_constant(&name).unwrap().clone(); - expr.set_position(pos); + let mut expr = state.find_constant(&x.0.name).unwrap().clone(); + expr.set_position(x.0.pos); expr } // Custom syntax - #[cfg(feature = "internals")] - Expr::Custom(x) => Expr::Custom(Box::new(( - CustomExpr( - (x.0).0.into_iter().map(|expr| optimize_expr(expr, state)).collect(), - (x.0).1), - x.1 - ))), + Expr::Custom(x) => Expr::Custom(Box::new(CustomExpr { + keywords: x.keywords.into_iter().map(|expr| optimize_expr(expr, state)).collect(), + ..*x + })), // All other expressions - skip expr => expr, @@ -776,7 +772,7 @@ fn optimize( let expr = optimize_expr(expr, &mut state); if expr.is_literal() { - state.push_constant(&var_def.0, expr.clone()); + state.push_constant(&var_def.name, expr.clone()); } // Keep it in the global scope @@ -788,7 +784,7 @@ fn optimize( } } Stmt::Const(ref var_def, None, _) => { - state.push_constant(&var_def.0, Expr::Unit(var_def.1)); + state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); // Keep it in the global scope stmt diff --git a/src/parser.rs b/src/parser.rs index 4329de2a..84a8e6d9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -733,6 +733,37 @@ impl ParseSettings { } } +/// An identifier containing a string name and a position. +#[derive(Debug, Clone, Hash)] +pub struct Ident { + pub name: String, + pub pos: Position, +} + +impl Ident { + /// Create a new `Identifier`. + pub fn new(name: String, pos: Position) -> Self { + Self { name, pos } + } +} + +/// An identifier containing an immutable name and a position. +#[derive(Debug, Clone, Hash)] +pub struct IdentX { + pub name: ImmutableString, + pub pos: Position, +} + +impl IdentX { + /// Create a new `Identifier`. + pub fn new(name: impl Into, pos: Position) -> Self { + Self { + name: name.into(), + pos, + } + } +} + /// _[INTERNALS]_ A Rhai statement. /// Exported under the `internals` feature only. /// @@ -751,21 +782,15 @@ pub enum Stmt { /// for id in expr { stmt } For(Expr, Box<(String, Stmt)>, Position), /// let id = expr - Let(Box<(String, Position)>, Option, Position), + Let(Box, Option, Position), /// const id = expr - Const(Box<(String, Position)>, Option, Position), + Const(Box, Option, Position), /// expr op= expr Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), /// { stmt; ... } Block(Vec, Position), /// try { stmt; ... } catch ( var ) { stmt; ... } - TryCatch( - Box<( - (Stmt, Position), - Option<(String, Position)>, - (Stmt, Position), - )>, - ), + TryCatch(Box<(Stmt, Option, Stmt, (Position, Position))>), /// expr Expr(Expr), /// continue @@ -776,16 +801,13 @@ pub enum Stmt { ReturnWithVal((ReturnType, Position), Option, Position), /// import expr as var #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), + Import(Expr, Option>, Position), /// export var as var, ... #[cfg(not(feature = "no_module"))] - Export( - Vec<((String, Position), Option<(String, Position)>)>, - Position, - ), + Export(Vec<(Ident, Option)>, Position), /// Convert a variable to shared. #[cfg(not(feature = "no_closure"))] - Share(String, Position), + Share(Ident), } impl Default for Stmt { @@ -818,8 +840,8 @@ impl Stmt { | Self::For(_, _, pos) | Self::ReturnWithVal((_, pos), _, _) => *pos, - Self::Let(x, _, _) | Self::Const(x, _, _) => x.1, - Self::TryCatch(x) => (x.0).1, + Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos, + Self::TryCatch(x) => (x.3).0, Self::Expr(x) => x.position(), @@ -829,7 +851,7 @@ impl Stmt { Self::Export(_, pos) => *pos, #[cfg(not(feature = "no_closure"))] - Self::Share(_, pos) => *pos, + Self::Share(Ident { pos, .. }) => *pos, } } @@ -847,8 +869,8 @@ impl Stmt { | Self::For(_, _, pos) | Self::ReturnWithVal((_, pos), _, _) => *pos = new_pos, - Self::Let(x, _, _) | Self::Const(x, _, _) => x.1 = new_pos, - Self::TryCatch(x) => (x.0).1 = new_pos, + Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos = new_pos, + Self::TryCatch(x) => (x.3).0 = new_pos, Self::Expr(x) => { x.set_position(new_pos); @@ -860,7 +882,7 @@ impl Stmt { Self::Export(_, pos) => *pos = new_pos, #[cfg(not(feature = "no_closure"))] - Self::Share(_, pos) => *pos = new_pos, + Self::Share(Ident { pos, .. }) => *pos = new_pos, } self @@ -891,7 +913,7 @@ impl Stmt { Self::Import(_, _, _) | Self::Export(_, _) => false, #[cfg(not(feature = "no_closure"))] - Self::Share(_, _) => false, + Self::Share(_) => false, } } @@ -910,7 +932,7 @@ impl Stmt { Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, - Self::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(), + Self::TryCatch(x) => x.0.is_pure() && x.2.is_pure(), #[cfg(not(feature = "no_module"))] Self::Import(_, _, _) => false, @@ -918,7 +940,7 @@ impl Stmt { Self::Export(_, _) => false, #[cfg(not(feature = "no_closure"))] - Self::Share(_, _) => false, + Self::Share(_) => false, } } } @@ -931,9 +953,9 @@ impl Stmt { /// This type is volatile and may change. #[derive(Clone)] pub struct CustomExpr { - keywords: StaticVec, - func: Shared, - pos: Position, + pub(crate) keywords: StaticVec, + pub(crate) func: Shared, + pub(crate) pos: Position, } impl fmt::Debug for CustomExpr { @@ -990,6 +1012,7 @@ impl Hash for FloatWrapper { } } +/// A binary expression structure. #[derive(Debug, Clone, Hash)] pub struct BinaryExpr { pub lhs: Expr, @@ -1016,18 +1039,11 @@ pub enum Expr { /// Character constant. CharConstant(Box<(char, Position)>), /// String constant. - StringConstant(Box<(ImmutableString, Position)>), + StringConstant(Box), /// FnPtr constant. - FnPointer(Box<(ImmutableString, Position)>), + FnPointer(Box), /// Variable access - ((variable name, position), optional modules, hash, optional index) - Variable( - Box<( - (String, Position), - Option>, - u64, - Option, - )>, - ), + Variable(Box<(Ident, Option>, u64, Option)>), /// Property access. Property(Box<((ImmutableString, String, String), Position)>), /// { stmt } @@ -1053,7 +1069,7 @@ pub enum Expr { /// [ expr, ... ] Array(Box<(StaticVec, Position)>), /// #{ name:expr, ... } - Map(Box<(StaticVec<((ImmutableString, Position), Expr)>, Position)>), + Map(Box<(StaticVec<(IdentX, Expr)>, Position)>), /// lhs in rhs In(Box), /// lhs && rhs @@ -1117,9 +1133,9 @@ impl Expr { #[cfg(not(feature = "no_float"))] Self::FloatConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(), - Self::StringConstant(x) => x.0.clone().into(), + Self::StringConstant(x) => x.name.clone().into(), Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( - x.0.clone(), + x.name.clone(), Default::default(), )))), Self::True(_) => true.into(), @@ -1137,7 +1153,7 @@ impl Expr { Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { Dynamic(Union::Map(Box::new( x.0.iter() - .map(|((k, _), v)| (k.clone(), v.get_constant_value().unwrap())) + .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())) .collect(), ))) } @@ -1149,7 +1165,7 @@ impl Expr { /// Is the expression a simple variable access? pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { match self { - Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).0.as_str()), + Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).name.as_str()), _ => None, } } @@ -1164,13 +1180,13 @@ impl Expr { Self::IntegerConstant(x) => x.1, Self::CharConstant(x) => x.1, - Self::StringConstant(x) => x.1, - Self::FnPointer(x) => x.1, + Self::StringConstant(x) => x.pos, + Self::FnPointer(x) => x.pos, Self::Array(x) => x.1, Self::Map(x) => x.1, Self::Property(x) => x.1, Self::Stmt(x) => x.1, - Self::Variable(x) => (x.0).1, + Self::Variable(x) => (x.0).pos, Self::FnCall(x) => (x.0).3, Self::And(x) | Self::Or(x) | Self::In(x) => x.pos, @@ -1195,11 +1211,11 @@ impl Expr { Self::IntegerConstant(x) => x.1 = new_pos, Self::CharConstant(x) => x.1 = new_pos, - Self::StringConstant(x) => x.1 = new_pos, - Self::FnPointer(x) => x.1 = new_pos, + Self::StringConstant(x) => x.pos = new_pos, + Self::FnPointer(x) => x.pos = new_pos, Self::Array(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos, - Self::Variable(x) => (x.0).1 = new_pos, + Self::Variable(x) => (x.0).pos = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, Self::FnCall(x) => (x.0).3 = new_pos, @@ -1364,7 +1380,7 @@ impl Expr { pub(crate) fn into_property(self) -> Self { match self { Self::Variable(x) if x.1.is_none() => { - let (name, pos) = x.0; + let Ident { name, pos } = x.0; let getter = make_getter(&name); let setter = make_setter(&name); Self::Property(Box::new(((name.into(), getter, setter), pos))) @@ -1622,7 +1638,7 @@ fn parse_index_chain( return Err(PERR::MalformedIndexExpr( "Array or string expects numeric index, not a string".into(), ) - .into_err(x.1)) + .into_err(x.pos)) } #[cfg(not(feature = "no_float"))] @@ -1872,7 +1888,7 @@ fn parse_map_literal( } let expr = parse_expr(input, state, lib, settings.level_up())?; - map.push(((Into::::into(name), pos), expr)); + map.push((IdentX::new(name, pos), expr)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -1899,11 +1915,11 @@ fn parse_map_literal( // Check for duplicating properties map.iter() .enumerate() - .try_for_each(|(i, ((k1, _), _))| { + .try_for_each(|(i, (IdentX { name: k1, .. }, _))| { map.iter() .skip(i + 1) - .find(|((k2, _), _)| k2 == k1) - .map_or_else(|| Ok(()), |((k2, pos), _)| Err((k2, *pos))) + .find(|(IdentX { name: k2, .. }, _)| k2 == k1) + .map_or_else(|| Ok(()), |(IdentX { name: k2, pos }, _)| Err((k2, *pos))) }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; @@ -1940,7 +1956,7 @@ fn parse_primary( #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), - Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), + Token::StringConstant(s) => Expr::StringConstant(Box::new(IdentX::new(s, settings.pos))), // Function call Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { @@ -1949,7 +1965,7 @@ fn parse_primary( { state.allow_capture = true; } - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } // Module qualification #[cfg(not(feature = "no_module"))] @@ -1959,18 +1975,18 @@ fn parse_primary( { state.allow_capture = true; } - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } // Normal variable access Token::Identifier(s) => { let index = state.access_var(&s, settings.pos); - Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { if is_keyword_function(&s) { - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } else { return Err(PERR::Reserved(s).into_err(settings.pos)); } @@ -1984,7 +2000,7 @@ fn parse_primary( .into_err(settings.pos), ); } else { - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } } @@ -2040,13 +2056,13 @@ fn parse_primary( .into_err(pos)); } - let ((name, pos), modules, _, _) = *x; + let (Ident { name, pos }, modules, _, _) = *x; settings.pos = pos; parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? } // Function call (Expr::Variable(x), Token::LeftParen) => { - let ((name, pos), modules, _, _) = *x; + let (Ident { name, pos }, modules, _, _) = *x; settings.pos = pos; parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? } @@ -2054,7 +2070,7 @@ fn parse_primary( // module access (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - let ((name, pos), mut modules, _, index) = *x; + let (Ident { name, pos }, mut modules, _, index) = *x; if let Some(ref mut modules) = modules { modules.push((name, pos)); @@ -2064,7 +2080,7 @@ fn parse_primary( modules = Some(Box::new(m)); } - Expr::Variable(Box::new(((id2, pos2), modules, 0, index))) + Expr::Variable(Box::new((Ident::new(id2, pos2), modules, 0, index))) } (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { return Err(PERR::Reserved(id2).into_err(pos2)); @@ -2088,7 +2104,7 @@ fn parse_primary( match &mut root_expr { // Cache the hash key for module-qualified variables Expr::Variable(x) if x.1.is_some() => { - let ((name, _), modules, hash, _) = x.as_mut(); + let (Ident { name, .. }, modules, hash, _) = x.as_mut(); let modules = modules.as_mut().unwrap(); // Qualifiers + variable name @@ -2249,7 +2265,15 @@ fn make_assignment_stmt<'a>( } // var (indexed) = rhs Expr::Variable(x) => { - let ((name, name_pos), _, _, index) = x.as_ref(); + let ( + Ident { + name, + pos: name_pos, + }, + _, + _, + index, + ) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) @@ -2268,7 +2292,15 @@ fn make_assignment_stmt<'a>( } // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { - let ((name, name_pos), _, _, index) = x.as_ref(); + let ( + Ident { + name, + pos: name_pos, + }, + _, + _, + index, + ) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) @@ -2344,7 +2376,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - let (name, pos) = x.0; + let Ident { name, pos } = x.0; let getter = make_getter(&name); let setter = make_setter(&name); @@ -2758,7 +2790,12 @@ fn parse_custom_syntax( MARKER_IDENT => match input.next().unwrap() { (Token::Identifier(s), pos) => { segments.push(s.clone()); - exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None)))); + exprs.push(Expr::Variable(Box::new(( + Ident::new(s, pos), + None, + 0, + None, + )))); } (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -3051,12 +3088,20 @@ fn parse_let( // let name = expr ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new((name, pos)), init_value, token_pos)) + Ok(Stmt::Let( + Box::new(Ident::new(name, pos)), + init_value, + token_pos, + )) } // const name = { expr:constant } ScopeEntryType::Constant => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new((name, pos)), init_value, token_pos)) + Ok(Stmt::Const( + Box::new(Ident::new(name, pos)), + init_value, + token_pos, + )) } } } @@ -3098,7 +3143,7 @@ fn parse_import( Ok(Stmt::Import( expr, - Some(Box::new((name.into(), settings.pos))), + Some(Box::new(IdentX::new(name, settings.pos))), token_pos, )) } @@ -3131,7 +3176,7 @@ fn parse_export( let rename = if match_token(input, Token::As).0 { match input.next().unwrap() { - (Token::Identifier(s), pos) => Some((s.clone(), pos)), + (Token::Identifier(s), pos) => Some(Ident::new(s.clone(), pos)), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); } @@ -3142,7 +3187,7 @@ fn parse_export( None }; - exports.push(((id, id_pos), rename)); + exports.push((Ident::new(id, id_pos), rename)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -3163,12 +3208,12 @@ fn parse_export( exports .iter() .enumerate() - .try_for_each(|(i, ((id1, _), _))| { + .try_for_each(|(i, (Ident { name: id1, .. }, _))| { exports .iter() .skip(i + 1) - .find(|((id2, _), _)| id2 == id1) - .map_or_else(|| Ok(()), |((id2, pos), _)| Err((id2, *pos))) + .find(|(Ident { name: id2, .. }, _)| id2 == id1) + .map_or_else(|| Ok(()), |(Ident { name: id2, pos }, _)| Err((id2, *pos))) }) .map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?; @@ -3451,7 +3496,7 @@ fn parse_try_catch( // try { body } catch ( let var_def = if match_token(input, Token::LeftParen).0 { let id = match input.next().unwrap() { - (Token::Identifier(s), pos) => (s, pos), + (Token::Identifier(s), pos) => Ident::new(s, pos), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -3474,9 +3519,10 @@ fn parse_try_catch( let catch_body = parse_block(input, state, lib, settings.level_up())?; Ok(Stmt::TryCatch(Box::new(( - (body, token_pos), + body, var_def, - (catch_body, catch_pos), + catch_body, + (token_pos, catch_pos), )))) } @@ -3588,11 +3634,7 @@ fn parse_fn( /// Creates a curried expression from a list of external variables #[cfg(not(feature = "no_function"))] -fn make_curry_from_externals( - fn_expr: Expr, - externals: StaticVec<(String, Position)>, - pos: Position, -) -> Expr { +fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Position) -> Expr { if externals.is_empty() { return fn_expr; } @@ -3603,13 +3645,8 @@ fn make_curry_from_externals( args.push(fn_expr); #[cfg(not(feature = "no_closure"))] - externals.iter().for_each(|(var_name, pos)| { - args.push(Expr::Variable(Box::new(( - (var_name.into(), *pos), - None, - 0, - None, - )))); + externals.iter().for_each(|x| { + args.push(Expr::Variable(Box::new((x.clone(), None, 0, None)))); }); #[cfg(feature = "no_closure")] @@ -3634,11 +3671,7 @@ fn make_curry_from_externals( // Statement block let mut statements: Vec<_> = Default::default(); // Insert `Share` statements - statements.extend( - externals - .into_iter() - .map(|(var_name, pos)| Stmt::Share(var_name, pos)), - ); + statements.extend(externals.into_iter().map(Stmt::Share)); // Final expression statements.push(Stmt::Expr(expr)); Expr::Stmt(Box::new((Stmt::Block(statements, pos), pos))) @@ -3723,7 +3756,7 @@ fn parse_anon_fn( state .externals .iter() - .map(|(k, &v)| (k.clone(), v)) + .map(|(k, &v)| Ident::new(k.clone(), v)) .collect() } #[cfg(feature = "no_closure")] @@ -3733,8 +3766,7 @@ fn parse_anon_fn( let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { externals .iter() - .map(|(k, _)| k) - .cloned() + .map(|k| k.name.clone()) .chain(params.into_iter().map(|(v, _)| v)) .collect() } else { @@ -3767,7 +3799,7 @@ fn parse_anon_fn( lib: None, }; - let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + let expr = Expr::FnPointer(Box::new(IdentX::new(fn_name, settings.pos))); let expr = if cfg!(not(feature = "no_closure")) { make_curry_from_externals(expr, externals, settings.pos) @@ -3920,7 +3952,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))), - Union::Str(value) => Some(Expr::StringConstant(Box::new((value, pos)))), + Union::Str(value) => Some(Expr::StringConstant(Box::new(IdentX::new(value, pos)))), Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] @@ -3943,14 +3975,14 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Map(map) => { let items: Vec<_> = map .into_iter() - .map(|(k, v)| ((k, pos), map_dynamic_to_expr(v, pos))) + .map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos))) .collect(); if items.iter().all(|(_, expr)| expr.is_some()) { Some(Expr::Map(Box::new(( items .into_iter() - .map(|((k, pos), expr)| ((k, pos), expr.unwrap())) + .map(|(k, expr)| (k, expr.unwrap())) .collect(), pos, )))) diff --git a/src/unsafe.rs b/src/unsafe.rs index da766950..2442459f 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -1,14 +1,11 @@ //! A helper module containing unsafe utility functions. use crate::any::Variant; -use crate::engine::State; use crate::stdlib::{ any::{Any, TypeId}, - borrow::Cow, boxed::Box, mem, ptr, - string::ToString, }; /// Cast a type into another type. @@ -46,7 +43,7 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// # DANGEROUS!!! /// -/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow` of +/// A dangerous function that blindly casts a `&str` from one lifetime to a `&str` of /// another lifetime. This is mainly used to let us push a block-local variable into the /// current `Scope` without cloning the variable name. Doing this is safe because all local /// variables in the `Scope` are cleared out before existing the block. @@ -54,15 +51,8 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves /// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. #[inline] -pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> { - // If not at global level, we can force-cast - if state.scope_level > 0 { - // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it - // this is safe because all local variables are cleared at the end of the block - unsafe { mem::transmute::<_, &'s str>(name) }.into() - } else { - // The variable is introduced at global (top) level and may persist after the script run. - // Therefore, clone the variable name. - name.to_string().into() - } +pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str) -> &'s str { + // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it + // this is safe because all local variables are cleared at the end of the block + unsafe { mem::transmute(name) } } From 53adc58f633706c7f465501d579ac78e2ee4ca79 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 28 Oct 2020 19:21:45 +0800 Subject: [PATCH 2/7] Fix no_closure build. --- src/parser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 84a8e6d9..fe9f9119 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3650,8 +3650,8 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po }); #[cfg(feature = "no_closure")] - externals.into_iter().for_each(|(var_name, pos)| { - args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); + externals.into_iter().for_each(|x| { + args.push(Expr::Variable(Box::new((x.clone(), None, 0, None)))); }); let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals + 1, empty()); @@ -3750,7 +3750,7 @@ fn parse_anon_fn( // External variables may need to be processed in a consistent order, // so extract them into a list. - let externals: StaticVec<_> = { + let externals: StaticVec = { #[cfg(not(feature = "no_closure"))] { state From f5ffbfbe061bca2c5127a09948a541a96620793e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 28 Oct 2020 22:18:44 +0800 Subject: [PATCH 3/7] Code structure cleanup. --- src/ast.rs | 427 ++++++++++++++++++++++ src/{any.rs => dynamic.rs} | 0 src/engine.rs | 13 +- src/{api.rs => engine_api.rs} | 8 +- src/{settings.rs => engine_settings.rs} | 0 src/fn_args.rs | 2 +- src/fn_call.rs | 53 +-- src/fn_func.rs | 6 +- src/fn_native.rs | 6 +- src/fn_register.rs | 2 +- src/lib.rs | 22 +- src/module/mod.rs | 52 ++- src/module/resolvers/file.rs | 2 +- src/optimize.rs | 14 +- src/packages/array_basic.rs | 2 +- src/packages/eval.rs | 2 +- src/packages/iter_basic.rs | 2 +- src/packages/map_basic.rs | 2 +- src/packages/string_more.rs | 2 +- src/packages/time_basic.rs | 2 +- src/{error.rs => parse_error.rs} | 0 src/parser.rs | 453 +----------------------- src/plugin.rs | 2 +- src/result.rs | 4 +- src/scope.rs | 2 +- src/serde_impl/de.rs | 4 +- src/serde_impl/ser.rs | 2 +- src/syntax.rs | 4 +- src/token.rs | 2 +- src/unsafe.rs | 2 +- src/utils.rs | 59 ++- 31 files changed, 590 insertions(+), 563 deletions(-) create mode 100644 src/ast.rs rename src/{any.rs => dynamic.rs} (100%) rename src/{api.rs => engine_api.rs} (99%) rename src/{settings.rs => engine_settings.rs} (100%) rename src/{error.rs => parse_error.rs} (100%) diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 00000000..7b282c24 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,427 @@ +//! Module defining the AST (abstract syntax tree). + +use crate::fn_native::Shared; +use crate::module::Module; +use crate::parser::{FnAccess, ScriptFnDef, Stmt}; + +use crate::stdlib::{ + ops::{Add, AddAssign}, + vec, + vec::Vec, +}; + +/// Compiled AST (abstract syntax tree) of a Rhai script. +/// +/// # Thread Safety +/// +/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +#[derive(Debug, Clone, Default)] +pub struct AST( + /// Global statements. + Vec, + /// Script-defined functions. + Module, +); + +impl AST { + /// Create a new `AST`. + #[inline(always)] + pub fn new(statements: Vec, lib: Module) -> Self { + Self(statements, lib) + } + + /// Get the statements. + #[cfg(not(feature = "internals"))] + #[inline(always)] + pub(crate) fn statements(&self) -> &[Stmt] { + &self.0 + } + + /// _[INTERNALS]_ Get the statements. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[deprecated(note = "this method is volatile and may change")] + #[inline(always)] + pub fn statements(&self) -> &[Stmt] { + &self.0 + } + + /// Get a mutable reference to the statements. + #[cfg(not(feature = "no_optimize"))] + #[inline(always)] + pub(crate) fn statements_mut(&mut self) -> &mut Vec { + &mut self.0 + } + + /// Get the internal `Module` containing all script-defined functions. + #[cfg(not(feature = "internals"))] + #[inline(always)] + pub(crate) fn lib(&self) -> &Module { + &self.1 + } + + /// _[INTERNALS]_ Get the internal `Module` containing all script-defined functions. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[deprecated(note = "this method is volatile and may change")] + #[inline(always)] + pub fn lib(&self) -> &Module { + &self.1 + } + + /// Clone the `AST`'s functions into a new `AST`. + /// No statements are cloned. + /// + /// This operation is cheap because functions are shared. + #[inline(always)] + pub fn clone_functions_only(&self) -> Self { + self.clone_functions_only_filtered(|_, _, _| true) + } + + /// Clone the `AST`'s functions into a new `AST` based on a filter predicate. + /// No statements are cloned. + /// + /// This operation is cheap because functions are shared. + #[inline(always)] + pub fn clone_functions_only_filtered( + &self, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + ) -> Self { + let mut functions: Module = Default::default(); + functions.merge_filtered(&self.1, &mut filter); + Self(Default::default(), functions) + } + + /// Clone the `AST`'s script statements into a new `AST`. + /// No functions are cloned. + #[inline(always)] + pub fn clone_statements_only(&self) -> Self { + Self(self.0.clone(), Default::default()) + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version + /// is returned. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// foo("!") + /// "#)?; + /// + /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' + /// + /// // Notice that using the '+' operator also works: + /// // let ast = &ast1 + &ast2; + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn merge(&self, other: &Self) -> Self { + self.merge_filtered(other, |_, _, _| true) + } + + /// Combine one `AST` with another. The second `AST` is consumed. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// foo("!") + /// "#)?; + /// + /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' + /// + /// // Notice that using the '+=' operator also works: + /// // ast1 += ast2; + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn combine(&mut self, other: Self) -> &mut Self { + self.combine_filtered(other, |_, _, _| true) + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version + /// is returned. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first `AST` with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// let ast = ast1.merge_filtered(&ast2, |_, name, params| name == "error" && params == 0); + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn merge_filtered( + &self, + other: &Self, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + ) -> Self { + let Self(statements, functions) = self; + + let ast = match (statements.is_empty(), other.0.is_empty()) { + (false, false) => { + let mut statements = statements.clone(); + statements.extend(other.0.iter().cloned()); + statements + } + (false, true) => statements.clone(), + (true, false) => other.0.clone(), + (true, true) => vec![], + }; + + let mut functions = functions.clone(); + functions.merge_filtered(&other.1, &mut filter); + + Self::new(ast, functions) + } + + /// Combine one `AST` with another. The second `AST` is consumed. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first `AST` with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// ast1.combine_filtered(ast2, |_, name, params| name == "error" && params == 0); + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn combine_filtered( + &mut self, + other: Self, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + ) -> &mut Self { + let Self(ref mut statements, ref mut functions) = self; + statements.extend(other.0.into_iter()); + functions.merge_filtered(&other.1, &mut filter); + self + } + + /// Filter out the functions, retaining only some based on a filter predicate. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast = engine.compile(r#" + /// fn foo(n) { n + 1 } + /// fn bar() { print("hello"); } + /// "#)?; + /// + /// // Remove all functions except 'foo(_)' + /// ast.retain_functions(|_, name, params| name == "foo" && params == 1); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) { + self.1.retain_functions(filter); + } + + /// Iterate through all functions + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn iter_functions<'a>( + &'a self, + ) -> impl Iterator)> + 'a { + self.1.iter_script_fn() + } + + /// Clear all function definitions in the `AST`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn clear_functions(&mut self) { + self.1 = Default::default(); + } + + /// Clear all statements in the `AST`, leaving only function definitions. + #[inline(always)] + pub fn clear_statements(&mut self) { + self.0 = vec![]; + } +} + +impl> Add for &AST { + type Output = AST; + + #[inline(always)] + fn add(self, rhs: A) -> Self::Output { + self.merge(rhs.as_ref()) + } +} + +impl> AddAssign for AST { + #[inline(always)] + fn add_assign(&mut self, rhs: A) { + self.combine(rhs.into()); + } +} + +impl AsRef<[Stmt]> for AST { + #[inline(always)] + fn as_ref(&self) -> &[Stmt] { + self.statements() + } +} + +impl AsRef for AST { + #[inline(always)] + fn as_ref(&self) -> &Module { + self.lib() + } +} diff --git a/src/any.rs b/src/dynamic.rs similarity index 100% rename from src/any.rs rename to src/dynamic.rs diff --git a/src/engine.rs b/src/engine.rs index dcd786e4..18a5f582 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, Union, Variant}; +use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant}; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{Callback, FnPtr, OnVarCallback}; use crate::module::{Module, ModuleRef}; @@ -12,7 +12,7 @@ use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::syntax::CustomSyntax; use crate::token::Position; -use crate::{calc_fn_hash, StaticVec}; +use crate::{calc_native_fn_hash, StaticVec}; #[cfg(not(feature = "no_index"))] use crate::parser::INT; @@ -29,7 +29,7 @@ use crate::utils::ImmutableString; #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_object"))] -use crate::any::DynamicWriteLock; +use crate::dynamic::DynamicWriteLock; use crate::stdlib::{ any::type_name, @@ -462,7 +462,7 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn imports(&self) -> &'a Imports { - self.mods + self.mods.as_ref() } /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline(always)] @@ -1447,8 +1447,7 @@ impl Engine { let args = &mut [&mut lhs_value.clone(), value]; // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let hash = - calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); + let hash = calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())); if self .call_native_fn(state, lib, op, hash, args, false, false, &def_value) @@ -1698,7 +1697,7 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); - let hash_fn = calc_fn_hash(empty(), op, 2, arg_types); + let hash_fn = calc_native_fn_hash(empty(), op, arg_types); match self .global_module diff --git a/src/api.rs b/src/engine_api.rs similarity index 99% rename from src/api.rs rename to src/engine_api.rs index bfa0abfa..729a7105 100644 --- a/src/api.rs +++ b/src/engine_api.rs @@ -1,11 +1,11 @@ //! Module that defines the extern API of `Engine`. -use crate::any::{Dynamic, Variant}; +use crate::ast::AST; +use crate::dynamic::{Dynamic, Variant}; use crate::engine::{Engine, EvalContext, Imports, State}; -use crate::error::ParseError; use crate::fn_native::{FnCallArgs, NativeCallContext, SendSync}; use crate::optimize::OptimizationLevel; -use crate::parser::AST; +use crate::parse_error::ParseError; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::Position; @@ -19,7 +19,7 @@ use crate::{ #[cfg(not(feature = "no_object"))] use crate::{ engine::{make_getter, make_setter, Map}, - error::ParseErrorType, + parse_error::ParseErrorType, token::Token, }; diff --git a/src/settings.rs b/src/engine_settings.rs similarity index 100% rename from src/settings.rs rename to src/engine_settings.rs diff --git a/src/fn_args.rs b/src/fn_args.rs index 07405c0f..db0f3cd4 100644 --- a/src/fn_args.rs +++ b/src/fn_args.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Dynamic, Variant}; +use crate::dynamic::{Dynamic, Variant}; use crate::StaticVec; /// Trait that represents arguments to a function call. diff --git a/src/fn_call.rs b/src/fn_call.rs index 531a9284..5cb9d7d5 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,21 +1,21 @@ //! Implement function-calling mechanism for `Engine`. -use crate::any::Dynamic; +use crate::dynamic::Dynamic; use crate::engine::{ search_imports, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; +use crate::parse_error::ParseErrorType; use crate::parser::{Expr, ImmutableString, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::stdlib::ops::Deref; use crate::token::Position; -use crate::{calc_fn_hash, StaticVec}; +use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::{ @@ -439,15 +439,8 @@ impl Engine { pub_only: bool, ) -> bool { let arg_types = arg_types.as_ref(); - - let arg_len = if arg_types.is_empty() { - usize::MAX - } else { - arg_types.len() - }; - - let hash_fn = calc_fn_hash(empty(), name, arg_len, arg_types.iter().cloned()); - let hash_script = calc_fn_hash(empty(), name, arg_types.len(), empty()); + let hash_fn = calc_native_fn_hash(empty(), name, arg_types.iter().cloned()); + let hash_script = calc_script_fn_hash(empty(), name, arg_types.len()); self.has_override(lib, hash_fn, hash_script, pub_only) } @@ -503,17 +496,7 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); - let hash_fn = calc_fn_hash( - empty(), - fn_name, - if args.is_empty() { - // Distinguish between a script function and a native function with no parameters - usize::MAX - } else { - args.len() - }, - arg_types, - ); + let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types); match fn_name { // type_of @@ -741,7 +724,7 @@ impl Engine { let hash = if native { 0 } else { - calc_fn_hash(empty(), fn_name, args_len, empty()) + calc_script_fn_hash(empty(), fn_name, args_len) }; // Arguments are passed as-is, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); @@ -768,7 +751,7 @@ impl Engine { let hash = if native { 0 } else { - calc_fn_hash(empty(), fn_name, args_len, empty()) + calc_script_fn_hash(empty(), fn_name, args_len) }; // Replace the first argument with the object pointer, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); @@ -831,7 +814,7 @@ impl Engine { hash = if native { 0 } else { - calc_fn_hash(empty(), _fn_name, call_args.len(), empty()) + calc_script_fn_hash(empty(), _fn_name, call_args.len()) }; } } @@ -882,7 +865,7 @@ impl Engine { // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { - let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); if !self.has_override(lib, hash_fn, hash_script, pub_only) { // Fn - only in function call style @@ -963,12 +946,12 @@ impl Engine { // Recalculate hash let args_len = args_expr.len() + curry.len(); - hash_script = calc_fn_hash(empty(), name, args_len, empty()); + hash_script = calc_script_fn_hash(empty(), name, args_len); } // Handle is_def_var() if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 { - let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); if !self.has_override(lib, hash_fn, hash_script, pub_only) { let var_name = @@ -982,10 +965,9 @@ impl Engine { // Handle is_def_fn() if name == KEYWORD_IS_DEF_FN && args_expr.len() == 2 { - let hash_fn = calc_fn_hash( + let hash_fn = calc_native_fn_hash( empty(), name, - 2, [TypeId::of::(), TypeId::of::()] .iter() .cloned(), @@ -1007,7 +989,7 @@ impl Engine { return Ok(if num_params < 0 { false } else { - let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty()); + let hash = calc_script_fn_hash(empty(), fn_name, num_params as usize); lib.iter().any(|&m| m.contains_fn(hash, false)) } .into()); @@ -1016,7 +998,7 @@ impl Engine { // Handle eval() if name == KEYWORD_EVAL && args_expr.len() == 1 { - let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); if !self.has_override(lib, hash_fn, hash_script, pub_only) { // eval - only in function call style @@ -1185,8 +1167,9 @@ impl Engine { // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id())); + // and the actual list of argument `TypeId`'.s + let hash_fn_args = + calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); // 3) The final hash is the XOR of the two hashes. let hash_qualified_fn = hash_script ^ hash_fn_args; diff --git a/src/fn_func.rs b/src/fn_func.rs index 27e73bb0..888043c9 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -3,10 +3,10 @@ #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] -use crate::any::Variant; +use crate::ast::AST; +use crate::dynamic::Variant; use crate::engine::Engine; -use crate::error::ParseError; -use crate::parser::AST; +use crate::parse_error::ParseError; use crate::result::EvalAltResult; use crate::scope::Scope; diff --git a/src/fn_native.rs b/src/fn_native.rs index b3758e88..9eebffbe 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,6 @@ //! Module defining interfaces to native-Rust functions. -use crate::any::Dynamic; +use crate::dynamic::Dynamic; use crate::engine::{Engine, EvalContext}; use crate::module::Module; use crate::parser::{FnAccess, ScriptFnDef}; @@ -8,7 +8,7 @@ use crate::plugin::PluginFunction; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; -use crate::{calc_fn_hash, StaticVec}; +use crate::{calc_script_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; @@ -176,7 +176,7 @@ impl FnPtr { let has_this = this_ptr.is_some(); let mut args = args_data.iter_mut().collect::>(); - let hash_script = calc_fn_hash(empty(), fn_name, args.len(), empty()); + let hash_script = calc_script_fn_hash(empty(), fn_name, args.len()); if let Some(obj) = this_ptr { args.insert(0, obj); diff --git a/src/fn_register.rs b/src/fn_register.rs index f394b348..2f0684f1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Dynamic, DynamicWriteLock, Variant}; +use crate::dynamic::{Dynamic, DynamicWriteLock, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, NativeCallContext, SendSync}; use crate::parser::FnAccess; diff --git a/src/lib.rs b/src/lib.rs index d1896d9f..018a0138 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,10 +57,11 @@ #[cfg(feature = "no_std")] extern crate alloc; -mod any; -mod api; +mod ast; +mod dynamic; mod engine; -mod error; +mod engine_api; +mod engine_settings; mod fn_args; mod fn_call; mod fn_func; @@ -69,36 +70,37 @@ mod fn_register; mod module; mod optimize; pub mod packages; +mod parse_error; mod parser; pub mod plugin; mod result; mod scope; #[cfg(feature = "serde")] mod serde_impl; -mod settings; mod stdlib; mod syntax; mod token; mod r#unsafe; mod utils; -pub use any::Dynamic; +pub use ast::AST; +pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext}; -pub use error::{ParseError, ParseErrorType}; pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::Module; -pub use parser::{ImmutableString, AST, INT}; +pub use parse_error::{ParseError, ParseErrorType}; +pub use parser::{ImmutableString, INT}; pub use result::EvalAltResult; pub use scope::Scope; pub use syntax::Expression; pub use token::Position; #[cfg(feature = "internals")] -pub use utils::calc_fn_hash; +pub use utils::{calc_native_fn_hash, calc_script_fn_hash}; #[cfg(not(feature = "internals"))] -pub(crate) use utils::calc_fn_hash; +pub(crate) use utils::{calc_native_fn_hash, calc_script_fn_hash}; pub use rhai_codegen::*; @@ -141,7 +143,7 @@ pub use optimize::OptimizationLevel; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use error::LexError; +pub use parse_error::LexError; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/module/mod.rs b/src/module/mod.rs index 176441e8..21050777 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,21 +1,21 @@ //! Module defining external-loaded modules for Rhai. -use crate::any::{Dynamic, Variant}; +use crate::dynamic::{Dynamic, Variant}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, NativeCallContext, SendSync}; use crate::fn_register::by_value as cast_arg; use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::token::{Position, Token}; use crate::utils::{ImmutableString, StraightHasherBuilder}; -use crate::{calc_fn_hash, StaticVec}; +use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::{fn_native::Shared, parser::ScriptFnDef}; #[cfg(not(feature = "no_module"))] use crate::{ + ast::AST, engine::{Engine, Imports}, - parser::AST, scope::{Entry as ScopeEntry, Scope}, }; @@ -268,7 +268,7 @@ impl Module { /// Get a mutable reference to a modules-qualified variable. /// Name and Position in `EvalAltResult` are None and must be set afterwards. /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`. #[inline(always)] pub(crate) fn get_qualified_var_mut( &mut self, @@ -291,7 +291,7 @@ impl Module { pub(crate) fn set_script_fn(&mut self, fn_def: Shared) -> u64 { // None + function name + number of arguments. let num_params = fn_def.params.len(); - let hash_script = calc_fn_hash(empty(), &fn_def.name, num_params, empty()); + let hash_script = calc_script_fn_hash(empty(), &fn_def.name, num_params); self.functions.insert( hash_script, ( @@ -399,7 +399,7 @@ impl Module { /// Does the particular Rust function exist in the module? /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. /// /// # Example @@ -441,31 +441,24 @@ impl Module { ) -> u64 { let name = name.into(); - let args_len = if arg_types.is_empty() { - // Distinguish between a script function and a function with no parameters - usize::MAX - } else { - arg_types.len() - }; + let hash_fn = calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()); let params = arg_types .into_iter() .cloned() .map(|id| { - if id == TypeId::of::<&str>() { - TypeId::of::() - } else if id == TypeId::of::() { + if id == TypeId::of::<&str>() || id == TypeId::of::() { TypeId::of::() } else { id } }) - .collect(); + .collect::>(); - let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); - - self.functions - .insert(hash_fn, (name, access, args_len, Some(params), func.into())); + self.functions.insert( + hash_fn, + (name, access, params.len(), Some(params), func.into()), + ); self.indexed = false; @@ -1094,7 +1087,7 @@ impl Module { /// Get a Rust function. /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. #[inline(always)] pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> { @@ -1114,7 +1107,7 @@ impl Module { /// Get a modules-qualified function. /// Name and Position in `EvalAltResult` are None and must be set afterwards. /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match + /// The `u64` hash is calculated by the function `crate::calc_native_fn_hash` and must match /// the hash calculated by `index_all_sub_modules`. #[inline(always)] pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { @@ -1408,7 +1401,7 @@ impl Module { // Index all variables module.variables.iter().for_each(|(var_name, value)| { // Qualifiers + variable name - let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty()); + let hash_var = calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0); variables.push((hash_var, value.clone())); }); // Index all Rust functions @@ -1422,10 +1415,10 @@ impl Module { // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + number of arguments. let hash_qualified_script = - calc_fn_hash(qualifiers.iter().cloned(), name, params.len(), empty()); + calc_script_fn_hash(qualifiers.iter().cloned(), name, params.len()); // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); + // and the actual list of argument `TypeId`'.s + let hash_fn_args = calc_native_fn_hash(empty(), "", params.iter().cloned()); // 3) The final hash is the XOR of the two hashes. let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; @@ -1435,12 +1428,7 @@ impl Module { _hash } else { // Qualifiers + function name + number of arguments. - calc_fn_hash( - qualifiers.iter().map(|&v| v), - &name, - *_num_params, - empty(), - ) + calc_script_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_params) }; functions.push((hash_qualified_script, func.clone())); } diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 73640768..a77bcc57 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,7 +1,7 @@ +use crate::ast::AST; use crate::engine::Engine; use crate::fn_native::Locked; use crate::module::{Module, ModuleResolver}; -use crate::parser::AST; use crate::result::EvalAltResult; use crate::token::Position; diff --git a/src/optimize.rs b/src/optimize.rs index de031742..a9433a3d 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,16 +1,17 @@ //! Module implementing the AST optimizer. -use crate::any::Dynamic; +use crate::ast::AST; +use crate::dynamic::Dynamic; use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::fn_call::run_builtin_binary_op; use crate::module::Module; -use crate::parser::{map_dynamic_to_expr, BinaryExpr, CustomExpr, Expr, ScriptFnDef, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, BinaryExpr, CustomExpr, Expr, ScriptFnDef, Stmt}; use crate::scope::{Entry as ScopeEntry, Scope}; use crate::token::{is_valid_identifier, Position}; -use crate::{calc_fn_hash, StaticVec}; +use crate::{calc_native_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::parser::ReturnType; @@ -134,12 +135,7 @@ fn call_fn_with_constant_arguments( arg_values: &mut [Dynamic], ) -> Option { // Search built-in's and external functions - let hash_fn = calc_fn_hash( - empty(), - fn_name, - arg_values.len(), - arg_values.iter().map(|a| a.type_id()), - ); + let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_values.iter().map(|a| a.type_id())); state .engine diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 77ef56e7..2bee4b0b 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_index"))] #![allow(non_snake_case)] -use crate::any::Dynamic; +use crate::dynamic::Dynamic; use crate::def_package; use crate::engine::Array; use crate::fn_native::{FnPtr, NativeCallContext}; diff --git a/src/packages/eval.rs b/src/packages/eval.rs index bb5716d8..312b7f02 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -1,4 +1,4 @@ -use crate::any::Dynamic; +use crate::dynamic::Dynamic; use crate::def_package; use crate::parser::ImmutableString; use crate::plugin::*; diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 0d8d1e8f..d62963f4 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,4 +1,4 @@ -use crate::any::Variant; +use crate::dynamic::Variant; use crate::def_package; use crate::parser::INT; use crate::result::EvalAltResult; diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 26fc3fe3..ae645fce 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_object"))] -use crate::any::Dynamic; use crate::def_package; +use crate::dynamic::Dynamic; use crate::engine::Map; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 1d515558..9130a9d3 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] -use crate::any::Dynamic; use crate::def_package; +use crate::dynamic::Dynamic; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 65b3ffaf..706233e2 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -2,8 +2,8 @@ use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT}; -use crate::any::Dynamic; use crate::def_package; +use crate::dynamic::Dynamic; use crate::parser::INT; use crate::plugin::*; use crate::result::EvalAltResult; diff --git a/src/error.rs b/src/parse_error.rs similarity index 100% rename from src/error.rs rename to src/parse_error.rs diff --git a/src/parser.rs b/src/parser.rs index fe9f9119..828c8c43 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,16 +1,17 @@ //! Main module defining the lexer and parser. -use crate::any::{Dynamic, Union}; +use crate::ast::AST; +use crate::dynamic::{Dynamic, Union}; use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; -use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; +use crate::parse_error::{LexError, ParseError, ParseErrorType}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::syntax::{CustomSyntax, FnCustomSyntaxEval}; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::utils::StraightHasherBuilder; -use crate::{calc_fn_hash, StaticVec}; +use crate::{calc_script_fn_hash, StaticVec}; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -31,7 +32,6 @@ use crate::stdlib::{ hash::{Hash, Hasher}, iter::empty, num::NonZeroUsize, - ops::{Add, AddAssign}, string::{String, ToString}, vec, vec::Vec, @@ -72,422 +72,6 @@ pub use crate::utils::ImmutableString; type FunctionsLib = HashMap; -/// Compiled AST (abstract syntax tree) of a Rhai script. -/// -/// # Thread Safety -/// -/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone, Default)] -pub struct AST( - /// Global statements. - Vec, - /// Script-defined functions. - Module, -); - -impl AST { - /// Create a new `AST`. - #[inline(always)] - pub fn new(statements: Vec, lib: Module) -> Self { - Self(statements, lib) - } - - /// Get the statements. - #[cfg(not(feature = "internals"))] - #[inline(always)] - pub(crate) fn statements(&self) -> &[Stmt] { - &self.0 - } - - /// _[INTERNALS]_ Get the statements. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[deprecated(note = "this method is volatile and may change")] - #[inline(always)] - pub fn statements(&self) -> &[Stmt] { - &self.0 - } - - /// Get a mutable reference to the statements. - #[cfg(not(feature = "no_optimize"))] - #[inline(always)] - pub(crate) fn statements_mut(&mut self) -> &mut Vec { - &mut self.0 - } - - /// Get the internal `Module` containing all script-defined functions. - #[cfg(not(feature = "internals"))] - #[inline(always)] - pub(crate) fn lib(&self) -> &Module { - &self.1 - } - - /// _[INTERNALS]_ Get the internal `Module` containing all script-defined functions. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[deprecated(note = "this method is volatile and may change")] - #[inline(always)] - pub fn lib(&self) -> &Module { - &self.1 - } - - /// Clone the `AST`'s functions into a new `AST`. - /// No statements are cloned. - /// - /// This operation is cheap because functions are shared. - #[inline(always)] - pub fn clone_functions_only(&self) -> Self { - self.clone_functions_only_filtered(|_, _, _| true) - } - - /// Clone the `AST`'s functions into a new `AST` based on a filter predicate. - /// No statements are cloned. - /// - /// This operation is cheap because functions are shared. - #[inline(always)] - pub fn clone_functions_only_filtered( - &self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, - ) -> Self { - let mut functions: Module = Default::default(); - functions.merge_filtered(&self.1, &mut filter); - Self(Default::default(), functions) - } - - /// Clone the `AST`'s script statements into a new `AST`. - /// No functions are cloned. - #[inline(always)] - pub fn clone_statements_only(&self) -> Self { - Self(self.0.clone(), Default::default()) - } - - /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version - /// is returned. - /// - /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. - /// Of course, if the first `AST` uses a `return` statement at the end, then - /// the second `AST` will essentially be dead code. - /// - /// All script-defined functions in the second `AST` overwrite similarly-named functions - /// in the first `AST` with the same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let ast1 = engine.compile(r#" - /// fn foo(x) { 42 + x } - /// foo(1) - /// "#)?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { "hello" + n } - /// foo("!") - /// "#)?; - /// - /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' - /// - /// // Notice that using the '+' operator also works: - /// // let ast = &ast1 + &ast2; - /// - /// // 'ast' is essentially: - /// // - /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten - /// // foo(1) // <- notice this will be "hello1" instead of 43, - /// // // but it is no longer the return value - /// // foo("!") // returns "hello!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn merge(&self, other: &Self) -> Self { - self.merge_filtered(other, |_, _, _| true) - } - - /// Combine one `AST` with another. The second `AST` is consumed. - /// - /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. - /// Of course, if the first `AST` uses a `return` statement at the end, then - /// the second `AST` will essentially be dead code. - /// - /// All script-defined functions in the second `AST` overwrite similarly-named functions - /// in the first `AST` with the same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast1 = engine.compile(r#" - /// fn foo(x) { 42 + x } - /// foo(1) - /// "#)?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { "hello" + n } - /// foo("!") - /// "#)?; - /// - /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' - /// - /// // Notice that using the '+=' operator also works: - /// // ast1 += ast2; - /// - /// // 'ast1' is essentially: - /// // - /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten - /// // foo(1) // <- notice this will be "hello1" instead of 43, - /// // // but it is no longer the return value - /// // foo("!") // returns "hello!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn combine(&mut self, other: Self) -> &mut Self { - self.combine_filtered(other, |_, _, _| true) - } - - /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version - /// is returned. - /// - /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. - /// Of course, if the first `AST` uses a `return` statement at the end, then - /// the second `AST` will essentially be dead code. - /// - /// All script-defined functions in the second `AST` are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first `AST` with the - /// same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let ast1 = engine.compile(r#" - /// fn foo(x) { 42 + x } - /// foo(1) - /// "#)?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { "hello" + n } - /// fn error() { 0 } - /// foo("!") - /// "#)?; - /// - /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// let ast = ast1.merge_filtered(&ast2, |_, name, params| name == "error" && params == 0); - /// - /// // 'ast' is essentially: - /// // - /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten - /// // // because 'ast2::foo' is filtered away - /// // foo(1) // <- notice this will be 43 instead of "hello1", - /// // // but it is no longer the return value - /// // fn error() { 0 } // <- this function passes the filter and is merged - /// // foo("!") // <- returns "42!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn merge_filtered( - &self, - other: &Self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, - ) -> Self { - let Self(statements, functions) = self; - - let ast = match (statements.is_empty(), other.0.is_empty()) { - (false, false) => { - let mut statements = statements.clone(); - statements.extend(other.0.iter().cloned()); - statements - } - (false, true) => statements.clone(), - (true, false) => other.0.clone(), - (true, true) => vec![], - }; - - let mut functions = functions.clone(); - functions.merge_filtered(&other.1, &mut filter); - - Self::new(ast, functions) - } - - /// Combine one `AST` with another. The second `AST` is consumed. - /// - /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. - /// Of course, if the first `AST` uses a `return` statement at the end, then - /// the second `AST` will essentially be dead code. - /// - /// All script-defined functions in the second `AST` are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first `AST` with the - /// same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast1 = engine.compile(r#" - /// fn foo(x) { 42 + x } - /// foo(1) - /// "#)?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { "hello" + n } - /// fn error() { 0 } - /// foo("!") - /// "#)?; - /// - /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// ast1.combine_filtered(ast2, |_, name, params| name == "error" && params == 0); - /// - /// // 'ast1' is essentially: - /// // - /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten - /// // // because 'ast2::foo' is filtered away - /// // foo(1) // <- notice this will be 43 instead of "hello1", - /// // // but it is no longer the return value - /// // fn error() { 0 } // <- this function passes the filter and is merged - /// // foo("!") // <- returns "42!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn combine_filtered( - &mut self, - other: Self, - mut filter: impl FnMut(FnAccess, &str, usize) -> bool, - ) -> &mut Self { - let Self(ref mut statements, ref mut functions) = self; - statements.extend(other.0.into_iter()); - functions.merge_filtered(&other.1, &mut filter); - self - } - - /// Filter out the functions, retaining only some based on a filter predicate. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast = engine.compile(r#" - /// fn foo(n) { n + 1 } - /// fn bar() { print("hello"); } - /// "#)?; - /// - /// // Remove all functions except 'foo(_)' - /// ast.retain_functions(|_, name, params| name == "foo" && params == 1); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) { - self.1.retain_functions(filter); - } - - /// Iterate through all functions - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn iter_functions<'a>( - &'a self, - ) -> impl Iterator)> + 'a { - self.1.iter_script_fn() - } - - /// Clear all function definitions in the `AST`. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn clear_functions(&mut self) { - self.1 = Default::default(); - } - - /// Clear all statements in the `AST`, leaving only function definitions. - #[inline(always)] - pub fn clear_statements(&mut self) { - self.0 = vec![]; - } -} - -impl> Add for &AST { - type Output = AST; - - #[inline(always)] - fn add(self, rhs: A) -> Self::Output { - self.merge(rhs.as_ref()) - } -} - -impl> AddAssign for AST { - #[inline(always)] - fn add_assign(&mut self, rhs: A) { - self.combine(rhs.into()); - } -} - -impl AsRef<[Stmt]> for AST { - #[inline(always)] - fn as_ref(&self) -> &[Stmt] { - self.statements() - } -} - -impl AsRef for AST { - #[inline(always)] - fn as_ref(&self) -> &Module { - self.lib() - } -} - /// A type representing the access mode of a scripted function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnAccess { @@ -556,9 +140,10 @@ impl fmt::Display for ScriptFnDef { write!( f, "{}{}({})", - match self.access { - FnAccess::Public => "", - FnAccess::Private => "private ", + if self.access.is_private() { + "private " + } else { + "" }, self.name, self.params @@ -1488,10 +1073,10 @@ fn parse_fn_call( // zero number of arguments, and the actual list of argument `TypeId`'s. // 3) The final hash is the XOR of the two hashes. let qualifiers = modules.iter().map(|(m, _)| m.as_str()); - calc_fn_hash(qualifiers, &id, 0, empty()) + calc_script_fn_hash(qualifiers, &id, 0) } else { // Qualifiers (none) + function name + no parameters. - calc_fn_hash(empty(), &id, 0, empty()) + calc_script_fn_hash(empty(), &id, 0) }; return Ok(Expr::FnCall(Box::new(( @@ -1531,10 +1116,10 @@ fn parse_fn_call( // zero number of arguments, and the actual list of argument `TypeId`'s. // 3) The final hash is the XOR of the two hashes. let qualifiers = modules.iter().map(|(m, _)| m.as_str()); - calc_fn_hash(qualifiers, &id, args.len(), empty()) + calc_script_fn_hash(qualifiers, &id, args.len()) } else { // Qualifiers (none) + function name + number of arguments. - calc_fn_hash(empty(), &id, args.len(), empty()) + calc_script_fn_hash(empty(), &id, args.len()) }; return Ok(Expr::FnCall(Box::new(( @@ -2108,7 +1693,7 @@ fn parse_primary( let modules = modules.as_mut().unwrap(); // Qualifiers + variable name - *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty()); + *hash = calc_script_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0); #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].0)); @@ -2171,7 +1756,7 @@ fn parse_unary( // Call negative function expr => { let op = "-"; - let hash = calc_fn_hash(empty(), op, 1, empty()); + let hash = calc_script_fn_hash(empty(), op, 1); let mut args = StaticVec::new(); args.push(expr); @@ -2198,7 +1783,7 @@ fn parse_unary( args.push(expr); let op = "!"; - let hash = calc_fn_hash(empty(), op, 1, empty()); + let hash = calc_script_fn_hash(empty(), op, 1); Ok(Expr::FnCall(Box::new(( (op.into(), true, false, pos), @@ -2238,7 +1823,7 @@ fn parse_unary( }); // Qualifiers (none) + function name + number of arguments. - let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); + let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()); lib.insert(hash, func); @@ -2671,7 +2256,7 @@ fn parse_binary_op( let cmp_def = Some(false); let op = op_token.syntax(); - let hash = calc_fn_hash(empty(), &op, 2, empty()); + let hash = calc_script_fn_hash(empty(), &op, 2); let op = (op, true, false, pos); let mut args = StaticVec::new(); @@ -3377,7 +2962,7 @@ fn parse_stmt( let func = parse_fn(input, &mut new_state, lib, access, settings)?; // Qualifiers (none) + function name + number of arguments. - let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); + let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()); lib.insert(hash, func); @@ -3654,7 +3239,7 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po args.push(Expr::Variable(Box::new((x.clone(), None, 0, None)))); }); - let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals + 1, empty()); + let hash = calc_script_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals + 1); let expr = Expr::FnCall(Box::new(( (KEYWORD_FN_PTR_CURRY.into(), false, false, pos), diff --git a/src/plugin.rs b/src/plugin.rs index b2fde8e6..168de561 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,6 +1,6 @@ //! Module defining macros for developing _plugins_. -pub use crate::any::Dynamic; +pub use crate::dynamic::Dynamic; pub use crate::engine::Engine; pub use crate::fn_native::{CallableFunction, FnCallArgs, NativeCallContext}; pub use crate::fn_register::{RegisterFn, RegisterResultFn}; diff --git a/src/result.rs b/src/result.rs index cb43cff3..f0ec841b 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,7 +1,7 @@ //! Module containing error definitions for the evaluation process. -use crate::any::Dynamic; -use crate::error::ParseErrorType; +use crate::dynamic::Dynamic; +use crate::parse_error::ParseErrorType; use crate::parser::INT; use crate::token::Position; use crate::utils::ImmutableString; diff --git a/src/scope.rs b/src/scope.rs index d36fa478..05188843 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,6 @@ //! Module that defines the `Scope` type representing a function call-stack scope. -use crate::any::{Dynamic, Variant}; +use crate::dynamic::{Dynamic, Variant}; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; diff --git a/src/serde_impl/de.rs b/src/serde_impl/de.rs index 8a199b14..3d14f1ad 100644 --- a/src/serde_impl/de.rs +++ b/src/serde_impl/de.rs @@ -1,8 +1,8 @@ //! Implement deserialization support of `Dynamic` for [`serde`](https://crates.io/crates/serde). use super::str::ImmutableStringDeserializer; -use crate::any::{Dynamic, Union}; -use crate::error::ParseErrorType; +use crate::dynamic::{Dynamic, Union}; +use crate::parse_error::ParseErrorType; use crate::result::EvalAltResult; use crate::token::Position; use crate::utils::ImmutableString; diff --git a/src/serde_impl/ser.rs b/src/serde_impl/ser.rs index 14689166..c7a77251 100644 --- a/src/serde_impl/ser.rs +++ b/src/serde_impl/ser.rs @@ -1,6 +1,6 @@ //! Implement serialization support of `Dynamic` for [`serde`](https://crates.io/crates/serde). -use crate::any::Dynamic; +use crate::dynamic::Dynamic; use crate::result::EvalAltResult; use crate::token::Position; diff --git a/src/syntax.rs b/src/syntax.rs index 83149911..1aefdc15 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,9 +1,9 @@ //! Module implementing custom syntax for `Engine`. -use crate::any::Dynamic; +use crate::dynamic::Dynamic; use crate::engine::{Engine, EvalContext, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; -use crate::error::{LexError, ParseError}; use crate::fn_native::{SendSync, Shared}; +use crate::parse_error::{LexError, ParseError}; use crate::parser::Expr; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position, Token}; diff --git a/src/token.rs b/src/token.rs index 82d7c611..044201b1 100644 --- a/src/token.rs +++ b/src/token.rs @@ -8,7 +8,7 @@ use crate::engine::{ #[cfg(not(feature = "no_closure"))] use crate::engine::KEYWORD_IS_SHARED; -use crate::error::LexError; +use crate::parse_error::LexError; use crate::parser::INT; use crate::StaticVec; diff --git a/src/unsafe.rs b/src/unsafe.rs index 2442459f..ed859735 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -1,6 +1,6 @@ //! A helper module containing unsafe utility functions. -use crate::any::Variant; +use crate::dynamic::Variant; use crate::stdlib::{ any::{Any, TypeId}, diff --git a/src/utils.rs b/src/utils.rs index 7c01117e..7596b6cb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,7 @@ use crate::stdlib::{ cmp::Ordering, fmt, hash::{BuildHasher, Hash, Hasher}, - iter::FromIterator, + iter::{empty, FromIterator}, ops::{Add, AddAssign, Deref}, str::FromStr, string::{String, ToString}, @@ -72,22 +72,69 @@ impl BuildHasher for StraightHasherBuilder { /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. -pub fn calc_fn_hash<'a>( +#[inline(always)] +pub fn calc_native_fn_hash<'a>( + modules: impl Iterator, + fn_name: &str, + params: impl Iterator, +) -> u64 { + calc_fn_hash(modules, fn_name, None, params) +} + +/// _[INTERNALS]_ Calculate a `u64` hash key from a module-qualified function name and the number of parameters, +/// but no parameter types. +/// Exported under the `internals` feature only. +/// +/// Module names are passed in via `&str` references from an iterator. +/// Parameter types are passed in via `TypeId` values from an iterator. +/// +/// # Note +/// +/// The first module name is skipped. Hashing starts from the _second_ module in the chain. +#[inline(always)] +pub fn calc_script_fn_hash<'a>( modules: impl Iterator, fn_name: &str, num: usize, +) -> u64 { + calc_fn_hash(modules, fn_name, Some(num), empty()) +} + +/// Calculate a `u64` hash key from a module-qualified function name and parameter types. +/// +/// Module names are passed in via `&str` references from an iterator. +/// Parameter types are passed in via `TypeId` values from an iterator. +/// +/// # Note +/// +/// The first module name is skipped. Hashing starts from the _second_ module in the chain. +fn calc_fn_hash<'a>( + modules: impl Iterator, + fn_name: &str, + num: Option, params: impl Iterator, ) -> u64 { #[cfg(feature = "no_std")] - let mut s: AHasher = Default::default(); + let s: &mut AHasher = &mut Default::default(); #[cfg(not(feature = "no_std"))] - let mut s = DefaultHasher::new(); + let s = &mut DefaultHasher::new(); // We always skip the first module - modules.skip(1).for_each(|m| m.hash(&mut s)); + modules.skip(1).for_each(|m| m.hash(s)); s.write(fn_name.as_bytes()); + let num = if let Some(num) = num { + num + } else { + let mut count = 0; + + params.for_each(|t| { + count += 1; + t.hash(s); + }); + + count + }; s.write_usize(num); - params.for_each(|t| t.hash(&mut s)); s.finish() } From bed6364dcb7c719377b17b7c421a306a3f980a3d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 28 Oct 2020 22:30:29 +0800 Subject: [PATCH 4/7] Simplify hash calculation. --- src/utils.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 7596b6cb..451b929a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -122,19 +122,11 @@ fn calc_fn_hash<'a>( // We always skip the first module modules.skip(1).for_each(|m| m.hash(s)); s.write(fn_name.as_bytes()); - let num = if let Some(num) = num { - num + if let Some(num) = num { + s.write_usize(num); } else { - let mut count = 0; - - params.for_each(|t| { - count += 1; - t.hash(s); - }); - - count - }; - s.write_usize(num); + params.for_each(|t| t.hash(s)); + } s.finish() } From cbd7ed2ca7c96f64290b1da213c9b7e630d6ba25 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 28 Oct 2020 22:30:35 +0800 Subject: [PATCH 5/7] Fix test. --- codegen/ui_tests/rhai_fn_non_clonable_return.stderr | 2 +- codegen/ui_tests/rhai_mod_non_clonable_return.stderr | 2 +- src/packages/array_basic.rs | 2 +- src/packages/eval.rs | 2 +- src/packages/iter_basic.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr index ebe6c264..73d02b28 100644 --- a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr @@ -4,7 +4,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied 11 | pub fn test_fn(input: f32) -> NonClonable { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | - ::: $WORKSPACE/src/any.rs + ::: $WORKSPACE/src/dynamic.rs | | pub fn from(value: T) -> Self { | ----- required by this bound in `rhai::Dynamic::from` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr index 99a42bb3..e7860394 100644 --- a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr @@ -4,7 +4,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied 12 | pub fn test_fn(input: f32) -> NonClonable { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | - ::: $WORKSPACE/src/any.rs + ::: $WORKSPACE/src/dynamic.rs | | pub fn from(value: T) -> Self { | ----- required by this bound in `rhai::Dynamic::from` diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 2bee4b0b..cf194682 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,8 +1,8 @@ #![cfg(not(feature = "no_index"))] #![allow(non_snake_case)] -use crate::dynamic::Dynamic; use crate::def_package; +use crate::dynamic::Dynamic; use crate::engine::Array; use crate::fn_native::{FnPtr, NativeCallContext}; use crate::parser::{ImmutableString, INT}; diff --git a/src/packages/eval.rs b/src/packages/eval.rs index 312b7f02..36d5b315 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -1,5 +1,5 @@ -use crate::dynamic::Dynamic; use crate::def_package; +use crate::dynamic::Dynamic; use crate::parser::ImmutableString; use crate::plugin::*; use crate::result::EvalAltResult; diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index d62963f4..b6f26fff 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,5 +1,5 @@ -use crate::dynamic::Variant; use crate::def_package; +use crate::dynamic::Variant; use crate::parser::INT; use crate::result::EvalAltResult; From 4e115d2bc2241f4f5be41aa1cad0fc88d519216e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 29 Oct 2020 11:37:51 +0800 Subject: [PATCH 6/7] Code structure refactor. --- RELEASES.md | 4 +- src/ast.rs | 804 +++++++++++++++++++++++++++++++++- src/dynamic.rs | 5 +- src/engine.rs | 13 +- src/engine_api.rs | 4 +- src/fn_call.rs | 15 +- src/fn_native.rs | 2 +- src/fn_register.rs | 2 +- src/lib.rs | 29 +- src/module/mod.rs | 4 +- src/optimize.rs | 6 +- src/packages/arithmetic.rs | 4 +- src/packages/array_basic.rs | 9 +- src/packages/eval.rs | 2 +- src/packages/iter_basic.rs | 2 +- src/packages/map_basic.rs | 3 +- src/packages/math_basic.rs | 8 +- src/packages/string_basic.rs | 3 +- src/packages/string_more.rs | 3 +- src/packages/time_basic.rs | 4 +- src/parser.rs | 817 +---------------------------------- src/plugin.rs | 2 +- src/result.rs | 2 +- src/scope.rs | 3 +- src/syntax.rs | 2 +- src/token.rs | 4 +- src/utils.rs | 10 +- 27 files changed, 899 insertions(+), 867 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 4fb86c9e..38e49c8c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,7 +5,9 @@ Rhai Release Notes Version 0.19.4 ============== -This version adds a low-level API for more flexibility when defining custom syntax. +This version basically cleans up the code structure in preparation for a potential `1.0` release in the future. + +This version also adds a low-level API for more flexibility when defining custom syntax. Bug fixes --------- diff --git a/src/ast.rs b/src/ast.rs index 7b282c24..3d67d832 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,15 +1,124 @@ //! Module defining the AST (abstract syntax tree). -use crate::fn_native::Shared; -use crate::module::Module; -use crate::parser::{FnAccess, ScriptFnDef, Stmt}; +use crate::dynamic::{Dynamic, Union}; +use crate::fn_native::{FnPtr, Shared}; +use crate::module::{Module, ModuleRef}; +use crate::syntax::FnCustomSyntaxEval; +use crate::token::{Position, Token}; +use crate::utils::ImmutableString; +use crate::StaticVec; +use crate::INT; + +#[cfg(not(feature = "no_float"))] +use crate::FLOAT; + +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + +#[cfg(not(feature = "no_object"))] +use crate::engine::{make_getter, make_setter, Map}; use crate::stdlib::{ + any::TypeId, + borrow::Cow, + fmt, + hash::{Hash, Hasher}, + num::NonZeroUsize, ops::{Add, AddAssign}, + string::String, vec, vec::Vec, }; +#[cfg(not(feature = "no_float"))] +use crate::stdlib::ops::Neg; + +#[cfg(not(feature = "no_closure"))] +use crate::stdlib::collections::HashSet; + +/// A type representing the access mode of a scripted function. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum FnAccess { + /// Public function. + Public, + /// Private function. + Private, +} + +impl fmt::Display for FnAccess { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Private => write!(f, "private"), + Self::Public => write!(f, "public"), + } + } +} + +impl FnAccess { + /// Is this access mode private? + #[inline(always)] + pub fn is_private(self) -> bool { + match self { + Self::Public => false, + Self::Private => true, + } + } + /// Is this access mode public? + #[inline(always)] + pub fn is_public(self) -> bool { + match self { + Self::Public => true, + Self::Private => false, + } + } +} + +/// _[INTERNALS]_ A type containing information on a scripted function. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. +#[derive(Debug, Clone)] +pub struct ScriptFnDef { + /// Function name. + pub name: ImmutableString, + /// Function access mode. + pub access: FnAccess, + /// Names of function parameters. + pub params: StaticVec, + /// Access to external variables. + #[cfg(not(feature = "no_closure"))] + pub externals: HashSet, + /// Function body. + pub body: Stmt, + /// Position of the function definition. + pub pos: Position, + /// Encapsulated running environment, if any. + pub lib: Option>, +} + +impl fmt::Display for ScriptFnDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{}({})", + if self.access.is_private() { + "private " + } else { + "" + }, + self.name, + self.params + .iter() + .map(|s| s.as_str()) + .collect::>() + .join(",") + ) + } +} + /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// # Thread Safety @@ -73,6 +182,7 @@ impl AST { /// No statements are cloned. /// /// This operation is cheap because functions are shared. + #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn clone_functions_only(&self) -> Self { self.clone_functions_only_filtered(|_, _, _| true) @@ -82,6 +192,7 @@ impl AST { /// No statements are cloned. /// /// This operation is cheap because functions are shared. + #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn clone_functions_only_filtered( &self, @@ -425,3 +536,690 @@ impl AsRef for AST { self.lib() } } + +/// An identifier containing a string name and a position. +#[derive(Debug, Clone, Hash)] +pub struct Ident { + pub name: String, + pub pos: Position, +} + +impl Ident { + /// Create a new `Identifier`. + pub fn new(name: String, pos: Position) -> Self { + Self { name, pos } + } +} + +/// An identifier containing an immutable name and a position. +#[derive(Debug, Clone, Hash)] +pub struct IdentX { + pub name: ImmutableString, + pub pos: Position, +} + +impl IdentX { + /// Create a new `Identifier`. + pub fn new(name: impl Into, pos: Position) -> Self { + Self { + name: name.into(), + pos, + } + } +} + +/// _[INTERNALS]_ A type encapsulating the mode of a `return`/`throw` statement. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub enum ReturnType { + /// `return` statement. + Return, + /// `throw` statement. + Exception, +} + +/// _[INTERNALS]_ A Rhai statement. +/// Exported under the `internals` feature only. +/// +/// Each variant is at most one pointer in size (for speed), +/// with everything being allocated together in one single tuple. +#[derive(Debug, Clone, Hash)] +pub enum Stmt { + /// No-op. + Noop(Position), + /// if expr { stmt } else { stmt } + IfThenElse(Expr, Box<(Stmt, Option)>, Position), + /// while expr { stmt } + While(Expr, Box, Position), + /// loop { stmt } + Loop(Box, Position), + /// for id in expr { stmt } + For(Expr, Box<(String, Stmt)>, Position), + /// let id = expr + Let(Box, Option, Position), + /// const id = expr + Const(Box, Option, Position), + /// expr op= expr + Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), + /// { stmt; ... } + Block(Vec, Position), + /// try { stmt; ... } catch ( var ) { stmt; ... } + TryCatch(Box<(Stmt, Option, Stmt, (Position, Position))>), + /// expr + Expr(Expr), + /// continue + Continue(Position), + /// break + Break(Position), + /// return/throw + ReturnWithVal((ReturnType, Position), Option, Position), + /// import expr as var + #[cfg(not(feature = "no_module"))] + Import(Expr, Option>, Position), + /// export var as var, ... + #[cfg(not(feature = "no_module"))] + Export(Vec<(Ident, Option)>, Position), + /// Convert a variable to shared. + #[cfg(not(feature = "no_closure"))] + Share(Ident), +} + +impl Default for Stmt { + #[inline(always)] + fn default() -> Self { + Self::Noop(Default::default()) + } +} + +impl Stmt { + /// Is this statement `Noop`? + pub fn is_noop(&self) -> bool { + match self { + Self::Noop(_) => true, + _ => false, + } + } + + /// Get the `Position` of this statement. + pub fn position(&self) -> Position { + match self { + Self::Noop(pos) + | Self::Continue(pos) + | Self::Break(pos) + | Self::Block(_, pos) + | Self::Assignment(_, pos) + | Self::IfThenElse(_, _, pos) + | Self::While(_, _, pos) + | Self::Loop(_, pos) + | Self::For(_, _, pos) + | Self::ReturnWithVal((_, pos), _, _) => *pos, + + Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos, + Self::TryCatch(x) => (x.3).0, + + Self::Expr(x) => x.position(), + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, pos) => *pos, + #[cfg(not(feature = "no_module"))] + Self::Export(_, pos) => *pos, + + #[cfg(not(feature = "no_closure"))] + Self::Share(Ident { pos, .. }) => *pos, + } + } + + /// Override the `Position` of this statement. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Noop(pos) + | Self::Continue(pos) + | Self::Break(pos) + | Self::Block(_, pos) + | Self::Assignment(_, pos) + | Self::IfThenElse(_, _, pos) + | Self::While(_, _, pos) + | Self::Loop(_, pos) + | Self::For(_, _, pos) + | Self::ReturnWithVal((_, pos), _, _) => *pos = new_pos, + + Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos = new_pos, + Self::TryCatch(x) => (x.3).0 = new_pos, + + Self::Expr(x) => { + x.set_position(new_pos); + } + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, pos) => *pos = new_pos, + #[cfg(not(feature = "no_module"))] + Self::Export(_, pos) => *pos = new_pos, + + #[cfg(not(feature = "no_closure"))] + Self::Share(Ident { pos, .. }) => *pos = new_pos, + } + + self + } + + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? + pub fn is_self_terminated(&self) -> bool { + match self { + Self::IfThenElse(_, _, _) + | Self::While(_, _, _) + | Self::Loop(_, _) + | Self::For(_, _, _) + | Self::Block(_, _) + | Self::TryCatch(_) => true, + + // A No-op requires a semicolon in order to know it is an empty statement! + Self::Noop(_) => false, + + Self::Let(_, _, _) + | Self::Const(_, _, _) + | Self::Assignment(_, _) + | Self::Expr(_) + | Self::Continue(_) + | Self::Break(_) + | Self::ReturnWithVal(_, _, _) => false, + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + + /// Is this statement _pure_? + pub fn is_pure(&self) -> bool { + match self { + Self::Noop(_) => true, + Self::Expr(expr) => expr.is_pure(), + Self::IfThenElse(condition, x, _) if x.1.is_some() => { + condition.is_pure() && x.0.is_pure() && x.1.as_ref().unwrap().is_pure() + } + Self::IfThenElse(condition, x, _) => condition.is_pure() && x.0.is_pure(), + Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), + Self::Loop(block, _) => block.is_pure(), + Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), + Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false, + Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), + Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, + Self::TryCatch(x) => x.0.is_pure() && x.2.is_pure(), + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) => false, + #[cfg(not(feature = "no_module"))] + Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } +} + +/// _[INTERNALS]_ A type wrapping a custom syntax definition. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. +#[derive(Clone)] +pub struct CustomExpr { + pub(crate) keywords: StaticVec, + pub(crate) func: Shared, + pub(crate) pos: Position, +} + +impl fmt::Debug for CustomExpr { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.keywords, f) + } +} + +impl Hash for CustomExpr { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.keywords.hash(state); + } +} + +impl CustomExpr { + /// Get the keywords for this `CustomExpr`. + #[inline(always)] + pub fn keywords(&self) -> &[Expr] { + &self.keywords + } + /// Get the implementation function for this `CustomExpr`. + #[inline(always)] + pub fn func(&self) -> &FnCustomSyntaxEval { + self.func.as_ref() + } + /// Get the position of this `CustomExpr`. + #[inline(always)] + pub fn position(&self) -> Position { + self.pos + } +} + +/// _[INTERNALS]_ A type wrapping a floating-point number. +/// Exported under the `internals` feature only. +/// +/// This type is mainly used to provide a standard `Hash` implementation +/// to floating-point numbers, allowing `Expr` to derive `Hash` automatically. +/// +/// ## WARNING +/// +/// This type is volatile and may change. +#[cfg(not(feature = "no_float"))] +#[derive(Debug, PartialEq, PartialOrd, Clone)] +pub struct FloatWrapper(pub FLOAT, pub Position); + +#[cfg(not(feature = "no_float"))] +impl Hash for FloatWrapper { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write(&self.0.to_le_bytes()); + self.1.hash(state); + } +} + +#[cfg(not(feature = "no_float"))] +impl Neg for FloatWrapper { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0, self.1) + } +} + +#[cfg(not(feature = "no_float"))] +impl From<(INT, Position)> for FloatWrapper { + fn from((value, pos): (INT, Position)) -> Self { + Self(value as FLOAT, pos) + } +} + +/// A binary expression structure. +#[derive(Debug, Clone, Hash)] +pub struct BinaryExpr { + pub lhs: Expr, + pub rhs: Expr, + pub pos: Position, +} + +/// _[INTERNALS]_ An expression sub-tree. +/// Exported under the `internals` feature only. +/// +/// Each variant is at most one pointer in size (for speed), +/// with everything being allocated together in one single tuple. +/// +/// ## WARNING +/// +/// This type is volatile and may change. +#[derive(Debug, Clone, Hash)] +pub enum Expr { + /// Integer constant. + IntegerConstant(Box<(INT, Position)>), + /// Floating-point constant. + #[cfg(not(feature = "no_float"))] + FloatConstant(Box), + /// Character constant. + CharConstant(Box<(char, Position)>), + /// String constant. + StringConstant(Box), + /// FnPtr constant. + FnPointer(Box), + /// Variable access - ((variable name, position), optional modules, hash, optional index) + Variable(Box<(Ident, Option>, u64, Option)>), + /// Property access. + Property(Box<((ImmutableString, String, String), Position)>), + /// { stmt } + Stmt(Box<(Stmt, Position)>), + /// Wrapped expression - should not be optimized away. + Expr(Box), + /// func(expr, ... ) - ((function name, native_only, capture, position), optional modules, hash, arguments, optional default value) + /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls + /// and the function names are predictable, so no need to allocate a new `String`. + FnCall( + Box<( + (Cow<'static, str>, bool, bool, Position), + Option>, + u64, + StaticVec, + Option, // Default value is `bool` in order for `Expr` to be `Hash`. + )>, + ), + /// lhs.rhs + Dot(Box), + /// expr[expr] + Index(Box), + /// [ expr, ... ] + Array(Box<(StaticVec, Position)>), + /// #{ name:expr, ... } + Map(Box<(StaticVec<(IdentX, Expr)>, Position)>), + /// lhs in rhs + In(Box), + /// lhs && rhs + And(Box), + /// lhs || rhs + Or(Box), + /// true + True(Position), + /// false + False(Position), + /// () + Unit(Position), + /// Custom syntax + Custom(Box), +} + +impl Default for Expr { + #[inline(always)] + fn default() -> Self { + Self::Unit(Default::default()) + } +} + +impl Expr { + /// Get the type of an expression. + /// + /// Returns `None` if the expression's result type is not constant. + pub fn get_type_id(&self) -> Option { + Some(match self { + Self::Expr(x) => return x.get_type_id(), + + Self::IntegerConstant(_) => TypeId::of::(), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_) => TypeId::of::(), + Self::CharConstant(_) => TypeId::of::(), + Self::StringConstant(_) => TypeId::of::(), + Self::FnPointer(_) => TypeId::of::(), + Self::True(_) | Self::False(_) | Self::In(_) | Self::And(_) | Self::Or(_) => { + TypeId::of::() + } + Self::Unit(_) => TypeId::of::<()>(), + + #[cfg(not(feature = "no_index"))] + Self::Array(_) => TypeId::of::(), + + #[cfg(not(feature = "no_object"))] + Self::Map(_) => TypeId::of::(), + + _ => return None, + }) + } + + /// Get the `Dynamic` value of a constant expression. + /// + /// Returns `None` if the expression is not constant. + pub fn get_constant_value(&self) -> Option { + Some(match self { + Self::Expr(x) => return x.get_constant_value(), + + Self::IntegerConstant(x) => x.0.into(), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(x) => x.0.into(), + Self::CharConstant(x) => x.0.into(), + Self::StringConstant(x) => x.name.clone().into(), + Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( + x.name.clone(), + Default::default(), + )))), + Self::True(_) => true.into(), + Self::False(_) => false.into(), + Self::Unit(_) => ().into(), + + #[cfg(not(feature = "no_index"))] + Self::Array(x) if x.0.iter().all(Self::is_constant) => Dynamic(Union::Array(Box::new( + x.0.iter() + .map(|v| v.get_constant_value().unwrap()) + .collect(), + ))), + + #[cfg(not(feature = "no_object"))] + Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { + Dynamic(Union::Map(Box::new( + x.0.iter() + .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())) + .collect(), + ))) + } + + _ => return None, + }) + } + + /// Is the expression a simple variable access? + pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { + match self { + Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).name.as_str()), + _ => None, + } + } + + /// Get the `Position` of the expression. + pub fn position(&self) -> Position { + match self { + Self::Expr(x) => x.position(), + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(x) => x.1, + + Self::IntegerConstant(x) => x.1, + Self::CharConstant(x) => x.1, + Self::StringConstant(x) => x.pos, + Self::FnPointer(x) => x.pos, + Self::Array(x) => x.1, + Self::Map(x) => x.1, + Self::Property(x) => x.1, + Self::Stmt(x) => x.1, + Self::Variable(x) => (x.0).pos, + Self::FnCall(x) => (x.0).3, + + Self::And(x) | Self::Or(x) | Self::In(x) => x.pos, + + Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, + + Self::Dot(x) | Self::Index(x) => x.lhs.position(), + + Self::Custom(x) => x.pos, + } + } + + /// Override the `Position` of the expression. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Expr(x) => { + x.set_position(new_pos); + } + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(x) => x.1 = new_pos, + + Self::IntegerConstant(x) => x.1 = new_pos, + Self::CharConstant(x) => x.1 = new_pos, + Self::StringConstant(x) => x.pos = new_pos, + Self::FnPointer(x) => x.pos = new_pos, + Self::Array(x) => x.1 = new_pos, + Self::Map(x) => x.1 = new_pos, + Self::Variable(x) => (x.0).pos = new_pos, + Self::Property(x) => x.1 = new_pos, + Self::Stmt(x) => x.1 = new_pos, + Self::FnCall(x) => (x.0).3 = new_pos, + Self::And(x) | Self::Or(x) | Self::In(x) => x.pos = new_pos, + Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos, + Self::Dot(x) | Self::Index(x) => x.pos = new_pos, + Self::Custom(x) => x.pos = new_pos, + } + + self + } + + /// Is the expression pure? + /// + /// A pure expression has no side effects. + pub fn is_pure(&self) -> bool { + match self { + Self::Expr(x) => x.is_pure(), + + Self::Array(x) => x.0.iter().all(Self::is_pure), + + Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => { + x.lhs.is_pure() && x.rhs.is_pure() + } + + Self::Stmt(x) => x.0.is_pure(), + + Self::Variable(_) => true, + + _ => self.is_constant(), + } + } + + /// Is the expression the unit `()` literal? + #[inline(always)] + pub fn is_unit(&self) -> bool { + match self { + Self::Unit(_) => true, + _ => false, + } + } + + /// Is the expression a simple constant literal? + pub fn is_literal(&self) -> bool { + match self { + Self::Expr(x) => x.is_literal(), + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_) => true, + + Self::IntegerConstant(_) + | Self::CharConstant(_) + | Self::StringConstant(_) + | Self::FnPointer(_) + | Self::True(_) + | Self::False(_) + | Self::Unit(_) => true, + + // An array literal is literal if all items are literals + Self::Array(x) => x.0.iter().all(Self::is_literal), + + // An map literal is literal if all items are literals + Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_literal), + + // Check in expression + Self::In(x) => match (&x.lhs, &x.rhs) { + (Self::StringConstant(_), Self::StringConstant(_)) + | (Self::CharConstant(_), Self::StringConstant(_)) => true, + _ => false, + }, + + _ => false, + } + } + + /// Is the expression a constant? + pub fn is_constant(&self) -> bool { + match self { + Self::Expr(x) => x.is_constant(), + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_) => true, + + Self::IntegerConstant(_) + | Self::CharConstant(_) + | Self::StringConstant(_) + | Self::FnPointer(_) + | Self::True(_) + | Self::False(_) + | Self::Unit(_) => true, + + // An array literal is constant if all items are constant + Self::Array(x) => x.0.iter().all(Self::is_constant), + + // An map literal is constant if all items are constant + Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), + + // Check in expression + Self::In(x) => match (&x.lhs, &x.rhs) { + (Self::StringConstant(_), Self::StringConstant(_)) + | (Self::CharConstant(_), Self::StringConstant(_)) => true, + _ => false, + }, + + _ => false, + } + } + + /// Is a particular token allowed as a postfix operator to this expression? + pub fn is_valid_postfix(&self, token: &Token) -> bool { + match self { + Self::Expr(x) => x.is_valid_postfix(token), + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_) => false, + + Self::IntegerConstant(_) + | Self::CharConstant(_) + | Self::FnPointer(_) + | Self::In(_) + | Self::And(_) + | Self::Or(_) + | Self::True(_) + | Self::False(_) + | Self::Unit(_) => false, + + Self::StringConstant(_) + | Self::Stmt(_) + | Self::FnCall(_) + | Self::Dot(_) + | Self::Index(_) + | Self::Array(_) + | Self::Map(_) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + _ => false, + }, + + Self::Variable(_) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + Token::LeftParen => true, + Token::Bang => true, + Token::DoubleColon => true, + _ => false, + }, + + Self::Property(_) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + Token::LeftParen => true, + _ => false, + }, + + Self::Custom(_) => false, + } + } + + /// Convert a `Variable` into a `Property`. All other variants are untouched. + #[cfg(not(feature = "no_object"))] + #[inline] + pub(crate) fn into_property(self) -> Self { + match self { + Self::Variable(x) if x.1.is_none() => { + let Ident { name, pos } = x.0; + let getter = make_getter(&name); + let setter = make_setter(&name); + Self::Property(Box::new(((name.into(), getter, setter), pos))) + } + _ => self, + } + } +} diff --git a/src/dynamic.rs b/src/dynamic.rs index 615637c8..b405fd8a 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1,14 +1,15 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. use crate::fn_native::{FnPtr, SendSync}; -use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; +use crate::utils::ImmutableString; +use crate::INT; #[cfg(not(feature = "no_closure"))] use crate::fn_native::{shared_try_take, Locked, Shared}; #[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; +use crate::FLOAT; #[cfg(not(feature = "no_index"))] use crate::engine::Array; diff --git a/src/engine.rs b/src/engine.rs index 18a5f582..59bd12bc 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,12 +1,12 @@ //! Main module defining the script evaluation `Engine`. +use crate::ast::{BinaryExpr, Expr, Ident, ReturnType, Stmt}; use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant}; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{Callback, FnPtr, OnVarCallback}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{BinaryExpr, Expr, Ident, ReturnType, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -15,7 +15,7 @@ use crate::token::Position; use crate::{calc_native_fn_hash, StaticVec}; #[cfg(not(feature = "no_index"))] -use crate::parser::INT; +use crate::INT; #[cfg(not(feature = "no_module"))] use crate::module::ModuleResolver; @@ -389,11 +389,6 @@ pub struct State { } impl State { - /// Create a new `State`. - #[inline(always)] - pub fn new() -> Self { - Default::default() - } /// Is the state currently at global (root) level? #[inline(always)] pub fn is_global(&self) -> bool { @@ -461,8 +456,8 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] - pub fn imports(&self) -> &'a Imports { - self.mods.as_ref() + pub fn imports<'z: 'a>(&'z self) -> &'a Imports { + self.mods } /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline(always)] diff --git a/src/engine_api.rs b/src/engine_api.rs index 729a7105..36e664dc 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -2,7 +2,7 @@ use crate::ast::AST; use crate::dynamic::{Dynamic, Variant}; -use crate::engine::{Engine, EvalContext, Imports, State}; +use crate::engine::{Engine, EvalContext, Imports}; use crate::fn_native::{FnCallArgs, NativeCallContext, SendSync}; use crate::optimize::OptimizationLevel; use crate::parse_error::ParseError; @@ -1619,7 +1619,7 @@ impl Engine { .get_script_fn(name, args.len(), true) .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?; - let mut state = State::new(); + let mut state = Default::default(); let mut mods = Default::default(); // Check for data race. diff --git a/src/fn_call.rs b/src/fn_call.rs index 5cb9d7d5..78c381a3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,5 +1,6 @@ //! Implement function-calling mechanism for `Engine`. +use crate::ast::{Expr, Stmt}; use crate::dynamic::Dynamic; use crate::engine::{ search_imports, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, @@ -10,21 +11,21 @@ use crate::fn_native::{FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::parse_error::ParseErrorType; -use crate::parser::{Expr, ImmutableString, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::stdlib::ops::Deref; use crate::token::Position; -use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec}; +use crate::utils::ImmutableString; +use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec, INT}; #[cfg(not(feature = "no_function"))] use crate::{ - parser::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, + ast::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, scope::EntryType as ScopeEntryType, }; #[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; +use crate::FLOAT; #[cfg(not(feature = "no_index"))] use crate::engine::{FN_IDX_GET, FN_IDX_SET}; @@ -41,7 +42,6 @@ use crate::scope::Entry as ScopeEntry; use crate::stdlib::{ any::{type_name, TypeId}, - borrow::Cow, boxed::Box, convert::TryFrom, format, @@ -51,6 +51,9 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_function"))] +use crate::stdlib::borrow::Cow; + #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::float::Float; @@ -628,7 +631,7 @@ impl Engine { statements: impl IntoIterator, lib: &[&Module], ) -> Result<(Dynamic, u64), Box> { - let mut state = State::new(); + let mut state = Default::default(); statements .into_iter() diff --git a/src/fn_native.rs b/src/fn_native.rs index 9eebffbe..db95c6b3 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,9 +1,9 @@ //! Module defining interfaces to native-Rust functions. +use crate::ast::{FnAccess, ScriptFnDef}; use crate::dynamic::Dynamic; use crate::engine::{Engine, EvalContext}; use crate::module::Module; -use crate::parser::{FnAccess, ScriptFnDef}; use crate::plugin::PluginFunction; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; diff --git a/src/fn_register.rs b/src/fn_register.rs index 2f0684f1..4a8ac472 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,10 +2,10 @@ #![allow(non_snake_case)] +use crate::ast::FnAccess; use crate::dynamic::{Dynamic, DynamicWriteLock, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, NativeCallContext, SendSync}; -use crate::parser::FnAccess; use crate::r#unsafe::unsafe_cast_box; use crate::result::EvalAltResult; use crate::utils::ImmutableString; diff --git a/src/lib.rs b/src/lib.rs index 018a0138..b2dc3c10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,24 @@ mod token; mod r#unsafe; mod utils; +/// The system integer type. +/// +/// If the `only_i32` feature is enabled, this will be `i32` instead. +#[cfg(not(feature = "only_i32"))] +pub type INT = i64; + +/// The system integer type. +/// +/// If the `only_i32` feature is not enabled, this will be `i64` instead. +#[cfg(feature = "only_i32")] +pub type INT = i32; + +/// The system floating-point type. +/// +/// Not available under the `no_float` feature. +#[cfg(not(feature = "no_float"))] +pub type FLOAT = f64; + pub use ast::AST; pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext}; @@ -90,11 +108,11 @@ pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::Module; pub use parse_error::{ParseError, ParseErrorType}; -pub use parser::{ImmutableString, INT}; pub use result::EvalAltResult; pub use scope::Scope; pub use syntax::Expression; pub use token::Position; +pub use utils::ImmutableString; #[cfg(feature = "internals")] pub use utils::{calc_native_fn_hash, calc_script_fn_hash}; @@ -105,9 +123,7 @@ pub(crate) use utils::{calc_native_fn_hash, calc_script_fn_hash}; pub use rhai_codegen::*; #[cfg(not(feature = "no_function"))] -pub use parser::FnAccess; -#[cfg(feature = "no_function")] -pub use parser::FnAccess; +pub use ast::FnAccess; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; @@ -118,9 +134,6 @@ pub use engine::Array; #[cfg(not(feature = "no_object"))] pub use engine::Map; -#[cfg(not(feature = "no_float"))] -pub use parser::FLOAT; - #[cfg(not(feature = "no_module"))] pub use module::ModuleResolver; @@ -151,7 +164,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use parser::{ +pub use ast::{ BinaryExpr, CustomExpr, Expr, FloatWrapper, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, }; diff --git a/src/module/mod.rs b/src/module/mod.rs index 21050777..863a8643 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,16 +1,16 @@ //! Module defining external-loaded modules for Rhai. +use crate::ast::FnAccess; use crate::dynamic::{Dynamic, Variant}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, NativeCallContext, SendSync}; use crate::fn_register::by_value as cast_arg; -use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::token::{Position, Token}; use crate::utils::{ImmutableString, StraightHasherBuilder}; use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] -use crate::{fn_native::Shared, parser::ScriptFnDef}; +use crate::{fn_native::Shared, ast::ScriptFnDef}; #[cfg(not(feature = "no_module"))] use crate::{ diff --git a/src/optimize.rs b/src/optimize.rs index a9433a3d..e72dba20 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the AST optimizer. -use crate::ast::AST; +use crate::ast::{BinaryExpr, CustomExpr, Expr, ScriptFnDef, Stmt, AST}; use crate::dynamic::Dynamic; use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, @@ -8,13 +8,13 @@ use crate::engine::{ }; use crate::fn_call::run_builtin_binary_op; use crate::module::Module; -use crate::parser::{map_dynamic_to_expr, BinaryExpr, CustomExpr, Expr, ScriptFnDef, Stmt}; +use crate::parser::map_dynamic_to_expr; use crate::scope::{Entry as ScopeEntry, Scope}; use crate::token::{is_valid_identifier, Position}; use crate::{calc_native_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] -use crate::parser::ReturnType; +use crate::ast::ReturnType; use crate::stdlib::{ boxed::Box, diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index c848fbe3..3f04b50d 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -1,13 +1,13 @@ #![allow(non_snake_case)] use crate::def_package; -use crate::parser::INT; use crate::plugin::*; +use crate::INT; use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; +use crate::FLOAT; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index cf194682..9f3179a5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -5,10 +5,11 @@ use crate::def_package; use crate::dynamic::Dynamic; use crate::engine::Array; use crate::fn_native::{FnPtr, NativeCallContext}; -use crate::parser::{ImmutableString, INT}; use crate::plugin::*; use crate::result::EvalAltResult; use crate::token::Position; +use crate::utils::ImmutableString; +use crate::INT; #[cfg(not(feature = "no_object"))] use crate::engine::Map; @@ -40,12 +41,12 @@ macro_rules! gen_array_functions { } #[rhai_fn(return_raw)] - pub fn pad(context: NativeCallContext, list: &mut Array, len: INT, item: $arg_type) -> Result> { + pub fn pad(_context: NativeCallContext, list: &mut Array, len: INT, item: $arg_type) -> Result> { // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] - if context.engine().max_array_size() > 0 && len > 0 && (len as usize) > context.engine().max_array_size() { + if _context.engine().max_array_size() > 0 && len > 0 && (len as usize) > _context.engine().max_array_size() { return EvalAltResult::ErrorDataTooLarge( - "Size of array".to_string(), context.engine().max_array_size(), len as usize, Position::none(), + "Size of array".to_string(), _context.engine().max_array_size(), len as usize, Position::none(), ).into(); } diff --git a/src/packages/eval.rs b/src/packages/eval.rs index 36d5b315..68012dc5 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -1,8 +1,8 @@ use crate::def_package; use crate::dynamic::Dynamic; -use crate::parser::ImmutableString; use crate::plugin::*; use crate::result::EvalAltResult; +use crate::utils::ImmutableString; def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { combine_with_exported_module!(lib, "eval", eval_override); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index b6f26fff..a9ff0f10 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,7 +1,7 @@ use crate::def_package; use crate::dynamic::Variant; -use crate::parser::INT; use crate::result::EvalAltResult; +use crate::INT; use crate::stdlib::{ boxed::Box, diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index ae645fce..045e55c0 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -3,8 +3,9 @@ use crate::def_package; use crate::dynamic::Dynamic; use crate::engine::Map; -use crate::parser::{ImmutableString, INT}; use crate::plugin::*; +use crate::utils::ImmutableString; +use crate::INT; #[cfg(not(feature = "no_index"))] use crate::engine::Array; diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 7b905ca9..67ae918e 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,12 +1,12 @@ #![allow(non_snake_case)] use crate::def_package; -use crate::parser::INT; use crate::plugin::*; use crate::token::Position; +use crate::INT; #[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; +use crate::FLOAT; #[cfg(not(feature = "no_float"))] use crate::result::EvalAltResult; @@ -111,7 +111,7 @@ mod int_functions { #[cfg(not(feature = "no_float"))] #[export_module] mod trig_functions { - use crate::parser::FLOAT; + use crate::FLOAT; pub fn sin(x: FLOAT) -> FLOAT { x.to_radians().sin() @@ -154,7 +154,7 @@ mod trig_functions { #[cfg(not(feature = "no_float"))] #[export_module] mod float_functions { - use crate::parser::FLOAT; + use crate::FLOAT; pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 988e1dac..e02a1a20 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -3,8 +3,9 @@ use crate::def_package; use crate::engine::{FN_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_native::FnPtr; -use crate::parser::{ImmutableString, INT}; use crate::plugin::*; +use crate::utils::ImmutableString; +use crate::INT; #[cfg(not(feature = "no_index"))] use crate::engine::Array; diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 9130a9d3..7a76d91e 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -3,9 +3,10 @@ use crate::def_package; use crate::dynamic::Dynamic; use crate::fn_native::FnPtr; -use crate::parser::{ImmutableString, INT}; use crate::plugin::*; +use crate::utils::ImmutableString; use crate::StaticVec; +use crate::INT; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 706233e2..f55288f2 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -4,12 +4,12 @@ use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT}; use crate::def_package; use crate::dynamic::Dynamic; -use crate::parser::INT; use crate::plugin::*; use crate::result::EvalAltResult; +use crate::INT; #[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; +use crate::FLOAT; use crate::stdlib::boxed::Box; diff --git a/src/parser.rs b/src/parser.rs index 828c8c43..d2a9d2f1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,35 +1,36 @@ //! Main module defining the lexer and parser. -use crate::ast::AST; +use crate::ast::{BinaryExpr, CustomExpr, Expr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, AST}; use crate::dynamic::{Dynamic, Union}; use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; -use crate::fn_native::{FnPtr, Shared}; -use crate::module::{Module, ModuleRef}; +use crate::module::ModuleRef; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::parse_error::{LexError, ParseError, ParseErrorType}; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::syntax::{CustomSyntax, FnCustomSyntaxEval}; +use crate::syntax::CustomSyntax; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::utils::StraightHasherBuilder; use crate::{calc_script_fn_hash, StaticVec}; -#[cfg(not(feature = "no_index"))] -use crate::engine::Array; +#[cfg(not(feature = "no_float"))] +use crate::ast::FloatWrapper; #[cfg(not(feature = "no_object"))] -use crate::engine::{make_getter, make_setter, Map, KEYWORD_EVAL, KEYWORD_FN_PTR}; +use crate::engine::{make_getter, make_setter, KEYWORD_EVAL, KEYWORD_FN_PTR}; #[cfg(not(feature = "no_function"))] -use crate::engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY}; +use crate::{ + ast::FnAccess, + engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY}, + utils::ImmutableString, +}; use crate::stdlib::{ - any::TypeId, borrow::Cow, boxed::Box, - char, collections::HashMap, - fmt, format, - hash::{Hash, Hasher}, + format, + hash::Hash, iter::empty, num::NonZeroUsize, string::{String, ToString}, @@ -37,138 +38,21 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_function"))] +use crate::stdlib::hash::Hasher; + #[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_function"))] use crate::stdlib::collections::hash_map::DefaultHasher; -#[cfg(not(feature = "no_closure"))] -use crate::stdlib::collections::HashSet; - #[cfg(feature = "no_std")] #[cfg(not(feature = "no_function"))] use ahash::AHasher; -/// The system integer type. -/// -/// If the `only_i32` feature is enabled, this will be `i32` instead. -#[cfg(not(feature = "only_i32"))] -pub type INT = i64; - -/// The system integer type. -/// -/// If the `only_i32` feature is not enabled, this will be `i64` instead. -#[cfg(feature = "only_i32")] -pub type INT = i32; - -/// The system floating-point type. -/// -/// Not available under the `no_float` feature. -#[cfg(not(feature = "no_float"))] -pub type FLOAT = f64; - type PERR = ParseErrorType; -pub use crate::utils::ImmutableString; - type FunctionsLib = HashMap; -/// A type representing the access mode of a scripted function. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub enum FnAccess { - /// Public function. - Public, - /// Private function. - Private, -} - -impl fmt::Display for FnAccess { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Private => write!(f, "private"), - Self::Public => write!(f, "public"), - } - } -} - -impl FnAccess { - /// Is this access mode private? - #[inline(always)] - pub fn is_private(self) -> bool { - match self { - Self::Public => false, - Self::Private => true, - } - } - /// Is this access mode public? - #[inline(always)] - pub fn is_public(self) -> bool { - match self { - Self::Public => true, - Self::Private => false, - } - } -} - -/// _[INTERNALS]_ A type containing information on a scripted function. -/// Exported under the `internals` feature only. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -#[derive(Debug, Clone)] -pub struct ScriptFnDef { - /// Function name. - pub name: ImmutableString, - /// Function access mode. - pub access: FnAccess, - /// Names of function parameters. - pub params: StaticVec, - /// Access to external variables. - #[cfg(not(feature = "no_closure"))] - pub externals: HashSet, - /// Function body. - pub body: Stmt, - /// Position of the function definition. - pub pos: Position, - /// Encapsulated running environment, if any. - pub lib: Option>, -} - -impl fmt::Display for ScriptFnDef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}({})", - if self.access.is_private() { - "private " - } else { - "" - }, - self.name, - self.params - .iter() - .map(|s| s.as_str()) - .collect::>() - .join(",") - ) - } -} - -/// _[INTERNALS]_ A type encapsulating the mode of a `return`/`throw` statement. -/// Exported under the `internals` feature only. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] -pub enum ReturnType { - /// `return` statement. - Return, - /// `throw` statement. - Exception, -} - #[derive(Clone)] struct ParseState<'e> { /// Reference to the scripting `Engine`. @@ -317,664 +201,6 @@ impl ParseSettings { } } } - -/// An identifier containing a string name and a position. -#[derive(Debug, Clone, Hash)] -pub struct Ident { - pub name: String, - pub pos: Position, -} - -impl Ident { - /// Create a new `Identifier`. - pub fn new(name: String, pos: Position) -> Self { - Self { name, pos } - } -} - -/// An identifier containing an immutable name and a position. -#[derive(Debug, Clone, Hash)] -pub struct IdentX { - pub name: ImmutableString, - pub pos: Position, -} - -impl IdentX { - /// Create a new `Identifier`. - pub fn new(name: impl Into, pos: Position) -> Self { - Self { - name: name.into(), - pos, - } - } -} - -/// _[INTERNALS]_ A Rhai statement. -/// Exported under the `internals` feature only. -/// -/// Each variant is at most one pointer in size (for speed), -/// with everything being allocated together in one single tuple. -#[derive(Debug, Clone, Hash)] -pub enum Stmt { - /// No-op. - Noop(Position), - /// if expr { stmt } else { stmt } - IfThenElse(Expr, Box<(Stmt, Option)>, Position), - /// while expr { stmt } - While(Expr, Box, Position), - /// loop { stmt } - Loop(Box, Position), - /// for id in expr { stmt } - For(Expr, Box<(String, Stmt)>, Position), - /// let id = expr - Let(Box, Option, Position), - /// const id = expr - Const(Box, Option, Position), - /// expr op= expr - Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), - /// { stmt; ... } - Block(Vec, Position), - /// try { stmt; ... } catch ( var ) { stmt; ... } - TryCatch(Box<(Stmt, Option, Stmt, (Position, Position))>), - /// expr - Expr(Expr), - /// continue - Continue(Position), - /// break - Break(Position), - /// return/throw - ReturnWithVal((ReturnType, Position), Option, Position), - /// import expr as var - #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), - /// export var as var, ... - #[cfg(not(feature = "no_module"))] - Export(Vec<(Ident, Option)>, Position), - /// Convert a variable to shared. - #[cfg(not(feature = "no_closure"))] - Share(Ident), -} - -impl Default for Stmt { - #[inline(always)] - fn default() -> Self { - Self::Noop(Default::default()) - } -} - -impl Stmt { - /// Is this statement `Noop`? - pub fn is_noop(&self) -> bool { - match self { - Self::Noop(_) => true, - _ => false, - } - } - - /// Get the `Position` of this statement. - pub fn position(&self) -> Position { - match self { - Self::Noop(pos) - | Self::Continue(pos) - | Self::Break(pos) - | Self::Block(_, pos) - | Self::Assignment(_, pos) - | Self::IfThenElse(_, _, pos) - | Self::While(_, _, pos) - | Self::Loop(_, pos) - | Self::For(_, _, pos) - | Self::ReturnWithVal((_, pos), _, _) => *pos, - - Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos, - Self::TryCatch(x) => (x.3).0, - - Self::Expr(x) => x.position(), - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, pos) => *pos, - #[cfg(not(feature = "no_module"))] - Self::Export(_, pos) => *pos, - - #[cfg(not(feature = "no_closure"))] - Self::Share(Ident { pos, .. }) => *pos, - } - } - - /// Override the `Position` of this statement. - pub fn set_position(&mut self, new_pos: Position) -> &mut Self { - match self { - Self::Noop(pos) - | Self::Continue(pos) - | Self::Break(pos) - | Self::Block(_, pos) - | Self::Assignment(_, pos) - | Self::IfThenElse(_, _, pos) - | Self::While(_, _, pos) - | Self::Loop(_, pos) - | Self::For(_, _, pos) - | Self::ReturnWithVal((_, pos), _, _) => *pos = new_pos, - - Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos = new_pos, - Self::TryCatch(x) => (x.3).0 = new_pos, - - Self::Expr(x) => { - x.set_position(new_pos); - } - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, pos) => *pos = new_pos, - #[cfg(not(feature = "no_module"))] - Self::Export(_, pos) => *pos = new_pos, - - #[cfg(not(feature = "no_closure"))] - Self::Share(Ident { pos, .. }) => *pos = new_pos, - } - - self - } - - /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? - pub fn is_self_terminated(&self) -> bool { - match self { - Self::IfThenElse(_, _, _) - | Self::While(_, _, _) - | Self::Loop(_, _) - | Self::For(_, _, _) - | Self::Block(_, _) - | Self::TryCatch(_) => true, - - // A No-op requires a semicolon in order to know it is an empty statement! - Self::Noop(_) => false, - - Self::Let(_, _, _) - | Self::Const(_, _, _) - | Self::Assignment(_, _) - | Self::Expr(_) - | Self::Continue(_) - | Self::Break(_) - | Self::ReturnWithVal(_, _, _) => false, - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) | Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - - /// Is this statement _pure_? - pub fn is_pure(&self) -> bool { - match self { - Self::Noop(_) => true, - Self::Expr(expr) => expr.is_pure(), - Self::IfThenElse(condition, x, _) if x.1.is_some() => { - condition.is_pure() && x.0.is_pure() && x.1.as_ref().unwrap().is_pure() - } - Self::IfThenElse(condition, x, _) => condition.is_pure() && x.0.is_pure(), - Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), - Self::Loop(block, _) => block.is_pure(), - Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), - Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false, - Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), - Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, - Self::TryCatch(x) => x.0.is_pure() && x.2.is_pure(), - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) => false, - #[cfg(not(feature = "no_module"))] - Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } -} - -/// _[INTERNALS]_ A type wrapping a custom syntax definition. -/// Exported under the `internals` feature only. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -#[derive(Clone)] -pub struct CustomExpr { - pub(crate) keywords: StaticVec, - pub(crate) func: Shared, - pub(crate) pos: Position, -} - -impl fmt::Debug for CustomExpr { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.keywords, f) - } -} - -impl Hash for CustomExpr { - #[inline(always)] - fn hash(&self, state: &mut H) { - self.keywords.hash(state); - } -} - -impl CustomExpr { - /// Get the keywords for this `CustomExpr`. - #[inline(always)] - pub fn keywords(&self) -> &[Expr] { - &self.keywords - } - /// Get the implementation function for this `CustomExpr`. - #[inline(always)] - pub fn func(&self) -> &FnCustomSyntaxEval { - self.func.as_ref() - } - /// Get the position of this `CustomExpr`. - #[inline(always)] - pub fn position(&self) -> Position { - self.pos - } -} - -/// _[INTERNALS]_ A type wrapping a floating-point number. -/// Exported under the `internals` feature only. -/// -/// This type is mainly used to provide a standard `Hash` implementation -/// to floating-point numbers, allowing `Expr` to derive `Hash` automatically. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -#[cfg(not(feature = "no_float"))] -#[derive(Debug, PartialEq, PartialOrd, Clone)] -pub struct FloatWrapper(pub FLOAT, pub Position); - -#[cfg(not(feature = "no_float"))] -impl Hash for FloatWrapper { - #[inline(always)] - fn hash(&self, state: &mut H) { - state.write(&self.0.to_le_bytes()); - self.1.hash(state); - } -} - -/// A binary expression structure. -#[derive(Debug, Clone, Hash)] -pub struct BinaryExpr { - pub lhs: Expr, - pub rhs: Expr, - pub pos: Position, -} - -/// _[INTERNALS]_ An expression sub-tree. -/// Exported under the `internals` feature only. -/// -/// Each variant is at most one pointer in size (for speed), -/// with everything being allocated together in one single tuple. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -#[derive(Debug, Clone, Hash)] -pub enum Expr { - /// Integer constant. - IntegerConstant(Box<(INT, Position)>), - /// Floating-point constant. - #[cfg(not(feature = "no_float"))] - FloatConstant(Box), - /// Character constant. - CharConstant(Box<(char, Position)>), - /// String constant. - StringConstant(Box), - /// FnPtr constant. - FnPointer(Box), - /// Variable access - ((variable name, position), optional modules, hash, optional index) - Variable(Box<(Ident, Option>, u64, Option)>), - /// Property access. - Property(Box<((ImmutableString, String, String), Position)>), - /// { stmt } - Stmt(Box<(Stmt, Position)>), - /// Wrapped expression - should not be optimized away. - Expr(Box), - /// func(expr, ... ) - ((function name, native_only, capture, position), optional modules, hash, arguments, optional default value) - /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls - /// and the function names are predictable, so no need to allocate a new `String`. - FnCall( - Box<( - (Cow<'static, str>, bool, bool, Position), - Option>, - u64, - StaticVec, - Option, // Default value is `bool` in order for `Expr` to be `Hash`. - )>, - ), - /// lhs.rhs - Dot(Box), - /// expr[expr] - Index(Box), - /// [ expr, ... ] - Array(Box<(StaticVec, Position)>), - /// #{ name:expr, ... } - Map(Box<(StaticVec<(IdentX, Expr)>, Position)>), - /// lhs in rhs - In(Box), - /// lhs && rhs - And(Box), - /// lhs || rhs - Or(Box), - /// true - True(Position), - /// false - False(Position), - /// () - Unit(Position), - /// Custom syntax - Custom(Box), -} - -impl Default for Expr { - #[inline(always)] - fn default() -> Self { - Self::Unit(Default::default()) - } -} - -impl Expr { - /// Get the type of an expression. - /// - /// Returns `None` if the expression's result type is not constant. - pub fn get_type_id(&self) -> Option { - Some(match self { - Self::Expr(x) => return x.get_type_id(), - - Self::IntegerConstant(_) => TypeId::of::(), - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_) => TypeId::of::(), - Self::CharConstant(_) => TypeId::of::(), - Self::StringConstant(_) => TypeId::of::(), - Self::FnPointer(_) => TypeId::of::(), - Self::True(_) | Self::False(_) | Self::In(_) | Self::And(_) | Self::Or(_) => { - TypeId::of::() - } - Self::Unit(_) => TypeId::of::<()>(), - - #[cfg(not(feature = "no_index"))] - Self::Array(_) => TypeId::of::(), - - #[cfg(not(feature = "no_object"))] - Self::Map(_) => TypeId::of::(), - - _ => return None, - }) - } - - /// Get the `Dynamic` value of a constant expression. - /// - /// Returns `None` if the expression is not constant. - pub fn get_constant_value(&self) -> Option { - Some(match self { - Self::Expr(x) => return x.get_constant_value(), - - Self::IntegerConstant(x) => x.0.into(), - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(x) => x.0.into(), - Self::CharConstant(x) => x.0.into(), - Self::StringConstant(x) => x.name.clone().into(), - Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( - x.name.clone(), - Default::default(), - )))), - Self::True(_) => true.into(), - Self::False(_) => false.into(), - Self::Unit(_) => ().into(), - - #[cfg(not(feature = "no_index"))] - Self::Array(x) if x.0.iter().all(Self::is_constant) => Dynamic(Union::Array(Box::new( - x.0.iter() - .map(|v| v.get_constant_value().unwrap()) - .collect(), - ))), - - #[cfg(not(feature = "no_object"))] - Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { - Dynamic(Union::Map(Box::new( - x.0.iter() - .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())) - .collect(), - ))) - } - - _ => return None, - }) - } - - /// Is the expression a simple variable access? - pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { - match self { - Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).name.as_str()), - _ => None, - } - } - - /// Get the `Position` of the expression. - pub fn position(&self) -> Position { - match self { - Self::Expr(x) => x.position(), - - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(x) => x.1, - - Self::IntegerConstant(x) => x.1, - Self::CharConstant(x) => x.1, - Self::StringConstant(x) => x.pos, - Self::FnPointer(x) => x.pos, - Self::Array(x) => x.1, - Self::Map(x) => x.1, - Self::Property(x) => x.1, - Self::Stmt(x) => x.1, - Self::Variable(x) => (x.0).pos, - Self::FnCall(x) => (x.0).3, - - Self::And(x) | Self::Or(x) | Self::In(x) => x.pos, - - Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, - - Self::Dot(x) | Self::Index(x) => x.lhs.position(), - - Self::Custom(x) => x.pos, - } - } - - /// Override the `Position` of the expression. - pub fn set_position(&mut self, new_pos: Position) -> &mut Self { - match self { - Self::Expr(x) => { - x.set_position(new_pos); - } - - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(x) => x.1 = new_pos, - - Self::IntegerConstant(x) => x.1 = new_pos, - Self::CharConstant(x) => x.1 = new_pos, - Self::StringConstant(x) => x.pos = new_pos, - Self::FnPointer(x) => x.pos = new_pos, - Self::Array(x) => x.1 = new_pos, - Self::Map(x) => x.1 = new_pos, - Self::Variable(x) => (x.0).pos = new_pos, - Self::Property(x) => x.1 = new_pos, - Self::Stmt(x) => x.1 = new_pos, - Self::FnCall(x) => (x.0).3 = new_pos, - Self::And(x) | Self::Or(x) | Self::In(x) => x.pos = new_pos, - Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos, - Self::Dot(x) | Self::Index(x) => x.pos = new_pos, - Self::Custom(x) => x.pos = new_pos, - } - - self - } - - /// Is the expression pure? - /// - /// A pure expression has no side effects. - pub fn is_pure(&self) -> bool { - match self { - Self::Expr(x) => x.is_pure(), - - Self::Array(x) => x.0.iter().all(Self::is_pure), - - Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => { - x.lhs.is_pure() && x.rhs.is_pure() - } - - Self::Stmt(x) => x.0.is_pure(), - - Self::Variable(_) => true, - - _ => self.is_constant(), - } - } - - /// Is the expression the unit `()` literal? - #[inline(always)] - pub fn is_unit(&self) -> bool { - match self { - Self::Unit(_) => true, - _ => false, - } - } - - /// Is the expression a simple constant literal? - pub fn is_literal(&self) -> bool { - match self { - Self::Expr(x) => x.is_literal(), - - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_) => true, - - Self::IntegerConstant(_) - | Self::CharConstant(_) - | Self::StringConstant(_) - | Self::FnPointer(_) - | Self::True(_) - | Self::False(_) - | Self::Unit(_) => true, - - // An array literal is literal if all items are literals - Self::Array(x) => x.0.iter().all(Self::is_literal), - - // An map literal is literal if all items are literals - Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_literal), - - // Check in expression - Self::In(x) => match (&x.lhs, &x.rhs) { - (Self::StringConstant(_), Self::StringConstant(_)) - | (Self::CharConstant(_), Self::StringConstant(_)) => true, - _ => false, - }, - - _ => false, - } - } - - /// Is the expression a constant? - pub fn is_constant(&self) -> bool { - match self { - Self::Expr(x) => x.is_constant(), - - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_) => true, - - Self::IntegerConstant(_) - | Self::CharConstant(_) - | Self::StringConstant(_) - | Self::FnPointer(_) - | Self::True(_) - | Self::False(_) - | Self::Unit(_) => true, - - // An array literal is constant if all items are constant - Self::Array(x) => x.0.iter().all(Self::is_constant), - - // An map literal is constant if all items are constant - Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), - - // Check in expression - Self::In(x) => match (&x.lhs, &x.rhs) { - (Self::StringConstant(_), Self::StringConstant(_)) - | (Self::CharConstant(_), Self::StringConstant(_)) => true, - _ => false, - }, - - _ => false, - } - } - - /// Is a particular token allowed as a postfix operator to this expression? - pub fn is_valid_postfix(&self, token: &Token) -> bool { - match self { - Self::Expr(x) => x.is_valid_postfix(token), - - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_) => false, - - Self::IntegerConstant(_) - | Self::CharConstant(_) - | Self::FnPointer(_) - | Self::In(_) - | Self::And(_) - | Self::Or(_) - | Self::True(_) - | Self::False(_) - | Self::Unit(_) => false, - - Self::StringConstant(_) - | Self::Stmt(_) - | Self::FnCall(_) - | Self::Dot(_) - | Self::Index(_) - | Self::Array(_) - | Self::Map(_) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - _ => false, - }, - - Self::Variable(_) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - Token::LeftParen => true, - Token::Bang => true, - Token::DoubleColon => true, - _ => false, - }, - - Self::Property(_) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - Token::LeftParen => true, - _ => false, - }, - - Self::Custom(_) => false, - } - } - - /// Convert a `Variable` into a `Property`. All other variants are untouched. - #[cfg(not(feature = "no_object"))] - #[inline] - pub(crate) fn into_property(self) -> Self { - match self { - Self::Variable(x) if x.1.is_none() => { - let Ident { name, pos } = x.0; - let getter = make_getter(&name); - let setter = make_setter(&name); - Self::Property(Box::new(((name.into(), getter, setter), pos))) - } - _ => self, - } - } -} - /// Consume a particular token, checking that it is the expected one. fn eat_token(input: &mut TokenStream, token: Token) -> Position { let (t, pos) = input.next().unwrap(); @@ -1737,10 +963,9 @@ fn parse_unary( .map(|i| Expr::IntegerConstant(Box::new((i, pos)))) .or_else(|| { #[cfg(not(feature = "no_float"))] - return Some(Expr::FloatConstant(Box::new(FloatWrapper( - -(x.0 as FLOAT), - pos, - )))); + return Some(Expr::FloatConstant(Box::new( + -Into::::into(*x), + ))); #[cfg(feature = "no_float")] return None; }) @@ -1749,9 +974,7 @@ fn parse_unary( // Negative float #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x) => { - Ok(Expr::FloatConstant(Box::new(FloatWrapper(-x.0, x.1)))) - } + Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new(-(*x)))), // Call negative function expr => { diff --git a/src/plugin.rs b/src/plugin.rs index 168de561..7bce9b5c 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,11 +1,11 @@ //! Module defining macros for developing _plugins_. +pub use crate::ast::FnAccess; pub use crate::dynamic::Dynamic; pub use crate::engine::Engine; pub use crate::fn_native::{CallableFunction, FnCallArgs, NativeCallContext}; pub use crate::fn_register::{RegisterFn, RegisterResultFn}; pub use crate::module::Module; -pub use crate::parser::FnAccess; pub use crate::result::EvalAltResult; pub use crate::utils::ImmutableString; diff --git a/src/result.rs b/src/result.rs index f0ec841b..1a42c118 100644 --- a/src/result.rs +++ b/src/result.rs @@ -2,9 +2,9 @@ use crate::dynamic::Dynamic; use crate::parse_error::ParseErrorType; -use crate::parser::INT; use crate::token::Position; use crate::utils::ImmutableString; +use crate::INT; #[cfg(not(feature = "no_function"))] use crate::engine::is_anonymous_fn; diff --git a/src/scope.rs b/src/scope.rs index 05188843..13442a3b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,7 +1,8 @@ //! Module that defines the `Scope` type representing a function call-stack scope. +use crate::ast::Expr; use crate::dynamic::{Dynamic, Variant}; -use crate::parser::{map_dynamic_to_expr, Expr}; +use crate::parser::map_dynamic_to_expr; use crate::token::Position; use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; diff --git a/src/syntax.rs b/src/syntax.rs index 1aefdc15..4d0a6519 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,10 +1,10 @@ //! Module implementing custom syntax for `Engine`. +use crate::ast::Expr; use crate::dynamic::Dynamic; use crate::engine::{Engine, EvalContext, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::fn_native::{SendSync, Shared}; use crate::parse_error::{LexError, ParseError}; -use crate::parser::Expr; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position, Token}; use crate::utils::ImmutableString; diff --git a/src/token.rs b/src/token.rs index 044201b1..3e4baba9 100644 --- a/src/token.rs +++ b/src/token.rs @@ -9,11 +9,11 @@ use crate::engine::{ use crate::engine::KEYWORD_IS_SHARED; use crate::parse_error::LexError; -use crate::parser::INT; use crate::StaticVec; +use crate::INT; #[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; +use crate::FLOAT; use crate::stdlib::{ borrow::Cow, diff --git a/src/utils.rs b/src/utils.rs index 451b929a..385c4536 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -42,14 +42,6 @@ impl Hasher for StraightHasher { } } -impl StraightHasher { - /// Create a `StraightHasher`. - #[inline(always)] - pub fn new() -> Self { - Self(0) - } -} - /// A hash builder for `StraightHasher`. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] pub struct StraightHasherBuilder; @@ -59,7 +51,7 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] fn build_hasher(&self) -> Self::Hasher { - StraightHasher::new() + Default::default() } } From a73584cd362397f4294e718d80a4fd918bc8704a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 29 Oct 2020 12:00:02 +0800 Subject: [PATCH 7/7] Fix no_std. --- src/ast.rs | 1 + src/engine.rs | 2 +- src/module/mod.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 3d67d832..c9d0d96c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -21,6 +21,7 @@ use crate::engine::{make_getter, make_setter, Map}; use crate::stdlib::{ any::TypeId, borrow::Cow, + boxed::Box, fmt, hash::{Hash, Hasher}, num::NonZeroUsize, diff --git a/src/engine.rs b/src/engine.rs index 59bd12bc..fc82de5c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -456,7 +456,7 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] - pub fn imports<'z: 'a>(&'z self) -> &'a Imports { + pub fn imports(&'a self) -> &'a Imports { self.mods } /// Get an iterator over the namespaces containing definition of all script-defined functions. diff --git a/src/module/mod.rs b/src/module/mod.rs index 863a8643..907db6f0 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -10,7 +10,7 @@ use crate::utils::{ImmutableString, StraightHasherBuilder}; use crate::{calc_native_fn_hash, calc_script_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] -use crate::{fn_native::Shared, ast::ScriptFnDef}; +use crate::{ast::ScriptFnDef, fn_native::Shared}; #[cfg(not(feature = "no_module"))] use crate::{