Transparently convert &str to ImmutableString for register_fn.

This commit is contained in:
Stephen Chung 2020-06-07 17:54:33 +08:00
parent 5f40a1376a
commit e942ef358c
5 changed files with 94 additions and 12 deletions

View File

@ -684,13 +684,18 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from
To call these functions, they need to be registered with the [`Engine`].
```rust
use rhai::{Dynamic, Engine, EvalAltResult};
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString};
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Normal function that returns any value type
fn add(x: i64, y: i64) -> i64 {
x + y
// Normal function that returns a standard type
// Remember to use 'ImmutableString' and not 'String'
fn add_len(x: i64, s: ImmutableString) -> i64 {
x + s.len()
}
// Alternatively, '&str' maps directly to 'ImmutableString'
fn add_len_str(x: i64, s: &str) -> i64 {
x + s.len()
}
// Function that returns a 'Dynamic' value - must return a 'Result'
@ -702,9 +707,14 @@ fn main() -> Result<(), Box<EvalAltResult>>
{
let engine = Engine::new();
engine.register_fn("add", add);
engine.register_fn("add", add_len);
engine.register_fn("add_str", add_len_str);
let result = engine.eval::<i64>("add(40, 2)")?;
let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
println!("Answer: {}", result); // prints 42
let result = engine.eval::<i64>(r#"add_str(40, "xx")"#)?;
println!("Answer: {}", result); // prints 42
@ -735,6 +745,25 @@ i.e. different functions can have the same name as long as their parameters are
and/or different number.
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
### `String` parameters
Functions accepting a parameter of `String` should use `&str` instead because it maps directly to `ImmutableString`
which is the type that Rhai uses to represent strings internally.
```rust
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai will not find this function
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
engine.register_fn("len1", get_len1);
engine.register_fn("len2", get_len2);
engine.register_fn("len3", get_len3);
let len = engine.eval::<i64>("x.len1()")?; // error: function 'len1 (string)' not found
let len = engine.eval::<i64>("x.len2()")?; // works fine
let len = engine.eval::<i64>("x.len3()")?; // works fine
```
Generic functions
-----------------
@ -1388,6 +1417,9 @@ Strings and Chars
[strings]: #strings-and-chars
[char]: #strings-and-chars
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions.
String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_')
and hex ('`\x`_xx_') escape sequences.

View File

@ -15,6 +15,7 @@ New features
------------
* Indexers are now split into getters ans setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added.
* `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters.
Version 0.15.0

View File

@ -906,6 +906,9 @@ impl Engine {
let mut idx_val = idx_values.pop();
if is_index {
#[cfg(feature = "no_index")]
unreachable!();
let pos = rhs.position();
match rhs {
@ -1292,6 +1295,7 @@ impl Engine {
}
}
#[cfg(not(feature = "no_index"))]
_ => {
let fn_name = FUNC_INDEXER_GET;
let type_name = self.map_type_name(val.type_name());
@ -1305,6 +1309,12 @@ impl Engine {
))
})
}
#[cfg(feature = "no_index")]
_ => Err(Box::new(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).into(),
Position::none(),
))),
}
}

View File

@ -7,6 +7,7 @@ use crate::engine::Engine;
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess;
use crate::result::EvalAltResult;
use crate::utils::ImmutableString;
use crate::stdlib::{any::TypeId, boxed::Box, mem};
@ -99,9 +100,16 @@ pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> &mut T {
/// Dereference into value.
#[inline(always)]
pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
mem::take(data).cast::<T>()
if TypeId::of::<T>() == TypeId::of::<&str>() {
// &str parameters are mapped to the underlying ImmutableString
let r = data.as_str().unwrap();
let x = unsafe { mem::transmute::<_, &T>(&r) };
x.clone()
} else {
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
mem::take(data).cast::<T>()
}
}
/// This macro creates a closure wrapping a registered function.
@ -146,6 +154,18 @@ pub fn map_result(
data
}
/// Remap `&str` to `ImmutableString`.
#[inline(always)]
fn map_type_id<T: 'static>() -> TypeId {
let id = TypeId::of::<T>();
if id == TypeId::of::<&str>() {
TypeId::of::<ImmutableString>()
} else {
id
}
}
macro_rules! def_register {
() => {
def_register!(imp from_pure :);
@ -170,7 +190,7 @@ macro_rules! def_register {
{
fn register_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*],
&[$(map_type_id::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
);
}
@ -187,7 +207,7 @@ macro_rules! def_register {
{
fn register_result_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*],
&[$(map_type_id::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
);
}

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
#[test]
fn test_string() -> Result<(), Box<EvalAltResult>> {
@ -154,3 +154,22 @@ fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_string_fn() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_fn("foo1", |s: &str| s.len() as INT);
engine.register_fn("foo2", |s: ImmutableString| s.len() as INT);
engine.register_fn("foo3", |s: String| s.len() as INT);
assert_eq!(engine.eval::<INT>(r#"foo1("hello")"#)?, 5);
assert_eq!(engine.eval::<INT>(r#"foo2("hello")"#)?, 5);
assert!(matches!(
*engine.eval::<INT>(r#"foo3("hello")"#).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(ref x, _) if x == "foo3 (string)"
));
Ok(())
}