From be448dfe4d7919d2a543820497a3b5e75f4ab8db Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 11 Aug 2022 19:01:23 +0800 Subject: [PATCH 01/11] Use identifiers in format! --- codegen/src/attrs.rs | 2 +- codegen/src/function.rs | 8 ++--- codegen/src/module.rs | 2 +- codegen/src/rhai_module.rs | 12 +++---- codegen/src/test/function.rs | 13 +++----- codegen/tests/test_functions.rs | 2 +- codegen/tests/test_modules.rs | 2 +- src/api/custom_syntax.rs | 6 ++-- src/api/mod.rs | 6 ++-- src/api/register.rs | 2 +- src/api/type_names.rs | 2 +- src/ast/expr.rs | 10 +++--- src/bin/rhai-dbg.rs | 2 +- src/bin/rhai-repl.rs | 2 +- src/bin/rhai-run.rs | 2 +- src/eval/expr.rs | 6 ++-- src/eval/stmt.rs | 2 +- src/func/builtin.rs | 8 ++--- src/func/call.rs | 19 ++++------- src/func/script.rs | 4 +-- src/module/mod.rs | 11 +++---- src/packages/arithmetic.rs | 57 +++++++++++++++------------------ src/packages/lang_core.rs | 23 ++++++------- src/packages/math_basic.rs | 49 ++++++++++++---------------- src/packages/string_basic.rs | 22 +++++++------ src/packages/string_more.rs | 6 ++-- src/packages/time_basic.rs | 27 ++++++---------- src/parser.rs | 21 +++++------- src/tokenizer.rs | 2 +- src/types/error.rs | 2 +- tests/custom_syntax.rs | 6 ++-- tests/native.rs | 4 +-- tests/optimizer.rs | 12 +++---- tests/plugins.rs | 2 +- tests/print.rs | 12 +++---- tests/serde.rs | 2 +- 36 files changed, 164 insertions(+), 206 deletions(-) diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index da376cf2..044252df 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -131,7 +131,7 @@ pub fn inner_item_attributes( { return Err(syn::Error::new( duplicate.span(), - format!("duplicated attribute '{}'", attr_name), + format!("duplicated attribute '{attr_name}'"), )); } diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 078c1c2a..d356645e 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -57,10 +57,10 @@ impl FnSpecialAccess { match self { FnSpecialAccess::None => None, FnSpecialAccess::Property(Property::Get(ref g)) => { - Some((format!("{}{}", FN_GET, g), g.to_string(), g.span())) + Some((format!("{FN_GET}{g}"), g.to_string(), g.span())) } FnSpecialAccess::Property(Property::Set(ref s)) => { - Some((format!("{}{}", FN_SET, s), s.to_string(), s.span())) + Some((format!("{FN_SET}{s}"), s.to_string(), s.span())) } FnSpecialAccess::Index(Index::Get) => Some(( FN_IDX_GET.to_string(), @@ -255,7 +255,7 @@ impl ExportedParams for ExportedFnParams { (attr, ..) => { return Err(syn::Error::new( key.span(), - format!("unknown attribute '{}'", attr), + format!("unknown attribute '{attr}'"), )) } } @@ -748,7 +748,7 @@ impl ExportedFn { let str_type_path = syn::parse2::(quote! { str }).unwrap(); let string_type_path = syn::parse2::(quote! { String }).unwrap(); for (i, arg) in self.arg_list().enumerate().skip(skip_first_arg as usize) { - let var = syn::Ident::new(&format!("arg{}", i), Span::call_site()); + let var = syn::Ident::new(&format!("arg{i}"), Span::call_site()); let is_string; let is_ref; match arg { diff --git a/codegen/src/module.rs b/codegen/src/module.rs index bd6a4c1f..9206be78 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -72,7 +72,7 @@ impl ExportedParams for ExportedModParams { (attr, ..) => { return Err(syn::Error::new( key.span(), - format!("unknown attribute '{}'", attr), + format!("unknown attribute '{attr}'"), )) } } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index b5c8df4a..4e52d83d 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -319,11 +319,11 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> { if let Some(other_span) = renames.insert(key, current_span) { let mut err = syn::Error::new( current_span, - format!("duplicate Rhai signature for '{}'", fn_name), + format!("duplicate Rhai signature for '{fn_name}'"), ); err.combine(syn::Error::new( other_span, - format!("duplicated function renamed '{}'", fn_name), + format!("duplicated function renamed '{fn_name}'"), )); return Err(err); } @@ -332,10 +332,10 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> { let ident = item_fn.name(); if let Some(other_span) = fn_defs.insert(ident.to_string(), ident.span()) { let mut err = - syn::Error::new(ident.span(), format!("duplicate function '{}'", ident)); + syn::Error::new(ident.span(), format!("duplicate function '{ident}'")); err.combine(syn::Error::new( other_span, - format!("duplicated function '{}'", ident), + format!("duplicated function '{ident}'"), )); return Err(err); } @@ -343,11 +343,11 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> { if let Some(fn_span) = renames.get(&key) { let mut err = syn::Error::new( ident.span(), - format!("duplicate Rhai signature for '{}'", ident), + format!("duplicate Rhai signature for '{ident}'"), ); err.combine(syn::Error::new( *fn_span, - format!("duplicated function '{}'", ident), + format!("duplicated function '{ident}'"), )); return Err(err); } diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index f187c5ed..8465cd61 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -88,10 +88,7 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "Rhai functions cannot return references" - ); + assert_eq!(format!("{err}"), "Rhai functions cannot return references"); } #[test] @@ -101,7 +98,7 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "Rhai functions cannot return pointers"); + assert_eq!(format!("{err}"), "Rhai functions cannot return pointers"); } #[test] @@ -112,7 +109,7 @@ mod function_tests { let err = syn::parse2::(input_tokens).unwrap_err(); assert_eq!( - format!("{}", err), + format!("{err}"), "references from Rhai in this position must be mutable" ); } @@ -125,7 +122,7 @@ mod function_tests { let err = syn::parse2::(input_tokens).unwrap_err(); assert_eq!( - format!("{}", err), + format!("{err}"), "function parameters other than the first one cannot be passed by reference" ); } @@ -138,7 +135,7 @@ mod function_tests { let err = syn::parse2::(input_tokens).unwrap_err(); assert_eq!( - format!("{}", err), + format!("{err}"), "function parameters other than the first one cannot be passed by reference" ); } diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 9becd777..cc665678 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -107,7 +107,7 @@ mod mut_opaque_ref { StatusMessage { is_ok, os_code: Some(os_code), - message: format!("OS Code {}", os_code), + message: format!("OS Code {os_code}"), } } diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index a4657871..4be606a7 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -127,7 +127,7 @@ pub mod mut_opaque_ref_module { StatusMessage { is_ok, os_code: Some(os_code), - message: format!("OS Code {}", os_code), + message: format!("OS Code {os_code}"), } } diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index b84a0c26..64954d27 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -258,9 +258,8 @@ impl Engine { return Err(LexError::ImproperSymbol( s.to_string(), format!( - "Improper symbol for custom syntax at position #{}: '{}'", + "Improper symbol for custom syntax at position #{}: '{s}'", segments.len() + 1, - s ), ) .into_err(Position::NONE)); @@ -282,9 +281,8 @@ impl Engine { return Err(LexError::ImproperSymbol( s.to_string(), format!( - "Improper symbol for custom syntax at position #{}: '{}'", + "Improper symbol for custom syntax at position #{}: '{s}'", segments.len() + 1, - s ), ) .into_err(Position::NONE)); diff --git a/src/api/mod.rs b/src/api/mod.rs index 635082dd..b5120787 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -181,7 +181,7 @@ impl Engine { if self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved keyword", keyword)); + return Err(format!("'{keyword}' is a reserved keyword")); } } // Active standard symbols cannot be made custom @@ -189,7 +189,7 @@ impl Engine { if self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved operator", keyword)); + return Err(format!("'{keyword}' is a reserved operator")); } } // Active standard symbols cannot be made custom @@ -197,7 +197,7 @@ impl Engine { if self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(&*token.syntax()) => { - return Err(format!("'{}' is a reserved symbol", keyword)) + return Err(format!("'{keyword}' is a reserved symbol")) } // Disabled symbols are OK Some(_) => (), diff --git a/src/api/register.rs b/src/api/register.rs index 7a782229..3ebb8695 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -1037,7 +1037,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] for (name, m) in &self.global_sub_modules { - signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))); + signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}"))); } signatures.extend( diff --git a/src/api/type_names.rs b/src/api/type_names.rs index db63ffbd..7d802376 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -150,7 +150,7 @@ impl Engine { return if x == r { name.into() } else { - format!("&mut {}", r).into() + format!("&mut {r}").into() }; } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 193324fa..16269430 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -476,7 +476,7 @@ impl fmt::Debug for Expr { write!(f, "{}{}", x.1, Token::DoubleColon.literal_syntax())?; let pos = x.1.position(); if !pos.is_none() { - display_pos = format!(" @ {:?}", pos); + display_pos = format!(" @ {pos:?}"); } } f.write_str(&x.3)?; @@ -490,7 +490,7 @@ impl fmt::Debug for Expr { Self::Stmt(x) => { let pos = x.span(); if !pos.is_none() { - display_pos = format!(" @ {:?}", pos); + display_pos = format!(" @ {pos:?}"); } f.write_str("ExprStmtBlock")?; f.debug_list().entries(x.iter()).finish() @@ -498,7 +498,7 @@ impl fmt::Debug for Expr { Self::FnCall(x, ..) => fmt::Debug::fmt(x, f), Self::Index(x, options, pos) => { if !pos.is_none() { - display_pos = format!(" @ {:?}", pos); + display_pos = format!(" @ {pos:?}"); } let mut f = f.debug_struct("Index"); @@ -511,7 +511,7 @@ impl fmt::Debug for Expr { } Self::Dot(x, options, pos) => { if !pos.is_none() { - display_pos = format!(" @ {:?}", pos); + display_pos = format!(" @ {pos:?}"); } let mut f = f.debug_struct("Dot"); @@ -531,7 +531,7 @@ impl fmt::Debug for Expr { }; if !pos.is_none() { - display_pos = format!(" @ {:?}", pos); + display_pos = format!(" @ {pos:?}"); } f.debug_struct(op_name) diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index 56ed34c8..d51d877d 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -20,7 +20,7 @@ fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, let line = pos.line().unwrap() - 1; let start = if line >= window.0 { line - window.0 } else { 0 }; let end = usize::min(line + window.1, lines.len() - 1); - let line_no_len = format!("{}", end).len(); + let line_no_len = end.to_string().len(); // Print error position if start >= end { diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 43118f42..932bcf30 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -260,7 +260,7 @@ mod sample_functions { /// print(result); // prints "42 123" /// ``` pub fn test(x: INT, y: INT) -> String { - format!("{} {}", x, y) + format!("{x} {y}") } /// This is a sample method for integers. diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index 0ccc6006..ec4816d6 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -5,7 +5,7 @@ use std::{env, fs::File, io::Read, path::Path, process::exit}; fn eprint_error(input: &str, mut err: EvalAltResult) { fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) { let line = pos.line().unwrap(); - let line_no = format!("{}: ", line); + let line_no = format!("{line}: "); eprintln!("{}{}", line_no, lines[line - 1]); eprintln!( diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 071aa45b..9b338b78 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -82,7 +82,7 @@ impl Engine { let sep = crate::tokenizer::Token::DoubleColon.literal_syntax(); Err(ERR::ErrorVariableNotFound( - format!("{}{}{}", namespace, sep, var_name), + format!("{namespace}{sep}{var_name}"), namespace.position(), ) .into()) @@ -106,7 +106,7 @@ impl Engine { let sep = crate::tokenizer::Token::DoubleColon.literal_syntax(); return Err(ERR::ErrorVariableNotFound( - format!("{}{}{}", namespace, sep, var_name), + format!("{namespace}{sep}{var_name}"), namespace.position(), ) .into()); @@ -496,7 +496,7 @@ impl Engine { // The key should exist, unless the AST is compiled in a different Engine let custom_def = self.custom_syntax.get(key_token).ok_or_else(|| { Box::new(ERR::ErrorCustomSyntax( - format!("Invalid custom syntax prefix: {}", key_token), + format!("Invalid custom syntax prefix: {key_token}"), custom.tokens.iter().map(<_>::to_string).collect(), *pos, )) diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 86a3d0a9..fb2b1a2c 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -642,7 +642,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] if x > INT::MAX as usize { loop_result = Err(ERR::ErrorArithmetic( - format!("for-loop counter overflow: {}", x), + format!("for-loop counter overflow: {x}"), counter.pos, ) .into()); diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 2214cfda..8304567f 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -212,7 +212,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { let x = args[0].as_char().expect(BUILTIN); let y = &*args[1].read_lock::().expect(BUILTIN); - Ok(format!("{}{}", x, y).into()) + Ok(format!("{x}{y}").into()) }), "==" => Some(impl_op!(get_s1s2(==))), "!=" => Some(impl_op!(get_s1s2(!=))), @@ -496,7 +496,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option Some(|_, args| { let x = args[0].as_char().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); - Ok(format!("{}{}", x, y).into()) + Ok(format!("{x}{y}").into()) }), "==" => Some(impl_op!(char => as_char == as_char)), "!=" => Some(impl_op!(char => as_char != as_char)), @@ -809,8 +809,8 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio return match op { "+=" => Some(|_, args| { let y = args[1].as_char().expect(BUILTIN); - let mut x = args[0].write_lock::().expect(BUILTIN); - Ok((*x = format!("{}{}", *x, y).into()).into()) + let x = &mut *args[0].write_lock::().expect(BUILTIN); + Ok((*x = format!("{x}{y}").into()).into()) }), _ => None, }; diff --git a/src/func/call.rs b/src/func/call.rs index c6ff25c0..eb1bffda 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -118,7 +118,7 @@ pub fn ensure_no_data_race( .find(|(.., a)| a.is_locked()) { return Err(ERR::ErrorDataRace( - format!("argument #{} of function '{}'", n + 1, fn_name), + format!("argument #{} of function '{fn_name}'", n + 1), Position::NONE, ) .into()); @@ -150,10 +150,7 @@ impl Engine { let (ns, sep) = ("", ""); format!( - "{}{}{} ({})", - ns, - sep, - fn_name, + "{ns}{sep}{fn_name} ({})", args.iter() .map(|a| if a.is::() { "&str | ImmutableString | String" @@ -493,7 +490,7 @@ impl Engine { let t0 = self.map_type_name(args[0].type_name()); let t1 = self.map_type_name(args[1].type_name()); - Err(ERR::ErrorIndexingType(format!("{} [{}]", t0, t1), pos).into()) + Err(ERR::ErrorIndexingType(format!("{t0} [{t1}]"), pos).into()) } // index setter function not found? @@ -505,7 +502,7 @@ impl Engine { let t1 = self.map_type_name(args[1].type_name()); let t2 = self.map_type_name(args[2].type_name()); - Err(ERR::ErrorIndexingType(format!("{} [{}] = {}", t0, t1, t2), pos).into()) + Err(ERR::ErrorIndexingType(format!("{t0} [{t1}] = {t2}"), pos).into()) } // Getter function not found? @@ -518,8 +515,7 @@ impl Engine { Err(ERR::ErrorDotExpr( format!( - "Unknown property '{}' - a getter is not registered for type '{}'", - prop, t0 + "Unknown property '{prop}' - a getter is not registered for type '{t0}'" ), pos, ) @@ -537,8 +533,7 @@ impl Engine { Err(ERR::ErrorDotExpr( format!( - "No writable property '{}' - a setter is not registered for type '{}' to handle '{}'", - prop, t0, t1 + "No writable property '{prop}' - a setter is not registered for type '{t0}' to handle '{t1}'" ), pos, ) @@ -586,7 +581,7 @@ impl Engine { ) -> RhaiResultOf<(Dynamic, bool)> { fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> { Err(ERR::ErrorRuntime( - (format!("'{0}' should not be called this way. Try {0}(...);", name)).into(), + format!("'{name}' should not be called this way. Try {name}(...);").into(), pos, ) .into()) diff --git a/src/func/script.rs b/src/func/script.rs index 5202e61a..eeb41693 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -161,9 +161,9 @@ impl Engine { // Error in sub function call ERR::ErrorInFunctionCall(name, src, err, ..) => { let fn_name = if src.is_empty() { - format!("{} < {}", name, fn_def.name) + format!("{name} < {}", fn_def.name) } else { - format!("{} @ '{}' < {}", name, src, fn_def.name) + format!("{name} @ '{src}' < {}", fn_def.name) }; make_error(fn_name, fn_def, global, err, pos) } diff --git a/src/module/mod.rs b/src/module/mod.rs index 969d2d58..9507fc16 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -135,7 +135,7 @@ impl FuncInfo { return if r == x { typ.into() } else { - format!("&mut {}", r).into() + format!("&mut {r}").into() }; } @@ -202,7 +202,7 @@ impl FuncInfo { }; let result: std::borrow::Cow = match seg.next() { Some(typ) => { - format!("{}: {}", name, FuncInfo::format_type(typ, false)).into() + format!("{name}: {}", FuncInfo::format_type(typ, false)).into() } None => name.into(), }; @@ -2016,12 +2016,11 @@ impl Module { return Err(crate::ERR::ErrorMismatchDataType( "".to_string(), if fn_ptr.is_anonymous() { - format!("cannot export closure in variable {}", _name) + format!("cannot export closure in variable {_name}") } else { format!( - "cannot export function pointer to local function '{}' in variable {}", - fn_ptr.fn_name(), - _name + "cannot export function pointer to local function '{}' in variable {_name}", + fn_ptr.fn_name() ) }, crate::Position::NONE, diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 883b29e3..2347f451 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -24,7 +24,7 @@ macro_rules! gen_arithmetic_functions { #[rhai_fn(name = "+", return_raw)] pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { - x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))) + x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {x} + {y}"))) } else { Ok(x + y) } @@ -32,7 +32,7 @@ macro_rules! gen_arithmetic_functions { #[rhai_fn(name = "-", return_raw)] pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { - x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))) + x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {x} - {y}"))) } else { Ok(x - y) } @@ -40,7 +40,7 @@ macro_rules! gen_arithmetic_functions { #[rhai_fn(name = "*", return_raw)] pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { - x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))) + x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {x} * {y}"))) } else { Ok(x * y) } @@ -50,9 +50,9 @@ macro_rules! gen_arithmetic_functions { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == 0 { - Err(make_err(format!("Division by zero: {} / {}", x, y))) + Err(make_err(format!("Division by zero: {x} / {y}"))) } else { - x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))) + x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {x} / {y}"))) } } else { Ok(x / y) @@ -61,7 +61,7 @@ macro_rules! gen_arithmetic_functions { #[rhai_fn(name = "%", return_raw)] pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { - x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))) + x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}"))) } else { Ok(x % y) } @@ -70,11 +70,11 @@ macro_rules! gen_arithmetic_functions { pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y))) + Err(make_err(format!("Integer raised to too large an index: {x} ** {y}"))) } else if y < 0 { - Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y))) + Err(make_err(format!("Integer raised to a negative index: {x} ** {y}"))) } else { - x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {} ~ {}", x, y))) + x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}"))) } } else { Ok(x.pow(y as u32)) @@ -85,11 +85,11 @@ macro_rules! gen_arithmetic_functions { pub fn shift_left(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y))) + Err(make_err(format!("Left-shift by too many bits: {x} << {y}"))) } else if y < 0 { - Err(make_err(format!("Left-shift by a negative number: {} << {}", x, y))) + Err(make_err(format!("Left-shift by a negative number: {x} << {y}"))) } else { - x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {} << {}", x, y))) + x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {x} << {y}"))) } } else { Ok(x << y) @@ -99,11 +99,11 @@ macro_rules! gen_arithmetic_functions { pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) + Err(make_err(format!("Right-shift by too many bits: {x} >> {y}"))) } else if y < 0 { - Err(make_err(format!("Right-shift by a negative number: {} >> {}", x, y))) + Err(make_err(format!("Right-shift by a negative number: {x} >> {y}"))) } else { - x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) + x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {x} >> {y}"))) } } else { Ok(x >> y) @@ -151,7 +151,7 @@ macro_rules! gen_signed_functions { #[rhai_fn(name = "-", return_raw)] pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { - x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))) + x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{x}"))) } else { Ok(-x) } @@ -164,7 +164,7 @@ macro_rules! gen_signed_functions { #[rhai_fn(return_raw)] pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { - x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))) + x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{x}"))) } else { Ok(x.abs()) } @@ -372,8 +372,7 @@ mod f32_functions { pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( - "Number raised to too large an index: {} ~ {}", - x, y + "Number raised to too large an index: {x} ** {y}" ))) } else { Ok(x.powi(y as i32)) @@ -495,7 +494,7 @@ pub mod decimal_functions { pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_add(y) - .ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))) + .ok_or_else(|| make_err(format!("Addition overflow: {x} + {y}"))) } else { Ok(x + y) } @@ -504,7 +503,7 @@ pub mod decimal_functions { pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_sub(y) - .ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))) + .ok_or_else(|| make_err(format!("Subtraction overflow: {x} - {y}"))) } else { Ok(x - y) } @@ -513,7 +512,7 @@ pub mod decimal_functions { pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_mul(y) - .ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))) + .ok_or_else(|| make_err(format!("Multiplication overflow: {x} * {y}"))) } else { Ok(x * y) } @@ -523,10 +522,10 @@ pub mod decimal_functions { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == Decimal::zero() { - Err(make_err(format!("Division by zero: {} / {}", x, y))) + Err(make_err(format!("Division by zero: {x} / {y}"))) } else { x.checked_div(y) - .ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))) + .ok_or_else(|| make_err(format!("Division overflow: {x} / {y}"))) } } else { Ok(x / y) @@ -535,12 +534,8 @@ pub mod decimal_functions { #[rhai_fn(skip, return_raw)] pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { - x.checked_rem(y).ok_or_else(|| { - make_err(format!( - "Modulo division by zero or overflow: {} % {}", - x, y - )) - }) + x.checked_rem(y) + .ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}"))) } else { Ok(x % y) } @@ -549,7 +544,7 @@ pub mod decimal_functions { pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_powd(y) - .ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y))) + .ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}"))) } else { Ok(x.pow(y)) } diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 72aca603..36a5486e 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -49,24 +49,21 @@ mod core_functions { /// ``` #[rhai_fn(name = "set_tag", set = "tag", return_raw)] pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> { - if tag < Tag::MIN as INT { + const TAG_MIN: Tag = Tag::MIN; + const TAG_MAX: Tag = Tag::MAX; + + if tag < TAG_MIN as INT { Err(ERR::ErrorArithmetic( format!( - "{} is too small to fit into a tag (must be between {} and {})", - tag, - Tag::MIN, - Tag::MAX + "{tag} is too small to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})" ), Position::NONE, ) .into()) - } else if tag > Tag::MAX as INT { + } else if tag > TAG_MAX as INT { Err(ERR::ErrorArithmetic( format!( - "{} is too large to fit into a tag (must be between {} and {})", - tag, - Tag::MIN, - Tag::MAX + "{tag} is too large to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})" ), Position::NONE, ) @@ -281,10 +278,8 @@ fn collect_fn_metadata( .for_each(|(.., f)| list.push(make_metadata(dict, namespace.into(), f).into())); for (ns, m) in module.iter_sub_modules() { let ns = format!( - "{}{}{}", - namespace, - crate::tokenizer::Token::DoubleColon.literal_syntax(), - ns + "{namespace}{}{ns}", + crate::tokenizer::Token::DoubleColon.literal_syntax() ); scan_module(list, dict, &ns, &**m, filter); } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 604ef992..f7c3c40f 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -139,16 +139,14 @@ mod int_functions { #[rhai_fn(name = "parse_int", return_raw)] pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf { if !(2..=36).contains(&radix) { - return Err(ERR::ErrorArithmetic( - format!("Invalid radix: '{}'", radix), - Position::NONE, - ) - .into()); + return Err( + ERR::ErrorArithmetic(format!("Invalid radix: '{radix}'"), Position::NONE).into(), + ); } INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { ERR::ErrorArithmetic( - format!("Error parsing integer number '{}': {}", string, err), + format!("Error parsing integer number '{string}': {err}"), Position::NONE, ) .into() @@ -316,7 +314,7 @@ mod float_functions { pub fn f32_to_int(x: f32) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f32) || x < (INT::MIN as f32)) { Err( - ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) + ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) .into(), ) } else { @@ -328,7 +326,7 @@ mod float_functions { pub fn f64_to_int(x: f64) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f64) || x < (INT::MIN as f64)) { Err( - ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) + ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) .into(), ) } else { @@ -348,7 +346,7 @@ mod float_functions { pub fn parse_float(string: &str) -> RhaiResultOf { string.trim().parse::().map_err(|err| { ERR::ErrorArithmetic( - format!("Error parsing floating-point number '{}': {}", string, err), + format!("Error parsing floating-point number '{string}': {err}"), Position::NONE, ) .into() @@ -416,14 +414,14 @@ mod decimal_functions { #[rhai_fn(return_raw)] pub fn sqrt(x: Decimal) -> RhaiResultOf { x.sqrt() - .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) + .ok_or_else(|| make_err(format!("Error taking the square root of {x}"))) } /// Return the exponential of the decimal number. #[rhai_fn(return_raw)] pub fn exp(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_exp() - .ok_or_else(|| make_err(format!("Exponential overflow: e ** {}", x,))) + .ok_or_else(|| make_err(format!("Exponential overflow: e ** {x}"))) } else { Ok(x.exp()) } @@ -433,7 +431,7 @@ mod decimal_functions { pub fn ln(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_ln() - .ok_or_else(|| make_err(format!("Error taking the natural log of {}", x))) + .ok_or_else(|| make_err(format!("Error taking the natural log of {x}"))) } else { Ok(x.ln()) } @@ -443,7 +441,7 @@ mod decimal_functions { pub fn log10(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_log10() - .ok_or_else(|| make_err(format!("Error taking the log of {}", x))) + .ok_or_else(|| make_err(format!("Error taking the log of {x}"))) } else { Ok(x.log10()) } @@ -471,8 +469,7 @@ mod decimal_functions { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( - "Invalid number of digits for rounding: {}", - digits + "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { @@ -489,8 +486,7 @@ mod decimal_functions { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( - "Invalid number of digits for rounding: {}", - digits + "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { @@ -507,8 +503,7 @@ mod decimal_functions { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( - "Invalid number of digits for rounding: {}", - digits + "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { @@ -525,8 +520,7 @@ mod decimal_functions { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( - "Invalid number of digits for rounding: {}", - digits + "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { @@ -543,8 +537,7 @@ mod decimal_functions { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( - "Invalid number of digits for rounding: {}", - digits + "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { @@ -572,7 +565,7 @@ mod decimal_functions { match n { Some(n) => Ok(n), _ => Err(ERR::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), + format!("Integer overflow: to_int({x})"), Position::NONE, ) .into()), @@ -603,7 +596,7 @@ mod decimal_functions { .or_else(|_| Decimal::from_scientific(string)) .map_err(|err| { ERR::ErrorArithmetic( - format!("Error parsing decimal number '{}': {}", string, err), + format!("Error parsing decimal number '{string}': {err}"), Position::NONE, ) .into() @@ -616,7 +609,7 @@ mod decimal_functions { pub fn f32_to_decimal(x: f32) -> RhaiResultOf { Decimal::try_from(x).map_err(|_| { ERR::ErrorArithmetic( - format!("Cannot convert to Decimal: to_decimal({})", x), + format!("Cannot convert to Decimal: to_decimal({x})"), Position::NONE, ) .into() @@ -628,7 +621,7 @@ mod decimal_functions { pub fn f64_to_decimal(x: f64) -> RhaiResultOf { Decimal::try_from(x).map_err(|_| { ERR::ErrorArithmetic( - format!("Cannot convert to Decimal: to_decimal({})", x), + format!("Cannot convert to Decimal: to_decimal({x})"), Position::NONE, ) .into() @@ -640,7 +633,7 @@ mod decimal_functions { pub fn to_float(x: Decimal) -> RhaiResultOf { FLOAT::try_from(x).map_err(|_| { ERR::ErrorArithmetic( - format!("Cannot convert to floating-point: to_float({})", x), + format!("Cannot convert to floating-point: to_float({x})"), Position::NONE, ) .into() diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index a64317b2..6f8af72c 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -69,7 +69,7 @@ mod print_debug_functions { /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "to_debug", pure)] pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { - ctx.engine().map_type_name(&format!("{:?}", item)).into() + ctx.engine().map_type_name(&format!("{item:?}")).into() } /// Return the empty string. @@ -86,7 +86,7 @@ mod print_debug_functions { /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug", pure)] pub fn debug_string(string: &mut ImmutableString) -> ImmutableString { - format!("{:?}", string).into() + format!("{string:?}").into() } /// Return the character into a string. @@ -97,7 +97,7 @@ mod print_debug_functions { /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_char(character: char) -> ImmutableString { - format!("{:?}", character).into() + format!("{character:?}").into() } /// Convert the function pointer into a string in debug format. @@ -109,12 +109,12 @@ mod print_debug_functions { /// Return the boolean value into a string. #[rhai_fn(name = "print", name = "to_string")] pub fn print_bool(value: bool) -> ImmutableString { - format!("{}", value).into() + value.to_string().into() } /// Convert the boolean value into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_bool(value: bool) -> ImmutableString { - format!("{:?}", value).into() + format!("{value:?}").into() } /// Return the empty string. @@ -146,13 +146,15 @@ mod print_debug_functions { #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { - format!("{:?}", crate::ast::FloatWrapper::new(number)).into() + let number = crate::ast::FloatWrapper::new(number); + format!("{number:?}").into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { - format!("{:?}", crate::ast::FloatWrapper::new(number)).into() + let number = crate::ast::FloatWrapper::new(number); + format!("{number:?}").into() } /// Convert the array into a string. @@ -215,13 +217,13 @@ mod print_debug_functions { #[export_module] mod number_formatting { fn to_hex(value: T) -> ImmutableString { - format!("{:x}", value).into() + format!("{value:x}").into() } fn to_octal(value: T) -> ImmutableString { - format!("{:o}", value).into() + format!("{value:o}").into() } fn to_binary(value: T) -> ImmutableString { - format!("{:b}", value).into() + format!("{value:b}").into() } /// Convert the `value` into a string in hex format. diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index c1ceebb3..edbdb4a6 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -30,7 +30,7 @@ mod string_functions { if s.is_empty() { string.clone() } else { - format!("{}{}", string, s).into() + format!("{string}{s}").into() } } #[rhai_fn(name = "+=", name = "append")] @@ -38,7 +38,7 @@ mod string_functions { let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); if !s.is_empty() { - *string = format!("{}{}", string, s).into(); + *string = format!("{string}{s}").into(); } } #[rhai_fn(name = "+", pure)] @@ -68,7 +68,7 @@ mod string_functions { } #[rhai_fn(name = "+")] pub fn add_prepend_char(character: char, string: &str) -> ImmutableString { - format!("{}{}", character, string).into() + format!("{character}{string}").into() } #[rhai_fn(name = "+")] diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index b8bfa175..16ade536 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -66,8 +66,7 @@ mod time_functions { if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { Err(make_arithmetic_err(format!( - "Integer overflow for timestamp.elapsed: {}", - seconds + "Integer overflow for timestamp.elapsed: {seconds}" ))) } else if timestamp > Instant::now() { Err(make_arithmetic_err("Time-stamp is later than now")) @@ -94,8 +93,7 @@ mod time_functions { if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { Err(make_arithmetic_err(format!( - "Integer overflow for timestamp duration: -{}", - seconds + "Integer overflow for timestamp duration: -{seconds}" ))) } else { Ok((-(seconds as INT)).into()) @@ -105,8 +103,7 @@ mod time_functions { if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { Err(make_arithmetic_err(format!( - "Integer overflow for timestamp duration: {}", - seconds + "Integer overflow for timestamp duration: {seconds}" ))) } else { Ok((seconds as INT).into()) @@ -122,16 +119,14 @@ mod time_functions { } else if cfg!(not(feature = "unchecked")) { if seconds > (INT::MAX as FLOAT) { Err(make_arithmetic_err(format!( - "Integer overflow for timestamp add: {}", - seconds + "Integer overflow for timestamp add: {seconds}" ))) } else { timestamp .checked_add(Duration::from_millis((seconds * 1000.0) as u64)) .ok_or_else(|| { make_arithmetic_err(format!( - "Timestamp overflow when adding {} second(s)", - seconds + "Timestamp overflow when adding {seconds} second(s)" )) }) } @@ -145,16 +140,14 @@ mod time_functions { } else if cfg!(not(feature = "unchecked")) { if seconds > (INT::MAX as FLOAT) { Err(make_arithmetic_err(format!( - "Integer overflow for timestamp add: {}", - seconds + "Integer overflow for timestamp add: {seconds}" ))) } else { timestamp .checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) .ok_or_else(|| { make_arithmetic_err(format!( - "Timestamp overflow when adding {} second(s)", - seconds + "Timestamp overflow when adding {seconds} second(s)" )) }) } @@ -195,8 +188,7 @@ mod time_functions { .checked_add(Duration::from_secs(seconds as u64)) .ok_or_else(|| { make_arithmetic_err(format!( - "Timestamp overflow when adding {} second(s)", - seconds + "Timestamp overflow when adding {seconds} second(s)" )) }) } else { @@ -211,8 +203,7 @@ mod time_functions { .checked_sub(Duration::from_secs(seconds as u64)) .ok_or_else(|| { make_arithmetic_err(format!( - "Timestamp overflow when adding {} second(s)", - seconds + "Timestamp overflow when adding {seconds} second(s)" )) }) } else { diff --git a/src/parser.rs b/src/parser.rs index 663cbed1..f6674cda 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -536,7 +536,7 @@ impl Engine { Token::EOF => { return Err(PERR::MissingToken( Token::RightParen.into(), - format!("to close the arguments list of this function call '{}'", id), + format!("to close the arguments list of this function call '{id}'"), ) .into_err(*token_pos)) } @@ -677,7 +677,7 @@ impl Engine { (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightParen.into(), - format!("to close the arguments list of this function call '{}'", id), + format!("to close the arguments list of this function call '{id}'"), ) .into_err(*pos)) } @@ -687,7 +687,7 @@ impl Engine { (.., pos) => { return Err(PERR::MissingToken( Token::Comma.into(), - format!("to separate the arguments to function call '{}'", id), + format!("to separate the arguments to function call '{id}'"), ) .into_err(*pos)) } @@ -1011,10 +1011,7 @@ impl Engine { (.., pos) => { return Err(PERR::MissingToken( Token::Colon.into(), - format!( - "to follow the property '{}' in this object map literal", - name - ), + format!("to follow the property '{name}' in this object map literal"), ) .into_err(pos)) } @@ -1609,7 +1606,7 @@ impl Engine { ), // Cannot access to `this` as a variable not in a function scope _ if &*s == KEYWORD_THIS => { - let msg = format!("'{}' can only be used in functions", s); + let msg = format!("'{s}' can only be used in functions"); return Err( LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos) ); @@ -1862,9 +1859,7 @@ impl Engine { #[cfg(feature = "no_float")] return None; }) - .ok_or_else(|| { - LexError::MalformedNumber(format!("-{}", num)).into_err(pos) - }), + .ok_or_else(|| LexError::MalformedNumber(format!("-{num}")).into_err(pos)), // Negative float #[cfg(not(feature = "no_float"))] @@ -3479,7 +3474,7 @@ impl Engine { let mut params = StaticVec::new_const(); if !no_params { - let sep_err = format!("to separate the parameters of function '{}'", name); + let sep_err = format!("to separate the parameters of function '{name}'"); loop { match input.next().expect(NEVER_ENDS) { @@ -3497,7 +3492,7 @@ impl Engine { (.., pos) => { return Err(PERR::MissingToken( Token::RightParen.into(), - format!("to close the parameters list of function '{}'", name), + format!("to close the parameters list of function '{name}'"), ) .into_err(pos)) } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 885960a9..a13de181 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2406,7 +2406,7 @@ impl<'a> Iterator for TokenIterator<'a> { (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. (token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => { - let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token); + let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) }, // Reserved keyword/operator that is not custom. diff --git a/src/types/error.rs b/src/types/error.rs index 47fb8274..9c0133f4 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -341,7 +341,7 @@ impl EvalAltResult { pub(crate) fn dump_fields(&self, map: &mut crate::Map) { map.insert( "error".into(), - format!("{:?}", self) + format!("{self:?}") .split('(') .next() .expect("`ErrorXXX(...)`") diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index 5d4cb113..7ba2fc59 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -45,7 +45,7 @@ fn test_custom_syntax() -> Result<(), Box> { ">=" => count < max, "==" => count != max, "!=" => count == max, - _ => return Err(format!("Unsupported operator: {}", op).into()), + _ => return Err(format!("Unsupported operator: {op}").into()), }; if done { @@ -63,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box> { context .scope_mut() - .push(format!("{}{}", var_name, count), count); + .push(format!("{var_name}{count}"), count); let stop = !context .eval_expression_tree(condition)? @@ -189,7 +189,7 @@ fn test_custom_syntax() -> Result<(), Box> { context.scope_mut().set_value(var_name.to_string(), value); Ok(Dynamic::UNIT) } else { - Err(format!("variable {} is constant", var_name).into()) + Err(format!("variable {var_name} is constant").into()) } }, )?; diff --git a/tests/native.rs b/tests/native.rs index 22d005a1..ac5c75b1 100644 --- a/tests/native.rs +++ b/tests/native.rs @@ -68,11 +68,11 @@ fn test_native_overload() -> Result<(), Box> { .register_fn( "+", |s1: ImmutableString, s2: ImmutableString| -> ImmutableString { - format!("{}***{}", s1, s2).into() + format!("{s1}***{s2}").into() }, ) .register_fn("+", |s1: ImmutableString, _: ()| -> ImmutableString { - format!("{} Foo!", s1).into() + format!("{s1} Foo!").into() }); assert_eq!( diff --git a/tests/optimizer.rs b/tests/optimizer.rs index da8ecd68..d231540b 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -81,21 +81,21 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; assert_eq!( - format!("{:?}", ast), + format!("{ast:?}"), r#"AST { source: "", doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"# ); let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; assert_eq!( - format!("{:?}", ast), + format!("{ast:?}"), r#"AST { source: "", doc: "", resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?; assert_eq!( - format!("{:?}", ast), + format!("{ast:?}"), r#"AST { source: "", doc: "", resolver: None, body: [] }"# ); @@ -104,14 +104,14 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("abs(-42)")?; assert_eq!( - format!("{:?}", ast), + format!("{ast:?}"), r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# ); let ast = engine.compile("NUMBER")?; assert_eq!( - format!("{:?}", ast), + format!("{ast:?}"), r#"AST { source: "", doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"# ); @@ -123,7 +123,7 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("NUMBER")?; assert_eq!( - format!("{:?}", ast), + format!("{ast:?}"), r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# ); diff --git a/tests/plugins.rs b/tests/plugins.rs index 85833a54..a5355d07 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -70,7 +70,7 @@ macro_rules! reg_functions { } fn make_greeting(n: impl std::fmt::Display) -> String { - format!("{} kitties", n) + format!("{n} kitties") } gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String); diff --git a/tests/print.rs b/tests/print.rs index ac7dbcef..820cce58 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -39,14 +39,12 @@ fn test_print_debug() -> Result<(), Box> { let mut engine = Engine::new(); engine - .on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s))) + .on_print(move |s| log1.write().unwrap().push(format!("entry: {s}"))) .on_debug(move |s, src, pos| { - log2.write().unwrap().push(format!( - "DEBUG of {} at {:?}: {}", - src.unwrap_or("unknown"), - pos, - s - )) + let src = src.unwrap_or("unknown"); + log2.write() + .unwrap() + .push(format!("DEBUG of {src} at {pos:?}: {s}")) }); // Evaluate script diff --git a/tests/serde.rs b/tests/serde.rs index 2d32ed3d..5df11139 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -744,7 +744,7 @@ fn test_serde_json() -> serde_json::Result<()> { let a = m.remove("b").unwrap().cast::(); assert_eq!(a.len(), 3); - assert_eq!(format!("{:?}", a), "[1, 2, 3]"); + assert_eq!(format!("{a:?}"), "[1, 2, 3]"); Ok(()) } From bfc766f7259908be300d4a0f35ef9634cb6b035a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 11 Aug 2022 22:56:23 +0800 Subject: [PATCH 02/11] Use strings interner. --- src/packages/lang_core.rs | 65 +++++++-------------- src/parser.rs | 120 +++++++++++++++++++++----------------- src/types/interner.rs | 12 +++- 3 files changed, 100 insertions(+), 97 deletions(-) diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 36a5486e..c4ef23a8 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -1,6 +1,6 @@ use crate::def_package; use crate::plugin::*; -use crate::types::dynamic::Tag; +use crate::types::{dynamic::Tag, StringsInterner}; use crate::{Dynamic, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -130,54 +130,47 @@ fn collect_fn_metadata( + Copy, ) -> crate::Array { use crate::{ast::ScriptFnDef, Array, Identifier, Map}; - use std::collections::BTreeSet; // Create a metadata record for a function. fn make_metadata( - dict: &BTreeSet, + dict: &mut StringsInterner, #[cfg(not(feature = "no_module"))] namespace: Identifier, func: &ScriptFnDef, ) -> Map { - const DICT: &str = "key exists"; - let mut map = Map::new(); #[cfg(not(feature = "no_module"))] if !namespace.is_empty() { - map.insert(dict.get("namespace").expect(DICT).clone(), namespace.into()); + map.insert("namespace".into(), dict.get(namespace).into()); } + map.insert("name".into(), dict.get(&func.name).into()); map.insert( - dict.get("name").expect(DICT).clone(), - func.name.clone().into(), - ); - map.insert( - dict.get("access").expect(DICT).clone(), - match func.access { - FnAccess::Public => dict.get("public").expect(DICT).clone(), - FnAccess::Private => dict.get("private").expect(DICT).clone(), - } + "access".into(), + dict.get(match func.access { + FnAccess::Public => "public", + FnAccess::Private => "private", + }) .into(), ); map.insert( - dict.get("is_anonymous").expect(DICT).clone(), + "is_anonymous".into(), func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), ); map.insert( - dict.get("params").expect(DICT).clone(), + "params".into(), func.params .iter() - .cloned() - .map(Into::into) + .map(|p| dict.get(p).into()) .collect::() .into(), ); #[cfg(feature = "metadata")] if !func.comments.is_empty() { map.insert( - dict.get("comments").expect(DICT).clone(), + "comments".into(), func.comments .iter() - .map(|s| Into::into(&**s)) + .map(|s| dict.get(s).into()) .collect::() .into(), ); @@ -186,23 +179,7 @@ fn collect_fn_metadata( map } - // Intern strings - let dict: BTreeSet = [ - #[cfg(not(feature = "no_module"))] - "namespace", - "name", - "access", - "public", - "private", - "is_anonymous", - "params", - #[cfg(feature = "metadata")] - "comments", - ] - .iter() - .map(|&s| s.into()) - .collect(); - + let dict = &mut StringsInterner::new(); let mut list = Array::new(); ctx.iter_namespaces() @@ -211,7 +188,7 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - &dict, + dict, #[cfg(not(feature = "no_module"))] Identifier::new_const(), f, @@ -228,7 +205,7 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - &dict, + dict, #[cfg(not(feature = "no_module"))] Identifier::new_const(), f, @@ -246,7 +223,7 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - &dict, + dict, #[cfg(not(feature = "no_module"))] Identifier::new_const(), f, @@ -259,8 +236,8 @@ fn collect_fn_metadata( { // Recursively scan modules for script-defined functions. fn scan_module( + dict: &mut StringsInterner, list: &mut Array, - dict: &BTreeSet, namespace: &str, module: &Module, filter: impl Fn( @@ -281,12 +258,12 @@ fn collect_fn_metadata( "{namespace}{}{ns}", crate::tokenizer::Token::DoubleColon.literal_syntax() ); - scan_module(list, dict, &ns, &**m, filter); + scan_module(dict, list, &ns, &**m, filter); } } for (ns, m) in ctx.iter_imports_raw() { - scan_module(&mut list, &dict, ns, &**m, filter); + scan_module(dict, &mut list, ns, &**m, filter); } } diff --git a/src/parser.rs b/src/parser.rs index f6674cda..139fca19 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -237,20 +237,39 @@ impl<'e> ParseState<'e> { /// Get an interned identifier, creating one if it is not yet interned. #[inline(always)] #[must_use] - pub fn get_identifier(&mut self, prefix: impl AsRef, text: impl AsRef) -> Identifier { - self.interned_strings.get(prefix, text).into() + pub fn get_identifier(&mut self, text: impl AsRef) -> Identifier { + self.get_identifier_with_prefix("", text).into() + } + + /// Get an interned identifier, creating one if it is not yet interned. + #[inline(always)] + #[must_use] + pub fn get_identifier_with_prefix( + &mut self, + prefix: impl AsRef, + text: impl AsRef, + ) -> Identifier { + self.interned_strings.get_with_prefix(prefix, text).into() } /// Get an interned string, creating one if it is not yet interned. #[inline(always)] #[allow(dead_code)] #[must_use] - pub fn get_interned_string( + pub fn get_interned_string(&mut self, text: impl AsRef) -> ImmutableString { + self.get_interned_string_with_prefix("", text) + } + + /// Get an interned string, creating one if it is not yet interned. + #[inline(always)] + #[allow(dead_code)] + #[must_use] + pub fn get_interned_string_with_prefix( &mut self, prefix: impl AsRef, text: impl AsRef, ) -> ImmutableString { - self.interned_strings.get(prefix, text) + self.interned_strings.get_with_prefix(prefix, text) } } @@ -327,16 +346,16 @@ impl Expr { Self::Variable(x, ..) if !x.1.is_empty() => unreachable!("qualified property"), Self::Variable(x, .., pos) => { let ident = x.3; - let getter = state.get_identifier(crate::engine::FN_GET, &ident); + let getter = state.get_identifier_with_prefix(crate::engine::FN_GET, &ident); let hash_get = calc_fn_hash(&getter, 1); - let setter = state.get_identifier(crate::engine::FN_SET, &ident); + let setter = state.get_identifier_with_prefix(crate::engine::FN_SET, &ident); let hash_set = calc_fn_hash(&setter, 2); Self::Property( Box::new(( (getter, hash_get), (setter, hash_set), - state.get_interned_string("", &ident), + state.get_interned_string(&ident), )), pos, ) @@ -588,7 +607,7 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_identifier("", id), + name: state.get_identifier(id), capture_parent_scope, #[cfg(not(feature = "no_module"))] namespace, @@ -659,7 +678,7 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_identifier("", id), + name: state.get_identifier(id), capture_parent_scope, #[cfg(not(feature = "no_module"))] namespace, @@ -1027,7 +1046,7 @@ impl Engine { } let expr = self.parse_expr(input, state, lib, settings.level_up())?; - let name = state.get_identifier("", name); + let name = state.get_identifier(name); template.insert(name.clone(), crate::Dynamic::UNIT); map.push((Ident { name, pos }, expr)); @@ -1307,7 +1326,7 @@ impl Engine { 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), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1478,7 +1497,7 @@ impl Engine { } if segments.is_empty() { - Expr::StringConstant(state.get_interned_string("", ""), settings.pos) + Expr::StringConstant(state.get_interned_string(""), settings.pos) } else { segments.shrink_to_fit(); Expr::InterpolatedString(segments.into(), settings.pos) @@ -1527,7 +1546,7 @@ impl Engine { state.allow_capture = true; } Expr::Variable( - (None, ns, 0, state.get_identifier("", s)).into(), + (None, ns, 0, state.get_identifier(s)).into(), None, settings.pos, ) @@ -1541,7 +1560,7 @@ impl Engine { state.allow_capture = true; } Expr::Variable( - (None, ns, 0, state.get_identifier("", s)).into(), + (None, ns, 0, state.get_identifier(s)).into(), None, settings.pos, ) @@ -1568,7 +1587,7 @@ impl Engine { } }); Expr::Variable( - (index, ns, 0, state.get_identifier("", s)).into(), + (index, ns, 0, state.get_identifier(s)).into(), short_index, settings.pos, ) @@ -1592,7 +1611,7 @@ impl Engine { // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { Expr::Variable( - (None, ns, 0, state.get_identifier("", s)).into(), + (None, ns, 0, state.get_identifier(s)).into(), None, settings.pos, ) @@ -1600,7 +1619,7 @@ impl Engine { // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.in_fn_scope => Expr::Variable( - (None, ns, 0, state.get_identifier("", s)).into(), + (None, ns, 0, state.get_identifier(s)).into(), None, settings.pos, ), @@ -1727,7 +1746,7 @@ impl Engine { namespace.push(var_name_def); Expr::Variable( - (None, namespace, 0, state.get_identifier("", id2)).into(), + (None, namespace, 0, state.get_identifier(id2)).into(), None, pos2, ) @@ -1872,7 +1891,7 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("", "-"), + name: state.get_identifier("-"), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), args, pos, @@ -1899,7 +1918,7 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("", "+"), + name: state.get_identifier("+"), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), args, pos, @@ -1917,7 +1936,7 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("", "!"), + name: state.get_identifier("!"), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), args, pos, @@ -2292,7 +2311,7 @@ impl Engine { let hash = calc_fn_hash(&op, 2); let op_base = FnCallExpr { - name: state.get_identifier("", op), + name: state.get_identifier(op), hashes: FnCallHashes::from_native(hash), pos, ..Default::default() @@ -2364,7 +2383,7 @@ impl Engine { FnCallExpr { hashes: calc_fn_hash(OP_CONTAINS, 2).into(), args, - name: state.get_identifier("", OP_CONTAINS), + name: state.get_identifier(OP_CONTAINS), ..op_base } .into_fn_call_expr(pos) @@ -2423,7 +2442,7 @@ impl Engine { if syntax.scope_may_be_changed { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. - let marker = state.get_identifier("", SCOPE_SEARCH_BARRIER_MARKER); + let marker = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); state.stack.push(marker, ()); } @@ -2443,10 +2462,7 @@ impl Engine { if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => { - inputs.push(Expr::StringConstant( - state.get_interned_string("", seg), - pos, - )); + inputs.push(Expr::StringConstant(state.get_interned_string(seg), pos)); break; } Ok(Some(seg)) => seg, @@ -2457,7 +2473,7 @@ impl Engine { match required_token.as_str() { CUSTOM_SYNTAX_MARKER_IDENT => { let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier("", name); + let name = state.get_identifier(name); #[cfg(not(feature = "no_module"))] let ns = crate::ast::Namespace::NONE; @@ -2465,19 +2481,19 @@ impl Engine { let ns = (); segments.push(name.clone().into()); - tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_IDENT)); + tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT)); inputs.push(Expr::Variable((None, ns, 0, name).into(), None, pos)); } CUSTOM_SYNTAX_MARKER_SYMBOL => { let (symbol, pos) = parse_symbol(input)?; - let symbol = state.get_interned_string("", symbol); + let symbol = state.get_interned_string(symbol); segments.push(symbol.clone()); - tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_SYMBOL)); + tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL)); inputs.push(Expr::StringConstant(symbol, pos)); } CUSTOM_SYNTAX_MARKER_EXPR => { inputs.push(self.parse_expr(input, state, lib, settings)?); - let keyword = state.get_identifier("", CUSTOM_SYNTAX_MARKER_EXPR); + let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR); segments.push(keyword.clone().into()); tokens.push(keyword); } @@ -2485,7 +2501,7 @@ impl Engine { match self.parse_block(input, state, lib, settings)? { block @ Stmt::Block(..) => { inputs.push(Expr::Stmt(Box::new(block.into()))); - let keyword = state.get_identifier("", CUSTOM_SYNTAX_MARKER_BLOCK); + let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK); segments.push(keyword.clone().into()); tokens.push(keyword); } @@ -2495,8 +2511,8 @@ impl Engine { CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { (b @ (Token::True | Token::False), pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); - segments.push(state.get_interned_string("", b.literal_syntax())); - tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_BOOL)); + segments.push(state.get_interned_string(b.literal_syntax())); + tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL)); } (.., pos) => { return Err( @@ -2509,7 +2525,7 @@ impl Engine { (Token::IntegerConstant(i), pos) => { inputs.push(Expr::IntegerConstant(i, pos)); segments.push(i.to_string().into()); - tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_INT)); + tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT)); } (.., pos) => { return Err( @@ -2523,7 +2539,7 @@ impl Engine { (Token::FloatConstant(f), pos) => { inputs.push(Expr::FloatConstant(f, pos)); segments.push(f.to_string().into()); - tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_FLOAT)); + tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT)); } (.., pos) => { return Err(PERR::MissingSymbol( @@ -2534,10 +2550,10 @@ impl Engine { }, CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { - let s = state.get_interned_string("", s); + let s = state.get_interned_string(s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); - tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_STRING)); + tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING)); } (.., pos) => { return Err( @@ -2801,11 +2817,11 @@ impl Engine { state.stack.push(name.clone(), ()); } let counter_var = Ident { - name: state.get_identifier("", counter_name), + name: state.get_identifier(counter_name), pos: counter_pos, }; - let loop_var = state.get_identifier("", name); + let loop_var = state.get_identifier(name); state.stack.push(loop_var.clone(), ()); let loop_var = Ident { name: loop_var, @@ -2878,7 +2894,7 @@ impl Engine { } } - let name = state.get_identifier("", name); + let name = state.get_identifier(name); // let name = ... let expr = if match_token(input, Token::Equals).0 { @@ -2951,7 +2967,7 @@ impl Engine { // import expr as name ... let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier("", name); + let name = state.get_identifier(name); state.imports.push(name.clone()); Ok(Stmt::Import( @@ -3004,11 +3020,11 @@ impl Engine { let export = ( Ident { - name: state.get_identifier("", id), + name: state.get_identifier(id), pos: id_pos, }, Ident { - name: state.get_identifier("", alias.as_ref().map_or("", <_>::as_ref)), + name: state.get_identifier(alias.as_ref().map_or("", <_>::as_ref)), pos: alias_pos, }, ); @@ -3407,7 +3423,7 @@ impl Engine { .into_err(err_pos)); } - let name = state.get_identifier("", name); + let name = state.get_identifier(name); state.stack.push(name.clone(), ()); Ident { name, pos } } else { @@ -3484,7 +3500,7 @@ impl Engine { return Err(PERR::FnDuplicatedParam(name.to_string(), s.to_string()) .into_err(pos)); } - let s = state.get_identifier("", s); + let s = state.get_identifier(s); state.stack.push(s.clone(), ()); params.push((s, pos)); } @@ -3523,7 +3539,7 @@ impl Engine { params.shrink_to_fit(); Ok(ScriptFnDef { - name: state.get_identifier("", name), + name: state.get_identifier(name), access, params, body, @@ -3573,7 +3589,7 @@ impl Engine { ); let expr = FnCallExpr { - name: state.get_identifier("", crate::engine::KEYWORD_FN_PTR_CURRY), + name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native(calc_fn_hash( crate::engine::KEYWORD_FN_PTR_CURRY, num_externals + 1, @@ -3620,7 +3636,7 @@ impl Engine { return Err(PERR::FnDuplicatedParam("".to_string(), s.to_string()) .into_err(pos)); } - let s = state.get_identifier("", s); + let s = state.get_identifier(s); state.stack.push(s.clone(), ()); params_list.push(s); } @@ -3678,7 +3694,7 @@ impl Engine { params.iter().for_each(|p| p.hash(hasher)); body.hash(hasher); let hash = hasher.finish(); - let fn_name = state.get_identifier("", make_anonymous_fn(hash)); + let fn_name = state.get_identifier(make_anonymous_fn(hash)); // Define the function let script = ScriptFnDef { diff --git a/src/types/interner.rs b/src/types/interner.rs index f9e85acc..866593ce 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -36,6 +36,12 @@ impl StringsInterner<'_> { } } /// Get an identifier from a text string and prefix, adding it to the interner if necessary. + #[inline(always)] + #[must_use] + pub fn get(&mut self, text: impl AsRef) -> ImmutableString { + self.get_with_prefix("", text) + } + /// Get an identifier from a text string and prefix, adding it to the interner if necessary. /// /// # Prefix /// @@ -50,7 +56,11 @@ impl StringsInterner<'_> { /// Panics if the prefix is not recognized. #[inline] #[must_use] - pub fn get(&mut self, prefix: impl AsRef, text: impl AsRef) -> ImmutableString { + pub fn get_with_prefix( + &mut self, + prefix: impl AsRef, + text: impl AsRef, + ) -> ImmutableString { let prefix = prefix.as_ref(); let text = text.as_ref(); From ac1f661ab09e0e23d0fa8d730398e1d98f100802 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 09:59:48 +0800 Subject: [PATCH 03/11] Add std feature. --- .gitignore | 8 ++++++-- CHANGELOG.md | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index de54f385..61b621fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,11 @@ Cargo.lock .vscode/ .cargo/ benches/results -before* -after* .rhai-repl-history.txt clippy.toml +Rhai.toml +before* +after* +**/*.bat +doc/rhai-sync.json +doc/rhai.json diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2858b9..c9f14d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,9 @@ Bug fixes New features ------------ -### New feature flag +### New feature flags +* A new feature flag, `std`, which is enabled by default, is added due to requirements from dependency crates. * A new feature flag, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most). ### Module documentation From 8bbb382d9cdb2a34df6b1963be1270a34a6504f9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 10:34:09 +0800 Subject: [PATCH 04/11] Use hashing for strings interner. --- src/types/interner.rs | 49 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/types/interner.rs b/src/types/interner.rs index 866593ce..6fc37188 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -1,7 +1,14 @@ +use crate::func::hashing::get_hasher; use crate::{Identifier, ImmutableString}; + #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{collections::BTreeMap, marker::PhantomData, ops::AddAssign}; +use std::{ + collections::BTreeMap, + hash::{Hash, Hasher}, + marker::PhantomData, + ops::AddAssign, +}; /// _(internals)_ A factory of identifiers from text strings. /// Exported under the `internals` feature only. @@ -10,13 +17,13 @@ use std::{collections::BTreeMap, marker::PhantomData, ops::AddAssign}; #[derive(Debug, Clone, Default, Hash)] pub struct StringsInterner<'a> { /// Normal strings. - strings: BTreeMap, + strings: BTreeMap, /// Property getters. #[cfg(not(feature = "no_object"))] - getters: BTreeMap, + getters: BTreeMap, /// Property setters. #[cfg(not(feature = "no_object"))] - setters: BTreeMap, + setters: BTreeMap, /// Take care of the lifetime parameter. dummy: PhantomData<&'a ()>, } @@ -35,12 +42,14 @@ impl StringsInterner<'_> { dummy: PhantomData, } } + /// Get an identifier from a text string and prefix, adding it to the interner if necessary. #[inline(always)] #[must_use] pub fn get(&mut self, text: impl AsRef) -> ImmutableString { self.get_with_prefix("", text) } + /// Get an identifier from a text string and prefix, adding it to the interner if necessary. /// /// # Prefix @@ -75,14 +84,40 @@ impl StringsInterner<'_> { _ => unreachable!("unsupported prefix {}", prefix), }; - if !dict.is_empty() && dict.contains_key(text) { - dict.get(text).unwrap().clone() + let hasher = &mut get_hasher(); + text.hash(hasher); + let key = hasher.finish(); + + if !dict.is_empty() && dict.contains_key(&key) { + dict.get(&key).unwrap().clone() } else { let value: ImmutableString = mapper(text).into(); - dict.insert(text.into(), value.clone()); + dict.insert(key, value.clone()); value } } + + /// Number of strings interned. + #[inline(always)] + #[must_use] + pub fn len(&self) -> usize { + #[cfg(not(feature = "no_object"))] + return self.strings.len() + self.getters.len() + self.setters.len(); + + #[cfg(feature = "no_object")] + return self.strings.len(); + } + + /// Number of strings interned. + #[inline(always)] + #[must_use] + pub fn is_empty(&self) -> bool { + #[cfg(not(feature = "no_object"))] + return self.strings.is_empty() || self.getters.is_empty() || self.setters.is_empty(); + + #[cfg(feature = "no_object")] + return self.strings.is_empty(); + } } impl AddAssign for StringsInterner<'_> { From 5ba9b3bd1cc96ff14dbabef21c265a4d2d0d031c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 12:48:35 +0800 Subject: [PATCH 05/11] Optimize Dynamic::is. --- src/types/dynamic.rs | 49 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index aeb3eaea..b4deb26d 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -346,11 +346,52 @@ impl Dynamic { #[inline] #[must_use] pub fn is(&self) -> bool { - if TypeId::of::() == TypeId::of::() { - self.type_id() == TypeId::of::() - } else { - self.type_id() == TypeId::of::() + if TypeId::of::() == TypeId::of::<()>() { + return matches!(self.0, Union::Unit(..)); } + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Bool(..)); + } + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Char(..)); + } + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Int(..)); + } + #[cfg(not(feature = "no_float"))] + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Float(..)); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::() + { + return matches!(self.0, Union::Str(..)); + } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Array(..)); + } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Blob(..)); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Map(..)); + } + #[cfg(not(feature = "decimal"))] + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::Decimal(..)); + } + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::FnPtr(..)); + } + #[cfg(not(feature = "no_std"))] + if TypeId::of::() == TypeId::of::() { + return matches!(self.0, Union::TimeStamp(..)); + } + + TypeId::of::() == self.type_id() } /// Get the [`TypeId`] of the value held by this [`Dynamic`]. /// From cba394d73c672a743dcd99df965b69fa7f80f0b1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 16:34:57 +0800 Subject: [PATCH 06/11] Revise strings interning. --- src/api/compile.rs | 4 +- src/api/eval.rs | 2 +- src/api/json.rs | 2 +- src/api/run.rs | 2 +- src/engine.rs | 44 +++++------ src/eval/expr.rs | 7 +- src/eval/stmt.rs | 48 +++++------- src/func/builtin.rs | 8 +- src/optimizer.rs | 2 +- src/packages/lang_core.rs | 2 +- src/packages/string_basic.rs | 4 +- src/packages/string_more.rs | 31 ++++++-- src/parser.rs | 54 ++++++++++---- src/types/immutable_string.rs | 51 +++++++++++-- src/types/interner.rs | 134 +++++++++++++++++++++++++++------- 15 files changed, 272 insertions(+), 123 deletions(-) diff --git a/src/api/compile.rs b/src/api/compile.rs index 78424402..73a3bf21 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -221,7 +221,7 @@ impl Engine { scripts.as_ref(), self.token_mapper.as_ref().map(<_>::as_ref), ); - let mut state = ParseState::new(self, scope, tokenizer_control); + let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); let mut _ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?; #[cfg(feature = "metadata")] _ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n")); @@ -294,7 +294,7 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); let mut peekable = stream.peekable(); - let mut state = ParseState::new(self, scope, tokenizer_control); + let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); self.parse_global_expr(&mut peekable, &mut state, self.optimization_level) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index cfa24a8f..4b22d99c 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -116,7 +116,7 @@ impl Engine { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); - let mut state = ParseState::new(self, scope, tokenizer_control); + let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); // No need to optimize a lone expression let ast = self.parse_global_expr( diff --git a/src/api/json.rs b/src/api/json.rs index d5fb38b9..ae00bbed 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -120,7 +120,7 @@ impl Engine { ); let scope = Scope::new(); - let mut state = ParseState::new(self, &scope, tokenizer_control); + let mut state = ParseState::new(self, &scope, Default::default(), tokenizer_control); let ast = self.parse_global_expr( &mut stream.peekable(), diff --git a/src/api/run.rs b/src/api/run.rs index c6bd0b54..b361200a 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -58,7 +58,7 @@ impl Engine { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); - let mut state = ParseState::new(self, scope, tokenizer_control); + let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; self.run_ast_with_scope(scope, &ast) } diff --git a/src/engine.rs b/src/engine.rs index c9c12eef..265a3aa0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,14 +2,15 @@ use crate::api::options::LangOptions; use crate::func::native::{ - OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, + locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, + OnVarCallback, }; use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; -use crate::types::dynamic::Union; +use crate::types::StringsInterner; use crate::{ - Dynamic, Identifier, ImmutableString, Module, OptimizationLevel, Position, RhaiResult, Shared, - StaticVec, + Dynamic, Identifier, ImmutableString, Locked, Module, OptimizationLevel, Position, RhaiResult, + Shared, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -105,7 +106,7 @@ pub struct Engine { pub(crate) module_resolver: Box, /// An empty [`ImmutableString`] for cloning purposes. - pub(crate) empty_string: ImmutableString, + pub(crate) interned_strings: Locked>, /// A set of symbols to disable. pub(crate) disabled_symbols: BTreeSet, @@ -269,7 +270,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()), - empty_string: ImmutableString::new(), + interned_strings: StringsInterner::new().into(), disabled_symbols: BTreeSet::new(), #[cfg(not(feature = "no_custom_syntax"))] custom_keywords: BTreeMap::new(), @@ -310,30 +311,21 @@ impl Engine { engine } - /// Get an empty [`ImmutableString`]. - /// - /// [`Engine`] keeps a single instance of an empty [`ImmutableString`] and uses this to create - /// shared instances for subsequent uses. This minimizes unnecessary allocations for empty strings. - #[inline(always)] + /// Get an interned string. #[must_use] - pub fn const_empty_string(&self) -> ImmutableString { - self.empty_string.clone() + #[inline(always)] + pub(crate) fn get_interned_string( + &self, + string: impl AsRef + Into, + ) -> ImmutableString { + locked_write(&self.interned_strings).get(string).into() } /// Check a result to ensure that it is valid. - pub(crate) fn check_return_value(&self, mut result: RhaiResult, _pos: Position) -> RhaiResult { - if let Ok(ref mut r) = result { - // Concentrate all empty strings into one instance to save memory - if let Dynamic(Union::Str(s, ..)) = r { - if s.is_empty() { - if !s.ptr_eq(&self.empty_string) { - *s = self.const_empty_string(); - } - return result; - } - } - - #[cfg(not(feature = "unchecked"))] + #[inline] + pub(crate) fn check_return_value(&self, result: RhaiResult, _pos: Position) -> RhaiResult { + #[cfg(not(feature = "unchecked"))] + if let Ok(ref r) = result { self.check_data_size(r, _pos)?; } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 9b338b78..49ca5292 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -328,7 +328,7 @@ impl Engine { // `... ${...} ...` Expr::InterpolatedString(x, _) => { - let mut concat = self.const_empty_string().into(); + let mut concat = self.get_interned_string("").into(); let target = &mut concat; let mut result = Ok(Dynamic::UNIT); @@ -355,7 +355,10 @@ impl Engine { } } - result.map(|_| concat.take_or_clone()) + self.check_return_value( + result.map(|_| concat.take_or_clone()), + expr.start_position(), + ) } #[cfg(not(feature = "no_index"))] diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index fb2b1a2c..45ed998a 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -7,7 +7,9 @@ use crate::ast::{ }; use crate::func::get_hasher; use crate::types::dynamic::{AccessMode, Union}; -use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT}; +use crate::{ + Dynamic, Engine, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT, +}; use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -137,23 +139,10 @@ impl Engine { pos: op_pos, } = op_info; - let mut lock_guard; - let lhs_ptr_inner; - - #[cfg(not(feature = "no_closure"))] - let target_is_shared = target.is_shared(); - #[cfg(feature = "no_closure")] - let target_is_shared = false; - - if target_is_shared { - lock_guard = target.write_lock::().unwrap(); - lhs_ptr_inner = &mut *lock_guard; - } else { - lhs_ptr_inner = &mut *target; - } + let mut lock_guard = target.write_lock::().unwrap(); let hash = hash_op_assign; - let args = &mut [lhs_ptr_inner, &mut new_val]; + let args = &mut [&mut *lock_guard, &mut new_val]; let level = level + 1; match self.call_native_fn( @@ -181,21 +170,17 @@ impl Engine { } } else { // Normal assignment + *target.write_lock::().unwrap() = new_val; + } - #[cfg(not(feature = "no_closure"))] - if target.is_shared() { - // Handle case where target is a `Dynamic` shared value - // (returned by a variable resolver, for example) - *target.write_lock::().unwrap() = new_val; - } else { - *target.as_mut() = new_val; - } - - #[cfg(feature = "no_closure")] - { - *target.as_mut() = new_val; + /* + if let Some(mut guard) = target.write_lock::() { + if guard.is::() { + let s = std::mem::take(&mut *guard).cast::(); + *guard = self.get_interned_string(s).into(); } } + */ target.propagate_changed_value(op_info.pos) } @@ -301,6 +286,13 @@ impl Engine { .map(Dynamic::flatten); if let Ok(rhs_val) = rhs_result { + let rhs_val = if rhs_val.is::() { + self.get_interned_string(rhs_val.cast::()) + .into() + } else { + rhs_val + }; + let _new_val = Some((rhs_val, *op_info)); // Must be either `var[index] op= val` or `var.prop op= val` diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 8304567f..4856c61f 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -820,14 +820,14 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio return match op { "+=" => Some(|_, args| { let (first, second) = args.split_first_mut().expect(BUILTIN); - let mut x = first.write_lock::().expect(BUILTIN); - let y = &*second[0].read_lock::().expect(BUILTIN); + let x = &mut *first.write_lock::().expect(BUILTIN); + let y = std::mem::take(second[0]).cast::(); Ok((*x += y).into()) }), "-=" => Some(|_, args| { let (first, second) = args.split_first_mut().expect(BUILTIN); - let mut x = first.write_lock::().expect(BUILTIN); - let y = &*second[0].read_lock::().expect(BUILTIN); + let x = &mut *first.write_lock::().expect(BUILTIN); + let y = std::mem::take(second[0]).cast::(); Ok((*x -= y).into()) }), _ => None, diff --git a/src/optimizer.rs b/src/optimizer.rs index 81264050..c45798a0 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1050,7 +1050,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // `` Expr::InterpolatedString(x, pos) if x.is_empty() => { state.set_dirty(); - *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos); + *expr = Expr::StringConstant(state.engine.get_interned_string(""), *pos); } // `... ${const} ...` Expr::InterpolatedString(..) if expr.is_constant() => { diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index c4ef23a8..e2a1b62c 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -170,7 +170,7 @@ fn collect_fn_metadata( "comments".into(), func.comments .iter() - .map(|s| dict.get(s).into()) + .map(|s| dict.get(s.as_ref()).into()) .collect::() .into(), ); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 6f8af72c..d8c3845a 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -75,7 +75,7 @@ mod print_debug_functions { /// Return the empty string. #[rhai_fn(name = "print", name = "debug")] pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { - ctx.engine().const_empty_string() + ctx.engine().get_interned_string("") } /// Return the `string`. @@ -121,7 +121,7 @@ mod print_debug_functions { #[rhai_fn(name = "print", name = "to_string")] pub fn print_unit(ctx: NativeCallContext, unit: ()) -> ImmutableString { let _ = unit; - ctx.engine().const_empty_string() + ctx.engine().get_interned_string("") } /// Convert the unit into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug")] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index edbdb4a6..0d476a87 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -59,7 +59,10 @@ mod string_functions { // The following are needed in order to override the generic versions with `Dynamic` parameters. #[rhai_fn(name = "+", pure)] - pub fn add_append_str(string1: &mut ImmutableString, string2: &str) -> ImmutableString { + pub fn add_append_str( + string1: &mut ImmutableString, + string2: ImmutableString, + ) -> ImmutableString { &*string1 + string2 } #[rhai_fn(name = "+", pure)] @@ -81,6 +84,20 @@ mod string_functions { string } + #[rhai_fn(name = "+=")] + pub fn add_assign_append_str(string1: &mut ImmutableString, string2: ImmutableString) { + *string1 += string2 + } + #[rhai_fn(name = "+=", pure)] + pub fn add_assign_append_char(string: &mut ImmutableString, character: char) { + *string += character + } + #[rhai_fn(name = "+=")] + pub fn add_assign_append_unit(string: &mut ImmutableString, item: ()) { + let _ = string; + let _ = item; + } + #[cfg(not(feature = "no_index"))] pub mod blob_functions { use crate::Blob; @@ -320,7 +337,7 @@ mod string_functions { len: INT, ) -> ImmutableString { if string.is_empty() || len <= 0 { - return ctx.engine().const_empty_string(); + return ctx.engine().get_interned_string(""); } let mut chars = StaticVec::::with_capacity(len as usize); @@ -803,13 +820,13 @@ mod string_functions { len: INT, ) -> ImmutableString { if string.is_empty() { - return ctx.engine().const_empty_string(); + return ctx.engine().get_interned_string(""); } let mut chars = StaticVec::with_capacity(string.len()); let offset = if string.is_empty() || len <= 0 { - return ctx.engine().const_empty_string(); + return ctx.engine().get_interned_string(""); } else if start < 0 { let abs_start = start.unsigned_abs() as usize; chars.extend(string.chars()); @@ -819,7 +836,7 @@ mod string_functions { chars.len() - abs_start } } else if start as usize >= string.chars().count() { - return ctx.engine().const_empty_string(); + return ctx.engine().get_interned_string(""); } else { start as usize }; @@ -865,7 +882,7 @@ mod string_functions { start: INT, ) -> ImmutableString { if string.is_empty() { - ctx.engine().const_empty_string() + ctx.engine().get_interned_string("") } else { let len = string.len() as INT; sub_string(ctx, string, start, len) @@ -1245,7 +1262,7 @@ mod string_functions { let num_chars = string.chars().count(); if abs_index > num_chars { vec![ - ctx.engine().const_empty_string().into(), + ctx.engine().get_interned_string("").into(), string.as_str().into(), ] } else { diff --git a/src/parser.rs b/src/parser.rs index 139fca19..44116d33 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -106,7 +106,12 @@ impl<'e> ParseState<'e> { /// Create a new [`ParseState`]. #[inline(always)] #[must_use] - pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self { + pub fn new( + engine: &Engine, + scope: &'e Scope, + interned_strings: StringsInterner<'e>, + tokenizer_control: TokenizerControl, + ) -> Self { Self { tokenizer_control, expr_filter: |_| true, @@ -114,7 +119,7 @@ impl<'e> ParseState<'e> { external_vars: Vec::new(), #[cfg(not(feature = "no_closure"))] allow_capture: true, - interned_strings: StringsInterner::new(), + interned_strings, scope, global: GlobalRuntimeState::new(engine), stack: Scope::new(), @@ -237,7 +242,7 @@ impl<'e> ParseState<'e> { /// Get an interned identifier, creating one if it is not yet interned. #[inline(always)] #[must_use] - pub fn get_identifier(&mut self, text: impl AsRef) -> Identifier { + pub fn get_identifier(&mut self, text: impl AsRef + Into) -> Identifier { self.get_identifier_with_prefix("", text).into() } @@ -247,7 +252,7 @@ impl<'e> ParseState<'e> { pub fn get_identifier_with_prefix( &mut self, prefix: impl AsRef, - text: impl AsRef, + text: impl AsRef + Into, ) -> Identifier { self.interned_strings.get_with_prefix(prefix, text).into() } @@ -256,7 +261,10 @@ impl<'e> ParseState<'e> { #[inline(always)] #[allow(dead_code)] #[must_use] - pub fn get_interned_string(&mut self, text: impl AsRef) -> ImmutableString { + pub fn get_interned_string( + &mut self, + text: impl AsRef + Into, + ) -> ImmutableString { self.get_interned_string_with_prefix("", text) } @@ -267,7 +275,7 @@ impl<'e> ParseState<'e> { pub fn get_interned_string_with_prefix( &mut self, prefix: impl AsRef, - text: impl AsRef, + text: impl AsRef + Into, ) -> ImmutableString { self.interned_strings.get_with_prefix(prefix, text) } @@ -1372,8 +1380,14 @@ impl Engine { // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { - let mut new_state = - ParseState::new(self, state.scope, state.tokenizer_control.clone()); + let interned_strings = std::mem::take(&mut state.interned_strings); + + let mut new_state = ParseState::new( + self, + state.scope, + interned_strings, + state.tokenizer_control.clone(), + ); #[cfg(not(feature = "no_module"))] { @@ -1415,7 +1429,11 @@ impl Engine { ..settings }; - let (expr, func) = self.parse_anon_fn(input, &mut new_state, lib, new_settings)?; + let result = self.parse_anon_fn(input, &mut new_state, lib, new_settings); + + state.interned_strings = new_state.interned_strings; + + let (expr, func) = result?; #[cfg(not(feature = "no_closure"))] new_state.external_vars.iter().try_for_each( @@ -2311,7 +2329,7 @@ impl Engine { let hash = calc_fn_hash(&op, 2); let op_base = FnCallExpr { - name: state.get_identifier(op), + name: state.get_identifier(op.as_ref()), hashes: FnCallHashes::from_native(hash), pos, ..Default::default() @@ -3233,8 +3251,14 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { - let mut new_state = - ParseState::new(self, state.scope, state.tokenizer_control.clone()); + let interned_strings = std::mem::take(&mut state.interned_strings); + + let mut new_state = ParseState::new( + self, + state.scope, + interned_strings, + state.tokenizer_control.clone(), + ); #[cfg(not(feature = "no_module"))] { @@ -3280,7 +3304,11 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments, - )?; + ); + + state.interned_strings = new_state.interned_strings; + + let func = func?; let hash = calc_fn_hash(&func.name, func.params.len()); diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index b668ee15..5b4fefa5 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -82,7 +82,7 @@ impl Borrow for ImmutableString { impl Borrow for ImmutableString { #[inline(always)] fn borrow(&self) -> &str { - self.0.as_str() + self.as_str() } } @@ -187,14 +187,14 @@ impl FromIterator for ImmutableString { impl fmt::Display for ImmutableString { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.0.as_str(), f) + fmt::Display::fmt(self.as_str(), f) } } impl fmt::Debug for ImmutableString { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self.0.as_str(), f) + fmt::Debug::fmt(self.as_str(), f) } } @@ -208,7 +208,7 @@ impl Add for ImmutableString { } else if self.is_empty() { rhs } else { - self.make_mut().push_str(rhs.0.as_str()); + self.make_mut().push_str(rhs.as_str()); self } } @@ -225,7 +225,40 @@ impl Add for &ImmutableString { rhs.clone() } else { let mut s = self.clone(); - s.make_mut().push_str(rhs.0.as_str()); + s.make_mut().push_str(rhs.as_str()); + s + } + } +} + +impl Add<&Self> for ImmutableString { + type Output = Self; + + #[inline] + fn add(mut self, rhs: &Self) -> Self::Output { + if rhs.is_empty() { + self + } else if self.is_empty() { + rhs.clone() + } else { + self.make_mut().push_str(rhs.as_str()); + self + } + } +} + +impl Add for &ImmutableString { + type Output = ImmutableString; + + #[inline] + fn add(self, rhs: ImmutableString) -> Self::Output { + if rhs.is_empty() { + self.clone() + } else if self.is_empty() { + rhs + } else { + let mut s = self.clone(); + s.make_mut().push_str(rhs.as_str()); s } } @@ -238,7 +271,7 @@ impl AddAssign<&ImmutableString> for ImmutableString { if self.is_empty() { self.0 = rhs.0.clone(); } else { - self.make_mut().push_str(rhs.0.as_str()); + self.make_mut().push_str(rhs.as_str()); } } } @@ -251,7 +284,7 @@ impl AddAssign for ImmutableString { if self.is_empty() { self.0 = rhs.0; } else { - self.make_mut().push_str(rhs.0.as_str()); + self.make_mut().push_str(rhs.as_str()); } } } @@ -580,6 +613,10 @@ impl ImmutableString { pub fn new() -> Self { Self(SmartString::new_const().into()) } + /// Strong count of references to the underlying string. + pub(crate) fn strong_count(&self) -> usize { + Shared::strong_count(&self.0) + } /// Consume the [`ImmutableString`] and convert it into a [`String`]. /// /// If there are other references to the same string, a cloned copy is returned. diff --git a/src/types/interner.rs b/src/types/interner.rs index 6fc37188..bc31fe6c 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -1,5 +1,5 @@ use crate::func::hashing::get_hasher; -use crate::{Identifier, ImmutableString}; +use crate::ImmutableString; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -10,12 +10,20 @@ use std::{ ops::AddAssign, }; +/// Maximum number of strings interned. +pub const MAX_INTERNED_STRINGS: usize = 256; + +/// Maximum length of strings interned. +pub const MAX_STRING_LEN: usize = 24; + /// _(internals)_ A factory of identifiers from text strings. /// Exported under the `internals` feature only. /// /// Normal identifiers, property getters and setters are interned separately. -#[derive(Debug, Clone, Default, Hash)] +#[derive(Debug, Clone, Hash)] pub struct StringsInterner<'a> { + /// Maximum capacity. + max: usize, /// Normal strings. strings: BTreeMap, /// Property getters. @@ -28,12 +36,27 @@ pub struct StringsInterner<'a> { dummy: PhantomData<&'a ()>, } +impl Default for StringsInterner<'_> { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + impl StringsInterner<'_> { /// Create a new [`StringsInterner`]. - #[inline] + #[inline(always)] #[must_use] pub fn new() -> Self { + Self::new_with_capacity(MAX_INTERNED_STRINGS) + } + + /// Create a new [`StringsInterner`] with a maximum capacity. + #[inline] + #[must_use] + pub fn new_with_capacity(capacity: usize) -> Self { Self { + max: capacity, strings: BTreeMap::new(), #[cfg(not(feature = "no_object"))] getters: BTreeMap::new(), @@ -46,7 +69,7 @@ impl StringsInterner<'_> { /// Get an identifier from a text string and prefix, adding it to the interner if necessary. #[inline(always)] #[must_use] - pub fn get(&mut self, text: impl AsRef) -> ImmutableString { + pub fn get(&mut self, text: impl AsRef + Into) -> ImmutableString { self.get_with_prefix("", text) } @@ -65,40 +88,81 @@ impl StringsInterner<'_> { /// Panics if the prefix is not recognized. #[inline] #[must_use] - pub fn get_with_prefix( + pub fn get_with_prefix + Into>( &mut self, prefix: impl AsRef, - text: impl AsRef, + text: T, ) -> ImmutableString { let prefix = prefix.as_ref(); - let text = text.as_ref(); + let key = text.as_ref(); - let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix { + // Do not intern numbers + if prefix == "" && key.bytes().all(|c| c == b'.' || (c >= b'0' && c <= b'9')) { + return text.into(); + } + + let (dict, mapper): (_, fn(T) -> ImmutableString) = match prefix { "" => (&mut self.strings, |s| s.into()), #[cfg(not(feature = "no_object"))] - crate::engine::FN_GET => (&mut self.getters, crate::engine::make_getter), + crate::engine::FN_GET => (&mut self.getters, |s| { + crate::engine::make_getter(s.as_ref()).into() + }), #[cfg(not(feature = "no_object"))] - crate::engine::FN_SET => (&mut self.setters, crate::engine::make_setter), + crate::engine::FN_SET => (&mut self.setters, |s| { + crate::engine::make_setter(s.as_ref()).into() + }), _ => unreachable!("unsupported prefix {}", prefix), }; + if key.len() > MAX_STRING_LEN { + return mapper(text); + } + let hasher = &mut get_hasher(); - text.hash(hasher); + key.hash(hasher); let key = hasher.finish(); if !dict.is_empty() && dict.contains_key(&key) { - dict.get(&key).unwrap().clone() - } else { - let value: ImmutableString = mapper(text).into(); - dict.insert(key, value.clone()); - value + return dict.get(&key).unwrap().clone(); } + + let value = mapper(text); + + if value.strong_count() > 1 { + return value; + } + + dict.insert(key, value.clone()); + + println!("Interning '{value}'"); + + // If the interner is over capacity, remove the longest entry + if self.strings.len() > self.max { + // Leave some buffer to grow when shrinking the cache. + // We leave at least two entries, one for the empty string, and one for the string + // that has just been inserted. + let max = if self.max < 5 { 2 } else { self.max - 3 }; + + while self.strings.len() > max { + let (_, n) = self.strings.iter().fold((0, 0), |(x, n), (&k, v)| { + if k != key && v.len() > x { + (v.len(), k) + } else { + (x, n) + } + }); + + self.strings.remove(&n); + } + } + + value } /// Number of strings interned. - #[inline(always)] + #[inline] #[must_use] pub fn len(&self) -> usize { #[cfg(not(feature = "no_object"))] @@ -109,7 +173,7 @@ impl StringsInterner<'_> { } /// Number of strings interned. - #[inline(always)] + #[inline] #[must_use] pub fn is_empty(&self) -> bool { #[cfg(not(feature = "no_object"))] @@ -118,16 +182,30 @@ impl StringsInterner<'_> { #[cfg(feature = "no_object")] return self.strings.is_empty(); } + + /// Clear all interned strings. + #[inline] + pub fn clear(&mut self) { + self.strings.clear(); + + #[cfg(not(feature = "no_object"))] + { + self.getters.clear(); + self.setters.clear(); + } + } } impl AddAssign for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.strings.extend(rhs.strings.into_iter()); + #[cfg(not(feature = "no_object"))] - self.getters.extend(rhs.getters.into_iter()); - #[cfg(not(feature = "no_object"))] - self.setters.extend(rhs.setters.into_iter()); + { + self.getters.extend(rhs.getters.into_iter()); + self.setters.extend(rhs.setters.into_iter()); + } } } @@ -135,12 +213,14 @@ impl AddAssign<&Self> for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: &Self) { self.strings - .extend(rhs.strings.iter().map(|(k, v)| (k.clone(), v.clone()))); + .extend(rhs.strings.iter().map(|(&k, v)| (k, v.clone()))); + #[cfg(not(feature = "no_object"))] - self.getters - .extend(rhs.getters.iter().map(|(k, v)| (k.clone(), v.clone()))); - #[cfg(not(feature = "no_object"))] - self.setters - .extend(rhs.setters.iter().map(|(k, v)| (k.clone(), v.clone()))); + { + self.getters + .extend(rhs.getters.iter().map(|(&k, v)| (k, v.clone()))); + self.setters + .extend(rhs.setters.iter().map(|(&k, v)| (k, v.clone()))); + } } } From 3cb66b7e23812c7f61c486baf0b641fb500aed78 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 16:44:13 +0800 Subject: [PATCH 07/11] Fix bug. --- src/types/dynamic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index b4deb26d..f95a2275 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -379,7 +379,7 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Map(..)); } - #[cfg(not(feature = "decimal"))] + #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Decimal(..)); } From 195c81c6ff53541f0606156c5740b958d3b749e7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 16:51:44 +0800 Subject: [PATCH 08/11] Remove debug print. --- src/types/interner.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types/interner.rs b/src/types/interner.rs index bc31fe6c..e8711e68 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -136,8 +136,6 @@ impl StringsInterner<'_> { dict.insert(key, value.clone()); - println!("Interning '{value}'"); - // If the interner is over capacity, remove the longest entry if self.strings.len() > self.max { // Leave some buffer to grow when shrinking the cache. From 1c7b80ed1361fb89044ec1296e39a7665e84d3e3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Aug 2022 22:48:15 +0800 Subject: [PATCH 09/11] Use turbofish for impl Trait. --- examples/event_handler_main/main.rs | 2 +- examples/event_handler_map/main.rs | 4 ++-- src/api/call_fn.rs | 8 ++++---- src/api/files.rs | 28 ++++++++++++++-------------- tests/call_fn.rs | 14 +++++++------- tests/closures.rs | 2 +- tests/modules.rs | 4 ++-- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/examples/event_handler_main/main.rs b/examples/event_handler_main/main.rs index bc64a8b6..c70c8740 100644 --- a/examples/event_handler_main/main.rs +++ b/examples/event_handler_main/main.rs @@ -124,7 +124,7 @@ pub fn main() { let scope = &mut handler.scope; let ast = &handler.ast; - let result: Result<(), _> = engine.call_fn(scope, ast, event, (arg.to_string(),)); + let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),)); if let Err(err) = result { eprintln!("! {}", err) diff --git a/examples/event_handler_map/main.rs b/examples/event_handler_map/main.rs index ad27e8ea..e45688db 100644 --- a/examples/event_handler_map/main.rs +++ b/examples/event_handler_map/main.rs @@ -100,7 +100,7 @@ pub fn main() { println!(); // Run the 'init' function to initialize the state, retaining variables. - let result: Result<(), _> = engine.call_fn(&mut scope, &ast, "init", ()); + let result = engine.call_fn::<()>(&mut scope, &ast, "init", ()); if let Err(err) = result { eprintln!("! {}", err) @@ -138,7 +138,7 @@ pub fn main() { let scope = &mut handler.scope; let ast = &handler.ast; - let result: Result<(), _> = engine.call_fn(scope, ast, event, (arg.to_string(),)); + let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),)); if let Err(err) = result { eprintln!("! {}", err) diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 53445c2c..611ede44 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -41,14 +41,14 @@ impl Engine { /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; + /// let result = engine.call_fn::(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; /// assert_eq!(result, 168); /// - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?; - /// // ^^^^^^^^^^ tuple of one + /// let result = engine.call_fn::(&mut scope, &ast, "add1", ( "abc", ) )?; + /// // ^^^^^^^^^^ tuple of one /// assert_eq!(result, 46); /// - /// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; + /// let result = engine.call_fn::(&mut scope, &ast, "bar", () )?; /// assert_eq!(result, 21); /// # } /// # Ok(()) diff --git a/src/api/files.rs b/src/api/files.rs index 9c452e0b..17bd324c 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -6,12 +6,18 @@ use crate::types::dynamic::Variant; use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{fs::File, io::Read, path::PathBuf}; +use std::{ + fs::File, + io::Read, + path::{Path, PathBuf}, +}; impl Engine { /// Read the contents of a file into a string. - fn read_file(path: PathBuf) -> RhaiResultOf { - let mut f = File::open(path.clone()).map_err(|err| { + fn read_file(path: impl AsRef) -> RhaiResultOf { + let path = path.as_ref(); + + let mut f = File::open(path).map_err(|err| { ERR::ErrorSystem( format!("Cannot open script file '{}'", path.to_string_lossy()), err.into(), @@ -214,7 +220,7 @@ impl Engine { } } -/// Evaluate a script file. +/// Evaluate a script file, returning the result value or an error. /// /// Not available under `no_std` or `WASM`. /// @@ -222,13 +228,12 @@ impl Engine { /// /// ```no_run /// # fn main() -> Result<(), Box> { -/// // Notice that a PathBuf is required which can easily be constructed from a string. -/// let result: i64 = rhai::eval_file("script.rhai".into())?; +/// let result = rhai::eval_file::("script.rhai")?; /// # Ok(()) /// # } /// ``` #[inline] -pub fn eval_file(path: PathBuf) -> RhaiResultOf { +pub fn eval_file(path: impl AsRef) -> RhaiResultOf { Engine::read_file(path).and_then(|contents| Engine::new().eval::(&contents)) } @@ -240,16 +245,11 @@ pub fn eval_file(path: PathBuf) -> RhaiResultOf { /// /// ```no_run /// # fn main() -> Result<(), Box> { -/// use rhai::Engine; -/// -/// let engine = Engine::new(); -/// -/// // Notice that a PathBuf is required which can easily be constructed from a string. -/// rhai::run_file("script.rhai".into())?; +/// rhai::run_file("script.rhai")?; /// # Ok(()) /// # } /// ``` #[inline] -pub fn run_file(path: PathBuf) -> RhaiResultOf<()> { +pub fn run_file(path: impl AsRef) -> RhaiResultOf<()> { Engine::read_file(path).and_then(|contents| Engine::new().run(&contents)) } diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 30a02cc1..0ac876a6 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -29,13 +29,13 @@ fn test_call_fn() -> Result<(), Box> { ", )?; - let r: INT = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; + let r = engine.call_fn::(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; assert_eq!(r, 165); - let r: INT = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?; + let r = engine.call_fn::(&mut scope, &ast, "hello", (123 as INT,))?; assert_eq!(r, 5166); - let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?; + let r = engine.call_fn::(&mut scope, &ast, "hello", ())?; assert_eq!(r, 42); assert_eq!( @@ -45,7 +45,7 @@ fn test_call_fn() -> Result<(), Box> { 1 ); - let r: INT = engine.call_fn(&mut scope, &ast, "define_var", (2 as INT,))?; + let r = engine.call_fn::(&mut scope, &ast, "define_var", (2 as INT,))?; assert_eq!(r, 42); assert!(!scope.contains("bar")); @@ -132,7 +132,7 @@ fn test_call_fn_args() -> Result<(), Box> { ", )?; - let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?; + let result = engine.call_fn::(&mut scope, &ast, "hello", options)?; assert_eq!(result, "world42"); @@ -146,12 +146,12 @@ fn test_call_fn_private() -> Result<(), Box> { let ast = engine.compile("fn add(x, n) { x + n }")?; - let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?; + let r = engine.call_fn::(&mut scope, &ast, "add", (40 as INT, 2 as INT))?; assert_eq!(r, 42); let ast = engine.compile("private fn add(x, n, ) { x + n }")?; - let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?; + let r = engine.call_fn::(&mut scope, &ast, "add", (40 as INT, 2 as INT))?; assert_eq!(r, 42); Ok(()) diff --git a/tests/closures.rs b/tests/closures.rs index 392e8e94..9be2b82c 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -338,7 +338,7 @@ fn test_closures_shared_obj() -> Result<(), Box> { let res = engine.eval_ast::(&ast)?; // Make closure - let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box> { + let f = move |p1: TestStruct, p2: TestStruct| { let action_ptr = res["action"].clone_cast::(); let name = action_ptr.fn_name(); engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2)) diff --git a/tests/modules.rs b/tests/modules.rs index d80fcfba..a781be3f 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -279,7 +279,7 @@ fn test_module_resolver() -> Result<(), Box> { assert!(engine.eval::(script).is_err()); - let result: INT = engine.call_fn(&mut Scope::new(), &ast, "foo", (2 as INT,))?; + let result = engine.call_fn::(&mut Scope::new(), &ast, "foo", (2 as INT,))?; assert_eq!(result, 84); @@ -296,7 +296,7 @@ fn test_module_resolver() -> Result<(), Box> { assert_eq!(ast2.resolver().unwrap().len(), len); } - let result: INT = engine.call_fn(&mut Scope::new(), &ast2, "foo", (2 as INT,))?; + let result = engine.call_fn::(&mut Scope::new(), &ast2, "foo", (2 as INT,))?; assert_eq!(result, 84); } From 28743594d016cb895cec6327fbdfb10c690cf0cc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Aug 2022 18:07:42 +0800 Subject: [PATCH 10/11] Use interned strings for AST nodes. --- src/ast/expr.rs | 14 ++- src/ast/ident.rs | 12 +-- src/ast/script_fn.rs | 10 +- src/ast/stmt.rs | 2 +- src/eval/expr.rs | 6 +- src/eval/global_state.rs | 4 +- src/eval/stmt.rs | 6 +- src/module/mod.rs | 4 +- src/packages/lang_core.rs | 4 +- src/parser.rs | 193 ++++++++++++++++++++------------------ src/types/interner.rs | 97 +++---------------- 11 files changed, 147 insertions(+), 205 deletions(-) diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 16269430..e5208eee 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -60,7 +60,7 @@ pub struct CustomExpr { /// List of keywords. pub inputs: StaticVec, /// List of tokens actually parsed. - pub tokens: StaticVec, + pub tokens: StaticVec, /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement /// (e.g. introducing a new variable)? pub scope_may_be_changed: bool, @@ -183,7 +183,7 @@ pub struct FnCallExpr { #[cfg(not(feature = "no_module"))] pub namespace: super::Namespace, /// Function name. - pub name: Identifier, + pub name: ImmutableString, /// Pre-calculated hashes. pub hashes: FnCallHashes, /// List of function call argument expressions. @@ -392,14 +392,18 @@ pub enum Expr { /// This is to avoid reading a pointer redirection during each variable access. Variable( #[cfg(not(feature = "no_module"))] - Box<(Option, super::Namespace, u64, Identifier)>, - #[cfg(feature = "no_module")] Box<(Option, (), u64, Identifier)>, + Box<(Option, super::Namespace, u64, ImmutableString)>, + #[cfg(feature = "no_module")] Box<(Option, (), u64, ImmutableString)>, Option, Position, ), /// Property access - ((getter, hash), (setter, hash), prop) Property( - Box<((Identifier, u64), (Identifier, u64), ImmutableString)>, + Box<( + (ImmutableString, u64), + (ImmutableString, u64), + ImmutableString, + )>, Position, ), /// xxx `.` method `(` expr `,` ... `)` diff --git a/src/ast/ident.rs b/src/ast/ident.rs index 415de2bc..13ab4ef9 100644 --- a/src/ast/ident.rs +++ b/src/ast/ident.rs @@ -1,6 +1,6 @@ //! Module defining script identifiers. -use crate::{Identifier, Position}; +use crate::{ImmutableString, Position}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -14,7 +14,7 @@ use std::{ #[derive(Clone, Eq, PartialEq, Hash)] pub struct Ident { /// Identifier name. - pub name: Identifier, + pub name: ImmutableString, /// Position. pub pos: Position, } @@ -34,7 +34,7 @@ impl AsRef for Ident { } impl Deref for Ident { - type Target = Identifier; + type Target = ImmutableString; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -50,12 +50,6 @@ impl DerefMut for Ident { } impl Ident { - /// An empty [`Ident`]. - pub const EMPTY: Self = Self { - name: Identifier::new_const(), - pos: Position::NONE, - }; - /// Get the name of the identifier as a string slice. #[inline(always)] #[must_use] diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index f56a7450..77b67107 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -2,7 +2,7 @@ #![cfg(not(feature = "no_function"))] use super::{FnAccess, StmtBlock}; -use crate::{Identifier, SmartString, StaticVec}; +use crate::{Identifier, ImmutableString, StaticVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, hash::Hash}; @@ -38,11 +38,11 @@ pub struct ScriptFnDef { #[cfg(not(feature = "no_function"))] pub environ: Option, /// Function name. - pub name: Identifier, + pub name: ImmutableString, /// Function access mode. pub access: FnAccess, /// Names of function parameters. - pub params: StaticVec, + pub params: StaticVec, /// _(metadata)_ Function doc-comments (if any). /// Exported under the `metadata` feature only. /// @@ -71,7 +71,7 @@ impl fmt::Display for ScriptFnDef { self.name, self.params .iter() - .map(SmartString::as_str) + .map(|s| s.as_str()) .collect::>() .join(", ") ) @@ -132,7 +132,7 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { fn from(value: &'a ScriptFnDef) -> Self { Self { name: &value.name, - params: value.params.iter().map(SmartString::as_str).collect(), + params: value.params.iter().map(|s| s.as_str()).collect(), access: value.access, #[cfg(feature = "metadata")] comments: value.comments.iter().map(<_>::as_ref).collect(), diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index e853701a..c634bfc1 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -594,7 +594,7 @@ pub enum Stmt { /// This variant does not map to any language structure. It is currently only used only to /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. #[cfg(not(feature = "no_closure"))] - Share(Box, Position), + Share(crate::ImmutableString, Position), } impl Default for Stmt { diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 49ca5292..51265efc 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -94,7 +94,7 @@ impl Engine { if namespace.len() == 1 && namespace.root() == crate::engine::KEYWORD_GLOBAL { if let Some(ref constants) = global.constants { if let Some(value) = - crate::func::locked_write(constants).get_mut(var_name) + crate::func::locked_write(constants).get_mut(var_name.as_str()) { let mut target: Target = value.clone().into(); // Module variables are constant @@ -155,7 +155,7 @@ impl Engine { if lib .iter() .flat_map(|&m| m.iter_script_fn()) - .any(|(_, _, f, ..)| f == v.3) => + .any(|(_, _, f, ..)| f == v.3.as_str()) => { let val: Dynamic = crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into(); @@ -497,7 +497,7 @@ impl Engine { // The first token acts as the custom syntax's key let key_token = custom.tokens.first().unwrap(); // The key should exist, unless the AST is compiled in a different Engine - let custom_def = self.custom_syntax.get(key_token).ok_or_else(|| { + let custom_def = self.custom_syntax.get(key_token.as_str()).ok_or_else(|| { Box::new(ERR::ErrorCustomSyntax( format!("Invalid custom syntax prefix: {key_token}"), custom.tokens.iter().map(<_>::to_string).collect(), diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 225f724c..ebee0e0a 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -1,6 +1,6 @@ //! Global runtime state. -use crate::{Dynamic, Engine, Identifier}; +use crate::{Dynamic, Engine, Identifier, ImmutableString}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, marker::PhantomData}; @@ -9,7 +9,7 @@ use std::{fmt, marker::PhantomData}; #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub type GlobalConstants = - crate::Shared>>; + crate::Shared>>; /// _(internals)_ Global runtime states. /// Exported under the `internals` feature only. diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 45ed998a..8e8ab47c 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -913,7 +913,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] if let Some(alias) = _alias { - scope.add_alias_by_index(scope.len() - 1, alias.name.clone()); + scope.add_alias_by_index(scope.len() - 1, alias.name.as_str().into()); } Ok(Dynamic::UNIT) @@ -995,11 +995,11 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(x, ..) => { - let (Ident { name, pos, .. }, alias) = &**x; + let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x; // Mark scope variables as public if let Some((index, ..)) = scope.get_index(name) { let alias = if alias.is_empty() { name } else { alias }.clone(); - scope.add_alias_by_index(index, alias); + scope.add_alias_by_index(index, alias.into()); Ok(Dynamic::UNIT) } else { Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) diff --git a/src/module/mod.rs b/src/module/mod.rs index 9507fc16..37828a66 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -759,12 +759,12 @@ impl Module { let num_params = fn_def.params.len(); let hash_script = crate::calc_fn_hash(&fn_def.name, num_params); #[cfg(feature = "metadata")] - let params_info = fn_def.params.iter().cloned().collect(); + let params_info = fn_def.params.iter().map(Into::into).collect(); self.functions.insert( hash_script, FuncInfo { metadata: FnMetadata { - name: fn_def.name.clone(), + name: fn_def.name.as_str().into(), namespace: FnNamespace::Internal, access: fn_def.access, params: num_params, diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index e2a1b62c..379fd4c7 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -143,7 +143,7 @@ fn collect_fn_metadata( if !namespace.is_empty() { map.insert("namespace".into(), dict.get(namespace).into()); } - map.insert("name".into(), dict.get(&func.name).into()); + map.insert("name".into(), dict.get(func.name.as_str()).into()); map.insert( "access".into(), dict.get(match func.access { @@ -160,7 +160,7 @@ fn collect_fn_metadata( "params".into(), func.params .iter() - .map(|p| dict.get(p).into()) + .map(|p| dict.get(p.as_str()).into()) .collect::() .into(), ); diff --git a/src/parser.rs b/src/parser.rs index 44116d33..d9ee3996 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -43,13 +43,22 @@ const NEVER_ENDS: &str = "`Token`"; /// Unroll `switch` ranges no larger than this. const SMALL_SWITCH_RANGE: usize = 16; +#[derive(Debug, Default, Clone)] +pub struct InternedStrings<'e> { + pub main: StringsInterner<'e>, + #[cfg(not(feature = "no_object"))] + pub getters: StringsInterner<'e>, + #[cfg(not(feature = "no_object"))] + pub setters: StringsInterner<'e>, +} + /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. pub struct ParseState<'e> { /// Input stream buffer containing the next character to read. pub tokenizer_control: TokenizerControl, - /// Interned strings. - interned_strings: StringsInterner<'e>, + /// String interners. + interned_strings: InternedStrings<'e>, /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, /// Global runtime state. @@ -71,10 +80,10 @@ pub struct ParseState<'e> { pub allow_capture: bool, /// Encapsulates a local stack with imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] - pub imports: StaticVec, + pub imports: StaticVec, /// List of globally-imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] - pub global_imports: StaticVec, + pub global_imports: StaticVec, /// Maximum levels of expression nesting (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub max_expr_depth: usize, @@ -109,7 +118,7 @@ impl<'e> ParseState<'e> { pub fn new( engine: &Engine, scope: &'e Scope, - interned_strings: StringsInterner<'e>, + interners: InternedStrings<'e>, tokenizer_control: TokenizerControl, ) -> Self { Self { @@ -119,7 +128,7 @@ impl<'e> ParseState<'e> { external_vars: Vec::new(), #[cfg(not(feature = "no_closure"))] allow_capture: true, - interned_strings, + interned_strings: interners, scope, global: GlobalRuntimeState::new(engine), stack: Scope::new(), @@ -235,28 +244,10 @@ impl<'e> ParseState<'e> { .iter() .rev() .enumerate() - .find(|&(.., n)| n == name) + .find(|(.., n)| n.as_str() == name) .and_then(|(i, ..)| NonZeroUsize::new(i + 1)) } - /// Get an interned identifier, creating one if it is not yet interned. - #[inline(always)] - #[must_use] - pub fn get_identifier(&mut self, text: impl AsRef + Into) -> Identifier { - self.get_identifier_with_prefix("", text).into() - } - - /// Get an interned identifier, creating one if it is not yet interned. - #[inline(always)] - #[must_use] - pub fn get_identifier_with_prefix( - &mut self, - prefix: impl AsRef, - text: impl AsRef + Into, - ) -> Identifier { - self.interned_strings.get_with_prefix(prefix, text).into() - } - /// Get an interned string, creating one if it is not yet interned. #[inline(always)] #[allow(dead_code)] @@ -265,19 +256,35 @@ impl<'e> ParseState<'e> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.get_interned_string_with_prefix("", text) + self.interned_strings.main.get(text) } - /// Get an interned string, creating one if it is not yet interned. + /// Get an interned property getter, creating one if it is not yet interned. + #[cfg(not(feature = "no_object"))] #[inline(always)] #[allow(dead_code)] #[must_use] - pub fn get_interned_string_with_prefix( + pub fn get_interned_getter( &mut self, - prefix: impl AsRef, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings.get_with_prefix(prefix, text) + self.interned_strings + .getters + .get_with_mapper(|s| crate::engine::make_getter(s.as_ref()).into(), text) + } + + /// Get an interned property setter, creating one if it is not yet interned. + #[cfg(not(feature = "no_object"))] + #[inline(always)] + #[allow(dead_code)] + #[must_use] + pub fn get_interned_setter( + &mut self, + text: impl AsRef + Into, + ) -> ImmutableString { + self.interned_strings + .setters + .get_with_mapper(|s| crate::engine::make_setter(s.as_ref()).into(), text) } } @@ -353,18 +360,14 @@ impl Expr { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) if !x.1.is_empty() => unreachable!("qualified property"), Self::Variable(x, .., pos) => { - let ident = x.3; - let getter = state.get_identifier_with_prefix(crate::engine::FN_GET, &ident); + let ident = x.3.clone(); + let getter = state.get_interned_getter(ident.as_str()); let hash_get = calc_fn_hash(&getter, 1); - let setter = state.get_identifier_with_prefix(crate::engine::FN_SET, &ident); + let setter = state.get_interned_setter(ident.as_str()); let hash_set = calc_fn_hash(&setter, 2); Self::Property( - Box::new(( - (getter, hash_get), - (setter, hash_set), - state.get_interned_string(&ident), - )), + Box::new(((getter, hash_get), (setter, hash_set), ident)), pos, ) } @@ -539,7 +542,7 @@ impl Engine { input: &mut TokenStream, state: &mut ParseState, lib: &mut FnLib, - id: Identifier, + id: ImmutableString, no_args: bool, capture_parent_scope: bool, #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace, @@ -591,7 +594,7 @@ impl Engine { if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m == root) + && !state.global_imports.iter().any(|m| m.as_str() == root) && !self.global_sub_modules.contains_key(root) { return Err( @@ -615,7 +618,7 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_identifier(id), + name: id, capture_parent_scope, #[cfg(not(feature = "no_module"))] namespace, @@ -659,7 +662,7 @@ impl Engine { if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m == root) + && !state.global_imports.iter().any(|m| m.as_str() == root) && !self.global_sub_modules.contains_key(root) { return Err(PERR::ModuleUndefined(root.to_string()) @@ -686,7 +689,7 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_identifier(id), + name: state.get_interned_string(id), capture_parent_scope, #[cfg(not(feature = "no_module"))] namespace, @@ -1054,8 +1057,9 @@ impl Engine { } let expr = self.parse_expr(input, state, lib, settings.level_up())?; - let name = state.get_identifier(name); template.insert(name.clone(), crate::Dynamic::UNIT); + + let name = state.get_interned_string(name); map.push((Ident { name, pos }, expr)); match input.peek().expect(NEVER_ENDS) { @@ -1380,12 +1384,12 @@ impl Engine { // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { - let interned_strings = std::mem::take(&mut state.interned_strings); + let interners = std::mem::take(&mut state.interned_strings); let mut new_state = ParseState::new( self, state.scope, - interned_strings, + interners, state.tokenizer_control.clone(), ); @@ -1564,7 +1568,7 @@ impl Engine { state.allow_capture = true; } Expr::Variable( - (None, ns, 0, state.get_identifier(s)).into(), + (None, ns, 0, state.get_interned_string(s)).into(), None, settings.pos, ) @@ -1578,7 +1582,7 @@ impl Engine { state.allow_capture = true; } Expr::Variable( - (None, ns, 0, state.get_identifier(s)).into(), + (None, ns, 0, state.get_interned_string(s)).into(), None, settings.pos, ) @@ -1605,7 +1609,7 @@ impl Engine { } }); Expr::Variable( - (index, ns, 0, state.get_identifier(s)).into(), + (index, ns, 0, state.get_interned_string(s)).into(), short_index, settings.pos, ) @@ -1629,7 +1633,7 @@ impl Engine { // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { Expr::Variable( - (None, ns, 0, state.get_identifier(s)).into(), + (None, ns, 0, state.get_interned_string(s)).into(), None, settings.pos, ) @@ -1637,7 +1641,7 @@ impl Engine { // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.in_fn_scope => Expr::Variable( - (None, ns, 0, state.get_identifier(s)).into(), + (None, ns, 0, state.get_interned_string(s)).into(), None, settings.pos, ), @@ -1764,7 +1768,7 @@ impl Engine { namespace.push(var_name_def); Expr::Variable( - (None, namespace, 0, state.get_identifier(id2)).into(), + (None, namespace, 0, state.get_interned_string(id2)).into(), None, pos2, ) @@ -1842,7 +1846,7 @@ impl Engine { if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m == root) + && !state.global_imports.iter().any(|m| m.as_str() == root) && !self.global_sub_modules.contains_key(root) { return Err( @@ -1909,7 +1913,7 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("-"), + name: state.get_interned_string("-"), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), args, pos, @@ -1936,7 +1940,7 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("+"), + name: state.get_interned_string("+"), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), args, pos, @@ -1954,7 +1958,7 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("!"), + name: state.get_interned_string("!"), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), args, pos, @@ -2329,7 +2333,7 @@ impl Engine { let hash = calc_fn_hash(&op, 2); let op_base = FnCallExpr { - name: state.get_identifier(op.as_ref()), + name: state.get_interned_string(op.as_ref()), hashes: FnCallHashes::from_native(hash), pos, ..Default::default() @@ -2401,7 +2405,7 @@ impl Engine { FnCallExpr { hashes: calc_fn_hash(OP_CONTAINS, 2).into(), args, - name: state.get_identifier(OP_CONTAINS), + name: state.get_interned_string(OP_CONTAINS), ..op_base } .into_fn_call_expr(pos) @@ -2460,14 +2464,14 @@ impl Engine { if syntax.scope_may_be_changed { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. - let marker = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); + let marker = state.get_interned_string(SCOPE_SEARCH_BARRIER_MARKER); state.stack.push(marker, ()); } let parse_func = &*syntax.parse; let mut required_token: ImmutableString = key.into(); - tokens.push(required_token.clone().into()); + tokens.push(required_token.clone()); segments.push(required_token.clone()); loop { @@ -2491,7 +2495,7 @@ impl Engine { match required_token.as_str() { CUSTOM_SYNTAX_MARKER_IDENT => { let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier(name); + let name = state.get_interned_string(name); #[cfg(not(feature = "no_module"))] let ns = crate::ast::Namespace::NONE; @@ -2499,19 +2503,19 @@ impl Engine { let ns = (); segments.push(name.clone().into()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT)); + tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); inputs.push(Expr::Variable((None, ns, 0, name).into(), None, pos)); } CUSTOM_SYNTAX_MARKER_SYMBOL => { let (symbol, pos) = parse_symbol(input)?; let symbol = state.get_interned_string(symbol); segments.push(symbol.clone()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL)); + tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_SYMBOL)); inputs.push(Expr::StringConstant(symbol, pos)); } CUSTOM_SYNTAX_MARKER_EXPR => { inputs.push(self.parse_expr(input, state, lib, settings)?); - let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR); + let keyword = state.get_interned_string(CUSTOM_SYNTAX_MARKER_EXPR); segments.push(keyword.clone().into()); tokens.push(keyword); } @@ -2519,7 +2523,7 @@ impl Engine { match self.parse_block(input, state, lib, settings)? { block @ Stmt::Block(..) => { inputs.push(Expr::Stmt(Box::new(block.into()))); - let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK); + let keyword = state.get_interned_string(CUSTOM_SYNTAX_MARKER_BLOCK); segments.push(keyword.clone().into()); tokens.push(keyword); } @@ -2530,7 +2534,7 @@ impl Engine { (b @ (Token::True | Token::False), pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); segments.push(state.get_interned_string(b.literal_syntax())); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL)); + tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_BOOL)); } (.., pos) => { return Err( @@ -2543,7 +2547,7 @@ impl Engine { (Token::IntegerConstant(i), pos) => { inputs.push(Expr::IntegerConstant(i, pos)); segments.push(i.to_string().into()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT)); + tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_INT)); } (.., pos) => { return Err( @@ -2557,7 +2561,7 @@ impl Engine { (Token::FloatConstant(f), pos) => { inputs.push(Expr::FloatConstant(f, pos)); segments.push(f.to_string().into()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT)); + tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT)); } (.., pos) => { return Err(PERR::MissingSymbol( @@ -2571,7 +2575,7 @@ impl Engine { let s = state.get_interned_string(s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING)); + tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); } (.., pos) => { return Err( @@ -2835,11 +2839,11 @@ impl Engine { state.stack.push(name.clone(), ()); } let counter_var = Ident { - name: state.get_identifier(counter_name), + name: state.get_interned_string(counter_name), pos: counter_pos, }; - let loop_var = state.get_identifier(name); + let loop_var = state.get_interned_string(name); state.stack.push(loop_var.clone(), ()); let loop_var = Ident { name: loop_var, @@ -2912,7 +2916,7 @@ impl Engine { } } - let name = state.get_identifier(name); + let name = state.get_interned_string(name); // let name = ... let expr = if match_token(input, Token::Equals).0 { @@ -2978,14 +2982,18 @@ impl Engine { // import expr ... let expr = self.parse_expr(input, state, lib, settings.level_up())?; - // import expr as ... + // import expr; if !match_token(input, Token::As).0 { - return Ok(Stmt::Import((expr, Ident::EMPTY).into(), settings.pos)); + let empty = Ident { + name: state.get_interned_string(""), + pos: Position::NONE, + }; + return Ok(Stmt::Import((expr, empty).into(), settings.pos)); } // import expr as name ... let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier(name); + let name = state.get_interned_string(name); state.imports.push(name.clone()); Ok(Stmt::Import( @@ -3038,11 +3046,11 @@ impl Engine { let export = ( Ident { - name: state.get_identifier(id), + name: state.get_interned_string(id), pos: id_pos, }, Ident { - name: state.get_identifier(alias.as_ref().map_or("", <_>::as_ref)), + name: state.get_interned_string(alias.as_ref().map_or("", <_>::as_ref)), pos: alias_pos, }, ); @@ -3251,12 +3259,12 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { - let interned_strings = std::mem::take(&mut state.interned_strings); + let interners = std::mem::take(&mut state.interned_strings); let mut new_state = ParseState::new( self, state.scope, - interned_strings, + interners, state.tokenizer_control.clone(), ); @@ -3451,11 +3459,14 @@ impl Engine { .into_err(err_pos)); } - let name = state.get_identifier(name); + let name = state.get_interned_string(name); state.stack.push(name.clone(), ()); Ident { name, pos } } else { - Ident::EMPTY + Ident { + name: state.get_interned_string(""), + pos: Position::NONE, + } }; // try { try_block } catch ( var ) { catch_block } @@ -3515,7 +3526,7 @@ impl Engine { (.., pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), }; - let mut params = StaticVec::new_const(); + let mut params = StaticVec::<(ImmutableString, _)>::new_const(); if !no_params { let sep_err = format!("to separate the parameters of function '{name}'"); @@ -3524,11 +3535,11 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::RightParen, ..) => break, (Token::Identifier(s), pos) => { - if params.iter().any(|(p, _)| p == &*s) { + if params.iter().any(|(p, _)| p.as_str() == &*s) { return Err(PERR::FnDuplicatedParam(name.to_string(), s.to_string()) .into_err(pos)); } - let s = state.get_identifier(s); + let s = state.get_interned_string(s); state.stack.push(s.clone(), ()); params.push((s, pos)); } @@ -3567,7 +3578,7 @@ impl Engine { params.shrink_to_fit(); Ok(ScriptFnDef { - name: state.get_identifier(name), + name: state.get_interned_string(name), access, params, body, @@ -3617,7 +3628,7 @@ impl Engine { ); let expr = FnCallExpr { - name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), + name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native(calc_fn_hash( crate::engine::KEYWORD_FN_PTR_CURRY, num_externals + 1, @@ -3634,7 +3645,7 @@ impl Engine { statements.extend( externals .into_iter() - .map(|crate::ast::Ident { name, pos }| Stmt::Share(name.into(), pos)), + .map(|crate::ast::Ident { name, pos }| Stmt::Share(name, pos)), ); statements.push(Stmt::Expr(expr.into())); Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into()) @@ -3653,18 +3664,18 @@ impl Engine { settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut settings = settings; - let mut params_list = StaticVec::new_const(); + let mut params_list = StaticVec::::new_const(); if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 { loop { match input.next().expect(NEVER_ENDS) { (Token::Pipe, ..) => break, (Token::Identifier(s), pos) => { - if params_list.iter().any(|p| p == &*s) { + if params_list.iter().any(|p| p.as_str() == &*s) { return Err(PERR::FnDuplicatedParam("".to_string(), s.to_string()) .into_err(pos)); } - let s = state.get_identifier(s); + let s = state.get_interned_string(s); state.stack.push(s.clone(), ()); params_list.push(s); } @@ -3722,7 +3733,7 @@ impl Engine { params.iter().for_each(|p| p.hash(hasher)); body.hash(hasher); let hash = hasher.finish(); - let fn_name = state.get_identifier(make_anonymous_fn(hash)); + let fn_name = state.get_interned_string(make_anonymous_fn(hash)); // Define the function let script = ScriptFnDef { diff --git a/src/types/interner.rs b/src/types/interner.rs index e8711e68..548f469a 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -26,12 +26,6 @@ pub struct StringsInterner<'a> { max: usize, /// Normal strings. strings: BTreeMap, - /// Property getters. - #[cfg(not(feature = "no_object"))] - getters: BTreeMap, - /// Property setters. - #[cfg(not(feature = "no_object"))] - setters: BTreeMap, /// Take care of the lifetime parameter. dummy: PhantomData<&'a ()>, } @@ -51,71 +45,38 @@ impl StringsInterner<'_> { Self::new_with_capacity(MAX_INTERNED_STRINGS) } - /// Create a new [`StringsInterner`] with a maximum capacity. + /// Create a new [`StringsInterner`] with maximum capacity. #[inline] #[must_use] pub fn new_with_capacity(capacity: usize) -> Self { Self { max: capacity, strings: BTreeMap::new(), - #[cfg(not(feature = "no_object"))] - getters: BTreeMap::new(), - #[cfg(not(feature = "no_object"))] - setters: BTreeMap::new(), dummy: PhantomData, } } - - /// Get an identifier from a text string and prefix, adding it to the interner if necessary. + /// Get an identifier from a text string, adding it to the interner if necessary. #[inline(always)] #[must_use] - pub fn get(&mut self, text: impl AsRef + Into) -> ImmutableString { - self.get_with_prefix("", text) + pub fn get + Into>(&mut self, text: T) -> ImmutableString { + self.get_with_mapper(|s| s.into(), text) } - /// Get an identifier from a text string and prefix, adding it to the interner if necessary. - /// - /// # Prefix - /// - /// Currently recognized prefixes are: - /// - /// * `""` - None (normal string) - /// * `"get$"` - Property getter, not available under `no_object` - /// * `"set$"` - Property setter, not available under `no_object` - /// - /// # Panics - /// - /// Panics if the prefix is not recognized. + /// Get an identifier from a text string, adding it to the interner if necessary. #[inline] #[must_use] - pub fn get_with_prefix + Into>( + pub fn get_with_mapper + Into>( &mut self, - prefix: impl AsRef, + mapper: fn(T) -> ImmutableString, text: T, ) -> ImmutableString { - let prefix = prefix.as_ref(); let key = text.as_ref(); // Do not intern numbers - if prefix == "" && key.bytes().all(|c| c == b'.' || (c >= b'0' && c <= b'9')) { + if key.bytes().all(|c| c == b'.' || (c >= b'0' && c <= b'9')) { return text.into(); } - let (dict, mapper): (_, fn(T) -> ImmutableString) = match prefix { - "" => (&mut self.strings, |s| s.into()), - - #[cfg(not(feature = "no_object"))] - crate::engine::FN_GET => (&mut self.getters, |s| { - crate::engine::make_getter(s.as_ref()).into() - }), - #[cfg(not(feature = "no_object"))] - crate::engine::FN_SET => (&mut self.setters, |s| { - crate::engine::make_setter(s.as_ref()).into() - }), - - _ => unreachable!("unsupported prefix {}", prefix), - }; - if key.len() > MAX_STRING_LEN { return mapper(text); } @@ -124,8 +85,8 @@ impl StringsInterner<'_> { key.hash(hasher); let key = hasher.finish(); - if !dict.is_empty() && dict.contains_key(&key) { - return dict.get(&key).unwrap().clone(); + if !self.strings.is_empty() && self.strings.contains_key(&key) { + return self.strings.get(&key).unwrap().clone(); } let value = mapper(text); @@ -134,7 +95,7 @@ impl StringsInterner<'_> { return value; } - dict.insert(key, value.clone()); + self.strings.insert(key, value.clone()); // If the interner is over capacity, remove the longest entry if self.strings.len() > self.max { @@ -160,37 +121,23 @@ impl StringsInterner<'_> { } /// Number of strings interned. - #[inline] + #[inline(always)] #[must_use] pub fn len(&self) -> usize { - #[cfg(not(feature = "no_object"))] - return self.strings.len() + self.getters.len() + self.setters.len(); - - #[cfg(feature = "no_object")] - return self.strings.len(); + self.strings.len() } /// Number of strings interned. - #[inline] + #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { - #[cfg(not(feature = "no_object"))] - return self.strings.is_empty() || self.getters.is_empty() || self.setters.is_empty(); - - #[cfg(feature = "no_object")] - return self.strings.is_empty(); + self.strings.is_empty() } /// Clear all interned strings. #[inline] pub fn clear(&mut self) { self.strings.clear(); - - #[cfg(not(feature = "no_object"))] - { - self.getters.clear(); - self.setters.clear(); - } } } @@ -198,12 +145,6 @@ impl AddAssign for StringsInterner<'_> { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.strings.extend(rhs.strings.into_iter()); - - #[cfg(not(feature = "no_object"))] - { - self.getters.extend(rhs.getters.into_iter()); - self.setters.extend(rhs.setters.into_iter()); - } } } @@ -212,13 +153,5 @@ impl AddAssign<&Self> for StringsInterner<'_> { fn add_assign(&mut self, rhs: &Self) { self.strings .extend(rhs.strings.iter().map(|(&k, v)| (k, v.clone()))); - - #[cfg(not(feature = "no_object"))] - { - self.getters - .extend(rhs.getters.iter().map(|(&k, v)| (k, v.clone()))); - self.setters - .extend(rhs.setters.iter().map(|(&k, v)| (k, v.clone()))); - } } } From 9813f657bb7659f0474a6996c2b81a752c4931ea Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Aug 2022 18:40:14 +0800 Subject: [PATCH 11/11] Use ImmutableString for AST. --- .gitignore | 3 --- src/ast/script_fn.rs | 4 ++-- src/eval/global_state.rs | 20 ++++++++++---------- src/func/native.rs | 2 +- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 61b621fb..fe87b992 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,8 @@ Cargo.lock .vscode/ .cargo/ benches/results -.rhai-repl-history.txt clippy.toml Rhai.toml -before* -after* **/*.bat doc/rhai-sync.json doc/rhai.json diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index 77b67107..1ce664e0 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -2,7 +2,7 @@ #![cfg(not(feature = "no_function"))] use super::{FnAccess, StmtBlock}; -use crate::{Identifier, ImmutableString, StaticVec}; +use crate::{ImmutableString, StaticVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, hash::Hash}; @@ -22,7 +22,7 @@ pub struct EncapsulatedEnviron { /// Functions defined within the same [`AST`][crate::AST]. pub lib: crate::Shared, /// Imported [modules][crate::Module]. - pub imports: Box<[(Identifier, crate::Shared)]>, + pub imports: Box<[(ImmutableString, crate::Shared)]>, /// Globally-defined constants. pub constants: Option, } diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index ebee0e0a..28c0589f 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -25,7 +25,7 @@ pub type GlobalConstants = pub struct GlobalRuntimeState<'a> { /// Stack of module names. #[cfg(not(feature = "no_module"))] - keys: crate::StaticVec, + keys: crate::StaticVec, /// Stack of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] modules: crate::StaticVec>, @@ -159,7 +159,7 @@ impl GlobalRuntimeState<'_> { self.keys .iter() .rev() - .position(|key| key == name) + .position(|key| key.as_str() == name) .map(|i| len - 1 - i) } /// Push an imported [module][crate::Module] onto the stack. @@ -169,7 +169,7 @@ impl GlobalRuntimeState<'_> { #[inline(always)] pub fn push_import( &mut self, - name: impl Into, + name: impl Into, module: impl Into>, ) { self.keys.push(name.into()); @@ -205,7 +205,7 @@ impl GlobalRuntimeState<'_> { #[inline] pub(crate) fn iter_imports_raw( &self, - ) -> impl Iterator)> { + ) -> impl Iterator)> { self.keys.iter().rev().zip(self.modules.iter().rev()) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order. @@ -216,7 +216,7 @@ impl GlobalRuntimeState<'_> { #[inline] pub fn scan_imports_raw( &self, - ) -> impl Iterator)> { + ) -> impl Iterator)> { self.keys.iter().zip(self.modules.iter()) } /// Does the specified function hash key exist in the stack of globally-imported @@ -310,9 +310,9 @@ impl GlobalRuntimeState<'_> { #[cfg(not(feature = "no_module"))] impl IntoIterator for GlobalRuntimeState<'_> { - type Item = (Identifier, crate::Shared); + type Item = (ImmutableString, crate::Shared); type IntoIter = std::iter::Zip< - std::iter::Rev>, + std::iter::Rev>, std::iter::Rev; 3]>>, >; @@ -327,9 +327,9 @@ impl IntoIterator for GlobalRuntimeState<'_> { #[cfg(not(feature = "no_module"))] impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> { - type Item = (&'a Identifier, &'a crate::Shared); + type Item = (&'a ImmutableString, &'a crate::Shared); type IntoIter = std::iter::Zip< - std::iter::Rev>, + std::iter::Rev>, std::iter::Rev>>, >; @@ -341,7 +341,7 @@ impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> { } #[cfg(not(feature = "no_module"))] -impl, M: Into>> Extend<(K, M)> +impl, M: Into>> Extend<(K, M)> for GlobalRuntimeState<'_> { #[inline] diff --git a/src/func/native.rs b/src/func/native.rs index a69ca830..6b3d46de 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -235,7 +235,7 @@ impl<'a> NativeCallContext<'a> { #[inline] pub(crate) fn iter_imports_raw( &self, - ) -> impl Iterator)> { + ) -> impl Iterator)> { self.global.iter().flat_map(|&m| m.iter_imports_raw()) } /// _(internals)_ The current [`GlobalRuntimeState`], if any.