From 5796e520eca04db44ff2a7655ea6484bae3ff6ce Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 10:27:08 +0800 Subject: [PATCH] Support Dynamic return values. --- README.md | 11 +++++-- examples/repl.rs | 34 ++++++++++----------- src/any.rs | 79 ++++++++++++++++++++++++++++-------------------- src/api.rs | 26 +++++++--------- src/engine.rs | 2 +- 5 files changed, 82 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7977dbc3..136ec458 100644 --- a/README.md +++ b/README.md @@ -180,13 +180,18 @@ fn main() -> Result<(), EvalAltResult> ### 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. -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 let result = engine.eval::("40 + 2")?; // return type is i64, specified using 'turbofish' notation let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 +result.is::() == true; + +let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be! + let result = engine.eval::("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. 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 let list: Array = engine.eval("...")?; // return type is 'Array' @@ -451,7 +456,7 @@ item.is::() == true; // 'is' returns whether a 'Dynam let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics let value: i64 = item.cast(); // type can also be inferred -let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error +let value = item.try_cast::().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. diff --git a/examples/repl.rs b/examples/repl.rs index 56f0ac69..0c513a59 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Position, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope, AST}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -137,7 +137,7 @@ fn main() { _ => (), } - if let Err(err) = engine + match engine .compile_with_scope(&scope, &script) .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { @@ -157,22 +157,20 @@ fn main() { main_ast = main_ast.merge(&ast); // Evaluate - let result = engine - .consume_ast_with_scope(&mut scope, &main_ast) - .or_else(|err| match err { - EvalAltResult::Return(_, _) => Ok(()), - err => Err(err), - }); - - // Throw away all the statements, leaving only the functions - main_ast.retain_functions(); - - result - }) - { - println!(); - print_error(&input, err); - println!(); + engine.eval_ast_with_scope::(&mut scope, &main_ast) + }) { + Ok(result) => { + println!("=> {:?}", result); + println!(); + } + Err(err) => { + println!(); + print_error(&input, err); + println!(); + } } + + // Throw away all the statements, leaving only the functions + main_ast.retain_functions(); } } diff --git a/src/any.rs b/src/any.rs index 803fe2d2..65f68cbd 100644 --- a/src/any.rs +++ b/src/any.rs @@ -143,6 +143,15 @@ pub enum Union { } 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? pub fn is(&self) -> bool { self.type_id() == TypeId::of::() @@ -212,7 +221,7 @@ impl fmt::Debug for Dynamic { Union::Float(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "{:?}", value), - Union::Variant(_) => write!(f, "?"), + Union::Variant(_) => write!(f, ""), } } } @@ -234,8 +243,23 @@ impl Clone for Dynamic { } } +/// Cast a Boxed type into another type. +fn cast_box(item: Box) -> Result> { + // Only allow casting to the exact same type + if TypeId::of::() == TypeId::of::() { + // SAFETY: just checked whether we are pointing to the correct type + unsafe { + let raw: *mut dyn Any = Box::into_raw(item as Box); + Ok(*Box::from_raw(raw as *mut T)) + } + } else { + // Return the consumed item for chaining. + Err(item) + } +} + impl Dynamic { - /// Create a `Dynamic` from any type. + /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// /// # Examples /// @@ -249,6 +273,10 @@ impl Dynamic { /// let result = Dynamic::from("hello".to_string()); /// assert_eq!(result.type_name(), "string"); /// 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(value: T) -> Self { if let Some(result) = (&value as &dyn Variant) @@ -288,29 +316,20 @@ impl Dynamic { } } - fn cast_box(item: Box) -> Result> { - if TypeId::of::() == TypeId::of::() { - unsafe { - let raw: *mut dyn Any = Box::into_raw(item as Box); - Ok(*Box::from_raw(raw as *mut T)) - } - } else { - Err(item) - } - } - let var = Box::new(value); Self( - cast_box::<_, String>(var) - .map(Union::Str) + cast_box::<_, Dynamic>(var) + .map(|x| x.0) .or_else(|var| { - cast_box::<_, Array>(var).map(Union::Array).or_else(|var| { - cast_box::<_, Map>(var) - .map(|v| Union::Map(Box::new(v))) - .or_else(|var| -> Result { - Ok(Union::Variant(var as Box)) - }) + cast_box::<_, String>(var).map(Union::Str).or_else(|var| { + cast_box::<_, Array>(var).map(Union::Array).or_else(|var| { + cast_box::<_, Map>(var) + .map(|v| Union::Map(Box::new(v))) + .or_else(|var| -> Result { + Ok(Union::Variant(var as Box)) + }) + }) }) }) .unwrap(), @@ -318,6 +337,7 @@ impl Dynamic { } /// 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. /// @@ -330,7 +350,11 @@ impl Dynamic { /// /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - pub fn try_cast(self) -> Result { + pub fn try_cast(self) -> Option { + if TypeId::of::() == TypeId::of::() { + return cast_box::<_, T>(Box::new(self)).ok(); + } + match &self.0 { Union::Unit(value) => (value as &dyn Variant).downcast_ref::().cloned(), Union::Bool(value) => (value as &dyn Variant).downcast_ref::().cloned(), @@ -345,7 +369,6 @@ impl Dynamic { .cloned(), Union::Variant(value) => value.as_ref().downcast_ref::().cloned(), } - .ok_or(self) } /// 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 { - match self.0 { - Union::Float(n) => Ok(n), - _ => Err(self.type_name()), - } - } - /// Cast the `Dynamic` as a `bool` and return it. /// Returns the name of the actual type if the cast fails. pub(crate) fn as_bool(&self) -> Result { diff --git a/src/api.rs b/src/api.rs index 9dc84298..837c0bc0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -799,14 +799,12 @@ impl<'e> Engine<'e> { scope: &mut Scope, ast: &AST, ) -> Result { - self.eval_ast_with_scope_raw(scope, ast)? - .try_cast::() - .map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(a.type_name()).to_string(), - Position::none(), - ) - }) + let result = self.eval_ast_with_scope_raw(scope, ast)?; + let return_type = self.map_type_name(result.type_name()); + + return result.try_cast::().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType(return_type.to_string(), Position::none()) + }); } 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 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() - .map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(a.type_name()).into(), - pos, - ) - }) + .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(return_type.into(), pos)); } /// Optimize the `AST` with constants defined in an external Scope. diff --git a/src/engine.rs b/src/engine.rs index 59906675..ea223926 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -543,7 +543,7 @@ impl Engine<'_> { // Raise error let types_list: Vec<_> = args .iter() - .map(|x| (*x).type_name()) + .map(|x| x.type_name()) .map(|name| self.map_type_name(name)) .collect();