Support Dynamic return values.
This commit is contained in:
parent
adaf086e90
commit
5796e520ec
11
README.md
11
README.md
@ -180,13 +180,18 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
### Script evaluation
|
### Script evaluation
|
||||||
|
|
||||||
The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned.
|
The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned.
|
||||||
Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference.
|
Rhai is very strict here. Use [`Dynamic`] for uncertain return types.
|
||||||
|
There are two ways to specify the return type - _turbofish_ notation, or type inference.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let result = engine.eval::<i64>("40 + 2")?; // return type is i64, specified using 'turbofish' notation
|
let result = engine.eval::<i64>("40 + 2")?; // return type is i64, specified using 'turbofish' notation
|
||||||
|
|
||||||
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
|
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
|
||||||
|
|
||||||
|
result.is::<i64>() == true;
|
||||||
|
|
||||||
|
let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
|
||||||
|
|
||||||
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -440,7 +445,7 @@ function and match against the name).
|
|||||||
|
|
||||||
A `Dynamic` value's actual type can be checked via the `is` method.
|
A `Dynamic` value's actual type can be checked via the `is` method.
|
||||||
The `cast` method then converts the value into a specific, known type.
|
The `cast` method then converts the value into a specific, known type.
|
||||||
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
|
Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||||
@ -451,7 +456,7 @@ item.is::<i64>() == true; // 'is' returns whether a 'Dynam
|
|||||||
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
||||||
let value: i64 = item.cast(); // type can also be inferred
|
let value: i64 = item.cast(); // type can also be inferred
|
||||||
|
|
||||||
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error
|
let value = item.try_cast::<i64>().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `type_name` method gets the name of the actual type as a static string slice, which you may match against.
|
The `type_name` method gets the name of the actual type as a static string slice, which you may match against.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Position, Scope, AST};
|
use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope, AST};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
use rhai::OptimizationLevel;
|
use rhai::OptimizationLevel;
|
||||||
@ -137,7 +137,7 @@ fn main() {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine
|
match engine
|
||||||
.compile_with_scope(&scope, &script)
|
.compile_with_scope(&scope, &script)
|
||||||
.map_err(EvalAltResult::ErrorParsing)
|
.map_err(EvalAltResult::ErrorParsing)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
@ -157,22 +157,20 @@ fn main() {
|
|||||||
main_ast = main_ast.merge(&ast);
|
main_ast = main_ast.merge(&ast);
|
||||||
|
|
||||||
// Evaluate
|
// Evaluate
|
||||||
let result = engine
|
engine.eval_ast_with_scope::<Dynamic>(&mut scope, &main_ast)
|
||||||
.consume_ast_with_scope(&mut scope, &main_ast)
|
}) {
|
||||||
.or_else(|err| match err {
|
Ok(result) => {
|
||||||
EvalAltResult::Return(_, _) => Ok(()),
|
println!("=> {:?}", result);
|
||||||
err => Err(err),
|
println!();
|
||||||
});
|
}
|
||||||
|
Err(err) => {
|
||||||
// Throw away all the statements, leaving only the functions
|
|
||||||
main_ast.retain_functions();
|
|
||||||
|
|
||||||
result
|
|
||||||
})
|
|
||||||
{
|
|
||||||
println!();
|
println!();
|
||||||
print_error(&input, err);
|
print_error(&input, err);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Throw away all the statements, leaving only the functions
|
||||||
|
main_ast.retain_functions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
67
src/any.rs
67
src/any.rs
@ -143,6 +143,15 @@ pub enum Union {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dynamic {
|
impl Dynamic {
|
||||||
|
/// Does this `Dynamic` hold a variant data type
|
||||||
|
/// instead of one of the support system primitive types?
|
||||||
|
pub fn is_variant(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
Union::Variant(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Is the value held by this `Dynamic` a particular type?
|
/// Is the value held by this `Dynamic` a particular type?
|
||||||
pub fn is<T: Variant + Clone>(&self) -> bool {
|
pub fn is<T: Variant + Clone>(&self) -> bool {
|
||||||
self.type_id() == TypeId::of::<T>()
|
self.type_id() == TypeId::of::<T>()
|
||||||
@ -212,7 +221,7 @@ impl fmt::Debug for Dynamic {
|
|||||||
Union::Float(value) => write!(f, "{:?}", value),
|
Union::Float(value) => write!(f, "{:?}", value),
|
||||||
Union::Array(value) => write!(f, "{:?}", value),
|
Union::Array(value) => write!(f, "{:?}", value),
|
||||||
Union::Map(value) => write!(f, "{:?}", value),
|
Union::Map(value) => write!(f, "{:?}", value),
|
||||||
Union::Variant(_) => write!(f, "?"),
|
Union::Variant(_) => write!(f, "<dynamic>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,8 +243,23 @@ impl Clone for Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cast a Boxed type into another type.
|
||||||
|
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<T, Box<X>> {
|
||||||
|
// Only allow casting to the exact same type
|
||||||
|
if TypeId::of::<X>() == TypeId::of::<T>() {
|
||||||
|
// SAFETY: just checked whether we are pointing to the correct type
|
||||||
|
unsafe {
|
||||||
|
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
|
||||||
|
Ok(*Box::from_raw(raw as *mut T))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Return the consumed item for chaining.
|
||||||
|
Err(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Dynamic {
|
impl Dynamic {
|
||||||
/// Create a `Dynamic` from any type.
|
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -249,6 +273,10 @@ impl Dynamic {
|
|||||||
/// let result = Dynamic::from("hello".to_string());
|
/// let result = Dynamic::from("hello".to_string());
|
||||||
/// assert_eq!(result.type_name(), "string");
|
/// assert_eq!(result.type_name(), "string");
|
||||||
/// assert_eq!(result.to_string(), "hello");
|
/// assert_eq!(result.to_string(), "hello");
|
||||||
|
///
|
||||||
|
/// let new_result = Dynamic::from(result);
|
||||||
|
/// assert_eq!(new_result.type_name(), "string");
|
||||||
|
/// assert_eq!(new_result.to_string(), "hello");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from<T: Variant + Clone>(value: T) -> Self {
|
pub fn from<T: Variant + Clone>(value: T) -> Self {
|
||||||
if let Some(result) = (&value as &dyn Variant)
|
if let Some(result) = (&value as &dyn Variant)
|
||||||
@ -288,23 +316,13 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<T, Box<X>> {
|
|
||||||
if TypeId::of::<X>() == TypeId::of::<T>() {
|
|
||||||
unsafe {
|
|
||||||
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
|
|
||||||
Ok(*Box::from_raw(raw as *mut T))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let var = Box::new(value);
|
let var = Box::new(value);
|
||||||
|
|
||||||
Self(
|
Self(
|
||||||
cast_box::<_, String>(var)
|
cast_box::<_, Dynamic>(var)
|
||||||
.map(Union::Str)
|
.map(|x| x.0)
|
||||||
.or_else(|var| {
|
.or_else(|var| {
|
||||||
|
cast_box::<_, String>(var).map(Union::Str).or_else(|var| {
|
||||||
cast_box::<_, Array>(var).map(Union::Array).or_else(|var| {
|
cast_box::<_, Array>(var).map(Union::Array).or_else(|var| {
|
||||||
cast_box::<_, Map>(var)
|
cast_box::<_, Map>(var)
|
||||||
.map(|v| Union::Map(Box::new(v)))
|
.map(|v| Union::Map(Box::new(v)))
|
||||||
@ -313,11 +331,13 @@ impl Dynamic {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a copy of the `Dynamic` value as a specific type.
|
/// Get a copy of the `Dynamic` value as a specific type.
|
||||||
|
/// Casting to a `Dynamic` just returns as is.
|
||||||
///
|
///
|
||||||
/// Returns an error with the name of the value's actual type when the cast fails.
|
/// Returns an error with the name of the value's actual type when the cast fails.
|
||||||
///
|
///
|
||||||
@ -330,7 +350,11 @@ impl Dynamic {
|
|||||||
///
|
///
|
||||||
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
|
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn try_cast<T: Variant + Clone>(self) -> Result<T, Self> {
|
pub fn try_cast<T: Variant + Clone>(self) -> Option<T> {
|
||||||
|
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||||
|
return cast_box::<_, T>(Box::new(self)).ok();
|
||||||
|
}
|
||||||
|
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
Union::Unit(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
Union::Unit(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||||
Union::Bool(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
Union::Bool(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
|
||||||
@ -345,7 +369,6 @@ impl Dynamic {
|
|||||||
.cloned(),
|
.cloned(),
|
||||||
Union::Variant(value) => value.as_ref().downcast_ref::<T>().cloned(),
|
Union::Variant(value) => value.as_ref().downcast_ref::<T>().cloned(),
|
||||||
}
|
}
|
||||||
.ok_or(self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a copy of the `Dynamic` value as a specific type.
|
/// Get a copy of the `Dynamic` value as a specific type.
|
||||||
@ -410,16 +433,6 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast the `Dynamic` as the system integer type `INT` and return it.
|
|
||||||
/// Returns the name of the actual type if the cast fails.
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
pub(crate) fn as_float(&self) -> Result<FLOAT, &'static str> {
|
|
||||||
match self.0 {
|
|
||||||
Union::Float(n) => Ok(n),
|
|
||||||
_ => Err(self.type_name()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cast the `Dynamic` as a `bool` and return it.
|
/// Cast the `Dynamic` as a `bool` and return it.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
pub(crate) fn as_bool(&self) -> Result<bool, &'static str> {
|
pub(crate) fn as_bool(&self) -> Result<bool, &'static str> {
|
||||||
|
26
src/api.rs
26
src/api.rs
@ -799,14 +799,12 @@ impl<'e> Engine<'e> {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
self.eval_ast_with_scope_raw(scope, ast)?
|
let result = self.eval_ast_with_scope_raw(scope, ast)?;
|
||||||
.try_cast::<T>()
|
let return_type = self.map_type_name(result.type_name());
|
||||||
.map_err(|a| {
|
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
return result.try_cast::<T>().ok_or_else(|| {
|
||||||
self.map_type_name(a.type_name()).to_string(),
|
EvalAltResult::ErrorMismatchOutputType(return_type.to_string(), Position::none())
|
||||||
Position::none(),
|
});
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn eval_ast_with_scope_raw(
|
pub(crate) fn eval_ast_with_scope_raw(
|
||||||
@ -933,14 +931,12 @@ impl<'e> Engine<'e> {
|
|||||||
let fn_lib = Some(ast.1.as_ref());
|
let fn_lib = Some(ast.1.as_ref());
|
||||||
let pos = Position::none();
|
let pos = Position::none();
|
||||||
|
|
||||||
self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?
|
let result = self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?;
|
||||||
|
let return_type = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
|
return result
|
||||||
.try_cast()
|
.try_cast()
|
||||||
.map_err(|a| {
|
.ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(return_type.into(), pos));
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
|
||||||
self.map_type_name(a.type_name()).into(),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize the `AST` with constants defined in an external Scope.
|
/// Optimize the `AST` with constants defined in an external Scope.
|
||||||
|
@ -543,7 +543,7 @@ impl Engine<'_> {
|
|||||||
// Raise error
|
// Raise error
|
||||||
let types_list: Vec<_> = args
|
let types_list: Vec<_> = args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| (*x).type_name())
|
.map(|x| x.type_name())
|
||||||
.map(|name| self.map_type_name(name))
|
.map(|name| self.map_type_name(name))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user