From a738f750f9121540df9d2b9451e4ead917d5ab03 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 2 Apr 2021 19:26:55 +0800 Subject: [PATCH] Implement string functions with to_string/to_debug. --- src/bin/rhai-repl.rs | 9 ++++++--- src/engine.rs | 8 ++++---- src/engine_settings.rs | 6 +++--- src/module/resolvers/file.rs | 13 ++++++------- src/module/resolvers/stat.rs | 10 +++++----- src/packages/string_basic.rs | 29 ++++++++++++++++++----------- src/packages/string_more.rs | 22 ++++++++++++++++------ src/parser.rs | 6 +++--- src/token.rs | 4 ++-- tests/string.rs | 34 ++++++++++++++++++++++++++++++++++ 10 files changed, 97 insertions(+), 44 deletions(-) diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 43662ef3..a51dc96f 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -141,9 +141,12 @@ fn main() { engine.set_optimization_level(rhai::OptimizationLevel::None); // Set a file module resolver without caching - let mut resolver = FileModuleResolver::new(); - resolver.enable_cache(false); - engine.set_module_resolver(resolver); + #[cfg(not(feature = "no_module"))] + { + let mut resolver = FileModuleResolver::new(); + resolver.enable_cache(false); + engine.set_module_resolver(resolver); + } // Make Engine immutable let engine = engine; diff --git a/src/engine.rs b/src/engine.rs index 69a87931..fa8d8713 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -712,12 +712,12 @@ pub struct Engine { pub(crate) module_resolver: Box, /// A map mapping type names to pretty-print names. - pub(crate) type_names: BTreeMap, + pub(crate) type_names: BTreeMap, /// A set of symbols to disable. - pub(crate) disabled_symbols: BTreeSet, + pub(crate) disabled_symbols: BTreeSet, /// A map containing custom keywords and precedence to recognize. - pub(crate) custom_keywords: BTreeMap>, + pub(crate) custom_keywords: BTreeMap>, /// Custom syntax. pub(crate) custom_syntax: BTreeMap, /// Callback closure for resolving variable access. @@ -2653,7 +2653,7 @@ impl Engine { pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { self.type_names .get(name) - .map(String::as_str) + .map(|s| s.as_str()) .unwrap_or_else(|| map_std_type_name(name)) } diff --git a/src/engine_settings.rs b/src/engine_settings.rs index b666714e..017d19b0 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -1,9 +1,9 @@ //! Configuration settings for [`Engine`]. -use crate::engine::Precedence; use crate::stdlib::{format, string::String}; use crate::token::Token; use crate::Engine; +use crate::{engine::Precedence, Identifier}; #[cfg(not(feature = "unchecked"))] use crate::stdlib::num::{NonZeroU64, NonZeroUsize}; @@ -236,7 +236,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { + pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { self.disabled_symbols.insert(symbol.into()); self } @@ -270,7 +270,7 @@ impl Engine { /// ``` pub fn register_custom_operator( &mut self, - keyword: impl AsRef + Into, + keyword: impl AsRef + Into, precedence: u8, ) -> Result<&mut Self, String> { let precedence = Precedence::new(precedence); diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 081a8692..2915eb08 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -3,9 +3,8 @@ use crate::stdlib::{ collections::BTreeMap, io::Error as IoError, path::{Path, PathBuf}, - string::String, }; -use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; +use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared}; pub const RHAI_SCRIPT_EXTENSION: &str = "rhai"; @@ -42,7 +41,7 @@ pub const RHAI_SCRIPT_EXTENSION: &str = "rhai"; #[derive(Debug)] pub struct FileModuleResolver { base_path: Option, - extension: String, + extension: Identifier, cache_enabled: bool, #[cfg(not(feature = "sync"))] @@ -118,7 +117,7 @@ impl FileModuleResolver { /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] - pub fn new_with_extension(extension: impl Into) -> Self { + pub fn new_with_extension(extension: impl Into) -> Self { Self { base_path: None, extension: extension.into(), @@ -145,7 +144,7 @@ impl FileModuleResolver { #[inline(always)] pub fn new_with_path_and_extension( path: impl Into, - extension: impl Into, + extension: impl Into, ) -> Self { Self { base_path: Some(path.into()), @@ -175,7 +174,7 @@ impl FileModuleResolver { /// Set the script file extension. #[inline(always)] - pub fn set_extension(&mut self, extension: impl Into) -> &mut Self { + pub fn set_extension(&mut self, extension: impl Into) -> &mut Self { self.extension = extension.into(); self } @@ -256,7 +255,7 @@ impl FileModuleResolver { file_path = path.into(); } - file_path.set_extension(&self.extension); // Force extension + file_path.set_extension(self.extension.as_str()); // Force extension file_path } } diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index e8a0c705..8a697e20 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -1,5 +1,5 @@ use crate::stdlib::{boxed::Box, collections::BTreeMap, ops::AddAssign, string::String}; -use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; +use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared}; /// A static [module][Module] resolution service that serves [modules][Module] added into it. /// @@ -19,7 +19,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// engine.set_module_resolver(resolver); /// ``` #[derive(Debug, Clone, Default)] -pub struct StaticModuleResolver(BTreeMap>); +pub struct StaticModuleResolver(BTreeMap>); impl StaticModuleResolver { /// Create a new [`StaticModuleResolver`]. @@ -44,7 +44,7 @@ impl StaticModuleResolver { } /// Add a [module][Module] keyed by its path. #[inline(always)] - pub fn insert(&mut self, path: impl Into, mut module: Module) { + pub fn insert(&mut self, path: impl Into, mut module: Module) { module.build_index(); self.0.insert(path.into(), module.into()); } @@ -70,13 +70,13 @@ impl StaticModuleResolver { } /// Get a mutable iterator of all the modules. #[inline(always)] - pub fn into_iter(self) -> impl Iterator)> { + pub fn into_iter(self) -> impl Iterator)> { self.0.into_iter() } /// Get an iterator of all the [module][Module] paths. #[inline(always)] pub fn paths(&self) -> impl Iterator { - self.0.keys().map(String::as_str) + self.0.keys().map(|s| s.as_str()) } /// Get an iterator of all the [modules][Module]. #[inline(always)] diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 69f1e392..2cceda30 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -10,8 +10,8 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -const FUNC_TO_DEBUG: &'static str = "to_debug"; +pub const FUNC_TO_STRING: &'static str = "to_string"; +pub const FUNC_TO_DEBUG: &'static str = "to_debug"; def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { combine_with_exported_module!(lib, "print_debug", print_debug_functions); @@ -19,9 +19,8 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin // Register print and debug -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] -fn print_with_func( +pub fn print_with_func( fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic, @@ -39,17 +38,25 @@ fn print_with_func( mod print_debug_functions { use crate::ImmutableString; - #[rhai_fn(name = "print", name = "to_string", pure)] - pub fn print_generic(item: &mut Dynamic) -> ImmutableString { - item.to_string().into() + #[rhai_fn(name = "print", pure)] + pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { + print_with_func(FUNC_TO_STRING, &ctx, item) } - #[rhai_fn(name = "debug", name = "to_debug", pure)] - pub fn debug_generic(item: &mut Dynamic) -> ImmutableString { - format!("{:?}", item).into() + #[rhai_fn(name = "to_string", pure)] + pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { + ctx.engine().map_type_name(&item.to_string()).into() + } + #[rhai_fn(name = "debug", pure)] + pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { + print_with_func(FUNC_TO_DEBUG, &ctx, item) + } + #[rhai_fn(name = "to_debug", pure)] + pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { + ctx.engine().map_type_name(&format!("{:?}", item)).into() } #[rhai_fn(name = "print", name = "debug")] pub fn print_empty_string() -> ImmutableString { - "".to_string().into() + Default::default() } #[rhai_fn(name = "print", name = "to_string")] pub fn print_string(s: ImmutableString) -> ImmutableString { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 3477c7c2..817a43bb 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -6,6 +6,8 @@ use crate::stdlib::{ }; use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT}; +use super::string_basic::{print_with_func, FUNC_TO_STRING}; + def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { combine_with_exported_module!(lib, "string", string_functions); @@ -21,22 +23,30 @@ mod string_functions { use crate::ImmutableString; #[rhai_fn(name = "+", name = "append")] - pub fn add_append(string: &str, item: Dynamic) -> ImmutableString { - format!("{}{}", string, item).into() + pub fn add_append(ctx: NativeCallContext, string: &str, mut item: Dynamic) -> ImmutableString { + let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); + format!("{}{}", string, s).into() } #[rhai_fn(name = "+", pure)] - pub fn add_prepend(item: &mut Dynamic, string: &str) -> ImmutableString { - format!("{}{}", item, string).into() + pub fn add_prepend( + ctx: NativeCallContext, + item: &mut Dynamic, + string: &str, + ) -> ImmutableString { + let s = print_with_func(FUNC_TO_STRING, &ctx, item); + format!("{}{}", s, string).into() } #[rhai_fn(name = "+")] - pub fn add_append_unit(string: ImmutableString, _x: ()) -> ImmutableString { + pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString { string } #[rhai_fn(name = "+")] - pub fn add_prepend_unit(_x: (), string: ImmutableString) -> ImmutableString { + pub fn add_prepend_unit(_item: (), string: ImmutableString) -> ImmutableString { string } + #[rhai_fn(name = "+=")] + pub fn add_append_assign_unit(_string: &mut ImmutableString, _item: ()) {} #[rhai_fn(name = "len", get = "len")] pub fn len(string: &str) -> INT { diff --git a/src/parser.rs b/src/parser.rs index 0925cc38..8fb73c08 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1621,7 +1621,7 @@ fn parse_binary_op( Token::Custom(c) => state .engine .custom_keywords - .get(c) + .get(c.as_str()) .cloned() .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { @@ -1646,7 +1646,7 @@ fn parse_binary_op( Token::Custom(c) => state .engine .custom_keywords - .get(c) + .get(c.as_str()) .cloned() .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { @@ -1753,7 +1753,7 @@ fn parse_binary_op( if state .engine .custom_keywords - .get(&s) + .get(s.as_str()) .map_or(false, Option::is_some) => { let hash = calc_fn_hash(empty(), &s, 2); diff --git a/src/token.rs b/src/token.rs index 0aea77b5..20990833 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1828,7 +1828,7 @@ impl<'a> Iterator for TokenIterator<'a> { None => return None, // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (s.as_str(), self.engine.custom_keywords.contains_key(&s)) + (s.as_str(), self.engine.custom_keywords.contains_key(s.as_str())) { ("===", false) => Token::LexError(LERR::ImproperSymbol(s, "'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(), @@ -1869,7 +1869,7 @@ impl<'a> Iterator for TokenIterator<'a> { (_, false) => Token::Reserved(s), }, pos), // Custom keyword - Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => { + Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(s.as_str()) => { (Token::Custom(s), pos) } // Custom standard keyword/symbol - must be disabled diff --git a/tests/string.rs b/tests/string.rs index 2e2d74a7..236c5d12 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -222,6 +222,40 @@ fn test_string_substring() -> Result<(), Box> { Ok(()) } +#[test] +fn test_string_format() -> Result<(), Box> { + #[derive(Debug, Clone)] + struct TestStruct { + field: i64, + } + + let mut engine = Engine::new(); + + engine + .register_type_with_name::("TestStruct") + .register_fn("new_ts", || TestStruct { field: 42 }) + .register_fn("to_string", |ts: TestStruct| format!("TS={}", ts.field)) + .register_fn("to_debug", |ts: TestStruct| { + format!("!!!TS={}!!!", ts.field) + }); + + assert_eq!( + engine.eval::(r#"let x = new_ts(); "foo" + x"#)?, + "fooTS=42" + ); + assert_eq!( + engine.eval::(r#"let x = new_ts(); x + "foo""#)?, + "TS=42foo" + ); + #[cfg(not(feature = "no_index"))] + assert_eq!( + engine.eval::(r#"let x = [new_ts()]; "foo" + x"#)?, + "foo[!!!TS=42!!!]" + ); + + Ok(()) +} + #[test] fn test_string_fn() -> Result<(), Box> { let mut engine = Engine::new();