Merge pull request #615 from schungx/master

Use interned strings for AST.
This commit is contained in:
Stephen Chung 2022-08-13 18:59:36 +08:00 committed by GitHub
commit 4725cb974c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 689 additions and 561 deletions

7
.gitignore vendored
View File

@ -3,7 +3,8 @@ Cargo.lock
.vscode/ .vscode/
.cargo/ .cargo/
benches/results benches/results
before*
after*
.rhai-repl-history.txt
clippy.toml clippy.toml
Rhai.toml
**/*.bat
doc/rhai-sync.json
doc/rhai.json

View File

@ -18,8 +18,9 @@ Bug fixes
New features 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). * 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 ### Module documentation

View File

@ -131,7 +131,7 @@ pub fn inner_item_attributes<T: ExportedParams>(
{ {
return Err(syn::Error::new( return Err(syn::Error::new(
duplicate.span(), duplicate.span(),
format!("duplicated attribute '{}'", attr_name), format!("duplicated attribute '{attr_name}'"),
)); ));
} }

View File

@ -57,10 +57,10 @@ impl FnSpecialAccess {
match self { match self {
FnSpecialAccess::None => None, FnSpecialAccess::None => None,
FnSpecialAccess::Property(Property::Get(ref g)) => { 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)) => { 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(( FnSpecialAccess::Index(Index::Get) => Some((
FN_IDX_GET.to_string(), FN_IDX_GET.to_string(),
@ -255,7 +255,7 @@ impl ExportedParams for ExportedFnParams {
(attr, ..) => { (attr, ..) => {
return Err(syn::Error::new( return Err(syn::Error::new(
key.span(), key.span(),
format!("unknown attribute '{}'", attr), format!("unknown attribute '{attr}'"),
)) ))
} }
} }
@ -748,7 +748,7 @@ impl ExportedFn {
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap(); let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap(); let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap();
for (i, arg) in self.arg_list().enumerate().skip(skip_first_arg as usize) { 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_string;
let is_ref; let is_ref;
match arg { match arg {

View File

@ -72,7 +72,7 @@ impl ExportedParams for ExportedModParams {
(attr, ..) => { (attr, ..) => {
return Err(syn::Error::new( return Err(syn::Error::new(
key.span(), key.span(),
format!("unknown attribute '{}'", attr), format!("unknown attribute '{attr}'"),
)) ))
} }
} }

View File

@ -319,11 +319,11 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> {
if let Some(other_span) = renames.insert(key, current_span) { if let Some(other_span) = renames.insert(key, current_span) {
let mut err = syn::Error::new( let mut err = syn::Error::new(
current_span, current_span,
format!("duplicate Rhai signature for '{}'", fn_name), format!("duplicate Rhai signature for '{fn_name}'"),
); );
err.combine(syn::Error::new( err.combine(syn::Error::new(
other_span, other_span,
format!("duplicated function renamed '{}'", fn_name), format!("duplicated function renamed '{fn_name}'"),
)); ));
return Err(err); return Err(err);
} }
@ -332,10 +332,10 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> {
let ident = item_fn.name(); let ident = item_fn.name();
if let Some(other_span) = fn_defs.insert(ident.to_string(), ident.span()) { if let Some(other_span) = fn_defs.insert(ident.to_string(), ident.span()) {
let mut err = 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( err.combine(syn::Error::new(
other_span, other_span,
format!("duplicated function '{}'", ident), format!("duplicated function '{ident}'"),
)); ));
return Err(err); 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) { if let Some(fn_span) = renames.get(&key) {
let mut err = syn::Error::new( let mut err = syn::Error::new(
ident.span(), ident.span(),
format!("duplicate Rhai signature for '{}'", ident), format!("duplicate Rhai signature for '{ident}'"),
); );
err.combine(syn::Error::new( err.combine(syn::Error::new(
*fn_span, *fn_span,
format!("duplicated function '{}'", ident), format!("duplicated function '{ident}'"),
)); ));
return Err(err); return Err(err);
} }

View File

@ -88,10 +88,7 @@ mod function_tests {
}; };
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err(); let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!( assert_eq!(format!("{err}"), "Rhai functions cannot return references");
format!("{}", err),
"Rhai functions cannot return references"
);
} }
#[test] #[test]
@ -101,7 +98,7 @@ mod function_tests {
}; };
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err(); let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!(format!("{}", err), "Rhai functions cannot return pointers"); assert_eq!(format!("{err}"), "Rhai functions cannot return pointers");
} }
#[test] #[test]
@ -112,7 +109,7 @@ mod function_tests {
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err(); let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!( assert_eq!(
format!("{}", err), format!("{err}"),
"references from Rhai in this position must be mutable" "references from Rhai in this position must be mutable"
); );
} }
@ -125,7 +122,7 @@ mod function_tests {
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err(); let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!( assert_eq!(
format!("{}", err), format!("{err}"),
"function parameters other than the first one cannot be passed by reference" "function parameters other than the first one cannot be passed by reference"
); );
} }
@ -138,7 +135,7 @@ mod function_tests {
let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err(); let err = syn::parse2::<ExportedFn>(input_tokens).unwrap_err();
assert_eq!( assert_eq!(
format!("{}", err), format!("{err}"),
"function parameters other than the first one cannot be passed by reference" "function parameters other than the first one cannot be passed by reference"
); );
} }

View File

@ -107,7 +107,7 @@ mod mut_opaque_ref {
StatusMessage { StatusMessage {
is_ok, is_ok,
os_code: Some(os_code), os_code: Some(os_code),
message: format!("OS Code {}", os_code), message: format!("OS Code {os_code}"),
} }
} }

View File

@ -127,7 +127,7 @@ pub mod mut_opaque_ref_module {
StatusMessage { StatusMessage {
is_ok, is_ok,
os_code: Some(os_code), os_code: Some(os_code),
message: format!("OS Code {}", os_code), message: format!("OS Code {os_code}"),
} }
} }

View File

@ -124,7 +124,7 @@ pub fn main() {
let scope = &mut handler.scope; let scope = &mut handler.scope;
let ast = &handler.ast; 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 { if let Err(err) = result {
eprintln!("! {}", err) eprintln!("! {}", err)

View File

@ -100,7 +100,7 @@ pub fn main() {
println!(); println!();
// Run the 'init' function to initialize the state, retaining variables. // 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 { if let Err(err) = result {
eprintln!("! {}", err) eprintln!("! {}", err)
@ -138,7 +138,7 @@ pub fn main() {
let scope = &mut handler.scope; let scope = &mut handler.scope;
let ast = &handler.ast; 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 { if let Err(err) = result {
eprintln!("! {}", err) eprintln!("! {}", err)

View File

@ -41,14 +41,14 @@ impl Engine {
/// scope.push("foo", 42_i64); /// scope.push("foo", 42_i64);
/// ///
/// // Call the script-defined function /// // Call the script-defined function
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; /// let result = engine.call_fn::<i64>(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?;
/// assert_eq!(result, 168); /// assert_eq!(result, 168);
/// ///
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?; /// let result = engine.call_fn::<i64>(&mut scope, &ast, "add1", ( "abc", ) )?;
/// // ^^^^^^^^^^ tuple of one /// // ^^^^^^^^^^ tuple of one
/// assert_eq!(result, 46); /// assert_eq!(result, 46);
/// ///
/// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; /// let result = engine.call_fn::<i64>(&mut scope, &ast, "bar", () )?;
/// assert_eq!(result, 21); /// assert_eq!(result, 21);
/// # } /// # }
/// # Ok(()) /// # Ok(())

View File

@ -221,7 +221,7 @@ impl Engine {
scripts.as_ref(), scripts.as_ref(),
self.token_mapper.as_ref().map(<_>::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)?; let mut _ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?;
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
_ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n")); _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)); self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let mut peekable = stream.peekable(); 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) self.parse_global_expr(&mut peekable, &mut state, self.optimization_level)
} }
} }

View File

@ -258,9 +258,8 @@ impl Engine {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
s.to_string(), s.to_string(),
format!( format!(
"Improper symbol for custom syntax at position #{}: '{}'", "Improper symbol for custom syntax at position #{}: '{s}'",
segments.len() + 1, segments.len() + 1,
s
), ),
) )
.into_err(Position::NONE)); .into_err(Position::NONE));
@ -282,9 +281,8 @@ impl Engine {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
s.to_string(), s.to_string(),
format!( format!(
"Improper symbol for custom syntax at position #{}: '{}'", "Improper symbol for custom syntax at position #{}: '{s}'",
segments.len() + 1, segments.len() + 1,
s
), ),
) )
.into_err(Position::NONE)); .into_err(Position::NONE));

View File

@ -116,7 +116,7 @@ impl Engine {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); 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 // No need to optimize a lone expression
let ast = self.parse_global_expr( let ast = self.parse_global_expr(

View File

@ -6,12 +6,18 @@ use crate::types::dynamic::Variant;
use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; use crate::{Engine, RhaiResultOf, Scope, AST, ERR};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fs::File, io::Read, path::PathBuf}; use std::{
fs::File,
io::Read,
path::{Path, PathBuf},
};
impl Engine { impl Engine {
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
fn read_file(path: PathBuf) -> RhaiResultOf<String> { fn read_file(path: impl AsRef<Path>) -> RhaiResultOf<String> {
let mut f = File::open(path.clone()).map_err(|err| { let path = path.as_ref();
let mut f = File::open(path).map_err(|err| {
ERR::ErrorSystem( ERR::ErrorSystem(
format!("Cannot open script file '{}'", path.to_string_lossy()), format!("Cannot open script file '{}'", path.to_string_lossy()),
err.into(), 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`. /// Not available under `no_std` or `WASM`.
/// ///
@ -222,13 +228,12 @@ impl Engine {
/// ///
/// ```no_run /// ```no_run
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> { /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// // Notice that a PathBuf is required which can easily be constructed from a string. /// let result = rhai::eval_file::<i64>("script.rhai")?;
/// let result: i64 = rhai::eval_file("script.rhai".into())?;
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn eval_file<T: Variant + Clone>(path: PathBuf) -> RhaiResultOf<T> { pub fn eval_file<T: Variant + Clone>(path: impl AsRef<Path>) -> RhaiResultOf<T> {
Engine::read_file(path).and_then(|contents| Engine::new().eval::<T>(&contents)) Engine::read_file(path).and_then(|contents| Engine::new().eval::<T>(&contents))
} }
@ -240,16 +245,11 @@ pub fn eval_file<T: Variant + Clone>(path: PathBuf) -> RhaiResultOf<T> {
/// ///
/// ```no_run /// ```no_run
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> { /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine; /// rhai::run_file("script.rhai")?;
///
/// let engine = Engine::new();
///
/// // Notice that a PathBuf is required which can easily be constructed from a string.
/// rhai::run_file("script.rhai".into())?;
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn run_file(path: PathBuf) -> RhaiResultOf<()> { pub fn run_file(path: impl AsRef<Path>) -> RhaiResultOf<()> {
Engine::read_file(path).and_then(|contents| Engine::new().run(&contents)) Engine::read_file(path).and_then(|contents| Engine::new().run(&contents))
} }

View File

@ -120,7 +120,7 @@ impl Engine {
); );
let scope = Scope::new(); 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( let ast = self.parse_global_expr(
&mut stream.peekable(), &mut stream.peekable(),

View File

@ -181,7 +181,7 @@ impl Engine {
if self.disabled_symbols.is_empty() if self.disabled_symbols.is_empty()
|| !self.disabled_symbols.contains(&*token.syntax()) || !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 // Active standard symbols cannot be made custom
@ -189,7 +189,7 @@ impl Engine {
if self.disabled_symbols.is_empty() if self.disabled_symbols.is_empty()
|| !self.disabled_symbols.contains(&*token.syntax()) || !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 // Active standard symbols cannot be made custom
@ -197,7 +197,7 @@ impl Engine {
if self.disabled_symbols.is_empty() if self.disabled_symbols.is_empty()
|| !self.disabled_symbols.contains(&*token.syntax()) => || !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 // Disabled symbols are OK
Some(_) => (), Some(_) => (),

View File

@ -1037,7 +1037,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
for (name, m) in &self.global_sub_modules { 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( signatures.extend(

View File

@ -58,7 +58,7 @@ impl Engine {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); 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)?; let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?;
self.run_ast_with_scope(scope, &ast) self.run_ast_with_scope(scope, &ast)
} }

View File

@ -150,7 +150,7 @@ impl Engine {
return if x == r { return if x == r {
name.into() name.into()
} else { } else {
format!("&mut {}", r).into() format!("&mut {r}").into()
}; };
} }

View File

@ -60,7 +60,7 @@ pub struct CustomExpr {
/// List of keywords. /// List of keywords.
pub inputs: StaticVec<Expr>, pub inputs: StaticVec<Expr>,
/// List of tokens actually parsed. /// List of tokens actually parsed.
pub tokens: StaticVec<Identifier>, pub tokens: StaticVec<ImmutableString>,
/// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement
/// (e.g. introducing a new variable)? /// (e.g. introducing a new variable)?
pub scope_may_be_changed: bool, pub scope_may_be_changed: bool,
@ -183,7 +183,7 @@ pub struct FnCallExpr {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub namespace: super::Namespace, pub namespace: super::Namespace,
/// Function name. /// Function name.
pub name: Identifier, pub name: ImmutableString,
/// Pre-calculated hashes. /// Pre-calculated hashes.
pub hashes: FnCallHashes, pub hashes: FnCallHashes,
/// List of function call argument expressions. /// 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. /// This is to avoid reading a pointer redirection during each variable access.
Variable( Variable(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Box<(Option<NonZeroUsize>, super::Namespace, u64, Identifier)>, Box<(Option<NonZeroUsize>, super::Namespace, u64, ImmutableString)>,
#[cfg(feature = "no_module")] Box<(Option<NonZeroUsize>, (), u64, Identifier)>, #[cfg(feature = "no_module")] Box<(Option<NonZeroUsize>, (), u64, ImmutableString)>,
Option<NonZeroU8>, Option<NonZeroU8>,
Position, Position,
), ),
/// Property access - ((getter, hash), (setter, hash), prop) /// Property access - ((getter, hash), (setter, hash), prop)
Property( Property(
Box<((Identifier, u64), (Identifier, u64), ImmutableString)>, Box<(
(ImmutableString, u64),
(ImmutableString, u64),
ImmutableString,
)>,
Position, Position,
), ),
/// xxx `.` method `(` expr `,` ... `)` /// xxx `.` method `(` expr `,` ... `)`
@ -476,7 +480,7 @@ impl fmt::Debug for Expr {
write!(f, "{}{}", x.1, Token::DoubleColon.literal_syntax())?; write!(f, "{}{}", x.1, Token::DoubleColon.literal_syntax())?;
let pos = x.1.position(); let pos = x.1.position();
if !pos.is_none() { if !pos.is_none() {
display_pos = format!(" @ {:?}", pos); display_pos = format!(" @ {pos:?}");
} }
} }
f.write_str(&x.3)?; f.write_str(&x.3)?;
@ -490,7 +494,7 @@ impl fmt::Debug for Expr {
Self::Stmt(x) => { Self::Stmt(x) => {
let pos = x.span(); let pos = x.span();
if !pos.is_none() { if !pos.is_none() {
display_pos = format!(" @ {:?}", pos); display_pos = format!(" @ {pos:?}");
} }
f.write_str("ExprStmtBlock")?; f.write_str("ExprStmtBlock")?;
f.debug_list().entries(x.iter()).finish() f.debug_list().entries(x.iter()).finish()
@ -498,7 +502,7 @@ impl fmt::Debug for Expr {
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f), Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
Self::Index(x, options, pos) => { Self::Index(x, options, pos) => {
if !pos.is_none() { if !pos.is_none() {
display_pos = format!(" @ {:?}", pos); display_pos = format!(" @ {pos:?}");
} }
let mut f = f.debug_struct("Index"); let mut f = f.debug_struct("Index");
@ -511,7 +515,7 @@ impl fmt::Debug for Expr {
} }
Self::Dot(x, options, pos) => { Self::Dot(x, options, pos) => {
if !pos.is_none() { if !pos.is_none() {
display_pos = format!(" @ {:?}", pos); display_pos = format!(" @ {pos:?}");
} }
let mut f = f.debug_struct("Dot"); let mut f = f.debug_struct("Dot");
@ -531,7 +535,7 @@ impl fmt::Debug for Expr {
}; };
if !pos.is_none() { if !pos.is_none() {
display_pos = format!(" @ {:?}", pos); display_pos = format!(" @ {pos:?}");
} }
f.debug_struct(op_name) f.debug_struct(op_name)

View File

@ -1,6 +1,6 @@
//! Module defining script identifiers. //! Module defining script identifiers.
use crate::{Identifier, Position}; use crate::{ImmutableString, Position};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -14,7 +14,7 @@ use std::{
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct Ident { pub struct Ident {
/// Identifier name. /// Identifier name.
pub name: Identifier, pub name: ImmutableString,
/// Position. /// Position.
pub pos: Position, pub pos: Position,
} }
@ -34,7 +34,7 @@ impl AsRef<str> for Ident {
} }
impl Deref for Ident { impl Deref for Ident {
type Target = Identifier; type Target = ImmutableString;
#[inline(always)] #[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -50,12 +50,6 @@ impl DerefMut for Ident {
} }
impl 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. /// Get the name of the identifier as a string slice.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]

View File

@ -2,7 +2,7 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use super::{FnAccess, StmtBlock}; use super::{FnAccess, StmtBlock};
use crate::{Identifier, SmartString, StaticVec}; use crate::{ImmutableString, StaticVec};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt, hash::Hash}; use std::{fmt, hash::Hash};
@ -22,7 +22,7 @@ pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST]. /// Functions defined within the same [`AST`][crate::AST].
pub lib: crate::Shared<crate::Module>, pub lib: crate::Shared<crate::Module>,
/// Imported [modules][crate::Module]. /// Imported [modules][crate::Module].
pub imports: Box<[(Identifier, crate::Shared<crate::Module>)]>, pub imports: Box<[(ImmutableString, crate::Shared<crate::Module>)]>,
/// Globally-defined constants. /// Globally-defined constants.
pub constants: Option<crate::eval::GlobalConstants>, pub constants: Option<crate::eval::GlobalConstants>,
} }
@ -38,11 +38,11 @@ pub struct ScriptFnDef {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub environ: Option<EncapsulatedEnviron>, pub environ: Option<EncapsulatedEnviron>,
/// Function name. /// Function name.
pub name: Identifier, pub name: ImmutableString,
/// Function access mode. /// Function access mode.
pub access: FnAccess, pub access: FnAccess,
/// Names of function parameters. /// Names of function parameters.
pub params: StaticVec<Identifier>, pub params: StaticVec<ImmutableString>,
/// _(metadata)_ Function doc-comments (if any). /// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
@ -71,7 +71,7 @@ impl fmt::Display for ScriptFnDef {
self.name, self.name,
self.params self.params
.iter() .iter()
.map(SmartString::as_str) .map(|s| s.as_str())
.collect::<StaticVec<_>>() .collect::<StaticVec<_>>()
.join(", ") .join(", ")
) )
@ -132,7 +132,7 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
fn from(value: &'a ScriptFnDef) -> Self { fn from(value: &'a ScriptFnDef) -> Self {
Self { Self {
name: &value.name, 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, access: value.access,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: value.comments.iter().map(<_>::as_ref).collect(), comments: value.comments.iter().map(<_>::as_ref).collect(),

View File

@ -594,7 +594,7 @@ pub enum Stmt {
/// This variant does not map to any language structure. It is currently only used only to /// 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. /// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Share(Box<crate::Identifier>, Position), Share(crate::ImmutableString, Position),
} }
impl Default for Stmt { impl Default for Stmt {

View File

@ -20,7 +20,7 @@ fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize,
let line = pos.line().unwrap() - 1; let line = pos.line().unwrap() - 1;
let start = if line >= window.0 { line - window.0 } else { 0 }; let start = if line >= window.0 { line - window.0 } else { 0 };
let end = usize::min(line + window.1, lines.len() - 1); 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 // Print error position
if start >= end { if start >= end {

View File

@ -260,7 +260,7 @@ mod sample_functions {
/// print(result); // prints "42 123" /// print(result); // prints "42 123"
/// ``` /// ```
pub fn test(x: INT, y: INT) -> String { pub fn test(x: INT, y: INT) -> String {
format!("{} {}", x, y) format!("{x} {y}")
} }
/// This is a sample method for integers. /// This is a sample method for integers.

View File

@ -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_error(input: &str, mut err: EvalAltResult) {
fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) { fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) {
let line = pos.line().unwrap(); let line = pos.line().unwrap();
let line_no = format!("{}: ", line); let line_no = format!("{line}: ");
eprintln!("{}{}", line_no, lines[line - 1]); eprintln!("{}{}", line_no, lines[line - 1]);
eprintln!( eprintln!(

View File

@ -2,14 +2,15 @@
use crate::api::options::LangOptions; use crate::api::options::LangOptions;
use crate::func::native::{ use crate::func::native::{
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback,
OnVarCallback,
}; };
use crate::packages::{Package, StandardPackage}; use crate::packages::{Package, StandardPackage};
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::types::dynamic::Union; use crate::types::StringsInterner;
use crate::{ use crate::{
Dynamic, Identifier, ImmutableString, Module, OptimizationLevel, Position, RhaiResult, Shared, Dynamic, Identifier, ImmutableString, Locked, Module, OptimizationLevel, Position, RhaiResult,
StaticVec, Shared, StaticVec,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -105,7 +106,7 @@ pub struct Engine {
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>, pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
/// An empty [`ImmutableString`] for cloning purposes. /// An empty [`ImmutableString`] for cloning purposes.
pub(crate) empty_string: ImmutableString, pub(crate) interned_strings: Locked<StringsInterner<'static>>,
/// A set of symbols to disable. /// A set of symbols to disable.
pub(crate) disabled_symbols: BTreeSet<Identifier>, pub(crate) disabled_symbols: BTreeSet<Identifier>,
@ -269,7 +270,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()), module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
empty_string: ImmutableString::new(), interned_strings: StringsInterner::new().into(),
disabled_symbols: BTreeSet::new(), disabled_symbols: BTreeSet::new(),
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
custom_keywords: BTreeMap::new(), custom_keywords: BTreeMap::new(),
@ -310,30 +311,21 @@ impl Engine {
engine engine
} }
/// Get an empty [`ImmutableString`]. /// Get an interned string.
///
/// [`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)]
#[must_use] #[must_use]
pub fn const_empty_string(&self) -> ImmutableString { #[inline(always)]
self.empty_string.clone() pub(crate) fn get_interned_string(
&self,
string: impl AsRef<str> + Into<ImmutableString>,
) -> ImmutableString {
locked_write(&self.interned_strings).get(string).into()
} }
/// Check a result to ensure that it is valid. /// Check a result to ensure that it is valid.
pub(crate) fn check_return_value(&self, mut result: RhaiResult, _pos: Position) -> RhaiResult { #[inline]
if let Ok(ref mut r) = result { pub(crate) fn check_return_value(&self, result: RhaiResult, _pos: Position) -> RhaiResult {
// Concentrate all empty strings into one instance to save memory #[cfg(not(feature = "unchecked"))]
if let Dynamic(Union::Str(s, ..)) = r { if let Ok(ref r) = result {
if s.is_empty() {
if !s.ptr_eq(&self.empty_string) {
*s = self.const_empty_string();
}
return result;
}
}
#[cfg(not(feature = "unchecked"))]
self.check_data_size(r, _pos)?; self.check_data_size(r, _pos)?;
} }

View File

@ -82,7 +82,7 @@ impl Engine {
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax(); let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
Err(ERR::ErrorVariableNotFound( Err(ERR::ErrorVariableNotFound(
format!("{}{}{}", namespace, sep, var_name), format!("{namespace}{sep}{var_name}"),
namespace.position(), namespace.position(),
) )
.into()) .into())
@ -94,7 +94,7 @@ impl Engine {
if namespace.len() == 1 && namespace.root() == crate::engine::KEYWORD_GLOBAL { if namespace.len() == 1 && namespace.root() == crate::engine::KEYWORD_GLOBAL {
if let Some(ref constants) = global.constants { if let Some(ref constants) = global.constants {
if let Some(value) = 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(); let mut target: Target = value.clone().into();
// Module variables are constant // Module variables are constant
@ -106,7 +106,7 @@ impl Engine {
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax(); let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
return Err(ERR::ErrorVariableNotFound( return Err(ERR::ErrorVariableNotFound(
format!("{}{}{}", namespace, sep, var_name), format!("{namespace}{sep}{var_name}"),
namespace.position(), namespace.position(),
) )
.into()); .into());
@ -155,7 +155,7 @@ impl Engine {
if lib if lib
.iter() .iter()
.flat_map(|&m| m.iter_script_fn()) .flat_map(|&m| m.iter_script_fn())
.any(|(_, _, f, ..)| f == v.3) => .any(|(_, _, f, ..)| f == v.3.as_str()) =>
{ {
let val: Dynamic = let val: Dynamic =
crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into(); crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into();
@ -328,7 +328,7 @@ impl Engine {
// `... ${...} ...` // `... ${...} ...`
Expr::InterpolatedString(x, _) => { Expr::InterpolatedString(x, _) => {
let mut concat = self.const_empty_string().into(); let mut concat = self.get_interned_string("").into();
let target = &mut concat; let target = &mut concat;
let mut result = Ok(Dynamic::UNIT); 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"))] #[cfg(not(feature = "no_index"))]
@ -494,9 +497,9 @@ impl Engine {
// The first token acts as the custom syntax's key // The first token acts as the custom syntax's key
let key_token = custom.tokens.first().unwrap(); let key_token = custom.tokens.first().unwrap();
// The key should exist, unless the AST is compiled in a different 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(|| { let custom_def = self.custom_syntax.get(key_token.as_str()).ok_or_else(|| {
Box::new(ERR::ErrorCustomSyntax( 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(), custom.tokens.iter().map(<_>::to_string).collect(),
*pos, *pos,
)) ))

View File

@ -1,6 +1,6 @@
//! Global runtime state. //! Global runtime state.
use crate::{Dynamic, Engine, Identifier}; use crate::{Dynamic, Engine, Identifier, ImmutableString};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt, marker::PhantomData}; use std::{fmt, marker::PhantomData};
@ -9,7 +9,7 @@ use std::{fmt, marker::PhantomData};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub type GlobalConstants = pub type GlobalConstants =
crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, Dynamic>>>; crate::Shared<crate::Locked<std::collections::BTreeMap<ImmutableString, Dynamic>>>;
/// _(internals)_ Global runtime states. /// _(internals)_ Global runtime states.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -25,7 +25,7 @@ pub type GlobalConstants =
pub struct GlobalRuntimeState<'a> { pub struct GlobalRuntimeState<'a> {
/// Stack of module names. /// Stack of module names.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
keys: crate::StaticVec<Identifier>, keys: crate::StaticVec<ImmutableString>,
/// Stack of imported [modules][crate::Module]. /// Stack of imported [modules][crate::Module].
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: crate::StaticVec<crate::Shared<crate::Module>>, modules: crate::StaticVec<crate::Shared<crate::Module>>,
@ -159,7 +159,7 @@ impl GlobalRuntimeState<'_> {
self.keys self.keys
.iter() .iter()
.rev() .rev()
.position(|key| key == name) .position(|key| key.as_str() == name)
.map(|i| len - 1 - i) .map(|i| len - 1 - i)
} }
/// Push an imported [module][crate::Module] onto the stack. /// Push an imported [module][crate::Module] onto the stack.
@ -169,7 +169,7 @@ impl GlobalRuntimeState<'_> {
#[inline(always)] #[inline(always)]
pub fn push_import( pub fn push_import(
&mut self, &mut self,
name: impl Into<Identifier>, name: impl Into<ImmutableString>,
module: impl Into<crate::Shared<crate::Module>>, module: impl Into<crate::Shared<crate::Module>>,
) { ) {
self.keys.push(name.into()); self.keys.push(name.into());
@ -205,7 +205,7 @@ impl GlobalRuntimeState<'_> {
#[inline] #[inline]
pub(crate) fn iter_imports_raw( pub(crate) fn iter_imports_raw(
&self, &self,
) -> impl Iterator<Item = (&Identifier, &crate::Shared<crate::Module>)> { ) -> impl Iterator<Item = (&ImmutableString, &crate::Shared<crate::Module>)> {
self.keys.iter().rev().zip(self.modules.iter().rev()) self.keys.iter().rev().zip(self.modules.iter().rev())
} }
/// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order.
@ -216,7 +216,7 @@ impl GlobalRuntimeState<'_> {
#[inline] #[inline]
pub fn scan_imports_raw( pub fn scan_imports_raw(
&self, &self,
) -> impl Iterator<Item = (&Identifier, &crate::Shared<crate::Module>)> { ) -> impl Iterator<Item = (&ImmutableString, &crate::Shared<crate::Module>)> {
self.keys.iter().zip(self.modules.iter()) self.keys.iter().zip(self.modules.iter())
} }
/// Does the specified function hash key exist in the stack of globally-imported /// Does the specified function hash key exist in the stack of globally-imported
@ -310,9 +310,9 @@ impl GlobalRuntimeState<'_> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
impl IntoIterator for GlobalRuntimeState<'_> { impl IntoIterator for GlobalRuntimeState<'_> {
type Item = (Identifier, crate::Shared<crate::Module>); type Item = (ImmutableString, crate::Shared<crate::Module>);
type IntoIter = std::iter::Zip< type IntoIter = std::iter::Zip<
std::iter::Rev<smallvec::IntoIter<[Identifier; 3]>>, std::iter::Rev<smallvec::IntoIter<[ImmutableString; 3]>>,
std::iter::Rev<smallvec::IntoIter<[crate::Shared<crate::Module>; 3]>>, std::iter::Rev<smallvec::IntoIter<[crate::Shared<crate::Module>; 3]>>,
>; >;
@ -327,9 +327,9 @@ impl IntoIterator for GlobalRuntimeState<'_> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> { impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> {
type Item = (&'a Identifier, &'a crate::Shared<crate::Module>); type Item = (&'a ImmutableString, &'a crate::Shared<crate::Module>);
type IntoIter = std::iter::Zip< type IntoIter = std::iter::Zip<
std::iter::Rev<std::slice::Iter<'a, Identifier>>, std::iter::Rev<std::slice::Iter<'a, ImmutableString>>,
std::iter::Rev<std::slice::Iter<'a, crate::Shared<crate::Module>>>, std::iter::Rev<std::slice::Iter<'a, crate::Shared<crate::Module>>>,
>; >;
@ -341,7 +341,7 @@ impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> {
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
impl<K: Into<Identifier>, M: Into<crate::Shared<crate::Module>>> Extend<(K, M)> impl<K: Into<ImmutableString>, M: Into<crate::Shared<crate::Module>>> Extend<(K, M)>
for GlobalRuntimeState<'_> for GlobalRuntimeState<'_>
{ {
#[inline] #[inline]

View File

@ -7,7 +7,9 @@ use crate::ast::{
}; };
use crate::func::get_hasher; use crate::func::get_hasher;
use crate::types::dynamic::{AccessMode, Union}; 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}; use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -137,23 +139,10 @@ impl Engine {
pos: op_pos, pos: op_pos,
} = op_info; } = op_info;
let mut lock_guard; let mut lock_guard = target.write_lock::<Dynamic>().unwrap();
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::<Dynamic>().unwrap();
lhs_ptr_inner = &mut *lock_guard;
} else {
lhs_ptr_inner = &mut *target;
}
let hash = hash_op_assign; 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; let level = level + 1;
match self.call_native_fn( match self.call_native_fn(
@ -181,21 +170,17 @@ impl Engine {
} }
} else { } else {
// Normal assignment // Normal assignment
*target.write_lock::<Dynamic>().unwrap() = new_val;
}
#[cfg(not(feature = "no_closure"))] /*
if target.is_shared() { if let Some(mut guard) = target.write_lock::<Dynamic>() {
// Handle case where target is a `Dynamic` shared value if guard.is::<ImmutableString>() {
// (returned by a variable resolver, for example) let s = std::mem::take(&mut *guard).cast::<ImmutableString>();
*target.write_lock::<Dynamic>().unwrap() = new_val; *guard = self.get_interned_string(s).into();
} else {
*target.as_mut() = new_val;
}
#[cfg(feature = "no_closure")]
{
*target.as_mut() = new_val;
} }
} }
*/
target.propagate_changed_value(op_info.pos) target.propagate_changed_value(op_info.pos)
} }
@ -301,6 +286,13 @@ impl Engine {
.map(Dynamic::flatten); .map(Dynamic::flatten);
if let Ok(rhs_val) = rhs_result { if let Ok(rhs_val) = rhs_result {
let rhs_val = if rhs_val.is::<ImmutableString>() {
self.get_interned_string(rhs_val.cast::<ImmutableString>())
.into()
} else {
rhs_val
};
let _new_val = Some((rhs_val, *op_info)); let _new_val = Some((rhs_val, *op_info));
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`
@ -642,7 +634,7 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if x > INT::MAX as usize { if x > INT::MAX as usize {
loop_result = Err(ERR::ErrorArithmetic( loop_result = Err(ERR::ErrorArithmetic(
format!("for-loop counter overflow: {}", x), format!("for-loop counter overflow: {x}"),
counter.pos, counter.pos,
) )
.into()); .into());
@ -921,7 +913,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(alias) = _alias { 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) Ok(Dynamic::UNIT)
@ -1003,11 +995,11 @@ impl Engine {
// Export statement // Export statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(x, ..) => { Stmt::Export(x, ..) => {
let (Ident { name, pos, .. }, alias) = &**x; let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x;
// Mark scope variables as public // Mark scope variables as public
if let Some((index, ..)) = scope.get_index(name) { if let Some((index, ..)) = scope.get_index(name) {
let alias = if alias.is_empty() { name } else { alias }.clone(); 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) Ok(Dynamic::UNIT)
} else { } else {
Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into())

View File

@ -212,7 +212,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
"+" => Some(|_, args| { "+" => Some(|_, args| {
let x = args[0].as_char().expect(BUILTIN); let x = args[0].as_char().expect(BUILTIN);
let y = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN); let y = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN);
Ok(format!("{}{}", x, y).into()) Ok(format!("{x}{y}").into())
}), }),
"==" => Some(impl_op!(get_s1s2(==))), "==" => Some(impl_op!(get_s1s2(==))),
"!=" => 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<Fn
"+" => Some(|_, args| { "+" => Some(|_, args| {
let x = args[0].as_char().expect(BUILTIN); let x = args[0].as_char().expect(BUILTIN);
let y = args[1].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)),
"!=" => 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 { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let y = args[1].as_char().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN);
let mut x = args[0].write_lock::<Dynamic>().expect(BUILTIN); let x = &mut *args[0].write_lock::<Dynamic>().expect(BUILTIN);
Ok((*x = format!("{}{}", *x, y).into()).into()) Ok((*x = format!("{x}{y}").into()).into())
}), }),
_ => None, _ => None,
}; };
@ -820,14 +820,14 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let (first, second) = args.split_first_mut().expect(BUILTIN); let (first, second) = args.split_first_mut().expect(BUILTIN);
let mut x = first.write_lock::<ImmutableString>().expect(BUILTIN); let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN); let y = std::mem::take(second[0]).cast::<ImmutableString>();
Ok((*x += y).into()) Ok((*x += y).into())
}), }),
"-=" => Some(|_, args| { "-=" => Some(|_, args| {
let (first, second) = args.split_first_mut().expect(BUILTIN); let (first, second) = args.split_first_mut().expect(BUILTIN);
let mut x = first.write_lock::<ImmutableString>().expect(BUILTIN); let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = &*second[0].read_lock::<ImmutableString>().expect(BUILTIN); let y = std::mem::take(second[0]).cast::<ImmutableString>();
Ok((*x -= y).into()) Ok((*x -= y).into())
}), }),
_ => None, _ => None,

View File

@ -118,7 +118,7 @@ pub fn ensure_no_data_race(
.find(|(.., a)| a.is_locked()) .find(|(.., a)| a.is_locked())
{ {
return Err(ERR::ErrorDataRace( return Err(ERR::ErrorDataRace(
format!("argument #{} of function '{}'", n + 1, fn_name), format!("argument #{} of function '{fn_name}'", n + 1),
Position::NONE, Position::NONE,
) )
.into()); .into());
@ -150,10 +150,7 @@ impl Engine {
let (ns, sep) = ("", ""); let (ns, sep) = ("", "");
format!( format!(
"{}{}{} ({})", "{ns}{sep}{fn_name} ({})",
ns,
sep,
fn_name,
args.iter() args.iter()
.map(|a| if a.is::<ImmutableString>() { .map(|a| if a.is::<ImmutableString>() {
"&str | ImmutableString | String" "&str | ImmutableString | String"
@ -493,7 +490,7 @@ impl Engine {
let t0 = self.map_type_name(args[0].type_name()); let t0 = self.map_type_name(args[0].type_name());
let t1 = self.map_type_name(args[1].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? // index setter function not found?
@ -505,7 +502,7 @@ impl Engine {
let t1 = self.map_type_name(args[1].type_name()); let t1 = self.map_type_name(args[1].type_name());
let t2 = self.map_type_name(args[2].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? // Getter function not found?
@ -518,8 +515,7 @@ impl Engine {
Err(ERR::ErrorDotExpr( Err(ERR::ErrorDotExpr(
format!( format!(
"Unknown property '{}' - a getter is not registered for type '{}'", "Unknown property '{prop}' - a getter is not registered for type '{t0}'"
prop, t0
), ),
pos, pos,
) )
@ -537,8 +533,7 @@ impl Engine {
Err(ERR::ErrorDotExpr( Err(ERR::ErrorDotExpr(
format!( format!(
"No writable property '{}' - a setter is not registered for type '{}' to handle '{}'", "No writable property '{prop}' - a setter is not registered for type '{t0}' to handle '{t1}'"
prop, t0, t1
), ),
pos, pos,
) )
@ -586,7 +581,7 @@ impl Engine {
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> { fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
Err(ERR::ErrorRuntime( 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, pos,
) )
.into()) .into())

View File

@ -235,7 +235,7 @@ impl<'a> NativeCallContext<'a> {
#[inline] #[inline]
pub(crate) fn iter_imports_raw( pub(crate) fn iter_imports_raw(
&self, &self,
) -> impl Iterator<Item = (&crate::Identifier, &Shared<Module>)> { ) -> impl Iterator<Item = (&crate::ImmutableString, &Shared<Module>)> {
self.global.iter().flat_map(|&m| m.iter_imports_raw()) self.global.iter().flat_map(|&m| m.iter_imports_raw())
} }
/// _(internals)_ The current [`GlobalRuntimeState`], if any. /// _(internals)_ The current [`GlobalRuntimeState`], if any.

View File

@ -161,9 +161,9 @@ impl Engine {
// Error in sub function call // Error in sub function call
ERR::ErrorInFunctionCall(name, src, err, ..) => { ERR::ErrorInFunctionCall(name, src, err, ..) => {
let fn_name = if src.is_empty() { let fn_name = if src.is_empty() {
format!("{} < {}", name, fn_def.name) format!("{name} < {}", fn_def.name)
} else { } else {
format!("{} @ '{}' < {}", name, src, fn_def.name) format!("{name} @ '{src}' < {}", fn_def.name)
}; };
make_error(fn_name, fn_def, global, err, pos) make_error(fn_name, fn_def, global, err, pos)
} }

View File

@ -135,7 +135,7 @@ impl FuncInfo {
return if r == x { return if r == x {
typ.into() typ.into()
} else { } else {
format!("&mut {}", r).into() format!("&mut {r}").into()
}; };
} }
@ -202,7 +202,7 @@ impl FuncInfo {
}; };
let result: std::borrow::Cow<str> = match seg.next() { let result: std::borrow::Cow<str> = match seg.next() {
Some(typ) => { Some(typ) => {
format!("{}: {}", name, FuncInfo::format_type(typ, false)).into() format!("{name}: {}", FuncInfo::format_type(typ, false)).into()
} }
None => name.into(), None => name.into(),
}; };
@ -759,12 +759,12 @@ impl Module {
let num_params = fn_def.params.len(); let num_params = fn_def.params.len();
let hash_script = crate::calc_fn_hash(&fn_def.name, num_params); let hash_script = crate::calc_fn_hash(&fn_def.name, num_params);
#[cfg(feature = "metadata")] #[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( self.functions.insert(
hash_script, hash_script,
FuncInfo { FuncInfo {
metadata: FnMetadata { metadata: FnMetadata {
name: fn_def.name.clone(), name: fn_def.name.as_str().into(),
namespace: FnNamespace::Internal, namespace: FnNamespace::Internal,
access: fn_def.access, access: fn_def.access,
params: num_params, params: num_params,
@ -2016,12 +2016,11 @@ impl Module {
return Err(crate::ERR::ErrorMismatchDataType( return Err(crate::ERR::ErrorMismatchDataType(
"".to_string(), "".to_string(),
if fn_ptr.is_anonymous() { if fn_ptr.is_anonymous() {
format!("cannot export closure in variable {}", _name) format!("cannot export closure in variable {_name}")
} else { } else {
format!( format!(
"cannot export function pointer to local function '{}' in variable {}", "cannot export function pointer to local function '{}' in variable {_name}",
fn_ptr.fn_name(), fn_ptr.fn_name()
_name
) )
}, },
crate::Position::NONE, crate::Position::NONE,

View File

@ -1050,7 +1050,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// `` // ``
Expr::InterpolatedString(x, pos) if x.is_empty() => { Expr::InterpolatedString(x, pos) if x.is_empty() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::StringConstant(state.engine.const_empty_string(), *pos); *expr = Expr::StringConstant(state.engine.get_interned_string(""), *pos);
} }
// `... ${const} ...` // `... ${const} ...`
Expr::InterpolatedString(..) if expr.is_constant() => { Expr::InterpolatedString(..) if expr.is_constant() => {

View File

@ -24,7 +24,7 @@ macro_rules! gen_arithmetic_functions {
#[rhai_fn(name = "+", return_raw)] #[rhai_fn(name = "+", return_raw)]
pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { 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 { } else {
Ok(x + y) Ok(x + y)
} }
@ -32,7 +32,7 @@ macro_rules! gen_arithmetic_functions {
#[rhai_fn(name = "-", return_raw)] #[rhai_fn(name = "-", return_raw)]
pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { 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 { } else {
Ok(x - y) Ok(x - y)
} }
@ -40,7 +40,7 @@ macro_rules! gen_arithmetic_functions {
#[rhai_fn(name = "*", return_raw)] #[rhai_fn(name = "*", return_raw)]
pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { 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 { } else {
Ok(x * y) Ok(x * y)
} }
@ -50,9 +50,9 @@ macro_rules! gen_arithmetic_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
// Detect division by zero // Detect division by zero
if y == 0 { if y == 0 {
Err(make_err(format!("Division by zero: {} / {}", x, y))) Err(make_err(format!("Division by zero: {x} / {y}")))
} else { } 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 { } else {
Ok(x / y) Ok(x / y)
@ -61,7 +61,7 @@ macro_rules! gen_arithmetic_functions {
#[rhai_fn(name = "%", return_raw)] #[rhai_fn(name = "%", return_raw)]
pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { 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 { } else {
Ok(x % y) Ok(x % y)
} }
@ -70,11 +70,11 @@ macro_rules! gen_arithmetic_functions {
pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { 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 { } 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 { } 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 { } else {
Ok(x.pow(y as u32)) 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> { pub fn shift_left(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { 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 { } 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 { } 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 { } else {
Ok(x << y) Ok(x << y)
@ -99,11 +99,11 @@ macro_rules! gen_arithmetic_functions {
pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { 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 { } 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 { } 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 { } else {
Ok(x >> y) Ok(x >> y)
@ -151,7 +151,7 @@ macro_rules! gen_signed_functions {
#[rhai_fn(name = "-", return_raw)] #[rhai_fn(name = "-", return_raw)]
pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> { pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { 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 { } else {
Ok(-x) Ok(-x)
} }
@ -164,7 +164,7 @@ macro_rules! gen_signed_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) { 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 { } else {
Ok(x.abs()) Ok(x.abs())
} }
@ -372,8 +372,7 @@ mod f32_functions {
pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf<f32> { pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf<f32> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
Err(make_err(format!( Err(make_err(format!(
"Number raised to too large an index: {} ~ {}", "Number raised to too large an index: {x} ** {y}"
x, y
))) )))
} else { } else {
Ok(x.powi(y as i32)) Ok(x.powi(y as i32))
@ -495,7 +494,7 @@ pub mod decimal_functions {
pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> { pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_add(y) 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 { } else {
Ok(x + y) Ok(x + y)
} }
@ -504,7 +503,7 @@ pub mod decimal_functions {
pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> { pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_sub(y) 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 { } else {
Ok(x - y) Ok(x - y)
} }
@ -513,7 +512,7 @@ pub mod decimal_functions {
pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> { pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_mul(y) 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 { } else {
Ok(x * y) Ok(x * y)
} }
@ -523,10 +522,10 @@ pub mod decimal_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
// Detect division by zero // Detect division by zero
if y == Decimal::zero() { if y == Decimal::zero() {
Err(make_err(format!("Division by zero: {} / {}", x, y))) Err(make_err(format!("Division by zero: {x} / {y}")))
} else { } else {
x.checked_div(y) 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 { } else {
Ok(x / y) Ok(x / y)
@ -535,12 +534,8 @@ pub mod decimal_functions {
#[rhai_fn(skip, return_raw)] #[rhai_fn(skip, return_raw)]
pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> { pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_rem(y).ok_or_else(|| { x.checked_rem(y)
make_err(format!( .ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}")))
"Modulo division by zero or overflow: {} % {}",
x, y
))
})
} else { } else {
Ok(x % y) Ok(x % y)
} }
@ -549,7 +544,7 @@ pub mod decimal_functions {
pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> { pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_powd(y) 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 { } else {
Ok(x.pow(y)) Ok(x.pow(y))
} }

View File

@ -1,6 +1,6 @@
use crate::def_package; use crate::def_package;
use crate::plugin::*; use crate::plugin::*;
use crate::types::dynamic::Tag; use crate::types::{dynamic::Tag, StringsInterner};
use crate::{Dynamic, RhaiResultOf, ERR, INT}; use crate::{Dynamic, RhaiResultOf, ERR, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -49,24 +49,21 @@ mod core_functions {
/// ``` /// ```
#[rhai_fn(name = "set_tag", set = "tag", return_raw)] #[rhai_fn(name = "set_tag", set = "tag", return_raw)]
pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> { 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( Err(ERR::ErrorArithmetic(
format!( format!(
"{} is too small to fit into a tag (must be between {} and {})", "{tag} is too small to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})"
tag,
Tag::MIN,
Tag::MAX
), ),
Position::NONE, Position::NONE,
) )
.into()) .into())
} else if tag > Tag::MAX as INT { } else if tag > TAG_MAX as INT {
Err(ERR::ErrorArithmetic( Err(ERR::ErrorArithmetic(
format!( format!(
"{} is too large to fit into a tag (must be between {} and {})", "{tag} is too large to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})"
tag,
Tag::MIN,
Tag::MAX
), ),
Position::NONE, Position::NONE,
) )
@ -133,54 +130,47 @@ fn collect_fn_metadata(
+ Copy, + Copy,
) -> crate::Array { ) -> crate::Array {
use crate::{ast::ScriptFnDef, Array, Identifier, Map}; use crate::{ast::ScriptFnDef, Array, Identifier, Map};
use std::collections::BTreeSet;
// Create a metadata record for a function. // Create a metadata record for a function.
fn make_metadata( fn make_metadata(
dict: &BTreeSet<Identifier>, dict: &mut StringsInterner,
#[cfg(not(feature = "no_module"))] namespace: Identifier, #[cfg(not(feature = "no_module"))] namespace: Identifier,
func: &ScriptFnDef, func: &ScriptFnDef,
) -> Map { ) -> Map {
const DICT: &str = "key exists";
let mut map = Map::new(); let mut map = Map::new();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if !namespace.is_empty() { 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.as_str()).into());
map.insert( map.insert(
dict.get("name").expect(DICT).clone(), "access".into(),
func.name.clone().into(), dict.get(match func.access {
); FnAccess::Public => "public",
map.insert( FnAccess::Private => "private",
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(),
}
.into(), .into(),
); );
map.insert( map.insert(
dict.get("is_anonymous").expect(DICT).clone(), "is_anonymous".into(),
func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), func.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
); );
map.insert( map.insert(
dict.get("params").expect(DICT).clone(), "params".into(),
func.params func.params
.iter() .iter()
.cloned() .map(|p| dict.get(p.as_str()).into())
.map(Into::into)
.collect::<Array>() .collect::<Array>()
.into(), .into(),
); );
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if !func.comments.is_empty() { if !func.comments.is_empty() {
map.insert( map.insert(
dict.get("comments").expect(DICT).clone(), "comments".into(),
func.comments func.comments
.iter() .iter()
.map(|s| Into::into(&**s)) .map(|s| dict.get(s.as_ref()).into())
.collect::<Array>() .collect::<Array>()
.into(), .into(),
); );
@ -189,23 +179,7 @@ fn collect_fn_metadata(
map map
} }
// Intern strings let dict = &mut StringsInterner::new();
let dict: BTreeSet<Identifier> = [
#[cfg(not(feature = "no_module"))]
"namespace",
"name",
"access",
"public",
"private",
"is_anonymous",
"params",
#[cfg(feature = "metadata")]
"comments",
]
.iter()
.map(|&s| s.into())
.collect();
let mut list = Array::new(); let mut list = Array::new();
ctx.iter_namespaces() ctx.iter_namespaces()
@ -214,7 +188,7 @@ fn collect_fn_metadata(
.for_each(|(.., f)| { .for_each(|(.., f)| {
list.push( list.push(
make_metadata( make_metadata(
&dict, dict,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Identifier::new_const(), Identifier::new_const(),
f, f,
@ -231,7 +205,7 @@ fn collect_fn_metadata(
.for_each(|(.., f)| { .for_each(|(.., f)| {
list.push( list.push(
make_metadata( make_metadata(
&dict, dict,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Identifier::new_const(), Identifier::new_const(),
f, f,
@ -249,7 +223,7 @@ fn collect_fn_metadata(
.for_each(|(.., f)| { .for_each(|(.., f)| {
list.push( list.push(
make_metadata( make_metadata(
&dict, dict,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Identifier::new_const(), Identifier::new_const(),
f, f,
@ -262,8 +236,8 @@ fn collect_fn_metadata(
{ {
// Recursively scan modules for script-defined functions. // Recursively scan modules for script-defined functions.
fn scan_module( fn scan_module(
dict: &mut StringsInterner,
list: &mut Array, list: &mut Array,
dict: &BTreeSet<Identifier>,
namespace: &str, namespace: &str,
module: &Module, module: &Module,
filter: impl Fn( filter: impl Fn(
@ -281,17 +255,15 @@ fn collect_fn_metadata(
.for_each(|(.., f)| list.push(make_metadata(dict, namespace.into(), f).into())); .for_each(|(.., f)| list.push(make_metadata(dict, namespace.into(), f).into()));
for (ns, m) in module.iter_sub_modules() { for (ns, m) in module.iter_sub_modules() {
let ns = format!( let ns = format!(
"{}{}{}", "{namespace}{}{ns}",
namespace, crate::tokenizer::Token::DoubleColon.literal_syntax()
crate::tokenizer::Token::DoubleColon.literal_syntax(),
ns
); );
scan_module(list, dict, &ns, &**m, filter); scan_module(dict, list, &ns, &**m, filter);
} }
} }
for (ns, m) in ctx.iter_imports_raw() { for (ns, m) in ctx.iter_imports_raw() {
scan_module(&mut list, &dict, ns, &**m, filter); scan_module(dict, &mut list, ns, &**m, filter);
} }
} }

View File

@ -139,16 +139,14 @@ mod int_functions {
#[rhai_fn(name = "parse_int", return_raw)] #[rhai_fn(name = "parse_int", return_raw)]
pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf<INT> { pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf<INT> {
if !(2..=36).contains(&radix) { if !(2..=36).contains(&radix) {
return Err(ERR::ErrorArithmetic( return Err(
format!("Invalid radix: '{}'", radix), ERR::ErrorArithmetic(format!("Invalid radix: '{radix}'"), Position::NONE).into(),
Position::NONE, );
)
.into());
} }
INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { INT::from_str_radix(string.trim(), radix as u32).map_err(|err| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Error parsing integer number '{}': {}", string, err), format!("Error parsing integer number '{string}': {err}"),
Position::NONE, Position::NONE,
) )
.into() .into()
@ -316,7 +314,7 @@ mod float_functions {
pub fn f32_to_int(x: f32) -> RhaiResultOf<INT> { pub fn f32_to_int(x: f32) -> RhaiResultOf<INT> {
if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f32) || x < (INT::MIN as f32)) { if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f32) || x < (INT::MIN as f32)) {
Err( Err(
ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE)
.into(), .into(),
) )
} else { } else {
@ -328,7 +326,7 @@ mod float_functions {
pub fn f64_to_int(x: f64) -> RhaiResultOf<INT> { pub fn f64_to_int(x: f64) -> RhaiResultOf<INT> {
if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f64) || x < (INT::MIN as f64)) { if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f64) || x < (INT::MIN as f64)) {
Err( Err(
ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE)
.into(), .into(),
) )
} else { } else {
@ -348,7 +346,7 @@ mod float_functions {
pub fn parse_float(string: &str) -> RhaiResultOf<FLOAT> { pub fn parse_float(string: &str) -> RhaiResultOf<FLOAT> {
string.trim().parse::<FLOAT>().map_err(|err| { string.trim().parse::<FLOAT>().map_err(|err| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Error parsing floating-point number '{}': {}", string, err), format!("Error parsing floating-point number '{string}': {err}"),
Position::NONE, Position::NONE,
) )
.into() .into()
@ -416,14 +414,14 @@ mod decimal_functions {
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> RhaiResultOf<Decimal> { pub fn sqrt(x: Decimal) -> RhaiResultOf<Decimal> {
x.sqrt() 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. /// Return the exponential of the decimal number.
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn exp(x: Decimal) -> RhaiResultOf<Decimal> { pub fn exp(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_exp() x.checked_exp()
.ok_or_else(|| make_err(format!("Exponential overflow: e ** {}", x,))) .ok_or_else(|| make_err(format!("Exponential overflow: e ** {x}")))
} else { } else {
Ok(x.exp()) Ok(x.exp())
} }
@ -433,7 +431,7 @@ mod decimal_functions {
pub fn ln(x: Decimal) -> RhaiResultOf<Decimal> { pub fn ln(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_ln() 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 { } else {
Ok(x.ln()) Ok(x.ln())
} }
@ -443,7 +441,7 @@ mod decimal_functions {
pub fn log10(x: Decimal) -> RhaiResultOf<Decimal> { pub fn log10(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
x.checked_log10() 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 { } else {
Ok(x.log10()) Ok(x.log10())
} }
@ -471,8 +469,7 @@ mod decimal_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if digits < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "Invalid number of digits for rounding: {digits}"
digits
))); )));
} }
if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) {
@ -489,8 +486,7 @@ mod decimal_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if digits < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "Invalid number of digits for rounding: {digits}"
digits
))); )));
} }
if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) {
@ -507,8 +503,7 @@ mod decimal_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if digits < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "Invalid number of digits for rounding: {digits}"
digits
))); )));
} }
if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) {
@ -525,8 +520,7 @@ mod decimal_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if digits < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "Invalid number of digits for rounding: {digits}"
digits
))); )));
} }
if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) {
@ -543,8 +537,7 @@ mod decimal_functions {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if digits < 0 { if digits < 0 {
return Err(make_err(format!( return Err(make_err(format!(
"Invalid number of digits for rounding: {}", "Invalid number of digits for rounding: {digits}"
digits
))); )));
} }
if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) {
@ -572,7 +565,7 @@ mod decimal_functions {
match n { match n {
Some(n) => Ok(n), Some(n) => Ok(n),
_ => Err(ERR::ErrorArithmetic( _ => Err(ERR::ErrorArithmetic(
format!("Integer overflow: to_int({})", x), format!("Integer overflow: to_int({x})"),
Position::NONE, Position::NONE,
) )
.into()), .into()),
@ -603,7 +596,7 @@ mod decimal_functions {
.or_else(|_| Decimal::from_scientific(string)) .or_else(|_| Decimal::from_scientific(string))
.map_err(|err| { .map_err(|err| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Error parsing decimal number '{}': {}", string, err), format!("Error parsing decimal number '{string}': {err}"),
Position::NONE, Position::NONE,
) )
.into() .into()
@ -616,7 +609,7 @@ mod decimal_functions {
pub fn f32_to_decimal(x: f32) -> RhaiResultOf<Decimal> { pub fn f32_to_decimal(x: f32) -> RhaiResultOf<Decimal> {
Decimal::try_from(x).map_err(|_| { Decimal::try_from(x).map_err(|_| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Cannot convert to Decimal: to_decimal({})", x), format!("Cannot convert to Decimal: to_decimal({x})"),
Position::NONE, Position::NONE,
) )
.into() .into()
@ -628,7 +621,7 @@ mod decimal_functions {
pub fn f64_to_decimal(x: f64) -> RhaiResultOf<Decimal> { pub fn f64_to_decimal(x: f64) -> RhaiResultOf<Decimal> {
Decimal::try_from(x).map_err(|_| { Decimal::try_from(x).map_err(|_| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Cannot convert to Decimal: to_decimal({})", x), format!("Cannot convert to Decimal: to_decimal({x})"),
Position::NONE, Position::NONE,
) )
.into() .into()
@ -640,7 +633,7 @@ mod decimal_functions {
pub fn to_float(x: Decimal) -> RhaiResultOf<FLOAT> { pub fn to_float(x: Decimal) -> RhaiResultOf<FLOAT> {
FLOAT::try_from(x).map_err(|_| { FLOAT::try_from(x).map_err(|_| {
ERR::ErrorArithmetic( ERR::ErrorArithmetic(
format!("Cannot convert to floating-point: to_float({})", x), format!("Cannot convert to floating-point: to_float({x})"),
Position::NONE, Position::NONE,
) )
.into() .into()

View File

@ -69,13 +69,13 @@ mod print_debug_functions {
/// Convert the value of the `item` into a string in debug format. /// Convert the value of the `item` into a string in debug format.
#[rhai_fn(name = "to_debug", pure)] #[rhai_fn(name = "to_debug", pure)]
pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { 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. /// Return the empty string.
#[rhai_fn(name = "print", name = "debug")] #[rhai_fn(name = "print", name = "debug")]
pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString {
ctx.engine().const_empty_string() ctx.engine().get_interned_string("")
} }
/// Return the `string`. /// Return the `string`.
@ -86,7 +86,7 @@ mod print_debug_functions {
/// Convert the string into debug format. /// Convert the string into debug format.
#[rhai_fn(name = "debug", name = "to_debug", pure)] #[rhai_fn(name = "debug", name = "to_debug", pure)]
pub fn debug_string(string: &mut ImmutableString) -> ImmutableString { pub fn debug_string(string: &mut ImmutableString) -> ImmutableString {
format!("{:?}", string).into() format!("{string:?}").into()
} }
/// Return the character into a string. /// Return the character into a string.
@ -97,7 +97,7 @@ mod print_debug_functions {
/// Convert the string into debug format. /// Convert the string into debug format.
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_char(character: char) -> ImmutableString { pub fn debug_char(character: char) -> ImmutableString {
format!("{:?}", character).into() format!("{character:?}").into()
} }
/// Convert the function pointer into a string in debug format. /// Convert the function pointer into a string in debug format.
@ -109,19 +109,19 @@ mod print_debug_functions {
/// Return the boolean value into a string. /// Return the boolean value into a string.
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_bool(value: bool) -> ImmutableString { pub fn print_bool(value: bool) -> ImmutableString {
format!("{}", value).into() value.to_string().into()
} }
/// Convert the boolean value into a string in debug format. /// Convert the boolean value into a string in debug format.
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_bool(value: bool) -> ImmutableString { pub fn debug_bool(value: bool) -> ImmutableString {
format!("{:?}", value).into() format!("{value:?}").into()
} }
/// Return the empty string. /// Return the empty string.
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_unit(ctx: NativeCallContext, unit: ()) -> ImmutableString { pub fn print_unit(ctx: NativeCallContext, unit: ()) -> ImmutableString {
let _ = unit; let _ = unit;
ctx.engine().const_empty_string() ctx.engine().get_interned_string("")
} }
/// Convert the unit into a string in debug format. /// Convert the unit into a string in debug format.
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
@ -146,13 +146,15 @@ mod print_debug_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f64(number: f64) -> ImmutableString { 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. /// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f32(number: f32) -> ImmutableString { 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. /// Convert the array into a string.
@ -215,13 +217,13 @@ mod print_debug_functions {
#[export_module] #[export_module]
mod number_formatting { mod number_formatting {
fn to_hex<T: LowerHex>(value: T) -> ImmutableString { fn to_hex<T: LowerHex>(value: T) -> ImmutableString {
format!("{:x}", value).into() format!("{value:x}").into()
} }
fn to_octal<T: Octal>(value: T) -> ImmutableString { fn to_octal<T: Octal>(value: T) -> ImmutableString {
format!("{:o}", value).into() format!("{value:o}").into()
} }
fn to_binary<T: Binary>(value: T) -> ImmutableString { fn to_binary<T: Binary>(value: T) -> ImmutableString {
format!("{:b}", value).into() format!("{value:b}").into()
} }
/// Convert the `value` into a string in hex format. /// Convert the `value` into a string in hex format.

View File

@ -30,7 +30,7 @@ mod string_functions {
if s.is_empty() { if s.is_empty() {
string.clone() string.clone()
} else { } else {
format!("{}{}", string, s).into() format!("{string}{s}").into()
} }
} }
#[rhai_fn(name = "+=", name = "append")] #[rhai_fn(name = "+=", name = "append")]
@ -38,7 +38,7 @@ mod string_functions {
let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
if !s.is_empty() { if !s.is_empty() {
*string = format!("{}{}", string, s).into(); *string = format!("{string}{s}").into();
} }
} }
#[rhai_fn(name = "+", pure)] #[rhai_fn(name = "+", pure)]
@ -59,7 +59,10 @@ mod string_functions {
// The following are needed in order to override the generic versions with `Dynamic` parameters. // The following are needed in order to override the generic versions with `Dynamic` parameters.
#[rhai_fn(name = "+", pure)] #[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 &*string1 + string2
} }
#[rhai_fn(name = "+", pure)] #[rhai_fn(name = "+", pure)]
@ -68,7 +71,7 @@ mod string_functions {
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn add_prepend_char(character: char, string: &str) -> ImmutableString { pub fn add_prepend_char(character: char, string: &str) -> ImmutableString {
format!("{}{}", character, string).into() format!("{character}{string}").into()
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
@ -81,6 +84,20 @@ mod string_functions {
string 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"))] #[cfg(not(feature = "no_index"))]
pub mod blob_functions { pub mod blob_functions {
use crate::Blob; use crate::Blob;
@ -320,7 +337,7 @@ mod string_functions {
len: INT, len: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() || len <= 0 { if string.is_empty() || len <= 0 {
return ctx.engine().const_empty_string(); return ctx.engine().get_interned_string("");
} }
let mut chars = StaticVec::<char>::with_capacity(len as usize); let mut chars = StaticVec::<char>::with_capacity(len as usize);
@ -803,13 +820,13 @@ mod string_functions {
len: INT, len: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() { 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 mut chars = StaticVec::with_capacity(string.len());
let offset = if string.is_empty() || len <= 0 { let offset = if string.is_empty() || len <= 0 {
return ctx.engine().const_empty_string(); return ctx.engine().get_interned_string("");
} else if start < 0 { } else if start < 0 {
let abs_start = start.unsigned_abs() as usize; let abs_start = start.unsigned_abs() as usize;
chars.extend(string.chars()); chars.extend(string.chars());
@ -819,7 +836,7 @@ mod string_functions {
chars.len() - abs_start chars.len() - abs_start
} }
} else if start as usize >= string.chars().count() { } else if start as usize >= string.chars().count() {
return ctx.engine().const_empty_string(); return ctx.engine().get_interned_string("");
} else { } else {
start as usize start as usize
}; };
@ -865,7 +882,7 @@ mod string_functions {
start: INT, start: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() { if string.is_empty() {
ctx.engine().const_empty_string() ctx.engine().get_interned_string("")
} else { } else {
let len = string.len() as INT; let len = string.len() as INT;
sub_string(ctx, string, start, len) sub_string(ctx, string, start, len)
@ -1245,7 +1262,7 @@ mod string_functions {
let num_chars = string.chars().count(); let num_chars = string.chars().count();
if abs_index > num_chars { if abs_index > num_chars {
vec![ vec![
ctx.engine().const_empty_string().into(), ctx.engine().get_interned_string("").into(),
string.as_str().into(), string.as_str().into(),
] ]
} else { } else {

View File

@ -66,8 +66,7 @@ mod time_functions {
if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp.elapsed: {}", "Integer overflow for timestamp.elapsed: {seconds}"
seconds
))) )))
} else if timestamp > Instant::now() { } else if timestamp > Instant::now() {
Err(make_arithmetic_err("Time-stamp is later than 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) { if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp duration: -{}", "Integer overflow for timestamp duration: -{seconds}"
seconds
))) )))
} else { } else {
Ok((-(seconds as INT)).into()) Ok((-(seconds as INT)).into())
@ -105,8 +103,7 @@ mod time_functions {
if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp duration: {}", "Integer overflow for timestamp duration: {seconds}"
seconds
))) )))
} else { } else {
Ok((seconds as INT).into()) Ok((seconds as INT).into())
@ -122,16 +119,14 @@ mod time_functions {
} else if cfg!(not(feature = "unchecked")) { } else if cfg!(not(feature = "unchecked")) {
if seconds > (INT::MAX as FLOAT) { if seconds > (INT::MAX as FLOAT) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {}", "Integer overflow for timestamp add: {seconds}"
seconds
))) )))
} else { } else {
timestamp timestamp
.checked_add(Duration::from_millis((seconds * 1000.0) as u64)) .checked_add(Duration::from_millis((seconds * 1000.0) as u64))
.ok_or_else(|| { .ok_or_else(|| {
make_arithmetic_err(format!( make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)", "Timestamp overflow when adding {seconds} second(s)"
seconds
)) ))
}) })
} }
@ -145,16 +140,14 @@ mod time_functions {
} else if cfg!(not(feature = "unchecked")) { } else if cfg!(not(feature = "unchecked")) {
if seconds > (INT::MAX as FLOAT) { if seconds > (INT::MAX as FLOAT) {
Err(make_arithmetic_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {}", "Integer overflow for timestamp add: {seconds}"
seconds
))) )))
} else { } else {
timestamp timestamp
.checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) .checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
.ok_or_else(|| { .ok_or_else(|| {
make_arithmetic_err(format!( make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)", "Timestamp overflow when adding {seconds} second(s)"
seconds
)) ))
}) })
} }
@ -195,8 +188,7 @@ mod time_functions {
.checked_add(Duration::from_secs(seconds as u64)) .checked_add(Duration::from_secs(seconds as u64))
.ok_or_else(|| { .ok_or_else(|| {
make_arithmetic_err(format!( make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)", "Timestamp overflow when adding {seconds} second(s)"
seconds
)) ))
}) })
} else { } else {
@ -211,8 +203,7 @@ mod time_functions {
.checked_sub(Duration::from_secs(seconds as u64)) .checked_sub(Duration::from_secs(seconds as u64))
.ok_or_else(|| { .ok_or_else(|| {
make_arithmetic_err(format!( make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)", "Timestamp overflow when adding {seconds} second(s)"
seconds
)) ))
}) })
} else { } else {

View File

@ -43,13 +43,22 @@ const NEVER_ENDS: &str = "`Token`";
/// Unroll `switch` ranges no larger than this. /// Unroll `switch` ranges no larger than this.
const SMALL_SWITCH_RANGE: usize = 16; 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. /// _(internals)_ A type that encapsulates the current state of the parser.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub struct ParseState<'e> { pub struct ParseState<'e> {
/// Input stream buffer containing the next character to read. /// Input stream buffer containing the next character to read.
pub tokenizer_control: TokenizerControl, pub tokenizer_control: TokenizerControl,
/// Interned strings. /// String interners.
interned_strings: StringsInterner<'e>, interned_strings: InternedStrings<'e>,
/// External [scope][Scope] with constants. /// External [scope][Scope] with constants.
pub scope: &'e Scope<'e>, pub scope: &'e Scope<'e>,
/// Global runtime state. /// Global runtime state.
@ -71,10 +80,10 @@ pub struct ParseState<'e> {
pub allow_capture: bool, pub allow_capture: bool,
/// Encapsulates a local stack with imported [module][crate::Module] names. /// Encapsulates a local stack with imported [module][crate::Module] names.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub imports: StaticVec<Identifier>, pub imports: StaticVec<ImmutableString>,
/// List of globally-imported [module][crate::Module] names. /// List of globally-imported [module][crate::Module] names.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub global_imports: StaticVec<Identifier>, pub global_imports: StaticVec<ImmutableString>,
/// Maximum levels of expression nesting (0 for unlimited). /// Maximum levels of expression nesting (0 for unlimited).
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub max_expr_depth: usize, pub max_expr_depth: usize,
@ -106,7 +115,12 @@ impl<'e> ParseState<'e> {
/// Create a new [`ParseState`]. /// Create a new [`ParseState`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self { pub fn new(
engine: &Engine,
scope: &'e Scope,
interners: InternedStrings<'e>,
tokenizer_control: TokenizerControl,
) -> Self {
Self { Self {
tokenizer_control, tokenizer_control,
expr_filter: |_| true, expr_filter: |_| true,
@ -114,7 +128,7 @@ impl<'e> ParseState<'e> {
external_vars: Vec::new(), external_vars: Vec::new(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
interned_strings: StringsInterner::new(), interned_strings: interners,
scope, scope,
global: GlobalRuntimeState::new(engine), global: GlobalRuntimeState::new(engine),
stack: Scope::new(), stack: Scope::new(),
@ -230,27 +244,47 @@ impl<'e> ParseState<'e> {
.iter() .iter()
.rev() .rev()
.enumerate() .enumerate()
.find(|&(.., n)| n == name) .find(|(.., n)| n.as_str() == name)
.and_then(|(i, ..)| NonZeroUsize::new(i + 1)) .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, prefix: impl AsRef<str>, text: impl AsRef<str>) -> Identifier {
self.interned_strings.get(prefix, text).into()
}
/// Get an interned string, creating one if it is not yet interned. /// Get an interned string, creating one if it is not yet interned.
#[inline(always)] #[inline(always)]
#[allow(dead_code)] #[allow(dead_code)]
#[must_use] #[must_use]
pub fn get_interned_string( pub fn get_interned_string(
&mut self, &mut self,
prefix: impl AsRef<str>, text: impl AsRef<str> + Into<ImmutableString>,
text: impl AsRef<str>,
) -> ImmutableString { ) -> ImmutableString {
self.interned_strings.get(prefix, text) self.interned_strings.main.get(text)
}
/// 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_getter(
&mut self,
text: impl AsRef<str> + Into<ImmutableString>,
) -> ImmutableString {
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<str> + Into<ImmutableString>,
) -> ImmutableString {
self.interned_strings
.setters
.get_with_mapper(|s| crate::engine::make_setter(s.as_ref()).into(), text)
} }
} }
@ -326,18 +360,14 @@ impl Expr {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Variable(x, ..) if !x.1.is_empty() => unreachable!("qualified property"), Self::Variable(x, ..) if !x.1.is_empty() => unreachable!("qualified property"),
Self::Variable(x, .., pos) => { Self::Variable(x, .., pos) => {
let ident = x.3; let ident = x.3.clone();
let getter = state.get_identifier(crate::engine::FN_GET, &ident); let getter = state.get_interned_getter(ident.as_str());
let hash_get = calc_fn_hash(&getter, 1); let hash_get = calc_fn_hash(&getter, 1);
let setter = state.get_identifier(crate::engine::FN_SET, &ident); let setter = state.get_interned_setter(ident.as_str());
let hash_set = calc_fn_hash(&setter, 2); let hash_set = calc_fn_hash(&setter, 2);
Self::Property( Self::Property(
Box::new(( Box::new(((getter, hash_get), (setter, hash_set), ident)),
(getter, hash_get),
(setter, hash_set),
state.get_interned_string("", &ident),
)),
pos, pos,
) )
} }
@ -512,7 +542,7 @@ impl Engine {
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FnLib, lib: &mut FnLib,
id: Identifier, id: ImmutableString,
no_args: bool, no_args: bool,
capture_parent_scope: bool, capture_parent_scope: bool,
#[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace, #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace,
@ -536,7 +566,7 @@ impl Engine {
Token::EOF => { Token::EOF => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::RightParen.into(), 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)) .into_err(*token_pos))
} }
@ -564,7 +594,7 @@ impl Engine {
if settings.options.contains(LangOptions::STRICT_VAR) if settings.options.contains(LangOptions::STRICT_VAR)
&& index.is_none() && index.is_none()
&& !is_global && !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) && !self.global_sub_modules.contains_key(root)
{ {
return Err( return Err(
@ -588,7 +618,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_identifier("", id), name: id,
capture_parent_scope, capture_parent_scope,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
@ -632,7 +662,7 @@ impl Engine {
if settings.options.contains(LangOptions::STRICT_VAR) if settings.options.contains(LangOptions::STRICT_VAR)
&& index.is_none() && index.is_none()
&& !is_global && !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) && !self.global_sub_modules.contains_key(root)
{ {
return Err(PERR::ModuleUndefined(root.to_string()) return Err(PERR::ModuleUndefined(root.to_string())
@ -659,7 +689,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_identifier("", id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
@ -677,7 +707,7 @@ impl Engine {
(Token::EOF, pos) => { (Token::EOF, pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::RightParen.into(), 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)) .into_err(*pos))
} }
@ -687,7 +717,7 @@ impl Engine {
(.., pos) => { (.., pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::Comma.into(), Token::Comma.into(),
format!("to separate the arguments to function call '{}'", id), format!("to separate the arguments to function call '{id}'"),
) )
.into_err(*pos)) .into_err(*pos))
} }
@ -1011,10 +1041,7 @@ impl Engine {
(.., pos) => { (.., pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::Colon.into(), Token::Colon.into(),
format!( format!("to follow the property '{name}' in this object map literal"),
"to follow the property '{}' in this object map literal",
name
),
) )
.into_err(pos)) .into_err(pos))
} }
@ -1030,8 +1057,9 @@ impl Engine {
} }
let expr = self.parse_expr(input, state, lib, settings.level_up())?; let expr = self.parse_expr(input, state, lib, settings.level_up())?;
let name = state.get_identifier("", name);
template.insert(name.clone(), crate::Dynamic::UNIT); template.insert(name.clone(), crate::Dynamic::UNIT);
let name = state.get_interned_string(name);
map.push((Ident { name, pos }, expr)); map.push((Ident { name, pos }, expr));
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
@ -1310,7 +1338,7 @@ impl Engine {
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
Token::StringConstant(s) => { 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::True => Expr::BoolConstant(true, settings.pos),
Token::False => Expr::BoolConstant(false, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos),
@ -1356,8 +1384,14 @@ impl Engine {
// | ... // | ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => {
let mut new_state = let interners = std::mem::take(&mut state.interned_strings);
ParseState::new(self, state.scope, state.tokenizer_control.clone());
let mut new_state = ParseState::new(
self,
state.scope,
interners,
state.tokenizer_control.clone(),
);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -1399,7 +1433,11 @@ impl Engine {
..settings ..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"))] #[cfg(not(feature = "no_closure"))]
new_state.external_vars.iter().try_for_each( new_state.external_vars.iter().try_for_each(
@ -1481,7 +1519,7 @@ impl Engine {
} }
if segments.is_empty() { if segments.is_empty() {
Expr::StringConstant(state.get_interned_string("", ""), settings.pos) Expr::StringConstant(state.get_interned_string(""), settings.pos)
} else { } else {
segments.shrink_to_fit(); segments.shrink_to_fit();
Expr::InterpolatedString(segments.into(), settings.pos) Expr::InterpolatedString(segments.into(), settings.pos)
@ -1530,7 +1568,7 @@ impl Engine {
state.allow_capture = true; state.allow_capture = true;
} }
Expr::Variable( Expr::Variable(
(None, ns, 0, state.get_identifier("", s)).into(), (None, ns, 0, state.get_interned_string(s)).into(),
None, None,
settings.pos, settings.pos,
) )
@ -1544,7 +1582,7 @@ impl Engine {
state.allow_capture = true; state.allow_capture = true;
} }
Expr::Variable( Expr::Variable(
(None, ns, 0, state.get_identifier("", s)).into(), (None, ns, 0, state.get_interned_string(s)).into(),
None, None,
settings.pos, settings.pos,
) )
@ -1571,7 +1609,7 @@ impl Engine {
} }
}); });
Expr::Variable( Expr::Variable(
(index, ns, 0, state.get_identifier("", s)).into(), (index, ns, 0, state.get_interned_string(s)).into(),
short_index, short_index,
settings.pos, settings.pos,
) )
@ -1595,7 +1633,7 @@ impl Engine {
// Function call is allowed to have reserved keyword // Function call is allowed to have reserved keyword
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => {
Expr::Variable( Expr::Variable(
(None, ns, 0, state.get_identifier("", s)).into(), (None, ns, 0, state.get_interned_string(s)).into(),
None, None,
settings.pos, settings.pos,
) )
@ -1603,13 +1641,13 @@ impl Engine {
// Access to `this` as a variable is OK within a function scope // Access to `this` as a variable is OK within a function scope
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ if &*s == KEYWORD_THIS && settings.in_fn_scope => Expr::Variable( _ 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, None,
settings.pos, settings.pos,
), ),
// Cannot access to `this` as a variable not in a function scope // Cannot access to `this` as a variable not in a function scope
_ if &*s == KEYWORD_THIS => { _ 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( return Err(
LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos) LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos)
); );
@ -1730,7 +1768,7 @@ impl Engine {
namespace.push(var_name_def); namespace.push(var_name_def);
Expr::Variable( Expr::Variable(
(None, namespace, 0, state.get_identifier("", id2)).into(), (None, namespace, 0, state.get_interned_string(id2)).into(),
None, None,
pos2, pos2,
) )
@ -1808,7 +1846,7 @@ impl Engine {
if settings.options.contains(LangOptions::STRICT_VAR) if settings.options.contains(LangOptions::STRICT_VAR)
&& index.is_none() && index.is_none()
&& !is_global && !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) && !self.global_sub_modules.contains_key(root)
{ {
return Err( return Err(
@ -1862,9 +1900,7 @@ impl Engine {
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
return None; return None;
}) })
.ok_or_else(|| { .ok_or_else(|| LexError::MalformedNumber(format!("-{num}")).into_err(pos)),
LexError::MalformedNumber(format!("-{}", num)).into_err(pos)
}),
// Negative float // Negative float
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -1877,7 +1913,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
Ok(FnCallExpr { Ok(FnCallExpr {
name: state.get_identifier("", "-"), name: state.get_interned_string("-"),
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
args, args,
pos, pos,
@ -1904,7 +1940,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
Ok(FnCallExpr { Ok(FnCallExpr {
name: state.get_identifier("", "+"), name: state.get_interned_string("+"),
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
args, args,
pos, pos,
@ -1922,7 +1958,7 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
Ok(FnCallExpr { Ok(FnCallExpr {
name: state.get_identifier("", "!"), name: state.get_interned_string("!"),
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
args, args,
pos, pos,
@ -2297,7 +2333,7 @@ impl Engine {
let hash = calc_fn_hash(&op, 2); let hash = calc_fn_hash(&op, 2);
let op_base = FnCallExpr { let op_base = FnCallExpr {
name: state.get_identifier("", op), name: state.get_interned_string(op.as_ref()),
hashes: FnCallHashes::from_native(hash), hashes: FnCallHashes::from_native(hash),
pos, pos,
..Default::default() ..Default::default()
@ -2369,7 +2405,7 @@ impl Engine {
FnCallExpr { FnCallExpr {
hashes: calc_fn_hash(OP_CONTAINS, 2).into(), hashes: calc_fn_hash(OP_CONTAINS, 2).into(),
args, args,
name: state.get_identifier("", OP_CONTAINS), name: state.get_interned_string(OP_CONTAINS),
..op_base ..op_base
} }
.into_fn_call_expr(pos) .into_fn_call_expr(pos)
@ -2428,14 +2464,14 @@ impl Engine {
if syntax.scope_may_be_changed { if syntax.scope_may_be_changed {
// Add a barrier variable to the stack so earlier variables will not be matched. // Add a barrier variable to the stack so earlier variables will not be matched.
// Variable searches stop at the first barrier. // 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, ()); state.stack.push(marker, ());
} }
let parse_func = &*syntax.parse; let parse_func = &*syntax.parse;
let mut required_token: ImmutableString = key.into(); let mut required_token: ImmutableString = key.into();
tokens.push(required_token.clone().into()); tokens.push(required_token.clone());
segments.push(required_token.clone()); segments.push(required_token.clone());
loop { loop {
@ -2448,10 +2484,7 @@ impl Engine {
if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT)
&& seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() =>
{ {
inputs.push(Expr::StringConstant( inputs.push(Expr::StringConstant(state.get_interned_string(seg), pos));
state.get_interned_string("", seg),
pos,
));
break; break;
} }
Ok(Some(seg)) => seg, Ok(Some(seg)) => seg,
@ -2462,7 +2495,7 @@ impl Engine {
match required_token.as_str() { match required_token.as_str() {
CUSTOM_SYNTAX_MARKER_IDENT => { CUSTOM_SYNTAX_MARKER_IDENT => {
let (name, pos) = parse_var_name(input)?; 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"))] #[cfg(not(feature = "no_module"))]
let ns = crate::ast::Namespace::NONE; let ns = crate::ast::Namespace::NONE;
@ -2470,19 +2503,19 @@ impl Engine {
let ns = (); let ns = ();
segments.push(name.clone().into()); 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)); inputs.push(Expr::Variable((None, ns, 0, name).into(), None, pos));
} }
CUSTOM_SYNTAX_MARKER_SYMBOL => { CUSTOM_SYNTAX_MARKER_SYMBOL => {
let (symbol, pos) = parse_symbol(input)?; let (symbol, pos) = parse_symbol(input)?;
let symbol = state.get_interned_string("", symbol); let symbol = state.get_interned_string(symbol);
segments.push(symbol.clone()); 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)); inputs.push(Expr::StringConstant(symbol, pos));
} }
CUSTOM_SYNTAX_MARKER_EXPR => { CUSTOM_SYNTAX_MARKER_EXPR => {
inputs.push(self.parse_expr(input, state, lib, settings)?); 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()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
@ -2490,7 +2523,7 @@ impl Engine {
match self.parse_block(input, state, lib, settings)? { match self.parse_block(input, state, lib, settings)? {
block @ Stmt::Block(..) => { block @ Stmt::Block(..) => {
inputs.push(Expr::Stmt(Box::new(block.into()))); 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()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
@ -2500,8 +2533,8 @@ impl Engine {
CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
(b @ (Token::True | Token::False), pos) => { (b @ (Token::True | Token::False), pos) => {
inputs.push(Expr::BoolConstant(b == Token::True, pos)); inputs.push(Expr::BoolConstant(b == Token::True, pos));
segments.push(state.get_interned_string("", b.literal_syntax())); 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) => { (.., pos) => {
return Err( return Err(
@ -2514,7 +2547,7 @@ impl Engine {
(Token::IntegerConstant(i), pos) => { (Token::IntegerConstant(i), pos) => {
inputs.push(Expr::IntegerConstant(i, pos)); inputs.push(Expr::IntegerConstant(i, pos));
segments.push(i.to_string().into()); 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) => { (.., pos) => {
return Err( return Err(
@ -2528,7 +2561,7 @@ impl Engine {
(Token::FloatConstant(f), pos) => { (Token::FloatConstant(f), pos) => {
inputs.push(Expr::FloatConstant(f, pos)); inputs.push(Expr::FloatConstant(f, pos));
segments.push(f.to_string().into()); 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) => { (.., pos) => {
return Err(PERR::MissingSymbol( return Err(PERR::MissingSymbol(
@ -2539,10 +2572,10 @@ impl Engine {
}, },
CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => { (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)); inputs.push(Expr::StringConstant(s.clone(), pos));
segments.push(s); segments.push(s);
tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_STRING)); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING));
} }
(.., pos) => { (.., pos) => {
return Err( return Err(
@ -2806,11 +2839,11 @@ impl Engine {
state.stack.push(name.clone(), ()); state.stack.push(name.clone(), ());
} }
let counter_var = Ident { let counter_var = Ident {
name: state.get_identifier("", counter_name), name: state.get_interned_string(counter_name),
pos: counter_pos, pos: counter_pos,
}; };
let loop_var = state.get_identifier("", name); let loop_var = state.get_interned_string(name);
state.stack.push(loop_var.clone(), ()); state.stack.push(loop_var.clone(), ());
let loop_var = Ident { let loop_var = Ident {
name: loop_var, name: loop_var,
@ -2883,7 +2916,7 @@ impl Engine {
} }
} }
let name = state.get_identifier("", name); let name = state.get_interned_string(name);
// let name = ... // let name = ...
let expr = if match_token(input, Token::Equals).0 { let expr = if match_token(input, Token::Equals).0 {
@ -2949,14 +2982,18 @@ impl Engine {
// import expr ... // import expr ...
let expr = self.parse_expr(input, state, lib, settings.level_up())?; let expr = self.parse_expr(input, state, lib, settings.level_up())?;
// import expr as ... // import expr;
if !match_token(input, Token::As).0 { 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 ... // import expr as name ...
let (name, pos) = parse_var_name(input)?; 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()); state.imports.push(name.clone());
Ok(Stmt::Import( Ok(Stmt::Import(
@ -3009,11 +3046,11 @@ impl Engine {
let export = ( let export = (
Ident { Ident {
name: state.get_identifier("", id), name: state.get_interned_string(id),
pos: id_pos, pos: id_pos,
}, },
Ident { 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, pos: alias_pos,
}, },
); );
@ -3222,8 +3259,14 @@ impl Engine {
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut new_state = let interners = std::mem::take(&mut state.interned_strings);
ParseState::new(self, state.scope, state.tokenizer_control.clone());
let mut new_state = ParseState::new(
self,
state.scope,
interners,
state.tokenizer_control.clone(),
);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -3269,7 +3312,11 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments, comments,
)?; );
state.interned_strings = new_state.interned_strings;
let func = func?;
let hash = calc_fn_hash(&func.name, func.params.len()); let hash = calc_fn_hash(&func.name, func.params.len());
@ -3412,11 +3459,14 @@ impl Engine {
.into_err(err_pos)); .into_err(err_pos));
} }
let name = state.get_identifier("", name); let name = state.get_interned_string(name);
state.stack.push(name.clone(), ()); state.stack.push(name.clone(), ());
Ident { name, pos } Ident { name, pos }
} else { } else {
Ident::EMPTY Ident {
name: state.get_interned_string(""),
pos: Position::NONE,
}
}; };
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
@ -3476,20 +3526,20 @@ impl Engine {
(.., pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), (.., 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 { 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 { loop {
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
(Token::RightParen, ..) => break, (Token::RightParen, ..) => break,
(Token::Identifier(s), pos) => { (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()) return Err(PERR::FnDuplicatedParam(name.to_string(), s.to_string())
.into_err(pos)); .into_err(pos));
} }
let s = state.get_identifier("", s); let s = state.get_interned_string(s);
state.stack.push(s.clone(), ()); state.stack.push(s.clone(), ());
params.push((s, pos)); params.push((s, pos));
} }
@ -3497,7 +3547,7 @@ impl Engine {
(.., pos) => { (.., pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::RightParen.into(), Token::RightParen.into(),
format!("to close the parameters list of function '{}'", name), format!("to close the parameters list of function '{name}'"),
) )
.into_err(pos)) .into_err(pos))
} }
@ -3528,7 +3578,7 @@ impl Engine {
params.shrink_to_fit(); params.shrink_to_fit();
Ok(ScriptFnDef { Ok(ScriptFnDef {
name: state.get_identifier("", name), name: state.get_interned_string(name),
access, access,
params, params,
body, body,
@ -3578,7 +3628,7 @@ impl Engine {
); );
let expr = FnCallExpr { 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( hashes: FnCallHashes::from_native(calc_fn_hash(
crate::engine::KEYWORD_FN_PTR_CURRY, crate::engine::KEYWORD_FN_PTR_CURRY,
num_externals + 1, num_externals + 1,
@ -3595,7 +3645,7 @@ impl Engine {
statements.extend( statements.extend(
externals externals
.into_iter() .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())); statements.push(Stmt::Expr(expr.into()));
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into()) Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
@ -3614,18 +3664,18 @@ impl Engine {
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut settings = settings; let mut settings = settings;
let mut params_list = StaticVec::new_const(); let mut params_list = StaticVec::<ImmutableString>::new_const();
if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 { if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 {
loop { loop {
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
(Token::Pipe, ..) => break, (Token::Pipe, ..) => break,
(Token::Identifier(s), pos) => { (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()) return Err(PERR::FnDuplicatedParam("".to_string(), s.to_string())
.into_err(pos)); .into_err(pos));
} }
let s = state.get_identifier("", s); let s = state.get_interned_string(s);
state.stack.push(s.clone(), ()); state.stack.push(s.clone(), ());
params_list.push(s); params_list.push(s);
} }
@ -3683,7 +3733,7 @@ impl Engine {
params.iter().for_each(|p| p.hash(hasher)); params.iter().for_each(|p| p.hash(hasher));
body.hash(hasher); body.hash(hasher);
let hash = hasher.finish(); 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 // Define the function
let script = ScriptFnDef { let script = ScriptFnDef {

View File

@ -2406,7 +2406,7 @@ impl<'a> Iterator for TokenIterator<'a> {
(.., true) => unreachable!("no custom operators"), (.., true) => unreachable!("no custom operators"),
// Reserved keyword that is not custom and disabled. // Reserved keyword that is not custom and disabled.
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => { (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()) Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
}, },
// Reserved keyword/operator that is not custom. // Reserved keyword/operator that is not custom.

View File

@ -346,11 +346,52 @@ impl Dynamic {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is<T: Any + Clone>(&self) -> bool { pub fn is<T: Any + Clone>(&self) -> bool {
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<()>() {
self.type_id() == TypeId::of::<ImmutableString>() return matches!(self.0, Union::Unit(..));
} else {
self.type_id() == TypeId::of::<T>()
} }
if TypeId::of::<T>() == TypeId::of::<bool>() {
return matches!(self.0, Union::Bool(..));
}
if TypeId::of::<T>() == TypeId::of::<char>() {
return matches!(self.0, Union::Char(..));
}
if TypeId::of::<T>() == TypeId::of::<INT>() {
return matches!(self.0, Union::Int(..));
}
#[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<crate::FLOAT>() {
return matches!(self.0, Union::Float(..));
}
if TypeId::of::<T>() == TypeId::of::<ImmutableString>()
|| TypeId::of::<T>() == TypeId::of::<String>()
{
return matches!(self.0, Union::Str(..));
}
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
return matches!(self.0, Union::Array(..));
}
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Blob>() {
return matches!(self.0, Union::Blob(..));
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<crate::Map>() {
return matches!(self.0, Union::Map(..));
}
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<rust_decimal::Decimal>() {
return matches!(self.0, Union::Decimal(..));
}
if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return matches!(self.0, Union::FnPtr(..));
}
#[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<crate::Instant>() {
return matches!(self.0, Union::TimeStamp(..));
}
TypeId::of::<T>() == self.type_id()
} }
/// Get the [`TypeId`] of the value held by this [`Dynamic`]. /// Get the [`TypeId`] of the value held by this [`Dynamic`].
/// ///

View File

@ -341,7 +341,7 @@ impl EvalAltResult {
pub(crate) fn dump_fields(&self, map: &mut crate::Map) { pub(crate) fn dump_fields(&self, map: &mut crate::Map) {
map.insert( map.insert(
"error".into(), "error".into(),
format!("{:?}", self) format!("{self:?}")
.split('(') .split('(')
.next() .next()
.expect("`ErrorXXX(...)`") .expect("`ErrorXXX(...)`")

View File

@ -82,7 +82,7 @@ impl Borrow<SmartString> for ImmutableString {
impl Borrow<str> for ImmutableString { impl Borrow<str> for ImmutableString {
#[inline(always)] #[inline(always)]
fn borrow(&self) -> &str { fn borrow(&self) -> &str {
self.0.as_str() self.as_str()
} }
} }
@ -187,14 +187,14 @@ impl FromIterator<SmartString> for ImmutableString {
impl fmt::Display for ImmutableString { impl fmt::Display for ImmutableString {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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 { impl fmt::Debug for ImmutableString {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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() { } else if self.is_empty() {
rhs rhs
} else { } else {
self.make_mut().push_str(rhs.0.as_str()); self.make_mut().push_str(rhs.as_str());
self self
} }
} }
@ -225,7 +225,40 @@ impl Add for &ImmutableString {
rhs.clone() rhs.clone()
} else { } else {
let mut s = self.clone(); 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<ImmutableString> 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 s
} }
} }
@ -238,7 +271,7 @@ impl AddAssign<&ImmutableString> for ImmutableString {
if self.is_empty() { if self.is_empty() {
self.0 = rhs.0.clone(); self.0 = rhs.0.clone();
} else { } else {
self.make_mut().push_str(rhs.0.as_str()); self.make_mut().push_str(rhs.as_str());
} }
} }
} }
@ -251,7 +284,7 @@ impl AddAssign<ImmutableString> for ImmutableString {
if self.is_empty() { if self.is_empty() {
self.0 = rhs.0; self.0 = rhs.0;
} else { } 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 { pub fn new() -> Self {
Self(SmartString::new_const().into()) 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`]. /// Consume the [`ImmutableString`] and convert it into a [`String`].
/// ///
/// If there are other references to the same string, a cloned copy is returned. /// If there are other references to the same string, a cloned copy is returned.

View File

@ -1,77 +1,143 @@
use crate::{Identifier, ImmutableString}; use crate::func::hashing::get_hasher;
use crate::ImmutableString;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{collections::BTreeMap, marker::PhantomData, ops::AddAssign}; use std::{
collections::BTreeMap,
hash::{Hash, Hasher},
marker::PhantomData,
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. /// _(internals)_ A factory of identifiers from text strings.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// Normal identifiers, property getters and setters are interned separately. /// Normal identifiers, property getters and setters are interned separately.
#[derive(Debug, Clone, Default, Hash)] #[derive(Debug, Clone, Hash)]
pub struct StringsInterner<'a> { pub struct StringsInterner<'a> {
/// Maximum capacity.
max: usize,
/// Normal strings. /// Normal strings.
strings: BTreeMap<Identifier, ImmutableString>, strings: BTreeMap<u64, ImmutableString>,
/// Property getters.
#[cfg(not(feature = "no_object"))]
getters: BTreeMap<Identifier, ImmutableString>,
/// Property setters.
#[cfg(not(feature = "no_object"))]
setters: BTreeMap<Identifier, ImmutableString>,
/// Take care of the lifetime parameter. /// Take care of the lifetime parameter.
dummy: PhantomData<&'a ()>, dummy: PhantomData<&'a ()>,
} }
impl Default for StringsInterner<'_> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl StringsInterner<'_> { impl StringsInterner<'_> {
/// Create a new [`StringsInterner`]. /// Create a new [`StringsInterner`].
#[inline] #[inline(always)]
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::new_with_capacity(MAX_INTERNED_STRINGS)
}
/// Create a new [`StringsInterner`] with maximum capacity.
#[inline]
#[must_use]
pub fn new_with_capacity(capacity: usize) -> Self {
Self { Self {
max: capacity,
strings: BTreeMap::new(), strings: BTreeMap::new(),
#[cfg(not(feature = "no_object"))]
getters: BTreeMap::new(),
#[cfg(not(feature = "no_object"))]
setters: BTreeMap::new(),
dummy: PhantomData, 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)]
/// # Prefix #[must_use]
/// pub fn get<T: AsRef<str> + Into<ImmutableString>>(&mut self, text: T) -> ImmutableString {
/// Currently recognized prefixes are: self.get_with_mapper(|s| s.into(), text)
/// }
/// * `""` - None (normal string)
/// * `"get$"` - Property getter, not available under `no_object` /// Get an identifier from a text string, adding it to the interner if necessary.
/// * `"set$"` - Property setter, not available under `no_object`
///
/// # Panics
///
/// Panics if the prefix is not recognized.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn get(&mut self, prefix: impl AsRef<str>, text: impl AsRef<str>) -> ImmutableString { pub fn get_with_mapper<T: AsRef<str> + Into<ImmutableString>>(
let prefix = prefix.as_ref(); &mut self,
let text = text.as_ref(); mapper: fn(T) -> ImmutableString,
text: T,
) -> ImmutableString {
let key = text.as_ref();
let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix { // Do not intern numbers
"" => (&mut self.strings, |s| s.into()), if key.bytes().all(|c| c == b'.' || (c >= b'0' && c <= b'9')) {
return text.into();
#[cfg(not(feature = "no_object"))]
crate::engine::FN_GET => (&mut self.getters, crate::engine::make_getter),
#[cfg(not(feature = "no_object"))]
crate::engine::FN_SET => (&mut self.setters, crate::engine::make_setter),
_ => unreachable!("unsupported prefix {}", prefix),
};
if !dict.is_empty() && dict.contains_key(text) {
dict.get(text).unwrap().clone()
} else {
let value: ImmutableString = mapper(text).into();
dict.insert(text.into(), value.clone());
value
} }
if key.len() > MAX_STRING_LEN {
return mapper(text);
}
let hasher = &mut get_hasher();
key.hash(hasher);
let key = hasher.finish();
if !self.strings.is_empty() && self.strings.contains_key(&key) {
return self.strings.get(&key).unwrap().clone();
}
let value = mapper(text);
if value.strong_count() > 1 {
return value;
}
self.strings.insert(key, value.clone());
// 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)]
#[must_use]
pub fn len(&self) -> usize {
self.strings.len()
}
/// Number of strings interned.
#[inline(always)]
#[must_use]
pub fn is_empty(&self) -> bool {
self.strings.is_empty()
}
/// Clear all interned strings.
#[inline]
pub fn clear(&mut self) {
self.strings.clear();
} }
} }
@ -79,10 +145,6 @@ impl AddAssign<Self> for StringsInterner<'_> {
#[inline(always)] #[inline(always)]
fn add_assign(&mut self, rhs: Self) { fn add_assign(&mut self, rhs: Self) {
self.strings.extend(rhs.strings.into_iter()); 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());
} }
} }
@ -90,12 +152,6 @@ impl AddAssign<&Self> for StringsInterner<'_> {
#[inline(always)] #[inline(always)]
fn add_assign(&mut self, rhs: &Self) { fn add_assign(&mut self, rhs: &Self) {
self.strings 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())));
} }
} }

View File

@ -29,13 +29,13 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
", ",
)?; )?;
let r: INT = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; let r = engine.call_fn::<INT>(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?;
assert_eq!(r, 165); assert_eq!(r, 165);
let r: INT = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?; let r = engine.call_fn::<INT>(&mut scope, &ast, "hello", (123 as INT,))?;
assert_eq!(r, 5166); assert_eq!(r, 5166);
let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?; let r = engine.call_fn::<INT>(&mut scope, &ast, "hello", ())?;
assert_eq!(r, 42); assert_eq!(r, 42);
assert_eq!( assert_eq!(
@ -45,7 +45,7 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
1 1
); );
let r: INT = engine.call_fn(&mut scope, &ast, "define_var", (2 as INT,))?; let r = engine.call_fn::<INT>(&mut scope, &ast, "define_var", (2 as INT,))?;
assert_eq!(r, 42); assert_eq!(r, 42);
assert!(!scope.contains("bar")); assert!(!scope.contains("bar"));
@ -132,7 +132,7 @@ fn test_call_fn_args() -> Result<(), Box<EvalAltResult>> {
", ",
)?; )?;
let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?; let result = engine.call_fn::<String>(&mut scope, &ast, "hello", options)?;
assert_eq!(result, "world42"); assert_eq!(result, "world42");
@ -146,12 +146,12 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("fn add(x, n) { x + n }")?; 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::<INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT))?;
assert_eq!(r, 42); assert_eq!(r, 42);
let ast = engine.compile("private fn add(x, n, ) { x + n }")?; 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::<INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT))?;
assert_eq!(r, 42); assert_eq!(r, 42);
Ok(()) Ok(())

View File

@ -338,7 +338,7 @@ fn test_closures_shared_obj() -> Result<(), Box<EvalAltResult>> {
let res = engine.eval_ast::<Map>(&ast)?; let res = engine.eval_ast::<Map>(&ast)?;
// Make closure // Make closure
let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box<EvalAltResult>> { let f = move |p1: TestStruct, p2: TestStruct| {
let action_ptr = res["action"].clone_cast::<FnPtr>(); let action_ptr = res["action"].clone_cast::<FnPtr>();
let name = action_ptr.fn_name(); let name = action_ptr.fn_name();
engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2)) engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2))

View File

@ -45,7 +45,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
">=" => count < max, ">=" => count < max,
"==" => count != max, "==" => count != max,
"!=" => count == max, "!=" => count == max,
_ => return Err(format!("Unsupported operator: {}", op).into()), _ => return Err(format!("Unsupported operator: {op}").into()),
}; };
if done { if done {
@ -63,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
context context
.scope_mut() .scope_mut()
.push(format!("{}{}", var_name, count), count); .push(format!("{var_name}{count}"), count);
let stop = !context let stop = !context
.eval_expression_tree(condition)? .eval_expression_tree(condition)?
@ -189,7 +189,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
context.scope_mut().set_value(var_name.to_string(), value); context.scope_mut().set_value(var_name.to_string(), value);
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} else { } else {
Err(format!("variable {} is constant", var_name).into()) Err(format!("variable {var_name} is constant").into())
} }
}, },
)?; )?;

View File

@ -279,7 +279,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
assert!(engine.eval::<INT>(script).is_err()); assert!(engine.eval::<INT>(script).is_err());
let result: INT = engine.call_fn(&mut Scope::new(), &ast, "foo", (2 as INT,))?; let result = engine.call_fn::<INT>(&mut Scope::new(), &ast, "foo", (2 as INT,))?;
assert_eq!(result, 84); assert_eq!(result, 84);
@ -296,7 +296,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
assert_eq!(ast2.resolver().unwrap().len(), len); 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::<INT>(&mut Scope::new(), &ast2, "foo", (2 as INT,))?;
assert_eq!(result, 84); assert_eq!(result, 84);
} }

View File

@ -68,11 +68,11 @@ fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
.register_fn( .register_fn(
"+", "+",
|s1: ImmutableString, s2: ImmutableString| -> ImmutableString { |s1: ImmutableString, s2: ImmutableString| -> ImmutableString {
format!("{}***{}", s1, s2).into() format!("{s1}***{s2}").into()
}, },
) )
.register_fn("+", |s1: ImmutableString, _: ()| -> ImmutableString { .register_fn("+", |s1: ImmutableString, _: ()| -> ImmutableString {
format!("{} Foo!", s1).into() format!("{s1} Foo!").into()
}); });
assert_eq!( assert_eq!(

View File

@ -81,21 +81,21 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{ast:?}"),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"# r#"AST { source: "", doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"#
); );
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
assert_eq!( 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)] }"# 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 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{ast:?}"),
r#"AST { source: "", doc: "", resolver: None, body: [] }"# r#"AST { source: "", doc: "", resolver: None, body: [] }"#
); );
@ -104,14 +104,14 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("abs(-42)")?; let ast = engine.compile("abs(-42)")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{ast:?}"),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
); );
let ast = engine.compile("NUMBER")?; let ast = engine.compile("NUMBER")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{ast:?}"),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"# r#"AST { source: "", doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"#
); );
@ -123,7 +123,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("NUMBER")?; let ast = engine.compile("NUMBER")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{ast:?}"),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"# r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
); );

View File

@ -70,7 +70,7 @@ macro_rules! reg_functions {
} }
fn make_greeting(n: impl std::fmt::Display) -> String { 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); gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String);

View File

@ -39,14 +39,12 @@ fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine 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| { .on_debug(move |s, src, pos| {
log2.write().unwrap().push(format!( let src = src.unwrap_or("unknown");
"DEBUG of {} at {:?}: {}", log2.write()
src.unwrap_or("unknown"), .unwrap()
pos, .push(format!("DEBUG of {src} at {pos:?}: {s}"))
s
))
}); });
// Evaluate script // Evaluate script

View File

@ -744,7 +744,7 @@ fn test_serde_json() -> serde_json::Result<()> {
let a = m.remove("b").unwrap().cast::<Array>(); let a = m.remove("b").unwrap().cast::<Array>();
assert_eq!(a.len(), 3); assert_eq!(a.len(), 3);
assert_eq!(format!("{:?}", a), "[1, 2, 3]"); assert_eq!(format!("{a:?}"), "[1, 2, 3]");
Ok(()) Ok(())
} }