From e2cb111e4b15db832c8649a7d8d2ee33a5521b72 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 17:28:38 +0800 Subject: [PATCH] Add type_of function. --- README.md | 35 ++++++++++++++++++++---------- src/api.rs | 6 +++--- src/builtin.rs | 10 +++++---- src/engine.rs | 58 +++++++++++++++++++++++++++++--------------------- tests/types.rs | 17 +++++++++++++++ 5 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 tests/types.rs diff --git a/README.md b/README.md index 2677e2ae..976ac0ae 100644 --- a/README.md +++ b/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. @@ -7,11 +7,11 @@ Rhai's current feature set: * Easy integration with Rust functions and data types * 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) -* Easy-to-use language based on JS+Rust +* Easy-to-use language similar to JS+Rust * Support for overloaded functions * 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 @@ -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 also a `type_of` function to detect the type of a value. + ```rust let x = 42; 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 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 @@ -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 fn decide(yes_no: bool) -> Dynamic { @@ -335,6 +346,12 @@ if let Ok(result) = engine.eval::("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 @@ -527,7 +544,7 @@ fn add(x, y) { 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. ```rust @@ -546,7 +563,6 @@ fn do_addition(x) { } ``` - ## Arrays 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 @@ -760,6 +776,3 @@ my_str += 12345; my_str == "abcABC12345" ``` - - -[`Dynamic`]: #values-and-types diff --git a/src/api.rs b/src/api.rs index fadbe73b..062d28cc 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,4 @@ -use crate::any::{Any, AnyExt, Dynamic}; +use crate::any::{Any, AnyExt}; use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec}; use crate::parser::{lex, parse, ParseError, Position, AST}; use crate::scope::Scope; @@ -83,7 +83,7 @@ impl Engine { let result = os .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 @@ -160,7 +160,7 @@ impl Engine { let val = os .iter() - .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)) + .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .map(|_| ()); self.script_fns.clear(); // Clean up engine diff --git a/src/builtin.rs b/src/builtin.rs index e8fb2f00..538a6f5b 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -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::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, (), 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| { if list.len() > 0 { list.remove(0) } else { - Box::new(()) + ().into_dynamic() } }); self.register_fn("len", |list: &mut Array| list.len() as i64); @@ -322,7 +324,7 @@ impl Engine { a.downcast_ref::>() .unwrap() .clone() - .map(|n| Box::new(n) as Dynamic), + .map(|n| n.into_dynamic()), ) }); diff --git a/src/engine.rs b/src/engine.rs index 2a876228..bccd33a7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -13,6 +13,10 @@ use crate::scope::Scope; pub type Array = Vec; 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)] pub enum EvalAltResult { ErrorParsing(ParseError), @@ -221,18 +225,19 @@ impl Engine { } let callback = match spec.ident.as_str() { - "print" => &self.on_print, - "debug" => &self.on_debug, + KEYWORD_PRINT => &self.on_print, + KEYWORD_DEBUG => &self.on_debug, _ => return r, }; - Ok(Box::new(callback( + Ok(callback( r.unwrap() .downcast::() .map(|x| *x) .unwrap_or("error: not a string".into()) .as_str(), - ))) + ) + .into_dynamic()) } FnIntExt::Int(ref f) => { 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 { // Return default value Ok(val.clone()) @@ -376,7 +386,7 @@ impl Engine { if idx >= 0 { s.chars() .nth(idx as usize) - .map(|ch| Box::new(ch) as Dynamic) + .map(|ch| ch.into_dynamic()) .ok_or_else(|| { EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) }) @@ -470,7 +480,7 @@ impl Engine { if idx >= 0 { s.chars() .nth(idx as usize) - .map(|ch| Box::new(ch) as Dynamic) + .map(|ch| ch.into_dynamic()) .ok_or_else(|| { EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin) }) @@ -631,10 +641,10 @@ impl Engine { fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result { match expr { - Expr::IntegerConstant(i, _) => Ok(Box::new(*i)), - Expr::FloatConstant(i, _) => Ok(Box::new(*i)), - Expr::StringConstant(s, _) => Ok(Box::new(s.clone())), - Expr::CharConstant(c, _) => Ok(Box::new(*c)), + Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), + Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), + Expr::StringConstant(s, _) => Ok(s.into_dynamic()), + Expr::CharConstant(c, _) => Ok((*c).into_dynamic()), Expr::Identifier(id, pos) => scope .get(id) @@ -652,7 +662,7 @@ impl Engine { Expr::Identifier(ref name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; - Ok(Box::new(()) as Dynamic) + Ok(().into_dynamic()) } else { Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos)) } @@ -684,7 +694,7 @@ impl Engine { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) } else { arr[idx as usize] = rhs_val; - Ok(Box::new(())) + Ok(().into_dynamic()) } } else if let Some(s) = val.downcast_mut() as Option<&mut String> { let s_len = s.chars().count(); @@ -699,7 +709,7 @@ impl Engine { idx as usize, *rhs_val.downcast::().unwrap(), ); - Ok(Box::new(())) + Ok(().into_dynamic()) } } else { Err(EvalAltResult::ErrorIndexExpr(idx_pos)) @@ -770,9 +780,9 @@ impl Engine { })?, )), - Expr::True(_) => Ok(Box::new(true)), - Expr::False(_) => Ok(Box::new(false)), - Expr::Unit(_) => Ok(Box::new(())), + Expr::True(_) => Ok(true.into_dynamic()), + Expr::False(_) => Ok(false.into_dynamic()), + Expr::Unit(_) => Ok(().into_dynamic()), } } @@ -786,7 +796,7 @@ impl Engine { Stmt::Block(block) => { let prev_len = scope.len(); - let mut last_result: Result = Ok(Box::new(())); + let mut last_result: Result = Ok(().into_dynamic()); for block_stmt in block.iter() { last_result = self.eval_stmt(scope, block_stmt); @@ -814,7 +824,7 @@ impl Engine { } else if else_body.is_some() { self.eval_stmt(scope, else_body.as_ref().unwrap()) } else { - Ok(Box::new(())) + Ok(().into_dynamic()) } }), @@ -823,12 +833,12 @@ impl Engine { Ok(guard_val) => { if *guard_val { 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), _ => (), } } else { - return Ok(Box::new(())); + return Ok(().into_dynamic()); } } Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())), @@ -837,7 +847,7 @@ impl Engine { Stmt::Loop(body) => loop { 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), _ => (), } @@ -861,7 +871,7 @@ impl Engine { } } scope.pop(); - Ok(Box::new(())) + Ok(().into_dynamic()) } else { return Err(EvalAltResult::ErrorFor(expr.position())); } @@ -869,7 +879,7 @@ impl Engine { 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) => { let result = self.eval_expr(scope, a)?; @@ -883,7 +893,7 @@ impl Engine { } else { scope.push(name.clone(), ()); } - Ok(Box::new(())) + Ok(().into_dynamic()) } } } diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 00000000..1cf303ad --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,17 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_type_of() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("type_of(60 + 5)")?, "i64"); + assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + assert_eq!( + engine.eval::(r#"type_of([1.0, 2, "hello"])"#)?, + "array" + ); + assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); + assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); + + Ok(()) +}