Print arrays and maps with to_debug.

This commit is contained in:
Stephen Chung 2020-11-30 11:20:51 +08:00
parent 65a4ceb3be
commit 1004bca5b5
9 changed files with 113 additions and 19 deletions

View File

@ -13,6 +13,8 @@ Enhancements
------------ ------------
* Property getters/setters and indexers defined in a plugin module are by default `#[rhai_fn(global)]`. * 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 Version 0.19.6

View File

@ -38,3 +38,5 @@ is an alias to `Rc<String>` or `Arc<String>` (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. 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_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.

View File

@ -61,7 +61,7 @@ mod MyEnumModule {
} }
} }
// Printing // 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 { pub fn to_string(a: &mut MyEnum) -> String {
format!("{:?}", a)) format!("{:?}", a))
} }

View File

@ -11,6 +11,7 @@ is `T: Display + Debug`):
| ----------- | ---------------------------------------------- | ---------------------------- | -------------------------------------------------------------------- | | ----------- | ---------------------------------------------- | ---------------------------- | -------------------------------------------------------------------- |
| `to_string` | <code>\|x: &mut T\| -> String</code> | `x.to_string()` | converts the custom type into a [string] | | `to_string` | <code>\|x: &mut T\| -> String</code> | `x.to_string()` | converts the custom type into a [string] |
| `print` | <code>\|x: &mut T\| -> String</code> | `x.to_string()` | converts the custom type into a [string] for the [`print`] statement | | `print` | <code>\|x: &mut T\| -> String</code> | `x.to_string()` | converts the custom type into a [string] for the [`print`] statement |
| `to_debug` | <code>\|x: &mut T\| -> String</code> | `format!("{:?}", x)` | converts the custom type into a [string] in debug format |
| `debug` | <code>\|x: &mut T\| -> String</code> | `format!("{:?}", x)` | converts the custom type into a [string] for the [`debug`] statement | | `debug` | <code>\|x: &mut T\| -> String</code> | `format!("{:?}", x)` | converts the custom type into a [string] for the [`debug`] statement |
| `+` | <code>\|s: &str, x: T\| -> String</code> | `format!("{}{}", s, x)` | concatenates the custom type with another [string] | | `+` | <code>\|s: &str, x: T\| -> String</code> | `format!("{}{}", s, x)` | concatenates the custom type with another [string] |
| `+` | <code>\|x: &mut T, s: &str\| -> String</code> | `x.to_string().push_str(s);` | concatenates another [string] with the custom type | | `+` | <code>\|x: &mut T, s: &str\| -> String</code> | `x.to_string().push_str(s);` | concatenates another [string] with the custom type |

View File

@ -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 /// If `is_method` is [`true`], the first argument is assumed to be passed
/// by reference and is not consumed. /// by reference and is not consumed.
pub fn call_fn_dynamic_raw( pub fn call_fn_dynamic_raw(
&mut self, &self,
fn_name: &str, fn_name: &str,
is_method: bool, is_method: bool,
public_only: bool, public_only: bool,
@ -262,7 +262,7 @@ impl FnPtr {
/// clone them _before_ calling this function. /// clone them _before_ calling this function.
pub fn call_dynamic( pub fn call_dynamic(
&self, &self,
mut ctx: NativeCallContext, ctx: NativeCallContext,
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, mut arg_values: impl AsMut<[Dynamic]>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {

View File

@ -650,7 +650,7 @@ mod array_functions {
} }
#[rhai_fn(name = "==", return_raw)] #[rhai_fn(name = "==", return_raw)]
pub fn equals( pub fn equals(
mut ctx: NativeCallContext, ctx: NativeCallContext,
arr1: &mut Array, arr1: &mut Array,
mut arr2: Array, mut arr2: Array,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {

View File

@ -45,7 +45,7 @@ mod map_functions {
} }
#[rhai_fn(name = "==", return_raw)] #[rhai_fn(name = "==", return_raw)]
pub fn equals( pub fn equals(
mut ctx: NativeCallContext, ctx: NativeCallContext,
map1: &mut Map, map1: &mut Map,
mut map2: Map, mut map2: Map,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {

View File

@ -15,6 +15,9 @@ use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; use crate::Map;
const FUNC_TO_STRING: &'static str = "to_string";
const FUNC_TO_DEBUG: &'static str = "to_debug";
type Unit = (); type Unit = ();
macro_rules! gen_functions { macro_rules! gen_functions {
@ -32,13 +35,14 @@ macro_rules! gen_functions {
macro_rules! reg_print_functions { macro_rules! reg_print_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( ($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); set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func);
)* } )* }
} }
macro_rules! reg_debug_functions { macro_rules! reg_debug_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( ($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); 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_print_functions!(lib += print_float; f32, f64);
reg_debug_functions!(lib += debug_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<T: Display>(x: &mut T) -> ImmutableString { fn to_string<T: Display>(x: &mut T) -> ImmutableString {
@ -109,10 +107,18 @@ gen_functions!(print_float => to_string(f32, f64));
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
gen_functions!(debug_float => to_debug(f32, f64)); gen_functions!(debug_float => to_debug(f32, f64));
#[cfg(not(feature = "no_index"))]
gen_functions!(print_array => to_debug(Array));
// Register print and debug // 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::<ImmutableString>() => 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] #[export_module]
mod print_debug_functions { mod print_debug_functions {
#[rhai_fn(name = "print", name = "debug")] #[rhai_fn(name = "print", name = "debug")]
@ -132,13 +138,50 @@ mod print_debug_functions {
to_string(f) 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"))] #[cfg(not(feature = "no_object"))]
pub mod map_functions { pub mod map_functions {
use super::*; use super::*;
#[rhai_fn(name = "print", name = "debug", name = "to_string")] #[rhai_fn(name = "print", name = "to_string", name = "to_debug", name = "debug")]
pub fn format_map(x: &mut Map) -> ImmutableString { pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString {
format!("#{:?}", x).into() 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()
} }
} }
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, RegisterFn, INT};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
#[test] #[test]
@ -30,3 +30,49 @@ fn test_print() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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<EvalAltResult>> {
let mut engine = Engine::new();
engine
.register_type_with_name::<MyStruct>("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::<String>(
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::<String>(
r#"
let x = #{ a:123, b:true, c:(), d:"world", e:new_ts() };
x.to_string()
"#
)?
.contains("e: hello: 42"));
Ok(())
}