From 7bdc2e3d20edb2fae588ae3b2539923d3b0867cb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 28 Mar 2021 19:04:25 +0800 Subject: [PATCH 01/11] Extract constant arguments from function calls. --- src/ast.rs | 15 ++++ src/engine.rs | 26 ++++-- src/fn_call.rs | 187 ++++++++++++++++++++++++++++---------------- src/optimize.rs | 35 +++++++-- src/parser.rs | 10 +-- tests/operations.rs | 2 +- 6 files changed, 190 insertions(+), 85 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 51da78e6..735d3f8c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1400,6 +1400,8 @@ pub struct FnCallExpr { pub capture: bool, /// List of function call arguments. pub args: StaticVec, + /// List of function call arguments that are constants. + pub constant_args: StaticVec<(Dynamic, Position)>, /// Namespace of the function, if any. Boxed because it occurs rarely. pub namespace: Option, /// Function name. @@ -1408,6 +1410,19 @@ pub struct FnCallExpr { pub name: Cow<'static, str>, } +impl FnCallExpr { + /// Are there no arguments to this function call? + #[inline(always)] + pub fn is_args_empty(&self) -> bool { + self.args.is_empty() && self.constant_args.is_empty() + } + /// Get the number of arguments to this function call. + #[inline(always)] + pub fn num_args(&self) -> usize { + self.args.len() + self.constant_args.len() + } +} + /// A type that wraps a [`FLOAT`] and implements [`Hash`]. #[cfg(not(feature = "no_float"))] #[derive(Clone, Copy, PartialEq, PartialOrd)] diff --git a/src/engine.rs b/src/engine.rs index 8652ef25..442e806f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1440,16 +1440,21 @@ impl Engine { Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => { let mut arg_positions: StaticVec<_> = Default::default(); - let arg_values = x + let mut arg_values = x .args .iter() + .inspect(|arg_expr| arg_positions.push(arg_expr.position())) .map(|arg_expr| { - arg_positions.push(arg_expr.position()); self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) .map(Dynamic::flatten) }) .collect::, _>>()?; + x.constant_args + .iter() + .inspect(|(_, pos)| arg_positions.push(*pos)) + .for_each(|(v, _)| arg_values.push(v.clone())); + idx_values.push((arg_values, arg_positions).into()); } Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => { @@ -1475,16 +1480,21 @@ impl Engine { { let mut arg_positions: StaticVec<_> = Default::default(); - let arg_values = x + let mut arg_values = x .args .iter() + .inspect(|arg_expr| arg_positions.push(arg_expr.position())) .map(|arg_expr| { - arg_positions.push(arg_expr.position()); self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) .map(Dynamic::flatten) }) .collect::, _>>()?; + x.constant_args + .iter() + .inspect(|(_, pos)| arg_positions.push(*pos)) + .for_each(|(v, _)| arg_values.push(v.clone())); + (arg_values, arg_positions).into() } Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => { @@ -1700,10 +1710,12 @@ impl Engine { capture, hash, args, + constant_args: c_args, .. } = x.as_ref(); self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, *hash, *pos, *capture, level, + scope, mods, state, lib, this_ptr, name, args, c_args, *hash, *pos, *capture, + level, ) } @@ -1714,12 +1726,14 @@ impl Engine { namespace, hash, args, + constant_args: c_args, .. } = x.as_ref(); let namespace = namespace.as_ref(); let hash = hash.native_hash(); self.make_qualified_function_call( - scope, mods, state, lib, this_ptr, namespace, name, args, hash, *pos, level, + scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos, + level, ) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 18cc509c..d02d75e6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1058,33 +1058,39 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, args_expr: &[Expr], + constant_args: &[(Dynamic, Position)], mut hash: FnCallHash, pos: Position, capture_scope: bool, level: usize, ) -> RhaiResult { - let args_expr = args_expr.as_ref(); - // Handle call() - Redirect function call let redirected; - let mut args_expr = args_expr.as_ref(); + let mut args_expr = args_expr; + let mut constant_args = constant_args; + let mut total_args = args_expr.len() + constant_args.len(); let mut curry = StaticVec::new(); let mut name = fn_name; match name { // Handle call() - KEYWORD_FN_PTR_CALL if args_expr.len() >= 1 => { - let fn_ptr = - self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + KEYWORD_FN_PTR_CALL if total_args >= 1 => { + let (arg, arg_pos) = args_expr.get(0).map_or_else( + || Ok(constant_args[0].clone()), + |arg| { + self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) + .map(|v| (v, arg.position())) + }, + )?; - if !fn_ptr.is::() { + if !arg.is::() { return Err(self.make_type_mismatch_err::( - self.map_type_name(fn_ptr.type_name()), - args_expr[0].position(), + self.map_type_name(arg.type_name()), + arg_pos, )); } - let fn_ptr = fn_ptr.cast::(); + let fn_ptr = arg.cast::(); curry.extend(fn_ptr.curry().iter().cloned()); // Redirect function name @@ -1092,10 +1098,15 @@ impl Engine { name = &redirected; // Skip the first argument - args_expr = &args_expr.as_ref()[1..]; + if !args_expr.is_empty() { + args_expr = &args_expr[1..]; + } else { + constant_args = &constant_args[1..]; + } + total_args -= 1; // Recalculate hash - let args_len = args_expr.len() + curry.len(); + let args_len = total_args + curry.len(); hash = if !hash.is_native_only() { FnCallHash::from_script(calc_fn_hash(empty(), name, args_len)) } else { @@ -1103,66 +1114,95 @@ impl Engine { }; } // Handle Fn() - KEYWORD_FN_PTR if args_expr.len() == 1 => { + KEYWORD_FN_PTR if total_args == 1 => { + let (arg, arg_pos) = args_expr.get(0).map_or_else( + || Ok(constant_args[0].clone()), + |arg| { + self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) + .map(|v| (v, arg.position())) + }, + )?; + // Fn - only in function call style - return self - .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? + return arg .take_immutable_string() - .map_err(|typ| { - self.make_type_mismatch_err::(typ, args_expr[0].position()) - }) + .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos)) .and_then(|s| FnPtr::try_from(s)) .map(Into::::into) - .map_err(|err| err.fill_position(args_expr[0].position())); + .map_err(|err| err.fill_position(arg_pos)); } // Handle curry() - KEYWORD_FN_PTR_CURRY if args_expr.len() > 1 => { - let fn_ptr = - self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + KEYWORD_FN_PTR_CURRY if total_args > 1 => { + let (arg, arg_pos) = args_expr.get(0).map_or_else( + || Ok(constant_args[0].clone()), + |arg| { + self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) + .map(|v| (v, arg.position())) + }, + )?; - if !fn_ptr.is::() { + if !arg.is::() { return Err(self.make_type_mismatch_err::( - self.map_type_name(fn_ptr.type_name()), - args_expr[0].position(), + self.map_type_name(arg.type_name()), + arg_pos, )); } - let (name, mut fn_curry) = fn_ptr.cast::().take_data(); + let (name, mut fn_curry) = arg.cast::().take_data(); // Append the new curried arguments to the existing list. - - args_expr.iter().skip(1).try_for_each(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|value| fn_curry.push(value)) - })?; + if !args_expr.is_empty() { + args_expr.iter().skip(1).try_for_each(|expr| { + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(|value| fn_curry.push(value)) + })?; + fn_curry.extend(constant_args.iter().map(|(v, _)| v.clone())); + } else { + fn_curry.extend(constant_args.iter().skip(1).map(|(v, _)| v.clone())); + } return Ok(FnPtr::new_unchecked(name, fn_curry).into()); } // Handle is_shared() #[cfg(not(feature = "no_closure"))] - crate::engine::KEYWORD_IS_SHARED if args_expr.len() == 1 => { - let value = - self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; - return Ok(value.is_shared().into()); + crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { + let arg = args_expr.get(0).map_or_else( + || Ok(constant_args[0].0.clone()), + |arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level), + )?; + return Ok(arg.is_shared().into()); } // Handle is_def_fn() #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN if args_expr.len() == 2 => { - let fn_name = self - .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? + crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { + let (arg, arg_pos) = if !args_expr.is_empty() { + ( + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?, + args_expr[0].position(), + ) + } else { + constant_args[0].clone() + }; + + let fn_name = arg .take_immutable_string() - .map_err(|err| { - self.make_type_mismatch_err::(err, args_expr[0].position()) - })?; - let num_params = self - .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)? + .map_err(|err| self.make_type_mismatch_err::(err, arg_pos))?; + + let (arg, arg_pos) = if args_expr.len() > 1 { + ( + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?, + args_expr[1].position(), + ) + } else { + constant_args[if args_expr.is_empty() { 1 } else { 0 }].clone() + }; + + let num_params = arg .as_int() - .map_err(|err| { - self.make_type_mismatch_err::(err, args_expr[0].position()) - })?; + .map_err(|err| self.make_type_mismatch_err::(err, arg_pos))?; return Ok(if num_params < 0 { Dynamic::FALSE @@ -1174,29 +1214,34 @@ impl Engine { } // Handle is_def_var() - KEYWORD_IS_DEF_VAR if args_expr.len() == 1 => { - let var_name = self - .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? + KEYWORD_IS_DEF_VAR if total_args == 1 => { + let (arg, arg_pos) = args_expr.get(0).map_or_else( + || Ok(constant_args[0].clone()), + |arg| { + self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) + .map(|v| (v, arg.position())) + }, + )?; + let var_name = arg .take_immutable_string() - .map_err(|err| { - self.make_type_mismatch_err::(err, args_expr[0].position()) - })?; + .map_err(|err| self.make_type_mismatch_err::(err, arg_pos))?; return Ok(scope.contains(&var_name).into()); } // Handle eval() - KEYWORD_EVAL if args_expr.len() == 1 => { - let script_expr = &args_expr[0]; - let script_pos = script_expr.position(); - + KEYWORD_EVAL if total_args == 1 => { // eval - only in function call style let prev_len = scope.len(); - let script = self - .eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)? - .take_immutable_string() - .map_err(|typ| { - self.make_type_mismatch_err::(typ, script_pos) - })?; + let (script, script_pos) = args_expr.get(0).map_or_else( + || Ok(constant_args[0].clone()), + |script_expr| { + self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level) + .map(|v| (v, script_expr.position())) + }, + )?; + let script = script.take_immutable_string().map_err(|typ| { + self.make_type_mismatch_err::(typ, script_pos) + })?; let result = self.eval_script_expr_in_place( scope, mods, @@ -1240,14 +1285,17 @@ impl Engine { None }; - if args_expr.is_empty() && curry.is_empty() { + if args_expr.is_empty() && constant_args.is_empty() && curry.is_empty() { // No arguments args = Default::default(); } else { // If the first argument is a variable, and there is no curried arguments, // convert to method-call style in order to leverage potential &mut first argument and // avoid cloning the value - if curry.is_empty() && args_expr[0].get_variable_access(false).is_some() { + if curry.is_empty() + && !args_expr.is_empty() + && args_expr[0].get_variable_access(false).is_some() + { // func(x, ...) -> x.func(...) arg_values = args_expr .iter() @@ -1256,6 +1304,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(Dynamic::flatten) }) + .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; let (mut target, pos) = @@ -1285,6 +1334,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(Dynamic::flatten) }) + .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); @@ -1310,25 +1360,24 @@ impl Engine { namespace: Option<&NamespaceRef>, fn_name: &str, args_expr: &[Expr], + constant_args: &[(Dynamic, Position)], hash: u64, pos: Position, level: usize, ) -> RhaiResult { - let args_expr = args_expr.as_ref(); - let namespace = namespace.unwrap(); let mut arg_values: StaticVec<_>; let mut first_arg_value = None; let mut args: StaticVec<_>; - if args_expr.is_empty() { + if args_expr.is_empty() && constant_args.is_empty() { // No arguments args = Default::default(); } else { // See if the first argument is a variable (not namespace-qualified). // If so, convert to method-call style in order to leverage potential // &mut first argument and avoid cloning the value - if args_expr[0].get_variable_access(true).is_some() { + if !args_expr.is_empty() && args_expr[0].get_variable_access(true).is_some() { // func(x, ...) -> x.func(...) arg_values = args_expr .iter() @@ -1342,6 +1391,7 @@ impl Engine { .map(Dynamic::flatten) } }) + .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; // Get target reference to first argument @@ -1368,6 +1418,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(Dynamic::flatten) }) + .chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .collect::>()?; args = arg_values.iter_mut().collect(); diff --git a/src/optimize.rs b/src/optimize.rs index 383ca44f..61edaac9 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -740,11 +740,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { Expr::FnCall(x, pos) if x.namespace.is_none() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations - && x.args.len() == 2 // binary call + && x.num_args() == 2 // binary call && x.args.iter().all(Expr::is_constant) // all arguments are constants //&& !is_valid_identifier(x.name.chars()) // cannot be scripted => { - let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); + let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) + .chain(x.constant_args.iter().map(|(v, _)| v).cloned()) + .collect(); + let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); // Search for overloaded operators (can override built-in). @@ -764,6 +767,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { } x.args.iter_mut().for_each(|a| optimize_expr(a, state)); + + // Move constant arguments to the right + while x.args.last().map(Expr::is_constant).unwrap_or(false) { + let arg = x.args.pop().unwrap(); + let arg_pos = arg.position(); + x.constant_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); + } + + x.args.shrink_to_fit(); } // Eagerly call functions @@ -774,12 +786,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { => { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len()).is_some()); + let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.num_args()).is_some()); #[cfg(feature = "no_function")] let has_script_fn = false; if !has_script_fn { - let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); + let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) + .chain(x.constant_args.iter().map(|(v, _)| v).cloned()) + .collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure @@ -810,7 +824,18 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { } // id(args ..) -> optimize function call arguments - Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)), + Expr::FnCall(x, _) => { + x.args.iter_mut().for_each(|a| optimize_expr(a, state)); + + // Move constant arguments to the right + while x.args.last().map(Expr::is_constant).unwrap_or(false) { + let arg = x.args.pop().unwrap(); + let arg_pos = arg.position(); + x.constant_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos)); + } + + x.args.shrink_to_fit(); + } // constant-name Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => { diff --git a/src/parser.rs b/src/parser.rs index fa5aa424..b0f6f963 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1531,8 +1531,8 @@ fn make_dot_expr( Expr::FnCall(mut func, func_pos) => { // Recalculate hash func.hash = FnCallHash::from_script_and_native( - calc_fn_hash(empty(), &func.name, func.args.len()), - calc_fn_hash(empty(), &func.name, func.args.len() + 1), + calc_fn_hash(empty(), &func.name, func.num_args()), + calc_fn_hash(empty(), &func.name, func.num_args() + 1), ); let rhs = Expr::Dot( @@ -1563,7 +1563,7 @@ fn make_dot_expr( } // lhs.Fn() or lhs.eval() (_, Expr::FnCall(x, pos)) - if x.args.len() == 0 + if x.is_args_empty() && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL] .contains(&x.name.as_ref()) => { @@ -1587,8 +1587,8 @@ fn make_dot_expr( (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash func.hash = FnCallHash::from_script_and_native( - calc_fn_hash(empty(), &func.name, func.args.len()), - calc_fn_hash(empty(), &func.name, func.args.len() + 1), + calc_fn_hash(empty(), &func.name, func.num_args()), + calc_fn_hash(empty(), &func.name, func.num_args() + 1), ); let rhs = Expr::FnCall(func, func_pos); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) diff --git a/tests/operations.rs b/tests/operations.rs index bcc9019f..9ebf5fcf 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -72,7 +72,7 @@ fn test_max_operations_functions() -> Result<(), Box> { fn inc(x) { x + 1 } let x = 0; - while x < 28 { + while x < 31 { print(x); x = inc(x); } From 241f5abe10ee17ad4956e0f034a908a466c4b9c0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 28 Mar 2021 23:06:59 +0800 Subject: [PATCH 02/11] Change function call name into ImmutableString from Cow. --- src/ast.rs | 7 ++--- src/parser.rs | 72 ++++++++++++++++++++++++++++----------------------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 735d3f8c..07c0870f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -4,7 +4,6 @@ use crate::dynamic::{AccessMode, Union}; use crate::fn_native::shared_make_mut; use crate::module::NamespaceRef; use crate::stdlib::{ - borrow::Cow, boxed::Box, collections::{BTreeMap, BTreeSet}, fmt, @@ -1398,16 +1397,14 @@ pub struct FnCallExpr { pub hash: FnCallHash, /// Does this function call capture the parent scope? pub capture: bool, - /// List of function call arguments. + /// List of function call argument expressions. pub args: StaticVec, /// List of function call arguments that are constants. pub constant_args: StaticVec<(Dynamic, Position)>, /// Namespace of the function, if any. Boxed because it occurs rarely. pub namespace: Option, /// Function name. - /// Use [`Cow<'static, str>`][Cow] 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`]. - pub name: Cow<'static, str>, + pub name: ImmutableString, } impl FnCallExpr { diff --git a/src/parser.rs b/src/parser.rs index b0f6f963..a662aaee 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -346,16 +346,18 @@ fn parse_fn_call( calc_fn_hash(empty(), &id, 0) }; + let hash = if is_valid_identifier(id.chars()) { + FnCallHash::from_script(hash) + } else { + FnCallHash::from_native(hash) + }; + return Ok(Expr::FnCall( Box::new(FnCallExpr { - name: id.to_string().into(), + name: state.get_interned_string(id), capture, namespace, - hash: if is_valid_identifier(id.chars()) { - FnCallHash::from_script(hash) - } else { - FnCallHash::from_native(hash) - }, + hash, args, ..Default::default() }), @@ -389,16 +391,18 @@ fn parse_fn_call( calc_fn_hash(empty(), &id, args.len()) }; + let hash = if is_valid_identifier(id.chars()) { + FnCallHash::from_script(hash) + } else { + FnCallHash::from_native(hash) + }; + return Ok(Expr::FnCall( Box::new(FnCallExpr { - name: id.to_string().into(), + name: state.get_interned_string(id), capture, namespace, - hash: if is_valid_identifier(id.chars()) { - FnCallHash::from_script(hash) - } else { - FnCallHash::from_native(hash) - }, + hash, args, ..Default::default() }), @@ -1276,14 +1280,13 @@ fn parse_unary( // Call negative function expr => { - let op = "-"; let mut args = StaticVec::new(); args.push(expr); Ok(Expr::FnCall( Box::new(FnCallExpr { - name: op.into(), - hash: FnCallHash::from_native(calc_fn_hash(empty(), op, 1)), + name: state.get_interned_string("-"), + hash: FnCallHash::from_native(calc_fn_hash(empty(), "-", 1)), args, ..Default::default() }), @@ -1303,14 +1306,13 @@ fn parse_unary( // Call plus function expr => { - let op = "+"; let mut args = StaticVec::new(); args.push(expr); Ok(Expr::FnCall( Box::new(FnCallExpr { - name: op.into(), - hash: FnCallHash::from_native(calc_fn_hash(empty(), op, 1)), + name: state.get_interned_string("+"), + hash: FnCallHash::from_native(calc_fn_hash(empty(), "+", 1)), args, ..Default::default() }), @@ -1326,12 +1328,10 @@ fn parse_unary( let expr = parse_unary(input, state, lib, settings.level_up())?; args.push(expr); - let op = "!"; - Ok(Expr::FnCall( Box::new(FnCallExpr { - name: op.into(), - hash: FnCallHash::from_native(calc_fn_hash(empty(), op, 1)), + name: state.get_interned_string("!"), + hash: FnCallHash::from_native(calc_fn_hash(empty(), "!", 1)), args, ..Default::default() }), @@ -1673,7 +1673,7 @@ fn parse_binary_op( let hash = calc_fn_hash(empty(), &op, 2); let op_base = FnCallExpr { - name: op, + name: state.get_interned_string(op.as_ref()), hash: FnCallHash::from_native(hash), capture: false, ..Default::default() @@ -1741,7 +1741,7 @@ fn parse_binary_op( Box::new(FnCallExpr { hash: FnCallHash::from_script(hash), args, - name: OP_CONTAINS.into(), + name: state.get_interned_string(OP_CONTAINS), ..op_base }), pos, @@ -2739,7 +2739,7 @@ fn parse_fn( .collect(); Ok(ScriptFnDef { - name: name.into(), + name: state.get_interned_string(&name), access, params, #[cfg(not(feature = "no_closure"))] @@ -2755,7 +2755,12 @@ fn parse_fn( /// Creates a curried expression from a list of external variables #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_closure"))] -fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Position) -> Expr { +fn make_curry_from_externals( + state: &mut ParseState, + fn_expr: Expr, + externals: StaticVec, + pos: Position, +) -> Expr { // If there are no captured variables, no need to curry if externals.is_empty() { return fn_expr; @@ -2770,12 +2775,14 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po args.push(Expr::Variable(Box::new((None, None, x.clone())))); }); - let curry_func = crate::engine::KEYWORD_FN_PTR_CURRY; - let expr = Expr::FnCall( Box::new(FnCallExpr { - name: curry_func.into(), - hash: FnCallHash::from_native(calc_fn_hash(empty(), curry_func, num_externals + 1)), + name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), + hash: FnCallHash::from_native(calc_fn_hash( + empty(), + crate::engine::KEYWORD_FN_PTR_CURRY, + num_externals + 1, + )), args, ..Default::default() }), @@ -2880,7 +2887,8 @@ fn parse_anon_fn( body.hash(hasher); let hash = hasher.finish(); - let fn_name: ImmutableString = format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash).into(); + let fn_name = + state.get_interned_string(&(format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash))); // Define the function let script = ScriptFnDef { @@ -2899,7 +2907,7 @@ fn parse_anon_fn( let expr = Expr::FnPointer(fn_name, settings.pos); #[cfg(not(feature = "no_closure"))] - let expr = make_curry_from_externals(expr, externals, settings.pos); + let expr = make_curry_from_externals(state, expr, externals, settings.pos); Ok((expr, script)) } From e5249cc1ae8dbadc48ca510d79e8412ab189792c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 11:36:02 +0800 Subject: [PATCH 03/11] Extract Identifier type. --- CHANGELOG.md | 1 + Cargo.toml | 1 + README.md | 2 +- src/ast.rs | 39 ++++++++++++------------ src/dynamic.rs | 12 ++++++++ src/engine.rs | 40 ++++++++++++------------ src/engine_api.rs | 22 +++++++------- src/fn_native.rs | 8 ++--- src/lib.rs | 2 ++ src/module/mod.rs | 32 +++++++++---------- src/optimize.rs | 12 ++++---- src/packages/fn_basic.rs | 25 ++++++++------- src/parser.rs | 66 ++++++++++++++++++++++------------------ src/scope.rs | 8 ++--- src/syntax.rs | 7 +++-- src/utils.rs | 26 +++++++++++----- 16 files changed, 170 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83391e6..e5df697b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Breaking changes * The _reflections_ API such as `Engine::gen_fn_signatures`, `Module::update_fn_metadata` etc. are put under the `metadata` feature gate. * The shebang `#!` is now a reserved symbol. * Shebangs at the very beginning of script files are skipped when loading them. +* [`smartstring`](https://crates.io/crates/smartstring) is used for identifiers. Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index b16375d0..f2f17533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.6", default-features = false, features = ["union"] } +smartstring = { version = "0.2.6" } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default_features = false } rhai_codegen = { version = "0.3.4", path = "codegen", features = ["metadata"] } diff --git a/README.md b/README.md index 57cc560e..8042448b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Standard features * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) and [object maps](https://rhai.rs/book/language/object-maps.html). * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). -* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits) and [`ahash`](https://crates.io/crates/ahash)). +* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash)) and [`smartstring`](https://crates.io/crates/smartstring)). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). diff --git a/src/ast.rs b/src/ast.rs index 07c0870f..02f30e1f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -16,7 +16,8 @@ use crate::stdlib::{ }; use crate::token::Token; use crate::{ - Dynamic, FnNamespace, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT, + Dynamic, FnNamespace, FnPtr, Identifier, ImmutableString, Module, Position, Shared, StaticVec, + INT, }; #[cfg(not(feature = "no_float"))] @@ -50,14 +51,14 @@ pub struct ScriptFnDef { #[cfg(not(feature = "no_module"))] pub mods: crate::engine::Imports, /// Function name. - pub name: ImmutableString, + pub name: Identifier, /// Function access mode. pub access: FnAccess, /// Names of function parameters. - pub params: StaticVec, + pub params: StaticVec, /// Access to external variables. #[cfg(not(feature = "no_closure"))] - pub externals: BTreeSet, + pub externals: BTreeSet, /// Function doc-comments (if any). pub comments: StaticVec, } @@ -144,7 +145,7 @@ impl<'a> Into> for &'a ScriptFnDef { #[derive(Debug, Clone)] pub struct AST { /// Source of the [`AST`]. - source: Option, + source: Option, /// Global statements. body: StmtBlock, /// Script-defined functions. @@ -190,7 +191,7 @@ impl AST { pub fn new_with_source( statements: impl IntoIterator, functions: impl Into>, - source: impl Into, + source: impl Into, ) -> Self { Self { source: Some(source.into()), @@ -210,12 +211,12 @@ impl AST { } /// Clone the source, if any. #[inline(always)] - pub(crate) fn clone_source(&self) -> Option { + pub(crate) fn clone_source(&self) -> Option { self.source.clone() } /// Set the source. #[inline(always)] - pub fn set_source(&mut self, source: impl Into) -> &mut Self { + pub fn set_source(&mut self, source: impl Into) -> &mut Self { self.source = Some(source.into()); if let Some(module) = Shared::get_mut(&mut self.functions) { @@ -756,7 +757,7 @@ impl AsRef for AST { } } -/// _(INTERNALS)_ An identifier containing an [immutable string][ImmutableString] name and a [position][Position]. +/// _(INTERNALS)_ An identifier containing a name and a [position][Position]. /// Exported under the `internals` feature only. /// /// # Volatile Data Structure @@ -765,7 +766,7 @@ impl AsRef for AST { #[derive(Clone, Eq, PartialEq, Hash)] pub struct Ident { /// Identifier name. - pub name: ImmutableString, + pub name: Identifier, /// Declaration position. pub pos: Position, } @@ -869,11 +870,11 @@ pub enum Stmt { /// `do` `{` stmt `}` `while`|`until` expr Do(Box, Expr, bool, Position), /// `for` id `in` expr `{` stmt `}` - For(Expr, Box<(String, StmtBlock)>, Position), + For(Expr, Box<(Identifier, StmtBlock)>, Position), /// \[`export`\] `let` id `=` expr - Let(Expr, Ident, bool, Position), + Let(Expr, Box, bool, Position), /// \[`export`\] `const` id `=` expr - Const(Expr, Ident, bool, Position), + Const(Expr, Box, bool, Position), /// expr op`=` expr Assignment(Box<(Expr, Expr, Option)>, Position), /// `{` stmt`;` ... `}` @@ -894,7 +895,7 @@ pub enum Stmt { Return(ReturnType, 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<(Ident, Option)>, Position), @@ -1256,7 +1257,7 @@ pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, /// List of tokens actually parsed. - pub tokens: Vec, + pub tokens: Vec, /// Delta number of variables in the scope. pub scope_delta: isize, } @@ -1400,11 +1401,11 @@ pub struct FnCallExpr { /// List of function call argument expressions. pub args: StaticVec, /// List of function call arguments that are constants. - pub constant_args: StaticVec<(Dynamic, Position)>, + pub constant_args: smallvec::SmallVec<[(Dynamic, Position); 2]>, /// Namespace of the function, if any. Boxed because it occurs rarely. pub namespace: Option, /// Function name. - pub name: ImmutableString, + pub name: Identifier, } impl FnCallExpr { @@ -1554,7 +1555,7 @@ pub enum Expr { /// Variable access - (optional index, optional (hash, modules), variable name) Variable(Box<(Option, Option<(u64, NamespaceRef)>, Ident)>), /// Property access - ((getter, hash), (setter, hash), prop) - Property(Box<((ImmutableString, u64), (ImmutableString, u64), Ident)>), + Property(Box<((Identifier, u64), (Identifier, u64), Ident)>), /// { [statement][Stmt] ... } Stmt(Box), /// func `(` expr `,` ... `)` @@ -1609,7 +1610,7 @@ impl Expr { Self::Map(x, _) if self.is_constant() => { let mut map = x.1.clone(); x.0.iter().for_each(|(k, v)| { - *map.get_mut(&k.name).unwrap() = v.get_constant_value().unwrap() + *map.get_mut(k.name.as_str()).unwrap() = v.get_constant_value().unwrap() }); Dynamic(Union::Map(Box::new(map), AccessMode::ReadOnly)) } diff --git a/src/dynamic.rs b/src/dynamic.rs index 7bde7ba2..f32b68b7 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1667,6 +1667,18 @@ impl> From for Dynamic { Self(Union::Str(value.into(), AccessMode::ReadWrite)) } } +impl From<&ImmutableString> for Dynamic { + #[inline(always)] + fn from(value: &ImmutableString) -> Self { + value.clone().into() + } +} +impl From<&smartstring::SmartString> for Dynamic { + #[inline(always)] + fn from(value: &smartstring::SmartString) -> Self { + value.to_string().into() + } +} #[cfg(not(feature = "no_index"))] impl From> for Dynamic { #[inline(always)] diff --git a/src/engine.rs b/src/engine.rs index 442e806f..e8255c63 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -25,8 +25,8 @@ use crate::stdlib::{ use crate::syntax::CustomSyntax; use crate::utils::get_hasher; use crate::{ - Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared, - StaticVec, + Dynamic, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, Position, RhaiResult, + Scope, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -50,7 +50,7 @@ pub type Precedence = NonZeroU8; // the module name will live beyond the AST of the eval script text. // The best we can do is a shared reference. #[derive(Debug, Clone, Default)] -pub struct Imports(StaticVec, StaticVec>); +pub struct Imports(StaticVec, StaticVec>); impl Imports { /// Get the length of this stack of imported [modules][Module]. @@ -79,7 +79,7 @@ impl Imports { } /// Push an imported [modules][Module] onto the stack. #[inline(always)] - pub fn push(&mut self, name: impl Into, module: impl Into>) { + pub fn push(&mut self, name: impl Into, module: impl Into>) { self.0.push(name.into()); self.1.push(module.into()); } @@ -102,18 +102,18 @@ impl Imports { /// Get an iterator to this stack of imported [modules][Module] in reverse order. #[allow(dead_code)] #[inline(always)] - pub(crate) fn iter_raw(&self) -> impl Iterator)> { + pub(crate) fn iter_raw(&self) -> impl Iterator)> { self.0.iter().rev().zip(self.1.iter().rev()) } /// Get an iterator to this stack of imported [modules][Module] in forward order. #[allow(dead_code)] #[inline(always)] - pub(crate) fn scan_raw(&self) -> impl Iterator)> { + pub(crate) fn scan_raw(&self) -> impl Iterator)> { self.0.iter().zip(self.1.iter()) } /// Get a consuming iterator to this stack of imported [modules][Module] in reverse order. #[inline(always)] - pub fn into_iter(self) -> impl Iterator)> { + pub fn into_iter(self) -> impl Iterator)> { self.0.into_iter().rev().zip(self.1.into_iter().rev()) } /// Does the specified function hash key exist in this stack of imported [modules][Module]? @@ -124,7 +124,7 @@ impl Imports { } /// Get specified function via its hash key. #[inline(always)] - pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&ImmutableString>)> { + pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> { self.1 .iter() .rev() @@ -491,7 +491,7 @@ pub struct FnResolutionCacheEntry { /// Function. pub func: CallableFunction, /// Optional source. - pub source: Option, + pub source: Option, } /// A function resolution cache. @@ -506,7 +506,7 @@ pub type FnResolutionCache = BTreeMap>; #[derive(Debug, Clone, Default)] pub struct State { /// Source of the current context. - pub source: Option, + pub source: Option, /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned. /// When that happens, this flag is turned on to force a scope lookup by name. @@ -703,7 +703,7 @@ pub struct Engine { /// A collection of all modules loaded into the global namespace of the Engine. pub(crate) global_modules: StaticVec>, /// A collection of all sub-modules directly loaded into the Engine. - pub(crate) global_sub_modules: BTreeMap>, + pub(crate) global_sub_modules: BTreeMap>, /// A module resolution service. #[cfg(not(feature = "no_module"))] @@ -717,7 +717,7 @@ pub struct Engine { /// A map containing custom keywords and precedence to recognize. pub(crate) custom_keywords: BTreeMap>, /// Custom syntax. - pub(crate) custom_syntax: BTreeMap, + pub(crate) custom_syntax: BTreeMap, /// Callback closure for resolving variable access. pub(crate) resolve_var: Option, @@ -1183,7 +1183,7 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x) if target_val.is::() && new_val.is_some() => { let Ident { name, pos, .. } = &x.2; - let index = name.clone().into(); + let index = name.into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, true, is_ref, false, level, )?; @@ -1196,7 +1196,7 @@ impl Engine { // {xxx:map}.id Expr::Property(x) if target_val.is::() => { let Ident { name, pos, .. } = &x.2; - let index = name.clone().into(); + let index = name.into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, false, level, )?; @@ -1231,7 +1231,7 @@ impl Engine { let mut val = match &x.lhs { Expr::Property(p) => { let Ident { name, pos, .. } = &p.2; - let index = name.clone().into(); + let index = name.into(); self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, true, level, @@ -1696,7 +1696,7 @@ impl Engine { Expr::Map(x, _) => { let mut map = x.1.clone(); for (Ident { name: key, .. }, expr) in &x.0 { - *map.get_mut(key).unwrap() = self + *map.get_mut(key.as_str()).unwrap() = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(); } @@ -2209,7 +2209,7 @@ impl Engine { if let Some(func) = func { // Add the loop variable let var_name: Cow<'_, str> = if state.is_global() { - name.clone().into() + name.to_string().into() } else { unsafe_cast_var_name_to_lifetime(name).into() }; @@ -2300,7 +2300,7 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); if let Some(ref source) = state.source { - err_map.insert("source".into(), source.clone().into()); + err_map.insert("source".into(), source.into()); } if err_pos.is_none() { @@ -2382,8 +2382,8 @@ impl Engine { } // Let/const statement - Stmt::Let(expr, Ident { name, .. }, export, _) - | Stmt::Const(expr, Ident { name, .. }, export, _) => { + Stmt::Let(expr, x, export, _) | Stmt::Const(expr, x, export, _) => { + let name = &x.name; let entry_type = match stmt { Stmt::Let(_, _, _, _) => AccessMode::ReadWrite, Stmt::Const(_, _, _, _) => AccessMode::ReadOnly, diff --git a/src/engine_api.rs b/src/engine_api.rs index 8703d3d0..cd37ac6c 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -12,7 +12,7 @@ use crate::stdlib::{ string::String, }; use crate::{ - scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, + scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, ParseError, Position, RhaiResult, Shared, AST, }; @@ -53,7 +53,7 @@ impl Engine { #[inline] pub fn register_fn(&mut self, name: N, func: F) -> &mut Self where - N: AsRef + Into, + N: AsRef + Into, F: RegisterNativeFunction, { let param_types = F::param_types(); @@ -113,7 +113,7 @@ impl Engine { #[inline] pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self where - N: AsRef + Into, + N: AsRef + Into, F: RegisterNativeFunction>>, { let param_types = F::param_types(); @@ -170,7 +170,7 @@ impl Engine { + 'static, ) -> &mut Self where - N: AsRef + Into, + N: AsRef + Into, T: Variant + Clone, { self.global_namespace.set_raw_fn( @@ -901,12 +901,12 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub fn register_static_module( &mut self, - name: impl AsRef + Into, + name: impl AsRef + Into, module: Shared, ) -> &mut Self { fn register_static_module_raw( - root: &mut crate::stdlib::collections::BTreeMap>, - name: impl AsRef + Into, + root: &mut crate::stdlib::collections::BTreeMap>, + name: impl AsRef + Into, module: Shared, ) { let separator = crate::token::Token::DoubleColon.syntax(); @@ -952,7 +952,7 @@ impl Engine { #[deprecated(since = "0.19.9", note = "use `register_static_module` instead")] pub fn register_module( &mut self, - name: impl AsRef + Into, + name: impl AsRef + Into, module: impl Into>, ) -> &mut Self { self.register_static_module(name, module.into()) @@ -1045,14 +1045,14 @@ impl Engine { fn collect_imports( ast: &AST, resolver: &StaticModuleResolver, - imports: &mut BTreeSet, + imports: &mut BTreeSet, ) { ast.walk(&mut |path| match path.last().unwrap() { // Collect all `import` statements with a string constant path ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) - if !resolver.contains_path(s) && !imports.contains(s) => + if !resolver.contains_path(s) && !imports.contains(s.as_str()) => { - imports.insert(s.clone()); + imports.insert(s.clone().into()); true } _ => true, diff --git a/src/fn_native.rs b/src/fn_native.rs index e893550b..4535cf8f 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -13,8 +13,8 @@ use crate::stdlib::{ }; use crate::token::is_valid_identifier; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, Position, - RhaiResult, StaticVec, + calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module, + Position, RhaiResult, StaticVec, }; /// Trait that maps to `Send + Sync` only under the `sync` feature. @@ -144,9 +144,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[allow(dead_code)] #[inline(always)] - pub(crate) fn iter_imports_raw( - &self, - ) -> impl Iterator)> { + pub(crate) fn iter_imports_raw(&self) -> impl Iterator)> { self.mods.iter().flat_map(|&m| m.iter_raw()) } /// _(INTERNALS)_ The current set of modules imported via `import` statements. diff --git a/src/lib.rs b/src/lib.rs index 87996279..04173c37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -292,6 +292,8 @@ type StaticVec = smallvec::SmallVec<[T; 4]>; #[cfg(feature = "internals")] pub type StaticVec = smallvec::SmallVec<[T; 4]>; +pub type Identifier = smartstring::SmartString; + // Compiler guards against mutually-exclusive feature flags #[cfg(feature = "no_float")] diff --git a/src/module/mod.rs b/src/module/mod.rs index 472e4319..2e7924be 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -18,8 +18,8 @@ use crate::stdlib::{ use crate::token::Token; use crate::utils::StringInterner; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ImmutableString, - NativeCallContext, Position, Shared, StaticVec, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, + ImmutableString, NativeCallContext, Position, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -55,14 +55,14 @@ pub struct FuncInfo { /// Function access mode. pub access: FnAccess, /// Function name. - pub name: ImmutableString, + pub name: Identifier, /// Number of parameters. pub params: usize, /// Parameter types (if applicable). pub param_types: StaticVec, /// Parameter names (if available). #[cfg(feature = "metadata")] - pub param_names: StaticVec, + pub param_names: StaticVec, } impl FuncInfo { @@ -128,11 +128,11 @@ fn calc_native_fn_hash<'a>( #[derive(Clone)] pub struct Module { /// ID identifying the module. - id: Option, + id: Option, /// Sub-modules. - modules: BTreeMap>, + modules: BTreeMap>, /// [`Module`] variables. - variables: BTreeMap, + variables: BTreeMap, /// Flattened collection of all [`Module`] variables, including those in sub-modules. all_variables: BTreeMap, /// External Rust functions. @@ -288,7 +288,7 @@ impl Module { self.id_raw().map(|s| s.as_str()) } - /// Get the ID of the [`Module`] as an [`ImmutableString`], if any. + /// Get the ID of the [`Module`] as an [`Identifier`], if any. /// /// # Example /// @@ -300,7 +300,7 @@ impl Module { /// assert_eq!(module.id_raw().map(|s| s.as_str()), Some("hello")); /// ``` #[inline(always)] - pub fn id_raw(&self) -> Option<&ImmutableString> { + pub fn id_raw(&self) -> Option<&Identifier> { self.id.as_ref() } @@ -316,7 +316,7 @@ impl Module { /// assert_eq!(module.id(), Some("hello")); /// ``` #[inline(always)] - pub fn set_id>(&mut self, id: Option) { + pub fn set_id>(&mut self, id: Option) { self.id = id.map(|s| s.into()); } @@ -440,7 +440,7 @@ impl Module { #[inline(always)] pub fn set_var( &mut self, - name: impl Into, + name: impl Into, value: impl Variant + Clone, ) -> &mut Self { self.variables.insert(name.into(), Dynamic::from(value)); @@ -514,7 +514,7 @@ impl Module { /// Thus the [`Module`] is automatically set to be non-indexed. #[cfg(not(feature = "no_module"))] #[inline(always)] - pub(crate) fn sub_modules_mut(&mut self) -> &mut BTreeMap> { + pub(crate) fn sub_modules_mut(&mut self) -> &mut BTreeMap> { // We must assume that the user has changed the sub-modules // (otherwise why take a mutable reference?) self.all_functions.clear(); @@ -577,7 +577,7 @@ impl Module { #[inline(always)] pub fn set_sub_module( &mut self, - name: impl Into, + name: impl Into, sub_module: impl Into>, ) -> &mut Self { self.modules.insert(name.into(), sub_module.into()); @@ -672,7 +672,7 @@ impl Module { #[inline] pub fn set_fn( &mut self, - name: impl AsRef + Into, + name: impl AsRef + Into, namespace: FnNamespace, access: FnAccess, _arg_names: Option<&[&str]>, @@ -793,7 +793,7 @@ impl Module { func: F, ) -> u64 where - N: AsRef + Into, + N: AsRef + Into, T: Variant + Clone, F: Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync @@ -838,7 +838,7 @@ impl Module { #[inline(always)] pub fn set_native_fn(&mut self, name: N, func: F) -> u64 where - N: AsRef + Into, + N: AsRef + Into, T: Variant + Clone, F: RegisterNativeFunction>>, { diff --git a/src/optimize.rs b/src/optimize.rs index 61edaac9..e4b62c26 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the [`AST`] optimizer. -use crate::ast::{Expr, Ident, Stmt, StmtBlock}; +use crate::ast::{Expr, Stmt, StmtBlock}; use crate::dynamic::AccessMode; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_builtin::get_builtin_binary_op_fn; @@ -214,17 +214,17 @@ fn optimize_stmt_block( statements.iter_mut().for_each(|stmt| { match stmt { // Add constant literals into the state - Stmt::Const(value_expr, Ident { name, .. }, _, _) => { + Stmt::Const(value_expr, x, _, _) => { optimize_expr(value_expr, state); if value_expr.is_constant() { - state.push_var(name, AccessMode::ReadOnly, value_expr.clone()); + state.push_var(&x.name, AccessMode::ReadOnly, value_expr.clone()); } } // Add variables into the state - Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => { + Stmt::Let(value_expr, x, _, _) => { optimize_expr(value_expr, state); - state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos)); + state.push_var(&x.name, AccessMode::ReadWrite, Expr::Unit(x.pos)); } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), @@ -649,7 +649,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - *expr = mem::take(&mut m.0).into_iter().find(|(x, _)| x.name == *s) + *expr = mem::take(&mut m.0).into_iter().find(|(x, _)| x.name.as_str() == s.as_str()) .map(|(_, mut expr)| { expr.set_position(*pos); expr }) .unwrap_or_else(|| Expr::Unit(*pos)); } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 65ae4469..634f5253 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,5 +1,5 @@ use crate::plugin::*; -use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; +use crate::{def_package, FnPtr, Identifier, ImmutableString, NativeCallContext}; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); @@ -38,18 +38,21 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { // Create a metadata record for a function. fn make_metadata( - dict: &BTreeSet, - namespace: Option, + dict: &BTreeSet, + namespace: Option, f: &ScriptFnDef, ) -> Map { let mut map = Map::new(); if let Some(ns) = namespace { - map.insert(dict.get("namespace").unwrap().clone(), ns.into()); + map.insert(dict.get("namespace").unwrap().clone().into(), ns.into()); } - map.insert(dict.get("name").unwrap().clone(), f.name.clone().into()); map.insert( - dict.get("access").unwrap().clone(), + dict.get("name").unwrap().clone().into(), + f.name.clone().into(), + ); + map.insert( + dict.get("access").unwrap().clone().into(), match f.access { FnAccess::Public => dict.get("public").unwrap().clone(), FnAccess::Private => dict.get("private").unwrap().clone(), @@ -57,11 +60,11 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { .into(), ); map.insert( - dict.get("is_anonymous").unwrap().clone(), + dict.get("is_anonymous").unwrap().clone().into(), f.name.starts_with(crate::engine::FN_ANONYMOUS).into(), ); map.insert( - dict.get("params").unwrap().clone(), + dict.get("params").unwrap().clone().into(), f.params .iter() .cloned() @@ -74,7 +77,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { } // Intern strings - let dict: BTreeSet = [ + let dict: BTreeSet = [ "namespace", "name", "access", @@ -98,8 +101,8 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { // Recursively scan modules for script-defined functions. fn scan_module( list: &mut Array, - dict: &BTreeSet, - namespace: ImmutableString, + dict: &BTreeSet, + namespace: Identifier, module: &Module, ) { module.iter_script_fn().for_each(|(_, _, _, _, f)| { diff --git a/src/parser.rs b/src/parser.rs index a662aaee..daa02cfb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -24,8 +24,8 @@ use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::utils::{get_hasher, StringInterner}; use crate::{ - calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position, - Scope, Shared, StaticVec, AST, + calc_fn_hash, Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, + ParseErrorType, Position, Scope, Shared, StaticVec, AST, }; #[cfg(not(feature = "no_float"))] @@ -46,12 +46,12 @@ struct ParseState<'e> { /// Interned strings. interned_strings: StringInterner, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. - stack: Vec<(ImmutableString, AccessMode)>, + stack: Vec<(Identifier, AccessMode)>, /// Size of the local variables stack upon entry of the current block scope. entry_stack_len: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] - external_vars: BTreeMap, + external_vars: BTreeMap, /// An indicator that disables variable capturing into externals one single time /// up until the nearest consumed Identifier token. /// If set to false the next call to `access_var` will not capture the variable. @@ -60,7 +60,7 @@ struct ParseState<'e> { allow_capture: bool, /// Encapsulates a local stack with imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] - modules: StaticVec, + modules: StaticVec, /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: Option, @@ -166,10 +166,7 @@ impl<'e> ParseState<'e> { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get_interned_string( - &mut self, - text: impl AsRef + Into, - ) -> ImmutableString { + pub fn get_interned_string(&mut self, text: impl AsRef) -> Identifier { self.interned_strings.get(text) } } @@ -310,7 +307,7 @@ fn parse_fn_call( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - id: ImmutableString, + id: Identifier, capture: bool, mut namespace: Option, settings: ParseSettings, @@ -668,6 +665,8 @@ fn parse_array_literal( }; } + arr.shrink_to_fit(); + Ok(Expr::Array(Box::new(arr), settings.pos)) } @@ -707,7 +706,7 @@ fn parse_map_literal( let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { - if map.iter().any(|(p, _)| p.name == &s) { + if map.iter().any(|(p, _)| p.name == s) { return Err(PERR::DuplicatedProperty(s).into_err(pos)); } (s, pos) @@ -757,7 +756,7 @@ fn parse_map_literal( let expr = parse_expr(input, state, lib, settings.level_up())?; let name = state.get_interned_string(name); - template.insert(name.clone(), Default::default()); + template.insert(name.clone().into(), Default::default()); map.push((Ident { name, pos }, expr)); match input.peek().unwrap() { @@ -782,6 +781,8 @@ fn parse_map_literal( } } + map.shrink_to_fit(); + Ok(Expr::Map(Box::new((map, template)), settings.pos)) } @@ -935,7 +936,7 @@ fn parse_primary( Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { - Expr::StringConstant(state.get_interned_string(s), settings.pos) + Expr::StringConstant(state.get_interned_string(s).into(), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1830,7 +1831,7 @@ fn parse_custom_syntax( MARKER_IDENT => match input.next().unwrap() { (Token::Identifier(s), pos) => { let name = state.get_interned_string(s); - segments.push(name.clone()); + segments.push(name.clone().into()); tokens.push(state.get_interned_string(MARKER_IDENT)); let var_name_def = Ident { name, pos }; keywords.push(Expr::Variable(Box::new((None, None, var_name_def)))); @@ -1843,14 +1844,14 @@ fn parse_custom_syntax( MARKER_EXPR => { keywords.push(parse_expr(input, state, lib, settings)?); let keyword = state.get_interned_string(MARKER_EXPR); - segments.push(keyword.clone()); + segments.push(keyword.clone().into()); tokens.push(keyword); } MARKER_BLOCK => match parse_block(input, state, lib, settings)? { block @ Stmt::Block(_, _) => { keywords.push(Expr::Stmt(Box::new(block.into()))); let keyword = state.get_interned_string(MARKER_BLOCK); - segments.push(keyword.clone()); + segments.push(keyword.clone().into()); tokens.push(keyword); } stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), @@ -1859,7 +1860,7 @@ fn parse_custom_syntax( (Token::LexError(err), pos) => return Err(err.into_err(pos)), (t, _) if t.syntax().as_ref() == s => { segments.push(required_token.clone()); - tokens.push(required_token.clone()); + tokens.push(required_token.clone().into()); } (_, pos) => { return Err(PERR::MissingToken( @@ -1901,7 +1902,7 @@ fn parse_expr( match token { Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { - match state.engine.custom_syntax.get_key_value(key) { + match state.engine.custom_syntax.get_key_value(key.as_str()) { Some((key, syntax)) => { input.next().unwrap(); return parse_custom_syntax( @@ -2119,16 +2120,20 @@ fn parse_for( ensure_not_statement_expr(input, "a boolean")?; let expr = parse_expr(input, state, lib, settings.level_up())?; - let loop_var = state.get_interned_string(name.clone()); + let loop_var = state.get_interned_string(name); let prev_stack_len = state.stack.len(); - state.stack.push((loop_var, AccessMode::ReadWrite)); + state.stack.push((loop_var.clone(), AccessMode::ReadWrite)); settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; state.stack.truncate(prev_stack_len); - Ok(Stmt::For(expr, Box::new((name, body.into())), settings.pos)) + Ok(Stmt::For( + expr, + Box::new((loop_var, body.into())), + settings.pos, + )) } /// Parse a variable definition statement. @@ -2174,9 +2179,9 @@ fn parse_let( match var_type { // let name = expr - AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def, export, settings.pos)), + AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def.into(), export, settings.pos)), // const name = { expr:constant } - AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def, export, settings.pos)), + AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def.into(), export, settings.pos)), } } @@ -2217,10 +2222,13 @@ fn parse_import( Ok(Stmt::Import( expr, - Some(Ident { - name, - pos: name_pos, - }), + Some( + Ident { + name, + pos: name_pos, + } + .into(), + ), settings.pos, )) } @@ -2511,7 +2519,7 @@ fn parse_stmt( if lib.contains_key(&hash) { return Err(PERR::FnDuplicatedDefinition( - func.name.into_owned(), + func.name.to_string(), func.params.len(), ) .into_err(pos)); @@ -2904,7 +2912,7 @@ fn parse_anon_fn( comments: Default::default(), }; - let expr = Expr::FnPointer(fn_name, settings.pos); + let expr = Expr::FnPointer(fn_name.into(), settings.pos); #[cfg(not(feature = "no_closure"))] let expr = make_curry_from_externals(state, expr, externals, settings.pos); diff --git a/src/scope.rs b/src/scope.rs index ac19bf2c..7d4b114b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,7 @@ use crate::dynamic::{AccessMode, Variant}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; -use crate::{Dynamic, ImmutableString, StaticVec}; +use crate::{Dynamic, Identifier, StaticVec}; /// Keep a number of entries inline (since [`Dynamic`] is usually small enough). const SCOPE_SIZE: usize = 16; @@ -54,7 +54,7 @@ pub struct Scope<'a> { /// Current value of the entry. values: smallvec::SmallVec<[Dynamic; SCOPE_SIZE]>, /// (Name, aliases) of the entry. - names: Vec<(Cow<'a, str>, Option>>)>, + names: Vec<(Cow<'a, str>, Option>>)>, } impl Default for Scope<'_> { @@ -414,7 +414,7 @@ impl<'a> Scope<'a> { pub(crate) fn add_entry_alias( &mut self, index: usize, - alias: impl Into + PartialEq, + alias: impl Into + PartialEq, ) -> &mut Self { let entry = self.names.get_mut(index).expect("invalid index in Scope"); if entry.1.is_none() { @@ -449,7 +449,7 @@ impl<'a> Scope<'a> { #[allow(dead_code)] pub(crate) fn into_iter( self, - ) -> impl Iterator, Dynamic, Vec)> { + ) -> impl Iterator, Dynamic, Vec)> { self.names .into_iter() .zip(self.values.into_iter()) diff --git a/src/syntax.rs b/src/syntax.rs index 2e39554b..3eea6244 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -6,7 +6,8 @@ use crate::fn_native::SendSync; use crate::stdlib::{boxed::Box, format, string::ToString}; use crate::token::{is_valid_identifier, Token}; use crate::{ - Engine, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, StaticVec, + Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, + StaticVec, }; pub const MARKER_EXPR: &str = "$expr$"; @@ -103,7 +104,7 @@ impl Engine { /// /// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the /// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. - pub fn register_custom_syntax + Into>( + pub fn register_custom_syntax + Into>( &mut self, keywords: &[S], new_vars: isize, @@ -221,7 +222,7 @@ impl Engine { /// Otherwise, custom keywords won't be recognized. pub fn register_custom_syntax_raw( &mut self, - key: impl Into, + key: impl Into, parse: impl Fn(&[ImmutableString], &str) -> Result, ParseError> + SendSync + 'static, diff --git a/src/utils.rs b/src/utils.rs index 60fa7e0f..380a1f8b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ str::FromStr, string::{String, ToString}, }; -use crate::Shared; +use crate::{Identifier, Shared}; /// A hasher that only takes one single [`u64`] and returns it as a hash key. /// @@ -590,6 +590,20 @@ impl PartialOrd for String { } } +impl From for Identifier { + #[inline(always)] + fn from(value: ImmutableString) -> Self { + value.into_owned().into() + } +} + +impl From for ImmutableString { + #[inline(always)] + fn from(value: Identifier) -> Self { + value.to_string().into() + } +} + impl ImmutableString { /// Consume the [`ImmutableString`] and convert it into a [`String`]. /// If there are other references to the same string, a cloned copy is returned. @@ -608,16 +622,12 @@ impl ImmutableString { /// A collection of interned strings. #[derive(Debug, Clone, Default, Hash)] -pub struct StringInterner(BTreeSet); +pub struct StringInterner(BTreeSet); impl StringInterner { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get(&mut self, text: impl AsRef + Into) -> ImmutableString { - self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { - let s = text.into(); - self.0.insert(s.clone()); - s - }) + pub fn get(&mut self, text: impl AsRef) -> Identifier { + text.as_ref().into() } } From 60f8a7f91c11c2be0bb980c0e72c8ed1fa4c0e36 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 11:36:15 +0800 Subject: [PATCH 04/11] Remove Float trait. --- src/ast.rs | 3 --- src/dynamic.rs | 2 +- src/fn_builtin.rs | 4 ---- src/packages/arithmetic.rs | 4 ---- src/packages/math_basic.rs | 4 ---- src/packages/string_basic.rs | 6 ------ 6 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 02f30e1f..86d0e017 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1480,9 +1480,6 @@ impl fmt::Debug for FloatWrapper { impl fmt::Display for FloatWrapper { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(feature = "no_std")] - use num_traits::Float; - let abs = self.0.abs(); if abs > 10000000000000.0 || abs < 0.0000000000001 { write!(f, "{:e}", self.0) diff --git a/src/dynamic.rs b/src/dynamic.rs index f32b68b7..477075af 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -8,7 +8,7 @@ use crate::stdlib::{ fmt, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, - string::String, + string::{String, ToString}, }; use crate::{FnPtr, ImmutableString, INT}; diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 4b79cc62..8e00463c 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -11,10 +11,6 @@ use crate::FLOAT; #[cfg(feature = "decimal")] use rust_decimal::Decimal; -#[cfg(feature = "no_std")] -#[cfg(not(feature = "no_float"))] -use num_traits::float::Float; - /// Is the type a numeric type? #[inline(always)] fn is_numeric(type_id: TypeId) -> bool { diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 1346a5b3..07d838b9 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -7,10 +7,6 @@ use crate::{def_package, EvalAltResult, Position, INT}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; -#[cfg(feature = "no_std")] -#[cfg(not(feature = "no_float"))] -use num_traits::float::Float; - #[inline(always)] pub fn make_err(msg: impl Into) -> Box { EvalAltResult::ErrorArithmetic(msg.into(), Position::NONE).into() diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 2ed73a62..e229e282 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -9,10 +9,6 @@ use crate::FLOAT; #[cfg(not(feature = "no_float"))] use crate::result::EvalAltResult; -#[cfg(feature = "no_std")] -#[cfg(not(feature = "no_float"))] -use num_traits::float::Float; - #[cfg(not(feature = "no_float"))] use crate::stdlib::format; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 3861a0a2..71341c44 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -57,9 +57,6 @@ mod print_debug_functions { pub mod float_functions { #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { - #[cfg(feature = "no_std")] - use num_traits::Float; - let abs = number.abs(); if abs > 10000000000000.0 || abs < 0.0000000000001 { format!("{:e}", number).into() @@ -69,9 +66,6 @@ mod print_debug_functions { } #[rhai_fn(name = "print", name = "to_string")] pub fn print_f32(number: f32) -> ImmutableString { - #[cfg(feature = "no_std")] - use num_traits::Float; - let abs = number.abs(); if abs > 10000000000000.0 || abs < 0.0000000000001 { format!("{:e}", number).into() From c0b2eee9f2afc64ba42aa940c81213517b4a337c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 12:46:46 +0800 Subject: [PATCH 05/11] Fix syn regression. --- codegen/Cargo.toml | 2 +- codegen/src/rhai_module.rs | 6 ++++-- codegen/src/test/module.rs | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 94b0b2cc..c4b22ab2 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -21,5 +21,5 @@ trybuild = "1" [dependencies] proc-macro2 = "1" -syn = { version = "1", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] } +syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] } quote = "1" diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 1fafd99e..77025db1 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -52,7 +52,8 @@ pub fn generate_body( .collect(); add_mod_blocks.push( syn::parse2::(quote! { - #(#cfg_attrs)* { + { + #(#cfg_attrs)* m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate()); } }) @@ -60,7 +61,8 @@ pub fn generate_body( ); set_flattened_mod_blocks.push( syn::parse2::(quote! { - #(#cfg_attrs)* { + { + #(#cfg_attrs)* self::#module_name::rhai_generate_into_module(m, flatten); } }) diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index fdf4cf12..410f25ed 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -1276,11 +1276,13 @@ mod generate_tests { #[allow(unused_mut)] pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { if flatten { - #[cfg(not(feature = "no_float"))] { + { + #[cfg(not(feature = "no_float"))] self::it_is::rhai_generate_into_module(m, flatten); } } else { - #[cfg(not(feature = "no_float"))] { + { + #[cfg(not(feature = "no_float"))] m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } } From fc6c5ecd00914ae541876dd66bd530214015bd11 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 13:07:10 +0800 Subject: [PATCH 06/11] Fix no_closure builds. --- src/ast.rs | 10 +++++----- src/fn_call.rs | 1 + src/packages/fn_basic.rs | 4 ++-- src/parser.rs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 86d0e017..81a277c5 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -5,7 +5,7 @@ use crate::fn_native::shared_make_mut; use crate::module::NamespaceRef; use crate::stdlib::{ boxed::Box, - collections::{BTreeMap, BTreeSet}, + collections::BTreeMap, fmt, hash::Hash, num::NonZeroUsize, @@ -58,7 +58,7 @@ pub struct ScriptFnDef { pub params: StaticVec, /// Access to external variables. #[cfg(not(feature = "no_closure"))] - pub externals: BTreeSet, + pub externals: crate::stdlib::collections::BTreeSet, /// Function doc-comments (if any). pub comments: StaticVec, } @@ -901,7 +901,7 @@ pub enum Stmt { Export(Vec<(Ident, Option)>, Position), /// Convert a variable to shared. #[cfg(not(feature = "no_closure"))] - Share(Ident), + Share(Box), } impl Default for Stmt { @@ -1864,8 +1864,8 @@ mod tests { assert_eq!(size_of::(), 4); assert_eq!(size_of::(), 16); assert_eq!(size_of::>(), 16); - assert_eq!(size_of::(), 40); - assert_eq!(size_of::>(), 40); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::>(), 32); assert_eq!(size_of::(), 80); assert_eq!(size_of::(), 288); assert_eq!(size_of::(), 56); diff --git a/src/fn_call.rs b/src/fn_call.rs index d02d75e6..e7cef8ca 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -96,6 +96,7 @@ impl Drop for ArgBackup<'_> { } } +#[cfg(not(feature = "no_closure"))] #[inline(always)] pub fn ensure_no_data_race( fn_name: &str, diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 634f5253..3331f32c 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,5 +1,5 @@ use crate::plugin::*; -use crate::{def_package, FnPtr, Identifier, ImmutableString, NativeCallContext}; +use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); @@ -34,7 +34,7 @@ mod fn_ptr_functions { #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { - use crate::{ast::ScriptFnDef, stdlib::collections::BTreeSet, Array, Map}; + use crate::{ast::ScriptFnDef, stdlib::collections::BTreeSet, Array, Identifier, Map}; // Create a metadata record for a function. fn make_metadata( diff --git a/src/parser.rs b/src/parser.rs index daa02cfb..0d6de2ba 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2800,7 +2800,7 @@ fn make_curry_from_externals( // Convert the entire expression into a statement block, then insert the relevant // [`Share`][Stmt::Share] statements. let mut statements: StaticVec<_> = Default::default(); - statements.extend(externals.into_iter().map(Stmt::Share)); + statements.extend(externals.into_iter().map(|v| Stmt::Share(Box::new(v)))); statements.push(Stmt::Expr(expr)); Expr::Stmt(Box::new(StmtBlock { statements, pos })) } From 07efdddba3ae6fe94e69e520cd6f8c4ab880c13a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 13:40:33 +0800 Subject: [PATCH 07/11] Add smartstring default feature. --- Cargo.toml | 7 +++++-- src/ast.rs | 4 ++++ src/dynamic.rs | 7 ++++--- src/engine_api.rs | 14 ++++++++------ src/fn_builtin.rs | 8 ++++++++ src/lib.rs | 11 +++++++++-- src/packages/arithmetic.rs | 4 ++++ src/packages/math_basic.rs | 4 ++++ src/packages/string_basic.rs | 4 ++++ src/parser.rs | 2 +- src/utils.rs | 14 ++++++++++++-- 11 files changed, 63 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2f17533..09fbbb93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,12 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.6", default-features = false, features = ["union"] } -smartstring = { version = "0.2.6" } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default_features = false } rhai_codegen = { version = "0.3.4", path = "codegen", features = ["metadata"] } [features] -default = [] +default = ["smartstring"] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_optimize = [] # no script optimizer @@ -76,6 +75,10 @@ default_features = false features = ["alloc"] optional = true +[dependencies.smartstring] +version = "0.2.6" +optional = true + [dependencies.unicode-xid] version = "0.2" default_features = false diff --git a/src/ast.rs b/src/ast.rs index 81a277c5..9af2bdf2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1480,6 +1480,10 @@ impl fmt::Debug for FloatWrapper { impl fmt::Display for FloatWrapper { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(feature = "no_std")] + #[cfg(not(feature = "no_float"))] + use num_traits::Float; + let abs = self.0.abs(); if abs > 10000000000000.0 || abs < 0.0000000000001 { write!(f, "{:e}", self.0) diff --git a/src/dynamic.rs b/src/dynamic.rs index 477075af..4edf8957 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -8,7 +8,7 @@ use crate::stdlib::{ fmt, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, - string::{String, ToString}, + string::String, }; use crate::{FnPtr, ImmutableString, INT}; @@ -1673,9 +1673,10 @@ impl From<&ImmutableString> for Dynamic { value.clone().into() } } -impl From<&smartstring::SmartString> for Dynamic { +#[cfg(feature = "smartstring")] +impl From<&crate::Identifier> for Dynamic { #[inline(always)] - fn from(value: &smartstring::SmartString) -> Self { + fn from(value: &crate::Identifier) -> Self { value.to_string().into() } } diff --git a/src/engine_api.rs b/src/engine_api.rs index cd37ac6c..3f1e1674 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -8,7 +8,6 @@ use crate::optimize::OptimizationLevel; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, - format, string::String, }; use crate::{ @@ -61,7 +60,7 @@ impl Engine { #[cfg(feature = "metadata")] let mut param_type_names: crate::StaticVec<_> = F::param_names() .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) + .map(|ty| crate::stdlib::format!("_: {}", self.map_type_name(ty))) .collect(); #[cfg(feature = "metadata")] @@ -121,7 +120,7 @@ impl Engine { #[cfg(feature = "metadata")] let param_type_names: crate::StaticVec<_> = F::param_names() .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) + .map(|ty| crate::stdlib::format!("_: {}", self.map_type_name(ty))) .chain(crate::stdlib::iter::once( self.map_type_name(F::return_type_name()).into(), )) @@ -1167,7 +1166,7 @@ impl Engine { let mut f = crate::stdlib::fs::File::open(path.clone()).map_err(|err| { EvalAltResult::ErrorSystem( - format!("Cannot open script file '{}'", path.to_string_lossy()), + crate::stdlib::format!("Cannot open script file '{}'", path.to_string_lossy()), err.into(), ) })?; @@ -1176,7 +1175,7 @@ impl Engine { f.read_to_string(&mut contents).map_err(|err| { EvalAltResult::ErrorSystem( - format!("Cannot read script file '{}'", path.to_string_lossy()), + crate::stdlib::format!("Cannot read script file '{}'", path.to_string_lossy()), err.into(), ) })?; @@ -1991,7 +1990,10 @@ impl Engine { signatures.extend(self.global_namespace.gen_fn_signatures()); self.global_sub_modules.iter().for_each(|(name, m)| { - signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) + signatures.extend( + m.gen_fn_signatures() + .map(|f| crate::stdlib::format!("{}::{}", name, f)), + ) }); if include_packages { diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 8e00463c..3558d098 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -41,6 +41,10 @@ pub fn get_builtin_binary_op_fn( x: &Dynamic, y: &Dynamic, ) -> Option RhaiResult> { + #[cfg(feature = "no_std")] + #[cfg(not(feature = "no_float"))] + use num_traits::Float; + let type1 = x.type_id(); let type2 = y.type_id(); @@ -411,6 +415,10 @@ pub fn get_builtin_op_assignment_fn( x: &Dynamic, y: &Dynamic, ) -> Option RhaiResult> { + #[cfg(feature = "no_std")] + #[cfg(not(feature = "no_float"))] + use num_traits::Float; + let type1 = x.type_id(); let type2 = y.type_id(); diff --git a/src/lib.rs b/src/lib.rs index 04173c37..de3eff14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,15 @@ pub use syntax::Expression; pub use token::Position; pub use utils::ImmutableString; +/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most +/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. +#[cfg(feature = "smartstring")] +pub type Identifier = smartstring::SmartString; + +/// An identifier in Rhai. +#[cfg(not(feature = "smartstring"))] +pub type Identifier = ImmutableString; + /// A trait to enable registering Rust functions. /// This trait is no longer needed and will be removed in the future. #[deprecated( @@ -292,8 +301,6 @@ type StaticVec = smallvec::SmallVec<[T; 4]>; #[cfg(feature = "internals")] pub type StaticVec = smallvec::SmallVec<[T; 4]>; -pub type Identifier = smartstring::SmartString; - // Compiler guards against mutually-exclusive feature flags #[cfg(feature = "no_float")] diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 07d838b9..efefbd87 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -7,6 +7,10 @@ use crate::{def_package, EvalAltResult, Position, INT}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::Float; + #[inline(always)] pub fn make_err(msg: impl Into) -> Box { EvalAltResult::ErrorArithmetic(msg.into(), Position::NONE).into() diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index e229e282..977fe2ed 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -12,6 +12,10 @@ use crate::result::EvalAltResult; #[cfg(not(feature = "no_float"))] use crate::stdlib::format; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::Float; + #[cfg(feature = "decimal")] use rust_decimal::Decimal; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 71341c44..08839b5b 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -55,6 +55,10 @@ mod print_debug_functions { #[cfg(not(feature = "no_float"))] pub mod float_functions { + #[cfg(feature = "no_std")] + #[cfg(not(feature = "no_float"))] + use num_traits::Float; + #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { let abs = number.abs(); diff --git a/src/parser.rs b/src/parser.rs index 0d6de2ba..1480a75f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -166,7 +166,7 @@ impl<'e> ParseState<'e> { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get_interned_string(&mut self, text: impl AsRef) -> Identifier { + pub fn get_interned_string(&mut self, text: impl AsRef + Into) -> Identifier { self.interned_strings.get(text) } } diff --git a/src/utils.rs b/src/utils.rs index 380a1f8b..040229e0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -590,6 +590,7 @@ impl PartialOrd for String { } } +#[cfg(feature = "smartstring")] impl From for Identifier { #[inline(always)] fn from(value: ImmutableString) -> Self { @@ -597,6 +598,7 @@ impl From for Identifier { } } +#[cfg(feature = "smartstring")] impl From for ImmutableString { #[inline(always)] fn from(value: Identifier) -> Self { @@ -627,7 +629,15 @@ pub struct StringInterner(BTreeSet); impl StringInterner { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get(&mut self, text: impl AsRef) -> Identifier { - text.as_ref().into() + pub fn get(&mut self, text: impl AsRef + Into) -> Identifier { + #[cfg(feature = "smartstring")] + return text.as_ref().into(); + + #[cfg(not(feature = "smartstring"))] + return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { + let s: Identifier = text.into(); + self.0.insert(s.clone()); + s + }); } } From 41b48d591f5853dce52dc351e60cd67e931d54f6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 17:13:54 +0800 Subject: [PATCH 08/11] Change to no_smartstring feature. --- CHANGELOG.md | 2 +- Cargo.toml | 9 ++--- no_std/no_std_test/Cargo.toml | 2 +- src/dynamic.rs | 4 +- src/lib.rs | 4 +- src/module/mod.rs | 8 ++-- src/parser.rs | 75 +++++++++++++++++------------------ src/utils.rs | 26 +++++++----- 8 files changed, 68 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5df697b..50b54db4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Breaking changes * The _reflections_ API such as `Engine::gen_fn_signatures`, `Module::update_fn_metadata` etc. are put under the `metadata` feature gate. * The shebang `#!` is now a reserved symbol. * Shebangs at the very beginning of script files are skipped when loading them. -* [`smartstring`](https://crates.io/crates/smartstring) is used for identifiers. +* [`smartstring`](https://crates.io/crates/smartstring) is used for identifiers by default. Currently, a PR branch is pulled because it breaks on `no-std` builds. The official crate will be used once `smartstring` is fixed to support `no-std`. Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index 09fbbb93..4c1d6c4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,12 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] smallvec = { version = "1.6", default-features = false, features = ["union"] } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default_features = false } +#smartstring = { version = "0.2.6" } +smartstring = { git = "https://github.com/okready/smartstring", branch = "fix-no_std-builds", default_features = false } rhai_codegen = { version = "0.3.4", path = "codegen", features = ["metadata"] } [features] -default = ["smartstring"] +default = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_optimize = [] # no script optimizer @@ -40,6 +42,7 @@ internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. metadata = ["serde_json"] # enable exporting functions metadata +no_smartstring = [] # set Identifier=ImmutableString no_std = ["smallvec/union", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"] # compiling for WASM @@ -75,10 +78,6 @@ default_features = false features = ["alloc"] optional = true -[dependencies.smartstring] -version = "0.2.6" -optional = true - [dependencies.unicode-xid] version = "0.2" default_features = false diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index 6a85b79d..ec38bcc0 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://github.com/rhaiscript/rhai/tree/no_std/no_std_test" repository = "https://github.com/rhaiscript/rhai" [dependencies] -rhai = { path = "../../", features = [ "no_std" ], default_features = false } +rhai = { path = "../../", features = [ "no_std" ] } wee_alloc = { version = "0.4.5", default_features = false } [profile.dev] diff --git a/src/dynamic.rs b/src/dynamic.rs index 4edf8957..1ec4d82c 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1673,11 +1673,11 @@ impl From<&ImmutableString> for Dynamic { value.clone().into() } } -#[cfg(feature = "smartstring")] +#[cfg(not(feature = "no_smartstring"))] impl From<&crate::Identifier> for Dynamic { #[inline(always)] fn from(value: &crate::Identifier) -> Self { - value.to_string().into() + crate::stdlib::string::ToString::to_string(value).into() } } #[cfg(not(feature = "no_index"))] diff --git a/src/lib.rs b/src/lib.rs index de3eff14..ae7785ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,11 +137,11 @@ pub use utils::ImmutableString; /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. -#[cfg(feature = "smartstring")] +#[cfg(not(feature = "no_smartstring"))] pub type Identifier = smartstring::SmartString; /// An identifier in Rhai. -#[cfg(not(feature = "smartstring"))] +#[cfg(feature = "no_smartstring")] pub type Identifier = ImmutableString; /// A trait to enable registering Rust functions. diff --git a/src/module/mod.rs b/src/module/mod.rs index 2e7924be..320307be 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -16,7 +16,7 @@ use crate::stdlib::{ vec::Vec, }; use crate::token::Token; -use crate::utils::StringInterner; +use crate::utils::IdentifierBuilder; use crate::{ calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, ImmutableString, NativeCallContext, Position, Shared, StaticVec, @@ -149,7 +149,7 @@ pub struct Module { /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? contains_indexed_global_functions: bool, /// Interned strings - interned_strings: StringInterner, + identifiers: IdentifierBuilder, } impl Default for Module { @@ -166,7 +166,7 @@ impl Default for Module { all_type_iterators: Default::default(), indexed: false, contains_indexed_global_functions: false, - interned_strings: Default::default(), + identifiers: Default::default(), } } } @@ -700,7 +700,7 @@ impl Module { self.functions.insert( hash_fn, Box::new(FuncInfo { - name: self.interned_strings.get(name), + name: self.identifiers.get(name), namespace, access, params: param_types.len(), diff --git a/src/parser.rs b/src/parser.rs index 1480a75f..810aba4c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -22,7 +22,7 @@ use crate::stdlib::{ }; use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; -use crate::utils::{get_hasher, StringInterner}; +use crate::utils::{get_hasher, IdentifierBuilder}; use crate::{ calc_fn_hash, Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST, @@ -44,7 +44,7 @@ struct ParseState<'e> { /// Reference to the scripting [`Engine`]. engine: &'e Engine, /// Interned strings. - interned_strings: StringInterner, + interned_strings: IdentifierBuilder, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(Identifier, AccessMode)>, /// Size of the local variables stack upon entry of the current block scope. @@ -166,7 +166,7 @@ impl<'e> ParseState<'e> { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get_interned_string(&mut self, text: impl AsRef + Into) -> Identifier { + pub fn get_identifier(&mut self, text: impl AsRef + Into) -> Identifier { self.interned_strings.get(text) } } @@ -228,9 +228,9 @@ impl Expr { match self { Self::Variable(x) if x.1.is_none() => { let ident = x.2; - let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); + let getter = state.get_identifier(crate::engine::make_getter(&ident.name)); let hash_get = calc_fn_hash(empty(), &getter, 1); - let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); + let setter = state.get_identifier(crate::engine::make_setter(&ident.name)); let hash_set = calc_fn_hash(empty(), &setter, 2); Self::Property(Box::new(( @@ -351,7 +351,7 @@ fn parse_fn_call( return Ok(Expr::FnCall( Box::new(FnCallExpr { - name: state.get_interned_string(id), + name: state.get_identifier(id), capture, namespace, hash, @@ -396,7 +396,7 @@ fn parse_fn_call( return Ok(Expr::FnCall( Box::new(FnCallExpr { - name: state.get_interned_string(id), + name: state.get_identifier(id), capture, namespace, hash, @@ -755,7 +755,7 @@ fn parse_map_literal( } let expr = parse_expr(input, state, lib, settings.level_up())?; - let name = state.get_interned_string(name); + let name = state.get_identifier(name); template.insert(name.clone().into(), Default::default()); map.push((Ident { name, pos }, expr)); @@ -936,7 +936,7 @@ fn parse_primary( Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { - Expr::StringConstant(state.get_interned_string(s).into(), settings.pos) + Expr::StringConstant(state.get_identifier(s).into(), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1034,7 +1034,7 @@ fn parse_primary( state.allow_capture = true; } let var_name_def = Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos: settings.pos, }; Expr::Variable(Box::new((None, None, var_name_def))) @@ -1048,7 +1048,7 @@ fn parse_primary( state.allow_capture = true; } let var_name_def = Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos: settings.pos, }; Expr::Variable(Box::new((None, None, var_name_def))) @@ -1057,7 +1057,7 @@ fn parse_primary( _ => { let index = state.access_var(&s, settings.pos); let var_name_def = Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos: settings.pos, }; Expr::Variable(Box::new((index, None, var_name_def))) @@ -1076,7 +1076,7 @@ fn parse_primary( // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang if is_keyword_function(&s) => { let var_name_def = Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos: settings.pos, }; Expr::Variable(Box::new((None, None, var_name_def))) @@ -1084,7 +1084,7 @@ fn parse_primary( // Access to `this` as a variable is OK within a function scope _ if s == KEYWORD_THIS && settings.is_function_scope => { let var_name_def = Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos: settings.pos, }; Expr::Variable(Box::new((None, None, var_name_def))) @@ -1177,7 +1177,7 @@ fn parse_primary( } let var_name_def = Ident { - name: state.get_interned_string(id2), + name: state.get_identifier(id2), pos: pos2, }; Expr::Variable(Box::new((index, namespace, var_name_def))) @@ -1286,7 +1286,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { - name: state.get_interned_string("-"), + name: state.get_identifier("-"), hash: FnCallHash::from_native(calc_fn_hash(empty(), "-", 1)), args, ..Default::default() @@ -1312,7 +1312,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { - name: state.get_interned_string("+"), + name: state.get_identifier("+"), hash: FnCallHash::from_native(calc_fn_hash(empty(), "+", 1)), args, ..Default::default() @@ -1331,7 +1331,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { - name: state.get_interned_string("!"), + name: state.get_identifier("!"), hash: FnCallHash::from_native(calc_fn_hash(empty(), "!", 1)), args, ..Default::default() @@ -1500,9 +1500,9 @@ fn make_dot_expr( // lhs.id (lhs, Expr::Variable(x)) if x.1.is_none() => { let ident = x.2; - let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); + let getter = state.get_identifier(crate::engine::make_getter(&ident.name)); let hash_get = calc_fn_hash(empty(), &getter, 1); - let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); + let setter = state.get_identifier(crate::engine::make_setter(&ident.name)); let hash_set = calc_fn_hash(empty(), &setter, 2); let rhs = Expr::Property(Box::new(((getter, hash_get), (setter, hash_set), ident))); @@ -1674,7 +1674,7 @@ fn parse_binary_op( let hash = calc_fn_hash(empty(), &op, 2); let op_base = FnCallExpr { - name: state.get_interned_string(op.as_ref()), + name: state.get_identifier(op.as_ref()), hash: FnCallHash::from_native(hash), capture: false, ..Default::default() @@ -1742,7 +1742,7 @@ fn parse_binary_op( Box::new(FnCallExpr { hash: FnCallHash::from_script(hash), args, - name: state.get_interned_string(OP_CONTAINS), + name: state.get_identifier(OP_CONTAINS), ..op_base }), pos, @@ -1830,9 +1830,9 @@ fn parse_custom_syntax( match required_token.as_str() { MARKER_IDENT => match input.next().unwrap() { (Token::Identifier(s), pos) => { - let name = state.get_interned_string(s); + let name = state.get_identifier(s); segments.push(name.clone().into()); - tokens.push(state.get_interned_string(MARKER_IDENT)); + tokens.push(state.get_identifier(MARKER_IDENT)); let var_name_def = Ident { name, pos }; keywords.push(Expr::Variable(Box::new((None, None, var_name_def)))); } @@ -1843,14 +1843,14 @@ fn parse_custom_syntax( }, MARKER_EXPR => { keywords.push(parse_expr(input, state, lib, settings)?); - let keyword = state.get_interned_string(MARKER_EXPR); + let keyword = state.get_identifier(MARKER_EXPR); segments.push(keyword.clone().into()); tokens.push(keyword); } MARKER_BLOCK => match parse_block(input, state, lib, settings)? { block @ Stmt::Block(_, _) => { keywords.push(Expr::Stmt(Box::new(block.into()))); - let keyword = state.get_interned_string(MARKER_BLOCK); + let keyword = state.get_identifier(MARKER_BLOCK); segments.push(keyword.clone().into()); tokens.push(keyword); } @@ -2120,7 +2120,7 @@ fn parse_for( ensure_not_statement_expr(input, "a boolean")?; let expr = parse_expr(input, state, lib, settings.level_up())?; - let loop_var = state.get_interned_string(name); + let loop_var = state.get_identifier(name); let prev_stack_len = state.stack.len(); state.stack.push((loop_var.clone(), AccessMode::ReadWrite)); @@ -2161,7 +2161,7 @@ fn parse_let( (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; - let name = state.get_interned_string(name); + let name = state.get_identifier(name); let var_def = Ident { name: name.clone(), pos, @@ -2217,7 +2217,7 @@ fn parse_import( (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; - let name = state.get_interned_string(name); + let name = state.get_identifier(name); state.modules.push(name.clone()); Ok(Stmt::Import( @@ -2277,7 +2277,7 @@ fn parse_export( let rename = if match_token(input, Token::As).0 { match input.next().unwrap() { (Token::Identifier(s), pos) => Some(Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos, }), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { @@ -2292,7 +2292,7 @@ fn parse_export( exports.push(( Ident { - name: state.get_interned_string(id), + name: state.get_identifier(id), pos: id_pos, }, rename, @@ -2630,7 +2630,7 @@ fn parse_try_catch( let var_def = if match_token(input, Token::LeftParen).0 { let id = match input.next().unwrap() { (Token::Identifier(s), pos) => Ident { - name: state.get_interned_string(s), + name: state.get_identifier(s), pos, }, (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), @@ -2700,7 +2700,7 @@ fn parse_fn( if params.iter().any(|(p, _)| p == &s) { return Err(PERR::FnDuplicatedParam(name, s).into_err(pos)); } - let s = state.get_interned_string(s); + let s = state.get_identifier(s); state.stack.push((s.clone(), AccessMode::ReadWrite)); params.push((s, pos)) } @@ -2747,7 +2747,7 @@ fn parse_fn( .collect(); Ok(ScriptFnDef { - name: state.get_interned_string(&name), + name: state.get_identifier(&name), access, params, #[cfg(not(feature = "no_closure"))] @@ -2785,7 +2785,7 @@ fn make_curry_from_externals( let expr = Expr::FnCall( Box::new(FnCallExpr { - name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), + name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), hash: FnCallHash::from_native(calc_fn_hash( empty(), crate::engine::KEYWORD_FN_PTR_CURRY, @@ -2827,7 +2827,7 @@ fn parse_anon_fn( if params.iter().any(|(p, _)| p == &s) { return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos)); } - let s = state.get_interned_string(s); + let s = state.get_identifier(s); state.stack.push((s.clone(), AccessMode::ReadWrite)); params.push((s, pos)) } @@ -2895,8 +2895,7 @@ fn parse_anon_fn( body.hash(hasher); let hash = hasher.finish(); - let fn_name = - state.get_interned_string(&(format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash))); + let fn_name = state.get_identifier(&(format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash))); // Define the function let script = ScriptFnDef { diff --git a/src/utils.rs b/src/utils.rs index 040229e0..e972d023 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,7 +6,6 @@ use crate::stdlib::{ borrow::Borrow, boxed::Box, cmp::Ordering, - collections::BTreeSet, fmt, fmt::{Debug, Display}, hash::{BuildHasher, Hash, Hasher}, @@ -590,7 +589,7 @@ impl PartialOrd for String { } } -#[cfg(feature = "smartstring")] +#[cfg(not(feature = "no_smartstring"))] impl From for Identifier { #[inline(always)] fn from(value: ImmutableString) -> Self { @@ -598,7 +597,7 @@ impl From for Identifier { } } -#[cfg(feature = "smartstring")] +#[cfg(not(feature = "no_smartstring"))] impl From for ImmutableString { #[inline(always)] fn from(value: Identifier) -> Self { @@ -622,18 +621,27 @@ impl ImmutableString { } } -/// A collection of interned strings. +/// A factory of identifiers from text strings. +/// +/// When [`SmartString`](https://crates.io/crates/smartstring) is used as [`Identifier`], +/// this just returns one because most identifiers in Rhai are short and ASCII-based. +/// +/// When [`ImmutableString`] is used as [`Identifier`], this type acts as an interner which keeps a +/// collection of strings and returns shared instances, only creating a new string when it is not +/// yet interned. #[derive(Debug, Clone, Default, Hash)] -pub struct StringInterner(BTreeSet); +pub struct IdentifierBuilder( + #[cfg(feature = "no_smartstring")] crate::stdlib::collections::BTreeSet, +); -impl StringInterner { - /// Get an interned string, creating one if it is not yet interned. +impl IdentifierBuilder { + /// Get an identifier from a text string. #[inline(always)] pub fn get(&mut self, text: impl AsRef + Into) -> Identifier { - #[cfg(feature = "smartstring")] + #[cfg(not(feature = "no_smartstring"))] return text.as_ref().into(); - #[cfg(not(feature = "smartstring"))] + #[cfg(feature = "no_smartstring")] return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { let s: Identifier = text.into(); self.0.insert(s.clone()); From e306a92ea0d80097604c58fc951dd5b910c7b865 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 17:14:22 +0800 Subject: [PATCH 09/11] Change Map keys to Identifier. --- src/ast.rs | 2 +- src/dynamic.rs | 6 +++--- src/engine.rs | 6 +++--- src/fn_builtin.rs | 9 ++++++++- src/lib.rs | 2 +- src/packages/map_basic.rs | 4 ++-- src/parser.rs | 6 +++--- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 9af2bdf2..ca012df1 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1548,7 +1548,7 @@ pub enum Expr { Array(Box>, Position), /// #{ name:expr, ... } Map( - Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, + Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, Position, ), /// () diff --git a/src/dynamic.rs b/src/dynamic.rs index 1ec4d82c..fd730ef3 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1712,7 +1712,7 @@ impl crate::stdlib::iter::FromIterator for Dynamic { } #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_std"))] -impl, T: Variant + Clone> From> +impl, T: Variant + Clone> From> for Dynamic { #[inline(always)] @@ -1729,8 +1729,8 @@ impl, T: Variant + Clone> From, T: Variant + Clone> From> - for Dynamic +impl, T: Variant + Clone> + From> for Dynamic { #[inline(always)] fn from(value: crate::stdlib::collections::BTreeMap) -> Self { diff --git a/src/engine.rs b/src/engine.rs index e8255c63..16f32037 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1573,12 +1573,12 @@ impl Engine { self.make_type_mismatch_err::(idx.type_name(), idx_pos) })?; - if _create && !map.contains_key(index) { - map.insert(index.clone(), Default::default()); + if _create && !map.contains_key(index.as_str()) { + map.insert(index.clone().into(), Default::default()); } Ok(map - .get_mut(index) + .get_mut(index.as_str()) .map(Target::from) .unwrap_or_else(|| Target::from(()))) } diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 3558d098..515c6a68 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -85,6 +85,13 @@ pub fn get_builtin_binary_op_fn( Ok(x.$func(y).into()) }) }; + ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { + return Some(|_, args| { + let x = &*args[0].read_lock::<$xx>().unwrap(); + let y = &*args[1].read_lock::<$yy>().unwrap(); + Ok(x.$func(y.$yyy()).into()) + }) + }; ($func:ident ( $op:tt )) => { return Some(|_, args| { let (x, y) = $func(args); @@ -284,7 +291,7 @@ pub fn get_builtin_binary_op_fn( use crate::Map; match op { - OP_CONTAINS => impl_op!(Map.contains_key(ImmutableString)), + OP_CONTAINS => impl_op!(Map.contains_key(ImmutableString.as_str())), _ => return None, } } diff --git a/src/lib.rs b/src/lib.rs index ae7785ea..146a3717 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,7 +189,7 @@ pub type Array = stdlib::vec::Vec; /// /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] -pub type Map = stdlib::collections::BTreeMap; +pub type Map = stdlib::collections::BTreeMap; #[cfg(not(feature = "no_module"))] pub use module::ModuleResolver; diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 41212ec0..d76dbcb7 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -15,7 +15,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { mod map_functions { #[rhai_fn(name = "has", pure)] pub fn contains(map: &mut Map, prop: ImmutableString) -> bool { - map.contains_key(&prop) + map.contains_key(prop.as_str()) } #[rhai_fn(pure)] pub fn len(map: &mut Map) -> INT { @@ -25,7 +25,7 @@ mod map_functions { map.clear(); } pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { - map.remove(&name).unwrap_or_else(|| ().into()) + map.remove(name.as_str()).unwrap_or_else(|| ().into()) } #[rhai_fn(name = "mixin", name = "+=")] pub fn mixin(map: &mut Map, map2: Map) { diff --git a/src/parser.rs b/src/parser.rs index 810aba4c..083bd996 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -24,8 +24,8 @@ use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::utils::{get_hasher, IdentifierBuilder}; use crate::{ - calc_fn_hash, Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, - ParseErrorType, Position, Scope, Shared, StaticVec, AST, + calc_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, ParseErrorType, Position, + Scope, Shared, StaticVec, AST, }; #[cfg(not(feature = "no_float"))] @@ -685,7 +685,7 @@ fn parse_map_literal( settings.pos = eat_token(input, Token::MapStart); let mut map: StaticVec<(Ident, Expr)> = Default::default(); - let mut template: BTreeMap = Default::default(); + let mut template: BTreeMap = Default::default(); loop { const MISSING_RBRACE: &str = "to end this object map literal"; From 3a6e6848fd2ba02bd8110919abb73f852912f87f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Mar 2021 18:46:32 +0800 Subject: [PATCH 10/11] Fix serde build. --- src/module/mod.rs | 4 ++-- src/serde/de.rs | 19 ++++++++++++------- src/serde/deserialize.rs | 4 ++-- src/serde/ser.rs | 4 ++-- src/serde/serialize.rs | 2 +- src/serde/str.rs | 16 ++++++++-------- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/module/mod.rs b/src/module/mod.rs index 320307be..824d4e72 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -622,7 +622,7 @@ impl Module { pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[&str]) -> &mut Self { let param_names = arg_names .iter() - .map(|&name| self.interned_strings.get(name)) + .map(|&name| self.identifiers.get(name)) .collect(); if let Some(f) = self.functions.get_mut(&hash_fn) { @@ -692,7 +692,7 @@ impl Module { let param_names = _arg_names .iter() .flat_map(|p| p.iter()) - .map(|&arg| self.interned_strings.get(arg)) + .map(|&arg| self.identifiers.get(arg)) .collect(); let hash_fn = calc_native_fn_hash(empty(), &name, ¶m_types); diff --git a/src/serde/de.rs b/src/serde/de.rs index 2d9adafa..802a47e2 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,6 +1,6 @@ //! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. -use super::str::ImmutableStringDeserializer; +use super::str::StringSliceDeserializer; use crate::dynamic::Union; use crate::stdlib::{any::type_name, boxed::Box, fmt, string::ToString}; use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; @@ -418,7 +418,12 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { #[cfg(not(feature = "no_object"))] return self.value.downcast_ref::().map_or_else( || self.type_error(), - |map| _visitor.visit_map(IterateMap::new(map.keys(), map.values())), + |map| { + _visitor.visit_map(IterateMap::new( + map.keys().map(|key| key.as_str()), + map.values(), + )) + }, ); #[cfg(feature = "no_object")] @@ -512,7 +517,7 @@ impl<'a: 'de, 'de, ITER: Iterator> SeqAccess<'de> for Iterat /// `MapAccess` implementation for maps. struct IterateMap<'a, KEYS, VALUES> where - KEYS: Iterator, + KEYS: Iterator, VALUES: Iterator, { // Iterator for a stream of [`Dynamic`][crate::Dynamic] keys. @@ -524,7 +529,7 @@ where #[cfg(not(feature = "no_object"))] impl<'a, KEYS, VALUES> IterateMap<'a, KEYS, VALUES> where - KEYS: Iterator, + KEYS: Iterator, VALUES: Iterator, { pub fn new(keys: KEYS, values: VALUES) -> Self { @@ -534,7 +539,7 @@ where impl<'a: 'de, 'de, KEYS, VALUES> MapAccess<'de> for IterateMap<'a, KEYS, VALUES> where - KEYS: Iterator, + KEYS: Iterator, VALUES: Iterator, { type Error = Box; @@ -543,11 +548,11 @@ where &mut self, seed: K, ) -> Result, Box> { - // Deserialize each `ImmutableString` key coming out of the keys iterator. + // Deserialize each `Identifier` key coming out of the keys iterator. match self.keys.next() { None => Ok(None), Some(item) => seed - .deserialize(&mut ImmutableStringDeserializer::from_str(item)) + .deserialize(&mut StringSliceDeserializer::from_str(item)) .map(Some), } } diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index 224fe5e7..cd8c852a 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -143,8 +143,8 @@ impl<'d> Visitor<'d> for DynamicVisitor { fn visit_map>(self, mut map: M) -> Result { let mut m: Map = Default::default(); - while let Some((k, v)) = map.next_entry()? { - m.insert(k, v); + while let Some((k, v)) = map.next_entry::<&str, _>()? { + m.insert(k.into(), v); } Ok(m.into()) diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 6b0d221e..73cab25c 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -550,7 +550,7 @@ impl SerializeMap for DynamicSerializer { })?; let _value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(key, _value); + map.insert(key.into(), _value); Ok(()) } #[cfg(feature = "no_object")] @@ -575,7 +575,7 @@ impl SerializeMap for DynamicSerializer { })?; let _value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); - map.insert(_key, _value); + map.insert(_key.into(), _value); Ok(()) } #[cfg(feature = "no_object")] diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 05afd31b..2491b2f9 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -54,7 +54,7 @@ impl Serialize for Dynamic { Union::Map(m, _) => { let mut map = ser.serialize_map(Some(m.len()))?; for (k, v) in m.iter() { - map.serialize_entry(k, v)?; + map.serialize_entry(k.as_str(), v)?; } map.end() } diff --git a/src/serde/str.rs b/src/serde/str.rs index 6c14be25..ccc7a0a7 100644 --- a/src/serde/str.rs +++ b/src/serde/str.rs @@ -1,17 +1,17 @@ //! Implement deserialization support of [`ImmutableString`][crate::ImmutableString] for [`serde`]. use crate::stdlib::{any::type_name, boxed::Box}; -use crate::{EvalAltResult, ImmutableString, Position}; +use crate::{EvalAltResult, Position}; use serde::de::{Deserializer, Visitor}; /// Deserializer for `ImmutableString`. -pub struct ImmutableStringDeserializer<'a> { - value: &'a ImmutableString, +pub struct StringSliceDeserializer<'a> { + value: &'a str, } -impl<'a> ImmutableStringDeserializer<'a> { - /// Create an `ImmutableStringDeserializer` from an `ImmutableString` reference. - pub fn from_str(value: &'a ImmutableString) -> Self { +impl<'a> StringSliceDeserializer<'a> { + /// Create an `ImmutableStringDeserializer` from an `&str` reference. + pub fn from_str(value: &'a str) -> Self { Self { value } } /// Shortcut for a type conversion error. @@ -25,7 +25,7 @@ impl<'a> ImmutableStringDeserializer<'a> { } } -impl<'de> Deserializer<'de> for &mut ImmutableStringDeserializer<'de> { +impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { type Error = Box; fn deserialize_any>(self, v: V) -> Result> { @@ -69,7 +69,7 @@ impl<'de> Deserializer<'de> for &mut ImmutableStringDeserializer<'de> { } fn deserialize_str>(self, v: V) -> Result> { // Only allow deserialization into a string. - v.visit_borrowed_str(self.value.as_str()) + v.visit_borrowed_str(self.value) } fn deserialize_string>( self, From d2ded7733a99c73b4a501f62695826754df0d108 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 30 Mar 2021 00:21:09 +0800 Subject: [PATCH 11/11] Add support for line continuation and multi-line string literals. --- CHANGELOG.md | 18 ++++++++-- src/token.rs | 95 +++++++++++++++++++++++++++++++------------------ tests/string.rs | 8 +++++ 3 files changed, 84 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b54db4..9b4bbbc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,14 @@ an object map is small. `HashMap` and `BTreeMap` have almost identical public API's so this change is unlikely to break existing code. -Im addition, all function signature/metadata methods are now grouped under the umbrella `metadata` feature. +[`SmartString`](https://crates.io/crates/smartstring) is used to store identifiers (which tends to +be short, fewer than 23 characters, and ASCII-based) because they can usually be stored inline. +`Map` keys now also use [`SmartString`](https://crates.io/crates/smartstring). + +In addition, there is now support for line continuation in strings (put `\` at the end of line) as +well as multi-line literal strings (wrapped by back-ticks: \`...\`). + +Finally, all function signature/metadata methods are now grouped under the umbrella `metadata` feature. This avoids spending precious resources maintaining metadata for functions for the vast majority of use cases where such information is not required. @@ -24,7 +31,6 @@ use cases where such information is not required. Breaking changes ---------------- -* `Map` is now an alias to `BTreeMap` instead of `HashMap` because most object maps hold few properties. * The traits `RegisterFn` and `RegisterResultFn` are removed. `Engine::register_fn` and `Engine::register_result_fn` are now implemented directly on `Engine`. * `FnPtr::call_dynamic` now takes `&NativeCallContext` instead of consuming it. * All `Module::set_fn_XXX` methods are removed, in favor of `Module::set_native_fn`. @@ -35,6 +41,13 @@ Breaking changes * The shebang `#!` is now a reserved symbol. * Shebangs at the very beginning of script files are skipped when loading them. * [`smartstring`](https://crates.io/crates/smartstring) is used for identifiers by default. Currently, a PR branch is pulled because it breaks on `no-std` builds. The official crate will be used once `smartstring` is fixed to support `no-std`. +* `Map` is now an alias to `BTreeMap` instead of `HashMap` because most object maps hold few properties. + +New features +------------ + +* Line continuation (via `\`) and multi-line literal strings (wrapped with \`) support are added. +* Rhai scripts can now start with a shebang `#!` which is ignored. Enhancements ------------ @@ -42,7 +55,6 @@ Enhancements * Replaced all `HashMap` usage with `BTreeMap` for better performance because collections in Rhai are tiny. * `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type. * `#[rhai_fn(return_raw)]` can now return `Result>` where `T` is any clonable type instead of `Result>`. -* Rhai scripts can now start with a shebang `#!`. Version 0.19.14 diff --git a/src/token.rs b/src/token.rs index 16087267..ecd4d661 100644 --- a/src/token.rs +++ b/src/token.rs @@ -842,7 +842,7 @@ pub trait InputStream { fn peek_next(&mut self) -> Option; } -/// _(INTERNALS)_ Parse a string literal wrapped by `enclosing_char`. +/// _(INTERNALS)_ Parse a string literal ended by `termination_char`. /// Exported under the `internals` feature only. /// /// # Volatile API @@ -852,12 +852,15 @@ pub fn parse_string_literal( stream: &mut impl InputStream, state: &mut TokenizeState, pos: &mut Position, - enclosing_char: char, + termination_char: char, + continuation: bool, + verbatim: bool, ) -> Result { let mut result: smallvec::SmallVec<[char; 16]> = Default::default(); let mut escape: smallvec::SmallVec<[char; 12]> = Default::default(); let start = *pos; + let mut skip_whitespace_until = 0; loop { let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?; @@ -871,8 +874,10 @@ pub fn parse_string_literal( } match next_char { + // \r - ignore if followed by \n + '\r' if stream.peek_next().unwrap_or('\0') == '\n' => {} // \... - '\\' if escape.is_empty() => { + '\\' if escape.is_empty() && !verbatim => { escape.push('\\'); } // \\ @@ -937,18 +942,37 @@ pub fn parse_string_literal( })?); } - // \{enclosing_char} - escaped - ch if enclosing_char == ch && !escape.is_empty() => { + // \{termination_char} - escaped + _ if termination_char == next_char && !escape.is_empty() => { escape.clear(); - result.push(ch) + result.push(next_char) } // Close wrapper - ch if enclosing_char == ch && escape.is_empty() => break, + _ if termination_char == next_char && escape.is_empty() => break, + + // Line continuation + '\n' if continuation && !escape.is_empty() => { + escape.clear(); + pos.new_line(); + skip_whitespace_until = start.position().unwrap() + 1; + } + + // New-line cannot be escaped + // Cannot have new-lines inside non-multi-line string literals + '\n' if !escape.is_empty() || !verbatim => { + pos.rewind(); + return Err((LERR::UnterminatedString, start)); + } + + '\n' => { + pos.new_line(); + result.push(next_char); + } // Unknown escape sequence - ch if !escape.is_empty() => { - escape.push(ch); + _ if !escape.is_empty() => { + escape.push(next_char); return Err(( LERR::MalformedEscapeSequence(escape.into_iter().collect()), @@ -956,16 +980,14 @@ pub fn parse_string_literal( )); } - // Cannot have new-lines inside string literals - '\n' => { - pos.rewind(); - return Err((LERR::UnterminatedString, start)); - } + // Whitespace to skip + _ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {} // All other characters - ch => { + _ => { escape.clear(); - result.push(ch); + result.push(next_char); + skip_whitespace_until = 0; } } } @@ -1272,12 +1294,15 @@ fn get_next_token_inner( return get_identifier(stream, pos, start_pos, c); } - // " - string literal - ('"', _) => { - return parse_string_literal(stream, state, pos, '"').map_or_else( - |err| Some((Token::LexError(err.0), err.1)), - |out| Some((Token::StringConstant(out), start_pos)), - ) + // " or ` - string literal + ('"', _) | ('`', _) => { + let multi_line = c == '`'; + + return parse_string_literal(stream, state, pos, c, !multi_line, multi_line) + .map_or_else( + |err| Some((Token::LexError(err.0), err.1)), + |out| Some((Token::StringConstant(out), start_pos)), + ); } // ' - character literal @@ -1288,19 +1313,21 @@ fn get_next_token_inner( )) } ('\'', _) => { - return Some(parse_string_literal(stream, state, pos, '\'').map_or_else( - |err| (Token::LexError(err.0), err.1), - |result| { - let mut chars = result.chars(); - let first = chars.next().unwrap(); + return Some( + parse_string_literal(stream, state, pos, c, false, false).map_or_else( + |err| (Token::LexError(err.0), err.1), + |result| { + let mut chars = result.chars(); + let first = chars.next().unwrap(); - if chars.next().is_some() { - (Token::LexError(LERR::MalformedChar(result)), start_pos) - } else { - (Token::CharConstant(first), start_pos) - } - }, - )) + if chars.next().is_some() { + (Token::LexError(LERR::MalformedChar(result)), start_pos) + } else { + (Token::CharConstant(first), start_pos) + } + }, + ), + ) } // Braces diff --git a/tests/string.rs b/tests/string.rs index ff787116..d6885a34 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -8,6 +8,14 @@ fn test_string() -> Result<(), Box> { engine.eval::(r#""Test string: \u2764""#)?, "Test string: ❤" ); + assert_eq!( + engine.eval::(" \"Test string: \\u2764\\\n hello, world!\"")?, + "Test string: ❤ hello, world!" + ); + assert_eq!( + engine.eval::(" `Test string: \\u2764\nhello,\\nworld!`")?, + "Test string: \\u2764\nhello,\\nworld!" + ); assert_eq!( engine.eval::(r#""Test string: \x58""#)?, "Test string: X"