From 1004bca5b5f670a3d7fec88f221208b261313baa Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 30 Nov 2020 11:20:51 +0800 Subject: [PATCH] Print arrays and maps with to_debug. --- RELEASES.md | 2 + doc/src/language/values-and-types.md | 2 + doc/src/patterns/enums.md | 2 +- doc/src/rust/print-custom.md | 1 + src/fn_native.rs | 4 +- src/packages/array_basic.rs | 2 +- src/packages/map_basic.rs | 2 +- src/packages/string_basic.rs | 69 ++++++++++++++++++++++------ tests/print.rs | 48 ++++++++++++++++++- 9 files changed, 113 insertions(+), 19 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 70c7d183..65f943c1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,6 +13,8 @@ Enhancements ------------ * Property getters/setters and indexers defined in a plugin module are by default `#[rhai_fn(global)]`. +* `to_debug` is a new standard function for converting a value into debug format. +* Arrays and object maps now print values using `to_debug` (if available). Version 0.19.6 diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index c8fe9181..fbae2013 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -38,3 +38,5 @@ is an alias to `Rc` or `Arc` (depending on the [`sync`] feature) Any modification done to a Rhai string will cause the string to be cloned and the modifications made to the copy. The `to_string` function converts a standard type into a [string] for display purposes. + +The `to_debug` function converts a standard type into a [string] in debug format. diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md index 1d693564..a4c9bc0b 100644 --- a/doc/src/patterns/enums.md +++ b/doc/src/patterns/enums.md @@ -61,7 +61,7 @@ mod MyEnumModule { } } // Printing - #[rhai(global, name = "to_string", name = "print", name = "debug")] + #[rhai(global, name = "to_string", name = "print", name = "to_debug", name = "debug")] pub fn to_string(a: &mut MyEnum) -> String { format!("{:?}", a)) } diff --git a/doc/src/rust/print-custom.md b/doc/src/rust/print-custom.md index 028a3519..c013559b 100644 --- a/doc/src/rust/print-custom.md +++ b/doc/src/rust/print-custom.md @@ -11,6 +11,7 @@ is `T: Display + Debug`): | ----------- | ---------------------------------------------- | ---------------------------- | -------------------------------------------------------------------- | | `to_string` | \|x: &mut T\| -> String | `x.to_string()` | converts the custom type into a [string] | | `print` | \|x: &mut T\| -> String | `x.to_string()` | converts the custom type into a [string] for the [`print`] statement | +| `to_debug` | \|x: &mut T\| -> String | `format!("{:?}", x)` | converts the custom type into a [string] in debug format | | `debug` | \|x: &mut T\| -> String | `format!("{:?}", x)` | converts the custom type into a [string] for the [`debug`] statement | | `+` | \|s: &str, x: T\| -> String | `format!("{}{}", s, x)` | concatenates the custom type with another [string] | | `+` | \|x: &mut T, s: &str\| -> String | `x.to_string().push_str(s);` | concatenates another [string] with the custom type | diff --git a/src/fn_native.rs b/src/fn_native.rs index b86063eb..ee87de84 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -134,7 +134,7 @@ impl<'e, 'a, 'm, 'pm> NativeCallContext<'e, 'a, 'm, 'pm> { /// If `is_method` is [`true`], the first argument is assumed to be passed /// by reference and is not consumed. pub fn call_fn_dynamic_raw( - &mut self, + &self, fn_name: &str, is_method: bool, public_only: bool, @@ -262,7 +262,7 @@ impl FnPtr { /// clone them _before_ calling this function. pub fn call_dynamic( &self, - mut ctx: NativeCallContext, + ctx: NativeCallContext, this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> Result> { diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 7858b775..4ab427db 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -650,7 +650,7 @@ mod array_functions { } #[rhai_fn(name = "==", return_raw)] pub fn equals( - mut ctx: NativeCallContext, + ctx: NativeCallContext, arr1: &mut Array, mut arr2: Array, ) -> Result> { diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index a0039dc9..fd328c7b 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -45,7 +45,7 @@ mod map_functions { } #[rhai_fn(name = "==", return_raw)] pub fn equals( - mut ctx: NativeCallContext, + ctx: NativeCallContext, map1: &mut Map, mut map2: Map, ) -> Result> { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 80686547..86db1c53 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -15,6 +15,9 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; +const FUNC_TO_STRING: &'static str = "to_string"; +const FUNC_TO_DEBUG: &'static str = "to_debug"; + type Unit = (); macro_rules! gen_functions { @@ -32,13 +35,14 @@ macro_rules! gen_functions { macro_rules! reg_print_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - set_exported_fn!($mod_name, "to_string", $root::$arg_type::to_string_func); + set_exported_fn!($mod_name, FUNC_TO_STRING, $root::$arg_type::to_string_func); set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func); )* } } macro_rules! reg_debug_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, FUNC_TO_DEBUG, $root::$arg_type::to_string_func); set_exported_fn!($mod_name, KEYWORD_DEBUG, $root::$arg_type::to_string_func); )* } } @@ -67,12 +71,6 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_print_functions!(lib += print_float; f32, f64); reg_debug_functions!(lib += debug_float; f32, f64); } - - #[cfg(not(feature = "no_index"))] - { - reg_print_functions!(lib += print_array; Array); - reg_debug_functions!(lib += print_array; Array); - } }); fn to_string(x: &mut T) -> ImmutableString { @@ -109,10 +107,18 @@ gen_functions!(print_float => to_string(f32, f64)); #[cfg(not(feature = "no_float"))] gen_functions!(debug_float => to_debug(f32, f64)); -#[cfg(not(feature = "no_index"))] -gen_functions!(print_array => to_debug(Array)); - // Register print and debug + +#[cfg(not(feature = "no_index"))] +#[inline(always)] +fn print_with_func(fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic) -> ImmutableString { + match ctx.call_fn_dynamic_raw(fn_name, true, false, &mut [value], None) { + Ok(result) if result.is::() => result.take_immutable_string().unwrap(), + Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), + Err(_) => ctx.engine().map_type_name(value.type_name()).into(), + } +} + #[export_module] mod print_debug_functions { #[rhai_fn(name = "print", name = "debug")] @@ -132,13 +138,50 @@ mod print_debug_functions { to_string(f) } + #[cfg(not(feature = "no_index"))] + pub mod array_functions { + use super::*; + + #[rhai_fn(name = "print", name = "to_string", name = "to_debug", name = "debug")] + pub fn format_array(ctx: NativeCallContext, arr: &mut Array) -> ImmutableString { + let mut result = String::with_capacity(16); + result.push_str("["); + + let len = arr.len(); + + arr.iter_mut().enumerate().for_each(|(i, x)| { + result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, x)); + if i < len - 1 { + result.push_str(", "); + } + }); + + result.push_str("]"); + result.into() + } + } #[cfg(not(feature = "no_object"))] pub mod map_functions { use super::*; - #[rhai_fn(name = "print", name = "debug", name = "to_string")] - pub fn format_map(x: &mut Map) -> ImmutableString { - format!("#{:?}", x).into() + #[rhai_fn(name = "print", name = "to_string", name = "to_debug", name = "debug")] + pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString { + let mut result = String::with_capacity(16); + result.push_str("#{"); + + let len = map.len(); + + map.iter_mut().enumerate().for_each(|(i, (k, v))| { + result.push_str(k); + result.push_str(": "); + result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, v)); + if i < len - 1 { + result.push_str(", "); + } + }); + + result.push_str("}"); + result.into() } } } diff --git a/tests/print.rs b/tests/print.rs index 8c580f42..fb50af3e 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; use std::sync::{Arc, RwLock}; #[test] @@ -30,3 +30,49 @@ fn test_print() -> Result<(), Box> { Ok(()) } + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] +struct MyStruct { + field: INT, +} + +impl std::fmt::Display for MyStruct { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "hello: {}", self.field) + } +} + +#[test] +fn test_print_custom_type() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine + .register_type_with_name::("MyStruct") + .register_fn("to_debug", |x: &mut MyStruct| x.to_string()) + .register_fn("debug", |x: &mut MyStruct| x.to_string()) + .register_fn("new_ts", || MyStruct { field: 42 }); + + engine.consume("let x = new_ts(); debug(x);")?; + + #[cfg(not(feature = "no_index"))] + assert_eq!( + engine.eval::( + r#" + let x = [ 123, true, (), "world", new_ts() ]; + x.to_string() + "# + )?, + r#"[123, true, (), "world", hello: 42]"# + ); + + #[cfg(not(feature = "no_object"))] + assert!(engine + .eval::( + r#" + let x = #{ a:123, b:true, c:(), d:"world", e:new_ts() }; + x.to_string() + "# + )? + .contains("e: hello: 42")); + Ok(()) +}