Rename downcast
to try_cast
and add cast
for Dynamic.
This commit is contained in:
parent
c4a51b1390
commit
5e7c9b47d5
94
README.md
94
README.md
@ -279,7 +279,6 @@ The following primitive types are supported natively:
|
|||||||
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
|
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
|
||||||
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
|
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
|
||||||
|
|
||||||
[`Dynamic`]: #values-and-types
|
|
||||||
[`()`]: #values-and-types
|
[`()`]: #values-and-types
|
||||||
|
|
||||||
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust.
|
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust.
|
||||||
@ -293,7 +292,7 @@ If no floating-point is needed or supported, use the [`no_float`] feature to rem
|
|||||||
|
|
||||||
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 `type_of` function detects the actual type of a value. This is useful because all variables are `Dynamic`.
|
The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use 'type_of()' to get the actual types of values
|
// Use 'type_of()' to get the actual types of values
|
||||||
@ -313,6 +312,74 @@ if type_of(x) == "string" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Dynamic values
|
||||||
|
--------------
|
||||||
|
|
||||||
|
[`Dynamic`]: #dynamic-values
|
||||||
|
|
||||||
|
A `Dynamic` value can be _any_ type.
|
||||||
|
|
||||||
|
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
|
||||||
|
actions based on the actual value's type.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mystery = get_some_dynamic_value();
|
||||||
|
|
||||||
|
if type_of(mystery) == "i64" {
|
||||||
|
print("Hey, I got an integer here!");
|
||||||
|
} else if type_of(mystery) == "f64" {
|
||||||
|
print("Hey, I got a float here!");
|
||||||
|
} else if type_of(mystery) == "string" {
|
||||||
|
print("Hey, I got a string here!");
|
||||||
|
} else if type_of(mystery) == "bool" {
|
||||||
|
print("Hey, I got a boolean here!");
|
||||||
|
} else if type_of(mystery) == "array" {
|
||||||
|
print("Hey, I got an array here!");
|
||||||
|
} else if type_of(mystery) == "map" {
|
||||||
|
print("Hey, I got an object map here!");
|
||||||
|
} else if type_of(mystery) == "TestStruct" {
|
||||||
|
print("Hey, I got the TestStruct custom type here!");
|
||||||
|
} else {
|
||||||
|
print("I don't know what this is: " + type_of(mystery));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`,
|
||||||
|
or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance.
|
||||||
|
There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is (short of using the `type_name`
|
||||||
|
function to get the textual name of the type and then matching on that).
|
||||||
|
|
||||||
|
To use a `Dynamic` value in Rust, use the `cast` method to convert 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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use rhai::AnyExt; // Pull in the trait.
|
||||||
|
|
||||||
|
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||||
|
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||||
|
|
||||||
|
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 = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error
|
||||||
|
```
|
||||||
|
|
||||||
|
The `type_name` method gets the name of the actual type as a string, which you may match against.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use rhai::Any; // Pull in the trait.
|
||||||
|
|
||||||
|
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||||
|
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||||
|
|
||||||
|
match item.type_name() { // 'type_name' returns the name of the actual Rust type
|
||||||
|
"i64" => ...
|
||||||
|
"std::string::String" => ...
|
||||||
|
"bool" => ...
|
||||||
|
"path::to::module::TestStruct" => ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Value conversions
|
Value conversions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@ -325,11 +392,11 @@ That's about it. For other conversions, register custom conversion functions.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 42;
|
let x = 42;
|
||||||
let y = x * 100.0; // <- error: cannot multiply i64 with f64
|
let y = x * 100.0; // <- error: cannot multiply i64 with f64
|
||||||
let y = x.to_float() * 100.0; // works
|
let y = x.to_float() * 100.0; // works
|
||||||
let z = y.to_int() + x; // works
|
let z = y.to_int() + x; // works
|
||||||
|
|
||||||
let c = 'X'; // character
|
let c = 'X'; // character
|
||||||
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
|
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -972,10 +1039,9 @@ Arrays
|
|||||||
|
|
||||||
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
|
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
|
||||||
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
|
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
|
||||||
|
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed.
|
||||||
|
|
||||||
The Rust type of a Rhai array is `rhai::Array`.
|
The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`.
|
||||||
|
|
||||||
[`type_of()`] an array returns `"array"`.
|
|
||||||
|
|
||||||
Arrays are disabled via the [`no_index`] feature.
|
Arrays are disabled via the [`no_index`] feature.
|
||||||
|
|
||||||
@ -1053,7 +1119,7 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i
|
|||||||
Object maps
|
Object maps
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Object maps are dictionaries. Properties of any type (`Dynamic`) can be freely added and retrieved.
|
Object maps are dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved.
|
||||||
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
|
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
|
||||||
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
|
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
|
||||||
naming rules as [variables], or an arbitrary string literal.
|
naming rules as [variables], or an arbitrary string literal.
|
||||||
@ -1064,9 +1130,7 @@ The index notation allows setting/getting properties of arbitrary names (even th
|
|||||||
|
|
||||||
**Important:** Trying to read a non-existent property returns `()` instead of causing an error.
|
**Important:** Trying to read a non-existent property returns `()` instead of causing an error.
|
||||||
|
|
||||||
The Rust type of a Rhai object map is `rhai::Map`.
|
The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`.
|
||||||
|
|
||||||
[`type_of()`] an object map returns `"map"`.
|
|
||||||
|
|
||||||
Object maps are disabled via the [`no_object`] feature.
|
Object maps are disabled via the [`no_object`] feature.
|
||||||
|
|
||||||
@ -1642,9 +1706,9 @@ Function volatility considerations
|
|||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
|
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
|
||||||
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
||||||
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
|
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
|
||||||
it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because
|
it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because
|
||||||
essentially the result of the function call will always be the same value.
|
essentially the result of the function call will always be the same value.
|
||||||
|
|
||||||
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
||||||
|
34
src/any.rs
34
src/any.rs
@ -89,7 +89,14 @@ impl Clone for Dynamic {
|
|||||||
/// An extension trait that allows down-casting a `Dynamic` value to a specific type.
|
/// An extension trait that allows down-casting a `Dynamic` value to a specific type.
|
||||||
pub trait AnyExt: Sized {
|
pub trait AnyExt: Sized {
|
||||||
/// Get a copy of a `Dynamic` value as a specific type.
|
/// Get a copy of a `Dynamic` value as a specific type.
|
||||||
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
|
fn try_cast<T: Any + Clone>(self) -> Result<T, Self>;
|
||||||
|
|
||||||
|
/// Get a copy of a `Dynamic` value as a specific type.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
|
||||||
|
fn cast<T: Any + Clone>(self) -> T;
|
||||||
|
|
||||||
/// This trait may only be implemented by `rhai`.
|
/// This trait may only be implemented by `rhai`.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -106,19 +113,38 @@ impl AnyExt for Dynamic {
|
|||||||
///
|
///
|
||||||
/// let x: Dynamic = 42_u32.into_dynamic();
|
/// let x: Dynamic = 42_u32.into_dynamic();
|
||||||
///
|
///
|
||||||
/// assert_eq!(*x.downcast::<u32>().unwrap(), 42);
|
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
|
||||||
/// ```
|
/// ```
|
||||||
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self> {
|
fn try_cast<T: Any + Clone>(self) -> Result<T, Self> {
|
||||||
if self.is::<T>() {
|
if self.is::<T>() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let raw: *mut Variant = Box::into_raw(self);
|
let raw: *mut Variant = Box::into_raw(self);
|
||||||
Ok(Box::from_raw(raw as *mut T))
|
Ok(*Box::from_raw(raw as *mut T))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(self)
|
Err(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a copy of the `Dynamic` value as a specific type.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Dynamic, Any, AnyExt};
|
||||||
|
///
|
||||||
|
/// let x: Dynamic = 42_u32.into_dynamic();
|
||||||
|
///
|
||||||
|
/// assert_eq!(x.cast::<u32>(), 42);
|
||||||
|
/// ```
|
||||||
|
fn cast<T: Any + Clone>(self) -> T {
|
||||||
|
self.try_cast::<T>().expect("cast failed")
|
||||||
|
}
|
||||||
|
|
||||||
fn _closed(&self) -> _Private {
|
fn _closed(&self) -> _Private {
|
||||||
_Private
|
_Private
|
||||||
}
|
}
|
||||||
|
36
src/api.rs
36
src/api.rs
@ -692,8 +692,7 @@ impl<'e> Engine<'e> {
|
|||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
self.eval_ast_with_scope_raw(scope, false, ast)?
|
self.eval_ast_with_scope_raw(scope, false, ast)?
|
||||||
.downcast::<T>()
|
.try_cast::<T>()
|
||||||
.map(|v| *v)
|
|
||||||
.map_err(|a| {
|
.map_err(|a| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name((*a).type_name()).to_string(),
|
self.map_type_name((*a).type_name()).to_string(),
|
||||||
@ -735,9 +734,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
///
|
/// and not cleared from run to run.
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn consume_file(
|
pub fn consume_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -750,9 +748,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
///
|
/// and not cleared from run to run.
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn consume_file_with_scope(
|
pub fn consume_file_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -767,9 +764,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate a string, but throw away the result and only return error (if any).
|
/// Evaluate a string, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
///
|
/// and not cleared from run to run.
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
|
||||||
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
||||||
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
||||||
}
|
}
|
||||||
@ -777,9 +773,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
|
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
///
|
/// and not cleared from run to run.
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
|
||||||
pub fn consume_with_scope(
|
pub fn consume_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -797,9 +792,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate an AST, but throw away the result and only return error (if any).
|
/// Evaluate an AST, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
///
|
/// and not cleared from run to run.
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
|
||||||
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
|
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
|
||||||
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
|
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
|
||||||
}
|
}
|
||||||
@ -807,9 +801,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
|
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
///
|
/// and not cleared from run to run.
|
||||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
|
||||||
pub fn consume_ast_with_scope(
|
pub fn consume_ast_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -884,8 +877,7 @@ impl<'e> Engine<'e> {
|
|||||||
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
||||||
|
|
||||||
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
|
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
|
||||||
.downcast()
|
.try_cast()
|
||||||
.map(|b| *b)
|
|
||||||
.map_err(|a| {
|
.map_err(|a| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name((*a).type_name()).into(),
|
self.map_type_name((*a).type_name()).into(),
|
||||||
|
@ -628,9 +628,9 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// val_array[idx]
|
// val_array[idx]
|
||||||
if let Some(arr) = val.downcast_ref::<Array>() {
|
if let Some(arr) = val.downcast_ref::<Array>() {
|
||||||
let idx = *self
|
let idx = self
|
||||||
.eval_expr(scope, idx_expr, level)?
|
.eval_expr(scope, idx_expr, level)?
|
||||||
.downcast::<INT>()
|
.try_cast::<INT>()
|
||||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
||||||
|
|
||||||
return if idx >= 0 {
|
return if idx >= 0 {
|
||||||
@ -647,9 +647,9 @@ impl Engine<'_> {
|
|||||||
{
|
{
|
||||||
// val_map[idx]
|
// val_map[idx]
|
||||||
if let Some(map) = val.downcast_ref::<Map>() {
|
if let Some(map) = val.downcast_ref::<Map>() {
|
||||||
let idx = *self
|
let idx = self
|
||||||
.eval_expr(scope, idx_expr, level)?
|
.eval_expr(scope, idx_expr, level)?
|
||||||
.downcast::<String>()
|
.try_cast::<String>()
|
||||||
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
|
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
|
||||||
|
|
||||||
return Ok((
|
return Ok((
|
||||||
@ -662,9 +662,9 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// val_string[idx]
|
// val_string[idx]
|
||||||
if let Some(s) = val.downcast_ref::<String>() {
|
if let Some(s) = val.downcast_ref::<String>() {
|
||||||
let idx = *self
|
let idx = self
|
||||||
.eval_expr(scope, idx_expr, level)?
|
.eval_expr(scope, idx_expr, level)?
|
||||||
.downcast::<INT>()
|
.try_cast::<INT>()
|
||||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
||||||
|
|
||||||
return if idx >= 0 {
|
return if idx >= 0 {
|
||||||
@ -795,9 +795,9 @@ impl Engine<'_> {
|
|||||||
let s = scope.get_mut_by_type::<String>(src);
|
let s = scope.get_mut_by_type::<String>(src);
|
||||||
let pos = new_val.1;
|
let pos = new_val.1;
|
||||||
// Value must be a character
|
// Value must be a character
|
||||||
let ch = *new_val
|
let ch = new_val
|
||||||
.0
|
.0
|
||||||
.downcast::<char>()
|
.try_cast::<char>()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
Self::str_replace_char(s, idx.as_num(), ch);
|
Self::str_replace_char(s, idx.as_num(), ch);
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
@ -830,8 +830,8 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
if let Some(s) = target.downcast_mut::<String>() {
|
if let Some(s) = target.downcast_mut::<String>() {
|
||||||
// Value must be a character
|
// Value must be a character
|
||||||
let ch = *new_val
|
let ch = new_val
|
||||||
.downcast::<char>()
|
.try_cast::<char>()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
Self::str_replace_char(s, idx.as_num(), ch);
|
Self::str_replace_char(s, idx.as_num(), ch);
|
||||||
return Ok(target);
|
return Ok(target);
|
||||||
@ -1258,32 +1258,32 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr::And(lhs, rhs) => Ok(Box::new(
|
Expr::And(lhs, rhs) => Ok(Box::new(
|
||||||
*self
|
self
|
||||||
.eval_expr(scope, &*lhs, level)?
|
.eval_expr(scope, &*lhs, level)?
|
||||||
.downcast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
||||||
})?
|
})?
|
||||||
&& // Short-circuit using &&
|
&& // Short-circuit using &&
|
||||||
*self
|
self
|
||||||
.eval_expr(scope, &*rhs, level)?
|
.eval_expr(scope, &*rhs, level)?
|
||||||
.downcast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
||||||
})?,
|
})?,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Expr::Or(lhs, rhs) => Ok(Box::new(
|
Expr::Or(lhs, rhs) => Ok(Box::new(
|
||||||
*self
|
self
|
||||||
.eval_expr(scope, &*lhs, level)?
|
.eval_expr(scope, &*lhs, level)?
|
||||||
.downcast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
||||||
})?
|
})?
|
||||||
|| // Short-circuit using ||
|
|| // Short-circuit using ||
|
||||||
*self
|
self
|
||||||
.eval_expr(scope, &*rhs, level)?
|
.eval_expr(scope, &*rhs, level)?
|
||||||
.downcast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
||||||
})?,
|
})?,
|
||||||
@ -1334,10 +1334,10 @@ impl Engine<'_> {
|
|||||||
// If-else statement
|
// If-else statement
|
||||||
Stmt::IfThenElse(guard, if_body, else_body) => self
|
Stmt::IfThenElse(guard, if_body, else_body) => self
|
||||||
.eval_expr(scope, guard, level)?
|
.eval_expr(scope, guard, level)?
|
||||||
.downcast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
|
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
|
||||||
.and_then(|guard_val| {
|
.and_then(|guard_val| {
|
||||||
if *guard_val {
|
if guard_val {
|
||||||
self.eval_stmt(scope, if_body, level)
|
self.eval_stmt(scope, if_body, level)
|
||||||
} else if let Some(stmt) = else_body {
|
} else if let Some(stmt) = else_body {
|
||||||
self.eval_stmt(scope, stmt.as_ref(), level)
|
self.eval_stmt(scope, stmt.as_ref(), level)
|
||||||
@ -1348,8 +1348,8 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// While loop
|
// While loop
|
||||||
Stmt::While(guard, body) => loop {
|
Stmt::While(guard, body) => loop {
|
||||||
match self.eval_expr(scope, guard, level)?.downcast::<bool>() {
|
match self.eval_expr(scope, guard, level)?.try_cast::<bool>() {
|
||||||
Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) {
|
Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) {
|
||||||
Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
|
Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
|
||||||
Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
|
Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
|
||||||
Err(x) => return Err(x),
|
Err(x) => return Err(x),
|
||||||
@ -1425,9 +1425,7 @@ impl Engine<'_> {
|
|||||||
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
|
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
|
||||||
let val = self.eval_expr(scope, a, level)?;
|
let val = self.eval_expr(scope, a, level)?;
|
||||||
Err(EvalAltResult::ErrorRuntime(
|
Err(EvalAltResult::ErrorRuntime(
|
||||||
val.downcast::<String>()
|
val.try_cast::<String>().unwrap_or_else(|_| "".to_string()),
|
||||||
.map(|s| *s)
|
|
||||||
.unwrap_or_else(|_| "".to_string()),
|
|
||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -2673,41 +2673,21 @@ pub fn parse<'a, 'e>(
|
|||||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||||
if value.is::<INT>() {
|
if value.is::<INT>() {
|
||||||
let value2 = value.clone();
|
let value2 = value.clone();
|
||||||
(
|
(Some(Expr::IntegerConstant(value.cast(), pos)), value2)
|
||||||
Some(Expr::IntegerConstant(
|
|
||||||
*value.downcast::<INT>().expect("value should be INT"),
|
|
||||||
pos,
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else if value.is::<char>() {
|
} else if value.is::<char>() {
|
||||||
let value2 = value.clone();
|
let value2 = value.clone();
|
||||||
(
|
(Some(Expr::CharConstant(value.cast(), pos)), value2)
|
||||||
Some(Expr::CharConstant(
|
|
||||||
*value.downcast::<char>().expect("value should be char"),
|
|
||||||
pos,
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else if value.is::<String>() {
|
} else if value.is::<String>() {
|
||||||
let value2 = value.clone();
|
let value2 = value.clone();
|
||||||
(
|
(Some(Expr::StringConstant(value.cast(), pos)), value2)
|
||||||
Some(Expr::StringConstant(
|
|
||||||
*value.downcast::<String>().expect("value should be String"),
|
|
||||||
pos,
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
)
|
|
||||||
} else if value.is::<bool>() {
|
} else if value.is::<bool>() {
|
||||||
let value2 = value.clone();
|
let value2 = value.clone();
|
||||||
(
|
(
|
||||||
Some(
|
Some(if value.cast::<bool>() {
|
||||||
if *value.downcast::<bool>().expect("value should be bool") {
|
Expr::True(pos)
|
||||||
Expr::True(pos)
|
} else {
|
||||||
} else {
|
Expr::False(pos)
|
||||||
Expr::False(pos)
|
}),
|
||||||
},
|
|
||||||
),
|
|
||||||
value2,
|
value2,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -2715,13 +2695,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dyna
|
|||||||
{
|
{
|
||||||
if value.is::<FLOAT>() {
|
if value.is::<FLOAT>() {
|
||||||
let value2 = value.clone();
|
let value2 = value.clone();
|
||||||
return (
|
return (Some(Expr::FloatConstant(value.cast(), pos)), value2);
|
||||||
Some(Expr::FloatConstant(
|
|
||||||
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
|
||||||
pos,
|
|
||||||
)),
|
|
||||||
value2,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
|
|||||||
fn test_map_assign() -> Result<(), EvalAltResult> {
|
fn test_map_assign() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?;
|
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?;
|
||||||
let a = x.get("a").cloned().unwrap();
|
let a = x.get("a").cloned().expect("should have property a");
|
||||||
let b = x.get("b").cloned().unwrap();
|
let b = x.get("b").cloned().expect("should have property b");
|
||||||
let c = x.get("c#").cloned().unwrap();
|
let c = x.get("c$").cloned().expect("should have property c$");
|
||||||
|
|
||||||
assert_eq!(*a.downcast::<INT>().unwrap(), 1);
|
assert_eq!(a.cast::<INT>(), 1);
|
||||||
assert_eq!(*b.downcast::<bool>().unwrap(), true);
|
assert_eq!(b.cast::<bool>(), true);
|
||||||
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
|
assert_eq!(c.cast::<String>(), "hello");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -89,14 +89,14 @@ fn test_map_assign() -> Result<(), EvalAltResult> {
|
|||||||
fn test_map_return() -> Result<(), EvalAltResult> {
|
fn test_map_return() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let x = engine.eval::<Map>(r#"#{a: 1, b: true, c: "hello"}"#)?;
|
let x = engine.eval::<Map>(r#"#{a: 1, b: true, "c$": "hello"}"#)?;
|
||||||
let a = x.get("a").cloned().unwrap();
|
let a = x.get("a").cloned().expect("should have property a");
|
||||||
let b = x.get("b").cloned().unwrap();
|
let b = x.get("b").cloned().expect("should have property b");
|
||||||
let c = x.get("c").cloned().unwrap();
|
let c = x.get("c$").cloned().expect("should have property c$");
|
||||||
|
|
||||||
assert_eq!(*a.downcast::<INT>().unwrap(), 1);
|
assert_eq!(a.cast::<INT>(), 1);
|
||||||
assert_eq!(*b.downcast::<bool>().unwrap(), true);
|
assert_eq!(b.cast::<bool>(), true);
|
||||||
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
|
assert_eq!(c.cast::<String>(), "hello");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user