Add type_of function.
This commit is contained in:
parent
71ec23e621
commit
e2cb111e4b
35
README.md
35
README.md
@ -1,4 +1,4 @@
|
|||||||
# Rhai - embedded scripting for Rust
|
# Rhai - Embedded Scripting for Rust
|
||||||
|
|
||||||
Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications.
|
Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications.
|
||||||
|
|
||||||
@ -7,11 +7,11 @@ Rhai's current feature set:
|
|||||||
* Easy integration with Rust functions and data types
|
* Easy integration with Rust functions and data types
|
||||||
* Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop)
|
* Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop)
|
||||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
||||||
* Easy-to-use language based on JS+Rust
|
* Easy-to-use language similar to JS+Rust
|
||||||
* Support for overloaded functions
|
* Support for overloaded functions
|
||||||
* No additional dependencies
|
* No additional dependencies
|
||||||
|
|
||||||
**Note:** Currently, the version is 0.10.1, so the language and APIs may change before they stabilize.*
|
**Note:** Currently, the version is 0.10.1, so the language and API may change before they stabilize.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -144,6 +144,8 @@ All types are treated strictly separate by Rhai, meaning that `i32` and `i64` an
|
|||||||
|
|
||||||
There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. For other conversions you can register your own conversion functions.
|
There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. For other conversions you can register your own conversion functions.
|
||||||
|
|
||||||
|
There is also a `type_of` function to detect the type of a value.
|
||||||
|
|
||||||
```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
|
||||||
@ -151,7 +153,16 @@ 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());
|
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
|
||||||
|
|
||||||
|
// Use 'type_of' to get the type of variables
|
||||||
|
type_of(c) == "char";
|
||||||
|
type_of(x) == "i64";
|
||||||
|
y.type_of() == "f64";
|
||||||
|
|
||||||
|
if z.type_of() == "string" {
|
||||||
|
do_something_with_strong(z);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Working with functions
|
# Working with functions
|
||||||
@ -190,7 +201,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To return a [`Dynamic`] value, simply `Box` it and return it.
|
To return a `Dynamic` value, simply `Box` it and return it.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn decide(yes_no: bool) -> Dynamic {
|
fn decide(yes_no: bool) -> Dynamic {
|
||||||
@ -335,6 +346,12 @@ if let Ok(result) = engine.eval::<i64>("let x = new_ts(); x.foo()") {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`type_of` works fine with custom types and returns the name of the type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let x = new_ts();
|
||||||
|
print(x.type_of()); // prints "foo::bar::TestStruct"
|
||||||
|
```
|
||||||
|
|
||||||
# Getters and setters
|
# Getters and setters
|
||||||
|
|
||||||
@ -527,7 +544,7 @@ fn add(x, y) {
|
|||||||
print(add(2, 3));
|
print(add(2, 3));
|
||||||
```
|
```
|
||||||
|
|
||||||
Remember that functions defined in script always take [`Dynamic`] arguments (i.e. the arguments can be of any type).
|
Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
|
||||||
Furthermore, functions can only be defined at the top level, never inside a block or another function.
|
Furthermore, functions can only be defined at the top level, never inside a block or another function.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -546,7 +563,6 @@ fn do_addition(x) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Arrays
|
## Arrays
|
||||||
|
|
||||||
You can create arrays of values, and then access them with numeric indices.
|
You can create arrays of values, and then access them with numeric indices.
|
||||||
@ -606,7 +622,7 @@ engine.register_fn("push",
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
The type of a Rhai array is `rhai::Array`.
|
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`.
|
||||||
|
|
||||||
## For loops
|
## For loops
|
||||||
|
|
||||||
@ -760,6 +776,3 @@ my_str += 12345;
|
|||||||
|
|
||||||
my_str == "abcABC12345"
|
my_str == "abcABC12345"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
[`Dynamic`]: #values-and-types
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt};
|
||||||
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec};
|
use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec};
|
||||||
use crate::parser::{lex, parse, ParseError, Position, AST};
|
use crate::parser::{lex, parse, ParseError, Position, AST};
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
@ -83,7 +83,7 @@ impl Engine {
|
|||||||
|
|
||||||
let result = os
|
let result = os
|
||||||
.iter()
|
.iter()
|
||||||
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o));
|
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o));
|
||||||
|
|
||||||
self.script_fns.clear(); // Clean up engine
|
self.script_fns.clear(); // Clean up engine
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ impl Engine {
|
|||||||
|
|
||||||
let val = os
|
let val = os
|
||||||
.iter()
|
.iter()
|
||||||
.try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o))
|
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
|
||||||
.map(|_| ());
|
.map(|_| ());
|
||||||
|
|
||||||
self.script_fns.clear(); // Clean up engine
|
self.script_fns.clear(); // Clean up engine
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn};
|
use crate::{any::Any, Array, Engine, RegisterDynamicFn, RegisterFn};
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
|
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub};
|
||||||
|
|
||||||
@ -239,12 +239,14 @@ impl Engine {
|
|||||||
reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char);
|
reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char);
|
||||||
reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ());
|
reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ());
|
||||||
|
|
||||||
self.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(())));
|
self.register_dynamic_fn("pop", |list: &mut Array| {
|
||||||
|
list.pop().unwrap_or(().into_dynamic())
|
||||||
|
});
|
||||||
self.register_dynamic_fn("shift", |list: &mut Array| {
|
self.register_dynamic_fn("shift", |list: &mut Array| {
|
||||||
if list.len() > 0 {
|
if list.len() > 0 {
|
||||||
list.remove(0)
|
list.remove(0)
|
||||||
} else {
|
} else {
|
||||||
Box::new(())
|
().into_dynamic()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.register_fn("len", |list: &mut Array| list.len() as i64);
|
self.register_fn("len", |list: &mut Array| list.len() as i64);
|
||||||
@ -322,7 +324,7 @@ impl Engine {
|
|||||||
a.downcast_ref::<Range<i64>>()
|
a.downcast_ref::<Range<i64>>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone()
|
.clone()
|
||||||
.map(|n| Box::new(n) as Dynamic),
|
.map(|n| n.into_dynamic()),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ use crate::scope::Scope;
|
|||||||
pub type Array = Vec<Dynamic>;
|
pub type Array = Vec<Dynamic>;
|
||||||
pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
|
pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
|
||||||
|
|
||||||
|
const KEYWORD_PRINT: &'static str = "print";
|
||||||
|
const KEYWORD_DEBUG: &'static str = "debug";
|
||||||
|
const KEYWORD_TYPE_OF: &'static str = "type_of";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EvalAltResult {
|
pub enum EvalAltResult {
|
||||||
ErrorParsing(ParseError),
|
ErrorParsing(ParseError),
|
||||||
@ -221,18 +225,19 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let callback = match spec.ident.as_str() {
|
let callback = match spec.ident.as_str() {
|
||||||
"print" => &self.on_print,
|
KEYWORD_PRINT => &self.on_print,
|
||||||
"debug" => &self.on_debug,
|
KEYWORD_DEBUG => &self.on_debug,
|
||||||
_ => return r,
|
_ => return r,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(callback(
|
Ok(callback(
|
||||||
r.unwrap()
|
r.unwrap()
|
||||||
.downcast::<String>()
|
.downcast::<String>()
|
||||||
.map(|x| *x)
|
.map(|x| *x)
|
||||||
.unwrap_or("error: not a string".into())
|
.unwrap_or("error: not a string".into())
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)))
|
)
|
||||||
|
.into_dynamic())
|
||||||
}
|
}
|
||||||
FnIntExt::Int(ref f) => {
|
FnIntExt::Int(ref f) => {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
@ -250,6 +255,11 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if spec.ident == KEYWORD_TYPE_OF && args.len() == 1 {
|
||||||
|
Ok(self
|
||||||
|
.map_type_name(args[0].type_name())
|
||||||
|
.to_string()
|
||||||
|
.into_dynamic())
|
||||||
} else if let Some(val) = def_value {
|
} else if let Some(val) = def_value {
|
||||||
// Return default value
|
// Return default value
|
||||||
Ok(val.clone())
|
Ok(val.clone())
|
||||||
@ -376,7 +386,7 @@ impl Engine {
|
|||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
s.chars()
|
s.chars()
|
||||||
.nth(idx as usize)
|
.nth(idx as usize)
|
||||||
.map(|ch| Box::new(ch) as Dynamic)
|
.map(|ch| ch.into_dynamic())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos)
|
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos)
|
||||||
})
|
})
|
||||||
@ -470,7 +480,7 @@ impl Engine {
|
|||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
s.chars()
|
s.chars()
|
||||||
.nth(idx as usize)
|
.nth(idx as usize)
|
||||||
.map(|ch| Box::new(ch) as Dynamic)
|
.map(|ch| ch.into_dynamic())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin)
|
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin)
|
||||||
})
|
})
|
||||||
@ -631,10 +641,10 @@ impl Engine {
|
|||||||
|
|
||||||
fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
|
fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::IntegerConstant(i, _) => Ok(Box::new(*i)),
|
Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()),
|
||||||
Expr::FloatConstant(i, _) => Ok(Box::new(*i)),
|
Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()),
|
||||||
Expr::StringConstant(s, _) => Ok(Box::new(s.clone())),
|
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||||
Expr::CharConstant(c, _) => Ok(Box::new(*c)),
|
Expr::CharConstant(c, _) => Ok((*c).into_dynamic()),
|
||||||
|
|
||||||
Expr::Identifier(id, pos) => scope
|
Expr::Identifier(id, pos) => scope
|
||||||
.get(id)
|
.get(id)
|
||||||
@ -652,7 +662,7 @@ impl Engine {
|
|||||||
Expr::Identifier(ref name, pos) => {
|
Expr::Identifier(ref name, pos) => {
|
||||||
if let Some((idx, _, _)) = scope.get(name) {
|
if let Some((idx, _, _)) = scope.get(name) {
|
||||||
*scope.get_mut(name, idx) = rhs_val;
|
*scope.get_mut(name, idx) = rhs_val;
|
||||||
Ok(Box::new(()) as Dynamic)
|
Ok(().into_dynamic())
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos))
|
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos))
|
||||||
}
|
}
|
||||||
@ -684,7 +694,7 @@ impl Engine {
|
|||||||
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
||||||
} else {
|
} else {
|
||||||
arr[idx as usize] = rhs_val;
|
arr[idx as usize] = rhs_val;
|
||||||
Ok(Box::new(()))
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
} else if let Some(s) = val.downcast_mut() as Option<&mut String> {
|
} else if let Some(s) = val.downcast_mut() as Option<&mut String> {
|
||||||
let s_len = s.chars().count();
|
let s_len = s.chars().count();
|
||||||
@ -699,7 +709,7 @@ impl Engine {
|
|||||||
idx as usize,
|
idx as usize,
|
||||||
*rhs_val.downcast::<char>().unwrap(),
|
*rhs_val.downcast::<char>().unwrap(),
|
||||||
);
|
);
|
||||||
Ok(Box::new(()))
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorIndexExpr(idx_pos))
|
Err(EvalAltResult::ErrorIndexExpr(idx_pos))
|
||||||
@ -770,9 +780,9 @@ impl Engine {
|
|||||||
})?,
|
})?,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Expr::True(_) => Ok(Box::new(true)),
|
Expr::True(_) => Ok(true.into_dynamic()),
|
||||||
Expr::False(_) => Ok(Box::new(false)),
|
Expr::False(_) => Ok(false.into_dynamic()),
|
||||||
Expr::Unit(_) => Ok(Box::new(())),
|
Expr::Unit(_) => Ok(().into_dynamic()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,7 +796,7 @@ impl Engine {
|
|||||||
|
|
||||||
Stmt::Block(block) => {
|
Stmt::Block(block) => {
|
||||||
let prev_len = scope.len();
|
let prev_len = scope.len();
|
||||||
let mut last_result: Result<Dynamic, EvalAltResult> = Ok(Box::new(()));
|
let mut last_result: Result<Dynamic, EvalAltResult> = Ok(().into_dynamic());
|
||||||
|
|
||||||
for block_stmt in block.iter() {
|
for block_stmt in block.iter() {
|
||||||
last_result = self.eval_stmt(scope, block_stmt);
|
last_result = self.eval_stmt(scope, block_stmt);
|
||||||
@ -814,7 +824,7 @@ impl Engine {
|
|||||||
} else if else_body.is_some() {
|
} else if else_body.is_some() {
|
||||||
self.eval_stmt(scope, else_body.as_ref().unwrap())
|
self.eval_stmt(scope, else_body.as_ref().unwrap())
|
||||||
} else {
|
} else {
|
||||||
Ok(Box::new(()))
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -823,12 +833,12 @@ impl Engine {
|
|||||||
Ok(guard_val) => {
|
Ok(guard_val) => {
|
||||||
if *guard_val {
|
if *guard_val {
|
||||||
match self.eval_stmt(scope, body) {
|
match self.eval_stmt(scope, body) {
|
||||||
Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())),
|
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
||||||
Err(x) => return Err(x),
|
Err(x) => return Err(x),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Ok(Box::new(()));
|
return Ok(().into_dynamic());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())),
|
Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())),
|
||||||
@ -837,7 +847,7 @@ impl Engine {
|
|||||||
|
|
||||||
Stmt::Loop(body) => loop {
|
Stmt::Loop(body) => loop {
|
||||||
match self.eval_stmt(scope, body) {
|
match self.eval_stmt(scope, body) {
|
||||||
Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())),
|
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
||||||
Err(x) => return Err(x),
|
Err(x) => return Err(x),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@ -861,7 +871,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope.pop();
|
scope.pop();
|
||||||
Ok(Box::new(()))
|
Ok(().into_dynamic())
|
||||||
} else {
|
} else {
|
||||||
return Err(EvalAltResult::ErrorFor(expr.position()));
|
return Err(EvalAltResult::ErrorFor(expr.position()));
|
||||||
}
|
}
|
||||||
@ -869,7 +879,7 @@ impl Engine {
|
|||||||
|
|
||||||
Stmt::Break(_) => Err(EvalAltResult::LoopBreak),
|
Stmt::Break(_) => Err(EvalAltResult::LoopBreak),
|
||||||
|
|
||||||
Stmt::Return(pos) => Err(EvalAltResult::Return(Box::new(()), *pos)),
|
Stmt::Return(pos) => Err(EvalAltResult::Return(().into_dynamic(), *pos)),
|
||||||
|
|
||||||
Stmt::ReturnWithVal(a, pos) => {
|
Stmt::ReturnWithVal(a, pos) => {
|
||||||
let result = self.eval_expr(scope, a)?;
|
let result = self.eval_expr(scope, a)?;
|
||||||
@ -883,7 +893,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
scope.push(name.clone(), ());
|
scope.push(name.clone(), ());
|
||||||
}
|
}
|
||||||
Ok(Box::new(()))
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
tests/types.rs
Normal file
17
tests/types.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_of() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<String>("type_of(60 + 5)")?, "i64");
|
||||||
|
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
|
||||||
|
"array"
|
||||||
|
);
|
||||||
|
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
|
||||||
|
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i64");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user