From fc66a7ecef6dc08bd71ddb13777ab38ac3f998be Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 2 May 2020 16:23:36 +0800 Subject: [PATCH 01/23] Pretty up scripts with print. --- scripts/array.rhai | 6 ++++-- scripts/assignment.rhai | 2 ++ scripts/comments.rhai | 3 ++- scripts/for1.rhai | 2 +- scripts/function_decl1.rhai | 4 +++- scripts/function_decl2.rhai | 6 +++++- scripts/function_decl3.rhai | 4 +++- scripts/if1.rhai | 6 +++--- scripts/op1.rhai | 4 +++- scripts/op2.rhai | 4 +++- scripts/op3.rhai | 4 +++- 11 files changed, 32 insertions(+), 13 deletions(-) diff --git a/scripts/array.rhai b/scripts/array.rhai index 1d0768bd..cc6fc642 100644 --- a/scripts/array.rhai +++ b/scripts/array.rhai @@ -1,7 +1,9 @@ let x = [1, 2, 3]; -print(x[1]); // prints 2 +print("x[1] should be 2:"); +print(x[1]); x[1] = 5; -print(x[1]); // prints 5 +print("x[1] should be 5:"); +print(x[1]); diff --git a/scripts/assignment.rhai b/scripts/assignment.rhai index 9554a7e5..82ff7d92 100644 --- a/scripts/assignment.rhai +++ b/scripts/assignment.rhai @@ -1,2 +1,4 @@ +print("x should be 78:"); + let x = 78; print(x); diff --git a/scripts/comments.rhai b/scripts/comments.rhai index 645105bb..f5d24c93 100644 --- a/scripts/comments.rhai +++ b/scripts/comments.rhai @@ -3,7 +3,8 @@ let /* I am a spy in a variable declaration! */ x = 5; /* I am a simple - multi-line comment */ + multi-line + comment */ /* look /* at /* that, /* multi-line */ comments */ can be */ nested */ diff --git a/scripts/for1.rhai b/scripts/for1.rhai index 8eafedde..8fc6c95e 100644 --- a/scripts/for1.rhai +++ b/scripts/for1.rhai @@ -1,6 +1,6 @@ // This script runs for-loops -let arr = [1,2,3,4]; +let arr = [1, 2, 3, 4]; for a in arr { for b in [10, 20] { diff --git a/scripts/function_decl1.rhai b/scripts/function_decl1.rhai index 4e97e47c..908bcdec 100644 --- a/scripts/function_decl1.rhai +++ b/scripts/function_decl1.rhai @@ -4,4 +4,6 @@ fn bob() { return 3; } -print(bob()); // should print 3 +print("bob() should be 3:"); + +print(bob()); diff --git a/scripts/function_decl2.rhai b/scripts/function_decl2.rhai index 201dcea4..0d72743e 100644 --- a/scripts/function_decl2.rhai +++ b/scripts/function_decl2.rhai @@ -7,6 +7,10 @@ fn addme(a, b) { a + b; // notice that the last value is returned even if terminated by a semicolon } -print(addme(a, 4)); // should print 46 +print("addme(a, 4) should be 46:"); + +print(addme(a, 4)); + +print("a should still be 3:"); print(a); // should print 3 - 'a' is never changed diff --git a/scripts/function_decl3.rhai b/scripts/function_decl3.rhai index 339c4b61..4901f5c9 100644 --- a/scripts/function_decl3.rhai +++ b/scripts/function_decl3.rhai @@ -4,4 +4,6 @@ fn f(a, b, c, d, e, f) { a - b * c - d * e - f } -print(f(100, 5, 2, 9, 6, 32)); // should print 4 +print("f() call should be 4:"); + +print(f(100, 5, 2, 9, 6, 32)); diff --git a/scripts/if1.rhai b/scripts/if1.rhai index 6f414a78..cbfe2938 100644 --- a/scripts/if1.rhai +++ b/scripts/if1.rhai @@ -3,12 +3,12 @@ let b = 123; let x = 999; if a > b { - print("a > b"); + print("Oops! a > b"); } else if a < b { - print("a < b"); + print("a < b, x should be 0"); let x = 0; // this 'x' shadows the global 'x' print(x); // should print 0 } else { - print("a == b"); + print("Oops! a == b"); } \ No newline at end of file diff --git a/scripts/op1.rhai b/scripts/op1.rhai index 5351f999..bedfa563 100644 --- a/scripts/op1.rhai +++ b/scripts/op1.rhai @@ -1 +1,3 @@ -print(34 + 12); // should be 46 +print("The result should be 46:"); + +print(34 + 12); diff --git a/scripts/op2.rhai b/scripts/op2.rhai index fedbd0aa..e00a1b99 100644 --- a/scripts/op2.rhai +++ b/scripts/op2.rhai @@ -1,2 +1,4 @@ +print("The result should be 182:"); + let x = 12 + 34 * 5; -print(x); // should be 182 +print(x); diff --git a/scripts/op3.rhai b/scripts/op3.rhai index 7811dbac..aa7349a8 100644 --- a/scripts/op3.rhai +++ b/scripts/op3.rhai @@ -1,2 +1,4 @@ +print("The result should be 230:"); + let x = (12 + 34) * 5; -print(x); // should be 230 +print(x); From d83b829810b2dca5b88239d2dd08f65fc44246b3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 3 May 2020 16:54:24 +0800 Subject: [PATCH 02/23] Avoid copying arguments for function calls. --- README.md | 33 +++++++++++++++++---------------- src/any.rs | 19 ++++++++++++++++++- src/engine.rs | 8 ++++---- src/fn_call.rs | 3 +-- src/fn_func.rs | 1 - src/fn_register.rs | 42 +++++++++++++++++++++++------------------- src/optimize.rs | 7 +------ src/parser.rs | 32 +++++++++++++++++--------------- 8 files changed, 81 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 02b3e27b..b453b04c 100644 --- a/README.md +++ b/README.md @@ -374,19 +374,19 @@ engine.load_package(package.get()); // load the package manually The follow packages are available: -| Package | Description | In `CorePackage` | In `StandardPackage` | -| ------------------------ | ----------------------------------------------- | :--------------: | :------------------: | -| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes | -| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | -| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes | -| `BasicStringPackage` | Basic string functions | Yes | Yes | -| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | -| `MoreStringPackage` | Additional string functions | No | Yes | -| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | -| `BasicArrayPackage` | Basic [array] functions | No | Yes | -| `BasicMapPackage` | Basic [object map] functions | No | Yes | -| `CorePackage` | Basic essentials | | | -| `StandardPackage` | Standard library | | | +| Package | Description | In `CorePackage` | In `StandardPackage` | +| ---------------------- | ----------------------------------------------- | :--------------: | :------------------: | +| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes | +| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | +| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes | +| `BasicStringPackage` | Basic string functions | Yes | Yes | +| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | +| `MoreStringPackage` | Additional string functions | No | Yes | +| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | +| `BasicArrayPackage` | Basic [array] functions | No | Yes | +| `BasicMapPackage` | Basic [object map] functions | No | Yes | +| `CorePackage` | Basic essentials | | | +| `StandardPackage` | Standard library | | | Evaluate expressions only ------------------------- @@ -427,7 +427,7 @@ The following primitive types are supported natively: | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | | **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | -| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` | +| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | | **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | @@ -1372,7 +1372,8 @@ y[2] == 3; y[3] == 4; (1 in y) == true; // use 'in' to test if an item exists in the array -(42 in y) == false; +(42 in y) == false; // 'in' uses the '==' operator (which users can override) + // to check if the target item exists in the array y[1] = 42; // array elements can be reassigned @@ -1494,7 +1495,7 @@ y.a == 42; y["baz!$@"] == 123.456; // access via index notation -"baz!$@" in y == true; // use 'in' to test if a property exists in the object map, prints true +"baz!$@" in y == true; // use 'in' to test if a property exists in the object map ("z" in y) == false; ts.obj = y; // object maps can be assigned completely (by value copy) diff --git a/src/any.rs b/src/any.rs index d9aad2a6..a2c8b59f 100644 --- a/src/any.rs +++ b/src/any.rs @@ -396,7 +396,24 @@ impl Dynamic { /// assert_eq!(x.cast::(), 42); /// ``` pub fn cast(self) -> T { - self.try_cast::().unwrap() + //self.try_cast::().unwrap() + + if TypeId::of::() == TypeId::of::() { + return cast_box::<_, T>(Box::new(self)).unwrap(); + } + + match self.0 { + Union::Unit(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Bool(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Str(value) => cast_box::<_, T>(value).unwrap(), + Union::Char(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Int(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + #[cfg(not(feature = "no_float"))] + Union::Float(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Array(value) => cast_box::<_, T>(value).unwrap(), + Union::Map(value) => cast_box::<_, T>(value).unwrap(), + Union::Variant(value) => value.as_ref().as_ref().downcast_ref::().unwrap().clone(), + } } /// Get a reference of a specific type to the `Dynamic`. diff --git a/src/engine.rs b/src/engine.rs index c24b924c..e4cdfa7c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -692,7 +692,7 @@ impl Engine { &self, fn_lib: &FunctionsLib, fn_name: &str, - args: &mut [&mut Dynamic], + args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, level: usize, @@ -1227,7 +1227,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => Ok(Dynamic(Union::Array(Box::new( contents - .into_iter() + .iter() .map(|item| self.eval_expr(scope, state, fn_lib, item, level)) .collect::, _>>()?, )))), @@ -1235,9 +1235,9 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Map(contents, _) => Ok(Dynamic(Union::Map(Box::new( contents - .into_iter() + .iter() .map(|(key, expr, _)| { - self.eval_expr(scope, state, fn_lib, &expr, level) + self.eval_expr(scope, state, fn_lib, expr, level) .map(|val| (key.clone(), val)) }) .collect::, _>>()?, diff --git a/src/fn_call.rs b/src/fn_call.rs index a28522de..c64ba035 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -22,7 +22,7 @@ macro_rules! impl_args { fn into_vec(self) -> Vec { let ($($p,)*) = self; - #[allow(unused_variables, unused_mut)] + #[allow(unused_mut)] let mut v = Vec::new(); $(v.push($p.into_dynamic());)* @@ -42,5 +42,4 @@ macro_rules! impl_args { }; } -#[rustfmt::skip] impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_func.rs b/src/fn_func.rs index b9add7e4..af538306 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -116,5 +116,4 @@ macro_rules! def_anonymous_fn { }; } -#[rustfmt::skip] def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_register.rs b/src/fn_register.rs index e711ee54..9368d6a3 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -7,7 +7,7 @@ use crate::engine::{calc_fn_spec, Engine, FnCallArgs}; use crate::result::EvalAltResult; use crate::token::Position; -use crate::stdlib::{any::TypeId, boxed::Box, string::ToString}; +use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; /// A trait to register custom functions with the `Engine`. pub trait RegisterFn { @@ -115,16 +115,19 @@ pub trait RegisterResultFn { pub struct Mut(T); //pub struct Ref(T); -/// Identity dereferencing function. +/// Dereference into &mut. #[inline] -pub fn identity(data: &mut T) -> &mut T { - data +pub fn by_ref(data: &mut Dynamic) -> &mut T { + // Directly cast the &mut Dynamic into &mut T to access the underlying data. + data.downcast_mut::().unwrap() } -/// Clone dereferencing function. +/// Dereference into value. #[inline] -pub fn cloned(data: &mut T) -> T { - data.clone() +pub fn by_value(data: &mut Dynamic) -> T { + // We consume the argument and then replace it with () - the argument is not supposed to be used again. + // This way, we avoid having to clone the argument again, because it is already a clone when passed here. + mem::replace(data, Default::default()).cast::() } /// This macro counts the number of arguments via recursion. @@ -135,7 +138,7 @@ macro_rules! count_args { /// This macro creates a closure wrapping a registered function. macro_rules! make_func { - ($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $clone:expr),*) => { + ($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { // ^ function name // ^ function pointer // ^ result mapping function @@ -153,14 +156,16 @@ macro_rules! make_func { #[allow(unused_variables, unused_mut)] let mut drain = args.iter_mut(); $( - // Downcast every element, return in case of a type mismatch - let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); + // Downcast every element, panic in case of a type mismatch (which shouldn't happen). + // Call the user-supplied function using ($convert) to access it either by value or by reference. + let $par = ($convert)(drain.next().unwrap()); )* - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - let r = $fn($(($clone)($par)),*); - $map(r, pos) + // Call the function with each parameter value + let r = $fn($($par),*); + + // Map the result + $map(r, pos) }; }; } @@ -259,18 +264,17 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp $p0 => $p0 => $p0 => cloned $(, $p => $p => $p => cloned)*); - def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => cloned)*); + def_register!(imp $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); + def_register!(imp $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*); // handle the first parameter ^ first parameter passed through - // ^ others passed by value (cloned) + // ^ others passed by value (by_value) // Currently does not support first argument which is a reference, as there will be // conflicting implementations since &T: Any and T: Any cannot be distinguished - //def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*); + //def_register!(imp $p0 => Ref<$p0> => &$p0 => by_ref $(, $p => $p => $p => by_value)*); def_register!($($p),*); }; } -#[rustfmt::skip] def_register!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/optimize.rs b/src/optimize.rs index 268c504a..d018b4c5 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -371,12 +371,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { (Expr::Variable(var, sp, _), Expr::Variable(var2, sp2, _)) if var == var2 && sp == sp2 => { // Assignment to the same variable - fold state.set_dirty(); - - Expr::Assignment( - Box::new(Expr::Variable(var, sp, pos)), - Box::new(optimize_expr(*expr2, state)), - pos, - ) + Expr::Assignment(Box::new(Expr::Variable(var, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) } // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( diff --git a/src/parser.rs b/src/parser.rs index e682ee12..df7889c9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ format, iter::Peekable, num::NonZeroUsize, - ops::Add, + ops::{Add, Deref, DerefMut}, rc::Rc, string::{String, ToString}, sync::Arc, @@ -193,18 +193,6 @@ impl Stack { pub fn new() -> Self { Self(Vec::new()) } - /// Get the number of variables in the `Stack`. - pub fn len(&self) -> usize { - self.0.len() - } - /// Push (add) a new variable onto the `Stack`. - pub fn push(&mut self, name: String) { - self.0.push(name); - } - /// Rewind the stack to a previous size. - pub fn rewind(&mut self, len: usize) { - self.0.truncate(len); - } /// Find a variable by name in the `Stack`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `Stack` is offset 1. @@ -219,6 +207,20 @@ impl Stack { } } +impl Deref for Stack { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Stack { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + /// A statement. #[derive(Debug, Clone)] pub enum Stmt { @@ -1639,7 +1641,7 @@ fn parse_for<'a>( let body = parse_block(input, stack, true, allow_stmt_expr)?; - stack.rewind(prev_len); + stack.truncate(prev_len); Ok(Stmt::For(Box::new(name), Box::new(expr), Box::new(body))) } @@ -1747,7 +1749,7 @@ fn parse_block<'a>( } } - stack.rewind(prev_len); + stack.truncate(prev_len); Ok(Stmt::Block(statements, pos)) } From 68732a6e475e80f822b4e8ff3a3ec818a257c905 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 3 May 2020 22:12:10 +0800 Subject: [PATCH 03/23] Use stepped range. --- scripts/primes.rhai | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/scripts/primes.rhai b/scripts/primes.rhai index 22defcb4..668fa250 100644 --- a/scripts/primes.rhai +++ b/scripts/primes.rhai @@ -13,18 +13,17 @@ prime_mask[1] = false; let total_primes_found = 0; for p in range(2, MAX_NUMBER_TO_CHECK) { - if prime_mask[p] { - print(p); + if !prime_mask[p] { continue; } - total_primes_found += 1; - let i = 2 * p; + print(p); - while i < MAX_NUMBER_TO_CHECK { - prime_mask[i] = false; - i += p; - } + total_primes_found += 1; + + for i in range(2 * p, MAX_NUMBER_TO_CHECK, p) { + prime_mask[i] = false; + i += p; } } -print("Total " + total_primes_found + " primes."); +print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK); print("Run time = " + now.elapsed() + " seconds."); From 3d05cc96cd72d6cd331d3aa7a0484631d8ce9b6a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 3 May 2020 22:17:28 +0800 Subject: [PATCH 04/23] Remove downcast_XXX from Variant. --- src/any.rs | 100 ++++++++++++++++++++++---------------------------- src/engine.rs | 2 +- 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/src/any.rs b/src/any.rs index a2c8b59f..28c462d1 100644 --- a/src/any.rs +++ b/src/any.rs @@ -119,18 +119,6 @@ impl dyn Variant { pub fn is(&self) -> bool { TypeId::of::() == self.type_id() } - - /// Get a reference of a specific type to the `Variant`. - /// Returns `None` if the cast fails. - pub fn downcast_ref(&self) -> Option<&T> { - Any::downcast_ref::(self.as_any()) - } - - /// Get a mutable reference of a specific type to the `Variant`. - /// Returns `None` if the cast fails. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - Any::downcast_mut::(self.as_mut_any()) - } } /// A dynamic type containing any value. @@ -237,17 +225,17 @@ impl fmt::Debug for Dynamic { impl Clone for Dynamic { fn clone(&self) -> Self { - match &self.0 { - Union::Unit(value) => Self(Union::Unit(value.clone())), - Union::Bool(value) => Self(Union::Bool(value.clone())), - Union::Str(value) => Self(Union::Str(value.clone())), - Union::Char(value) => Self(Union::Char(value.clone())), - Union::Int(value) => Self(Union::Int(value.clone())), + match self.0 { + Union::Unit(value) => Self(Union::Unit(value)), + Union::Bool(value) => Self(Union::Bool(value)), + Union::Str(ref value) => Self(Union::Str(value.clone())), + Union::Char(value) => Self(Union::Char(value)), + Union::Int(value) => Self(Union::Int(value)), #[cfg(not(feature = "no_float"))] - Union::Float(value) => Self(Union::Float(value.clone())), - Union::Array(value) => Self(Union::Array(value.clone())), - Union::Map(value) => Self(Union::Map(value.clone())), - Union::Variant(value) => (***value).clone_into_dynamic(), + Union::Float(value) => Self(Union::Float(value)), + Union::Array(ref value) => Self(Union::Array(value.clone())), + Union::Map(ref value) => Self(Union::Map(value.clone())), + Union::Variant(ref value) => (***value).clone_into_dynamic(), } } } @@ -300,7 +288,7 @@ impl Dynamic { /// assert_eq!(new_result.to_string(), "hello"); /// ``` pub fn from(value: T) -> Self { - let dyn_value = &value as &dyn Variant; + let dyn_value = &value as &dyn Any; if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) { return Self(result); @@ -337,7 +325,7 @@ impl Dynamic { .map(Box::new) .map(Union::Map) .or_else(|var| -> Result { - Ok(Union::Variant(Box::new(var as Box))) + Ok(Union::Variant(Box::new(var))) }) }) }) @@ -366,16 +354,16 @@ impl Dynamic { } match self.0 { - Union::Unit(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Bool(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Unit(ref value) => (value as &dyn Any).downcast_ref::().cloned(), + Union::Bool(ref value) => (value as &dyn Any).downcast_ref::().cloned(), Union::Str(value) => cast_box::<_, T>(value).ok(), - Union::Char(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Int(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Char(ref value) => (value as &dyn Any).downcast_ref::().cloned(), + Union::Int(ref value) => (value as &dyn Any).downcast_ref::().cloned(), #[cfg(not(feature = "no_float"))] - Union::Float(ref value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Float(ref value) => (value as &dyn Any).downcast_ref::().cloned(), Union::Array(value) => cast_box::<_, T>(value).ok(), Union::Map(value) => cast_box::<_, T>(value).ok(), - Union::Variant(value) => value.as_ref().as_ref().downcast_ref::().cloned(), + Union::Variant(value) => value.as_any().downcast_ref::().cloned(), } } @@ -403,16 +391,16 @@ impl Dynamic { } match self.0 { - Union::Unit(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), - Union::Bool(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Unit(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + Union::Bool(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), Union::Str(value) => cast_box::<_, T>(value).unwrap(), - Union::Char(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), - Union::Int(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Char(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), + Union::Int(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), #[cfg(not(feature = "no_float"))] - Union::Float(ref value) => (value as &dyn Variant).downcast_ref::().unwrap().clone(), + Union::Float(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), Union::Array(value) => cast_box::<_, T>(value).unwrap(), Union::Map(value) => cast_box::<_, T>(value).unwrap(), - Union::Variant(value) => value.as_ref().as_ref().downcast_ref::().unwrap().clone(), + Union::Variant(value) => value.as_any().downcast_ref::().unwrap().clone(), } } @@ -421,20 +409,20 @@ impl Dynamic { /// Returns `None` if the cast fails. pub fn downcast_ref(&self) -> Option<&T> { if TypeId::of::() == TypeId::of::() { - return (self as &dyn Variant).downcast_ref::(); + return (self as &dyn Any).downcast_ref::(); } match &self.0 { - Union::Unit(value) => (value as &dyn Variant).downcast_ref::(), - Union::Bool(value) => (value as &dyn Variant).downcast_ref::(), - Union::Str(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Char(value) => (value as &dyn Variant).downcast_ref::(), - Union::Int(value) => (value as &dyn Variant).downcast_ref::(), + Union::Unit(value) => (value as &dyn Any).downcast_ref::(), + Union::Bool(value) => (value as &dyn Any).downcast_ref::(), + Union::Str(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Char(value) => (value as &dyn Any).downcast_ref::(), + Union::Int(value) => (value as &dyn Any).downcast_ref::(), #[cfg(not(feature = "no_float"))] - Union::Float(value) => (value as &dyn Variant).downcast_ref::(), - Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Variant(value) => value.as_ref().as_ref().downcast_ref::(), + Union::Float(value) => (value as &dyn Any).downcast_ref::(), + Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), } } @@ -443,20 +431,20 @@ impl Dynamic { /// Returns `None` if the cast fails. pub fn downcast_mut(&mut self) -> Option<&mut T> { if TypeId::of::() == TypeId::of::() { - return (self as &mut dyn Variant).downcast_mut::(); + return (self as &mut dyn Any).downcast_mut::(); } match &mut self.0 { - Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Str(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Char(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Int(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Unit(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Bool(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Str(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Char(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Int(value) => (value as &mut dyn Any).downcast_mut::(), #[cfg(not(feature = "no_float"))] - Union::Float(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Variant(value) => value.as_mut().as_mut().downcast_mut::(), + Union::Float(value) => (value as &mut dyn Any).downcast_mut::(), + Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), } } diff --git a/src/engine.rs b/src/engine.rs index e4cdfa7c..8480664a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -828,7 +828,7 @@ impl Engine { self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)?; Ok((indexed_val.clone_into_dynamic(), false)) } - // xxx.id = ??? + // xxx.id = ??? a Expr::Property(id, pos) if new_val.is_some() => { let fn_name = make_setter(id); let mut args = [obj, new_val.as_mut().unwrap()]; From 7011e4068f04a936598c9cc5df2125f987cbb50f Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 3 May 2020 12:19:01 -0500 Subject: [PATCH 05/23] Start on namespaces --- src/engine.rs | 8 +++++++- src/parser.rs | 27 +++++++++++++++++++++++++-- src/scope.rs | 34 ++++++++++++++++++++++++++++++++++ src/token.rs | 9 ++++++++- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index e4cdfa7c..1b6c8e6c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1180,7 +1180,8 @@ impl Engine { Some((index, ScopeEntryType::Normal)) => { *scope.get_mut(index).0 = rhs_val; Ok(Default::default()) - } + }, + Some((_, ScopeEntryType::Subscope)) => unreachable!(), }, // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] @@ -1489,6 +1490,11 @@ impl Engine { Ok(Default::default()) } + // Import statement + Stmt::Import(_name_expr, _alias) => { + unimplemented!() + } + // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { let val = self.eval_expr(scope, state, fn_lib, expr, level)?; diff --git a/src/parser.rs b/src/parser.rs index df7889c9..716b3580 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -248,6 +248,8 @@ pub enum Stmt { Break(Position), /// `return`/`throw` ReturnWithVal(Option>, ReturnType, Position), + /// import expr + Import(Box, Option) } impl Stmt { @@ -261,7 +263,11 @@ impl Stmt { | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), + + Stmt::IfThenElse(expr, _, _) + | Stmt::Expr(expr) + | Stmt::Import(expr, _) => expr.position(), + Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), } } @@ -273,7 +279,8 @@ impl Stmt { | Stmt::While(_, _) | Stmt::Loop(_) | Stmt::For(_, _, _) - | Stmt::Block(_, _) => true, + | Stmt::Block(_, _) + | Stmt::Import(_, _) => true, // A No-op requires a semicolon in order to know it is an empty statement! Stmt::Noop(_) => false, @@ -303,6 +310,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, + Stmt::Import(_, _) => false, } } } @@ -333,6 +341,16 @@ pub enum Expr { Option>, Position, ), + /// subscope::func(expr, ... ) + /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls + /// and the function names are predictable, so no need to allocate a new `String`. + SubscopeFnCall( + String, + Box>, + Box>, + Option>, + Position, + ), /// expr = expr Assignment(Box, Box, Position), /// lhs.rhs @@ -430,6 +448,7 @@ impl Expr { | Self::Property(_, pos) | Self::Stmt(_, pos) | Self::FnCall(_, _, _, pos) + | Self::SubscopeFnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -456,6 +475,7 @@ impl Expr { | Self::Property(_, pos) | Self::Stmt(_, pos) | Self::FnCall(_, _, _, pos) + | Self::SubscopeFnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -533,6 +553,7 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) | Self::FnCall(_, _, _, _) + | Self::SubscopeFnCall(_, _, _, _, _) | Self::Assignment(_, _, _) | Self::Dot(_, _, _) | Self::Index(_, _, _) @@ -1683,6 +1704,8 @@ fn parse_let<'a>( ScopeEntryType::Constant => { Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } + + ScopeEntryType::Subscope => unreachable!(), } } else { // let name diff --git a/src/scope.rs b/src/scope.rs index b50d1af3..937efe1b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -13,6 +13,8 @@ pub enum EntryType { Normal, /// Immutable constant value. Constant, + /// Name of a subscope, allowing member access with the :: operator. + Subscope, } /// An entry in the Scope. @@ -165,6 +167,24 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, value, false); } + /// Add (push) a new subscope to the Scope. + /// + /// Subscopes are used for access to members in modules and plugins. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_subscope("x".to_string(), "My Plugin".to_string()); + /// assert_eq!(my_scope.get_subscope("x").unwrap(), "My Plugin"); + /// ``` + pub fn push_subscope(&mut self, name: String, value: String) { + self.push_dynamic_value(name, EntryType::Subscope, Dynamic::from(value), true); + } + /// Add (push) a new constant to the Scope. /// /// Constants are immutable and cannot be assigned to. Their values never change. @@ -297,6 +317,17 @@ impl<'a> Scope<'a> { }) } + /// Get the subscope of an entry in the Scope, starting from the last. + /// + pub fn get_subscope(&self, name: &str) -> Option { + self.0 + .iter() + .rev() + .find(|Entry { name: key, typ, .. }| name == key && + std::mem::discriminant(typ) == std::mem::discriminant(&EntryType::Subscope)) + .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) + } + /// Get the value of an entry in the Scope, starting from the last. /// /// # Examples @@ -344,6 +375,9 @@ impl<'a> Scope<'a> { Some((index, EntryType::Normal)) => { self.0.get_mut(index).unwrap().value = Dynamic::from(value) } + Some((index, EntryType::Subscope)) => { + self.0.get_mut(index).unwrap().value = Dynamic::from(value) + } None => self.push(name, value), } } diff --git a/src/token.rs b/src/token.rs index f202bccd..d136d172 100644 --- a/src/token.rs +++ b/src/token.rs @@ -153,6 +153,7 @@ pub enum Token { RightShift, SemiColon, Colon, + DoubleColon, Comma, Period, #[cfg(not(feature = "no_object"))] @@ -230,6 +231,7 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", + DoubleColon => "::", Comma => ",", Period => ".", #[cfg(not(feature = "no_object"))] @@ -874,7 +876,6 @@ impl<'a> TokenIterator<'a> { ('/', _) => return Some((Token::Divide, pos)), (';', _) => return Some((Token::SemiColon, pos)), - (':', _) => return Some((Token::Colon, pos)), (',', _) => return Some((Token::Comma, pos)), ('.', _) => return Some((Token::Period, pos)), @@ -896,6 +897,12 @@ impl<'a> TokenIterator<'a> { } ('=', _) => return Some((Token::Equals, pos)), + (':', ':') => { + self.eat_next(); + return Some((Token::DoubleColon, pos)); + } + (':', _) => return Some((Token::Colon, pos)), + ('<', '=') => { self.eat_next(); return Some((Token::LessThanEqualsTo, pos)); From 7da9d265d3ed4b2338d1f9b8530c3510a04f0800 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 May 2020 10:41:58 +0800 Subject: [PATCH 06/23] Avoid copying parameters for function calls. --- src/engine.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 8480664a..007adf40 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -628,11 +628,14 @@ impl Engine { // Put arguments into scope as variables - variable name is copied scope.extend( - // TODO - avoid copying variable name fn_def .params .iter() - .zip(args.into_iter().map(|v| v.clone())) + .zip( + // Actually consume the arguments instead of cloning them + args.into_iter() + .map(|v| mem::replace(*v, Default::default())), + ) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); @@ -659,7 +662,11 @@ impl Engine { fn_def .params .iter() - .zip(args.into_iter().map(|v| v.clone())) + .zip( + // Actually consume the arguments instead of cloning them + args.into_iter() + .map(|v| mem::replace(*v, Default::default())), + ) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); @@ -884,6 +891,7 @@ impl Engine { if may_be_changed { if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_setter(id); + // Reuse args because the first &mut parameter will not be consumed args[1] = indexed_val; self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0)?; } From ead9716f6d3026b3dc061fb90da267f00ac11e19 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 May 2020 17:43:54 +0800 Subject: [PATCH 07/23] Add namespacing syntax. --- Cargo.toml | 1 + examples/repl.rs | 2 +- src/engine.rs | 117 ++++++++++++++++++++++++++------------ src/optimize.rs | 22 ++++---- src/parser.rs | 144 ++++++++++++++++++++++++++++++++--------------- src/result.rs | 12 +++- src/scope.rs | 72 +++++++++++++++--------- src/token.rs | 26 ++++++++- 8 files changed, 271 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a50bc0b0..b2330d24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ no_float = [] # no floating-point no_function = [] # no script-defined functions no_object = [] # no custom objects no_optimize = [] # no script optimizer +no_import = [] # no namespaces/modules only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types sync = [] # restrict to only types that implement Send + Sync diff --git a/examples/repl.rs b/examples/repl.rs index 4ae1c44f..d2281b6a 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Map, Scope, AST, INT}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; diff --git a/src/engine.rs b/src/engine.rs index 6db8d849..a0c4be00 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -120,7 +120,7 @@ impl Target<'_> { chars.iter().for_each(|&ch| s.push(ch)); } } - _ => panic!("should be String"), + _ => unreachable!(), }, } @@ -139,9 +139,10 @@ impl> From for Target<'_> { } } -/// A type to hold a number of `Dynamic` values in static storage for speed, +/// A type to hold a number of values in static storage for speed, /// and any spill-overs in a `Vec`. -struct StaticVec { +#[derive(Debug, Clone)] +pub struct StaticVec { /// Total number of values held. len: usize, /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. @@ -150,7 +151,7 @@ struct StaticVec { more: Vec, } -impl StaticVec { +impl StaticVec { /// Create a new `StaticVec`. pub fn new() -> Self { Self { @@ -191,6 +192,21 @@ impl StaticVec { result } + /// Get the number of items in this `StaticVec`. + pub fn len(&self) -> usize { + self.len + } + pub fn get(&self, index: usize) -> &T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + if index < self.list.len() { + self.list.get(index).unwrap() + } else { + self.more.get(index - self.list.len()).unwrap() + } + } } /// A type that holds all the current states of the Engine. @@ -460,13 +476,38 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - begin: Position, + namespaces: Option<&Box>>, + pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - let (index, _) = scope - .get(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), begin)))?; + if let Some(namespaces) = namespaces { + let (id, root_pos) = namespaces.get(0); // First namespace + let mut sub_scope = scope + .find_sub_scope(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *root_pos)))?; - Ok(scope.get_mut(index)) + for x in 1..namespaces.len() { + let (id, id_pos) = namespaces.get(x); + + sub_scope = sub_scope + .get_mut(id) + .unwrap() + .downcast_mut::() + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *id_pos)) + })?; + } + + sub_scope + .get_mut(name) + .map(|v| (v, ScopeEntryType::Constant)) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos))) + } else { + let (index, _) = scope + .get(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; + + Ok(scope.get_mut(index)) + } } impl Engine { @@ -813,7 +854,7 @@ impl Engine { } else { match rhs { // xxx.fn_name(arg_expr_list) - Expr::FnCall(fn_name, _, def_val, pos) => { + Expr::FnCall(fn_name, None,_, def_val, pos) => { let mut args: Vec<_> = once(obj) .chain(idx_val.downcast_mut::().unwrap().iter_mut()) .collect(); @@ -822,6 +863,8 @@ impl Engine { // TODO - Remove assumption of side effects by checking whether the first parameter is &mut self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) } + // xxx.namespace::fn_name(...) - syntax error + Expr::FnCall(_,_,_,_,_) => unreachable!(), // {xxx:map}.id = ??? Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { let mut indexed_val = @@ -927,10 +970,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] - Expr::Variable(id, index, pos) => { + Expr::Variable(id, namespaces, index, pos) => { let (target, typ) = match index { Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), - _ => search_scope(scope, id, *pos)?, + _ => search_scope(scope, id, namespaces.as_ref(), *pos)?, }; // Constants cannot be modified @@ -984,7 +1027,7 @@ impl Engine { level: usize, ) -> Result<(), Box> { match expr { - Expr::FnCall(_, arg_exprs, _, _) => { + Expr::FnCall(_, None, arg_exprs, _, _) => { let arg_values = arg_exprs .iter() .map(|arg_expr| self.eval_expr(scope, state, fn_lib, arg_expr, level)) @@ -992,6 +1035,7 @@ impl Engine { idx_values.push(arg_values) } + Expr::FnCall(_, _, _, _, _) => unreachable!(), Expr::Property(_, _) => idx_values.push(()), // Store a placeholder - no need to copy the property name Expr::Index(lhs, rhs, _) | Expr::Dot(lhs, rhs, _) => { // Evaluate in left-to-right order @@ -1160,11 +1204,13 @@ impl Engine { Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::CharConstant(c, _) => Ok((*c).into()), - Expr::Variable(_, Some(index), _) if !state.always_search => { + Expr::Variable(_, None, Some(index), _) if !state.always_search => { Ok(scope.get_mut(scope.len() - index.get()).0.clone()) } - Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()), - Expr::Property(_, _) => panic!("unexpected property."), + Expr::Variable(id, namespaces, _, pos) => { + search_scope(scope, id, namespaces.as_ref(), *pos).map(|(v, _)| v.clone()) + } + Expr::Property(_, _) => unreachable!(), // Statement block Expr::Stmt(stmt, _) => self.eval_stmt(scope, state, fn_lib, stmt, level), @@ -1175,22 +1221,19 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, _, pos) => match scope.get(name) { - None => { - return Err(Box::new(EvalAltResult::ErrorVariableNotFound( - name.to_string(), - *pos, - ))) + Expr::Variable(name, namespaces, _, pos) => { + match search_scope(scope, name, namespaces.as_ref(), *pos)? { + (_, ScopeEntryType::Constant) => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), + )), + (value_ptr, ScopeEntryType::Normal) => { + *value_ptr = rhs_val; + Ok(Default::default()) + } + // End variable cannot be a sub-scope + (_, ScopeEntryType::SubScope) => unreachable!(), } - Some((_, ScopeEntryType::Constant)) => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), - )), - Some((index, ScopeEntryType::Normal)) => { - *scope.get_mut(index).0 = rhs_val; - Ok(Default::default()) - }, - Some((_, ScopeEntryType::Subscope)) => unreachable!(), - }, + } // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { @@ -1252,7 +1295,8 @@ impl Engine { .collect::, _>>()?, )))), - Expr::FnCall(fn_name, arg_exprs, def_val, pos) => { + // TODO - handle namespaced function call + Expr::FnCall(fn_name, namespaces, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) @@ -1322,7 +1366,7 @@ impl Engine { Expr::False(_) => Ok(false.into()), Expr::Unit(_) => Ok(().into()), - _ => panic!("should not appear: {:?}", expr), + _ => unreachable!(), } } @@ -1499,9 +1543,7 @@ impl Engine { } // Import statement - Stmt::Import(_name_expr, _alias) => { - unimplemented!() - } + Stmt::Import(_name_expr, _alias) => unimplemented!(), // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { @@ -1512,7 +1554,8 @@ impl Engine { Ok(Default::default()) } - Stmt::Const(_, _, _) => panic!("constant expression not constant!"), + // Const expression not constant + Stmt::Const(_, _, _) => unreachable!(), } } diff --git a/src/optimize.rs b/src/optimize.rs index d018b4c5..5143c480 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -368,10 +368,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { //id = id2 = expr2 Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) { // var = var = expr2 -> var = expr2 - (Expr::Variable(var, sp, _), Expr::Variable(var2, sp2, _)) if var == var2 && sp == sp2 => { + (Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _)) + if var == var2 && sp == sp2 => + { // Assignment to the same variable - fold state.set_dirty(); - Expr::Assignment(Box::new(Expr::Variable(var, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) + Expr::Assignment(Box::new(Expr::Variable(var, None, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) } // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( @@ -547,18 +549,18 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // Do not call some special keywords - Expr::FnCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> + Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // Eagerly call functions - Expr::FnCall(id, args, def_value, pos) + Expr::FnCall(id, None, args, def_value, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) if state.fn_lib.iter().find(|(name, len)| name == id.as_ref() && *len == args.len()).is_some() { // A script-defined function overrides the built-in function - do not make the call - return Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); + return Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); @@ -589,16 +591,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).unwrap_or_else(|| // Optimize function call arguments - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) + Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) ) } // id(args ..) -> optimize function call arguments - Expr::FnCall(id, args, def_value, pos) => - Expr::FnCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, namespaces, args, def_value, pos) => + Expr::FnCall(id, namespaces, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // constant-name - Expr::Variable(name, _, pos) if state.contains_constant(&name) => { + Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value diff --git a/src/parser.rs b/src/parser.rs index 716b3580..782fcb66 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{calc_fn_def, Engine, FunctionsLib}; +use crate::engine::{calc_fn_def, Engine, FunctionsLib, StaticVec}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -246,10 +246,10 @@ pub enum Stmt { Continue(Position), /// break Break(Position), - /// `return`/`throw` + /// return/throw ReturnWithVal(Option>, ReturnType, Position), /// import expr - Import(Box, Option) + Import(Box, Option), } impl Stmt { @@ -264,9 +264,9 @@ impl Stmt { | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfThenElse(expr, _, _) - | Stmt::Expr(expr) - | Stmt::Import(expr, _) => expr.position(), + Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) | Stmt::Import(expr, _) => { + expr.position() + } Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), } @@ -326,27 +326,23 @@ pub enum Expr { CharConstant(char, Position), /// String constant. StringConstant(String, Position), - /// Variable access. - Variable(Box, Option, Position), + /// Variable access - (variable name, optional namespaces, optional index, position) + Variable( + Box, + Option>>, + Option, + Position, + ), /// Property access. Property(String, Position), /// { stmt } Stmt(Box, Position), - /// func(expr, ... ) + /// func(expr, ... ) - (function name, optional namespaces, arguments, optional default value, position) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box>, - Box>, - Option>, - Position, - ), - /// subscope::func(expr, ... ) - /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls - /// and the function names are predictable, so no need to allocate a new `String`. - SubscopeFnCall( - String, - Box>, + Option>>, Box>, Option>, Position, @@ -444,11 +440,10 @@ impl Expr { | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, pos) + | Self::Variable(_, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, pos) - | Self::SubscopeFnCall(_, _, _, _, pos) + | Self::FnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -471,11 +466,10 @@ impl Expr { | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, pos) + | Self::Variable(_, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, pos) - | Self::SubscopeFnCall(_, _, _, _, pos) + | Self::FnCall(_, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -503,7 +497,7 @@ impl Expr { Self::Stmt(stmt, _) => stmt.is_pure(), - Self::Variable(_, _, _) => true, + Self::Variable(_, _, _, _) => true, expr => expr.is_constant(), } @@ -552,8 +546,7 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) - | Self::FnCall(_, _, _, _) - | Self::SubscopeFnCall(_, _, _, _, _) + | Self::FnCall(_, _, _, _, _) | Self::Assignment(_, _, _) | Self::Dot(_, _, _) | Self::Index(_, _, _) @@ -563,7 +556,19 @@ impl Expr { _ => false, }, - Self::Variable(_, _, _) | Self::Property(_, _) => match token { + Self::Variable(_, None, _, _) => match token { + Token::LeftBracket | Token::LeftParen => true, + #[cfg(not(feature = "no_import"))] + Token::DoubleColon => true, + _ => false, + }, + Self::Variable(_, _, _, _) => match token { + #[cfg(not(feature = "no_import"))] + Token::DoubleColon => true, + _ => false, + }, + + Self::Property(_, _) => match token { Token::LeftBracket | Token::LeftParen => true, _ => false, }, @@ -573,7 +578,7 @@ impl Expr { /// Convert a `Variable` into a `Property`. All other variants are untouched. pub(crate) fn into_property(self) -> Self { match self { - Self::Variable(id, _, pos) => Self::Property(*id, pos), + Self::Variable(id, None, _, pos) => Self::Property(*id, pos), _ => self, } } @@ -637,6 +642,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, + namespaces: Option>>, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -658,6 +664,7 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); return Ok(Expr::FnCall( Box::new(id.into()), + namespaces, Box::new(args), None, begin, @@ -673,8 +680,10 @@ fn parse_call_expr<'a>( match input.peek().unwrap() { (Token::RightParen, _) => { eat_token(input, Token::RightParen); + return Ok(Expr::FnCall( Box::new(id.into()), + namespaces, Box::new(args), None, begin, @@ -1001,7 +1010,7 @@ fn parse_primary<'a>( Token::StringConst(s) => Expr::StringConstant(s, pos), Token::Identifier(s) => { let index = stack.find(&s); - Expr::Variable(Box::new(s), index, pos) + Expr::Variable(Box::new(s), None, index, pos) } Token::LeftParen => parse_paren_expr(input, stack, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] @@ -1024,19 +1033,36 @@ fn parse_primary<'a>( break; } - let (token, pos) = input.next().unwrap(); + let (token, token_pos) = input.next().unwrap(); root_expr = match (root_expr, token) { // Function call - (Expr::Variable(id, _, pos), Token::LeftParen) => { - parse_call_expr(input, stack, *id, pos, allow_stmt_expr)? + (Expr::Variable(id, namespaces, _, pos), Token::LeftParen) => { + parse_call_expr(input, stack, *id, namespaces, pos, allow_stmt_expr)? } (Expr::Property(id, pos), Token::LeftParen) => { - parse_call_expr(input, stack, id, pos, allow_stmt_expr)? + parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? + } + // Namespaced + #[cfg(not(feature = "no_import"))] + (Expr::Variable(id, mut namespaces, _, pos), Token::DoubleColon) => { + match input.next().unwrap() { + (Token::Identifier(id2), pos2) => { + if let Some(ref mut namespaces) = namespaces { + namespaces.push((*id, pos)); + } else { + let mut vec = StaticVec::new(); + vec.push((*id, pos)); + namespaces = Some(Box::new(vec)); + } + Expr::Variable(Box::new(id2), namespaces, None, pos2) + } + (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), + } } // Indexing (expr, Token::LeftBracket) => { - parse_index_chain(input, stack, expr, pos, allow_stmt_expr)? + parse_index_chain(input, stack, expr, token_pos, allow_stmt_expr)? } // Unknown postfix operator (expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), @@ -1092,6 +1118,7 @@ fn parse_unary<'a>( // Call negative function e => Ok(Expr::FnCall( Box::new("-".into()), + None, Box::new(vec![e]), None, pos, @@ -1108,6 +1135,7 @@ fn parse_unary<'a>( let pos = eat_token(input, Token::Bang); Ok(Expr::FnCall( Box::new("!".into()), + None, Box::new(vec![parse_primary(input, stack, allow_stmt_expr)?]), Some(Box::new(false.into())), // NOT operator, when operating on invalid operand, defaults to false pos, @@ -1161,25 +1189,34 @@ fn parse_op_assignment_stmt<'a>( // lhs op= rhs -> lhs = op(lhs, rhs) let args = vec![lhs_copy, rhs]; - let rhs_expr = Expr::FnCall(Box::new(op.into()), Box::new(args), None, pos); + let rhs_expr = Expr::FnCall(Box::new(op.into()), None, Box::new(args), None, pos); Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs_expr), pos)) } /// Make a dot expression. -fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr { - match (lhs, rhs) { +fn make_dot_expr( + lhs: Expr, + rhs: Expr, + op_pos: Position, + is_index: bool, +) -> Result> { + Ok(match (lhs, rhs) { // idx_lhs[idx_rhs].rhs // Attach dot chain to the bottom level of indexing chain (Expr::Index(idx_lhs, idx_rhs, idx_pos), rhs) => Expr::Index( idx_lhs, - Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)), + Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)?), idx_pos, ), // lhs.id - (lhs, rhs @ Expr::Variable(_, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { + (lhs, rhs @ Expr::Variable(_, None, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { let lhs = if is_index { lhs.into_property() } else { lhs }; Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos) } + // lhs.namespace::id - syntax error + (_, Expr::Variable(_, Some(namespaces), _, _)) => { + return Err(PERR::PropertyExpected.into_err(namespaces.get(0).1)) + } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( Box::new(lhs), @@ -1202,7 +1239,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr ), // lhs.rhs (lhs, rhs) => Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos), - } + }) } /// Make an 'in' expression. @@ -1380,24 +1417,28 @@ fn parse_binary_op<'a>( current_lhs = match op_token { Token::Plus => Expr::FnCall( Box::new("+".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Minus => Expr::FnCall( Box::new("-".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Multiply => Expr::FnCall( Box::new("*".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Divide => Expr::FnCall( Box::new("/".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1405,24 +1446,28 @@ fn parse_binary_op<'a>( Token::LeftShift => Expr::FnCall( Box::new("<<".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::RightShift => Expr::FnCall( Box::new(">>".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Modulo => Expr::FnCall( Box::new("%".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::PowerOf => Expr::FnCall( Box::new("~".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1431,36 +1476,42 @@ fn parse_binary_op<'a>( // Comparison operators default to false when passed invalid operands Token::EqualsTo => Expr::FnCall( Box::new("==".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::NotEqualsTo => Expr::FnCall( Box::new("!=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::LessThan => Expr::FnCall( Box::new("<".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::LessThanEqualsTo => Expr::FnCall( Box::new("<=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::GreaterThan => Expr::FnCall( Box::new(">".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, ), Token::GreaterThanEqualsTo => Expr::FnCall( Box::new(">=".into()), + None, Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1470,18 +1521,21 @@ fn parse_binary_op<'a>( Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos), Token::Ampersand => Expr::FnCall( Box::new("&".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::Pipe => Expr::FnCall( Box::new("|".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, ), Token::XOr => Expr::FnCall( Box::new("^".into()), + None, Box::new(vec![current_lhs, rhs]), None, pos, @@ -1490,7 +1544,7 @@ fn parse_binary_op<'a>( Token::In => make_in_expr(current_lhs, rhs, pos)?, #[cfg(not(feature = "no_object"))] - Token::Period => make_dot_expr(current_lhs, rhs, pos, false), + Token::Period => make_dot_expr(current_lhs, rhs, pos, false)?, token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), }; @@ -1704,8 +1758,8 @@ fn parse_let<'a>( ScopeEntryType::Constant => { Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } - - ScopeEntryType::Subscope => unreachable!(), + // Variable cannot be a sub-scope + ScopeEntryType::SubScope => unreachable!(), } } else { // let name diff --git a/src/result.rs b/src/result.rs index 17fe389a..dbb5ec59 100644 --- a/src/result.rs +++ b/src/result.rs @@ -61,6 +61,8 @@ pub enum EvalAltResult { ErrorFor(Position), /// Usage of an unknown variable. Wrapped value is the name of the variable. ErrorVariableNotFound(String, Position), + /// Usage of an unknown namespace. Wrapped value is the name of the namespace. + ErrorNamespaceNotFound(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -119,6 +121,7 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", + Self::ErrorNamespaceNotFound(_, _) => "Namespace not found", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -150,9 +153,10 @@ impl fmt::Display for EvalAltResult { Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) => { - write!(f, "{}: '{}' ({})", desc, s, pos) - } + Self::ErrorFunctionNotFound(s, pos) + | Self::ErrorVariableNotFound(s, pos) + | Self::ErrorNamespaceNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorIndexingType(_, pos) @@ -269,6 +273,7 @@ impl EvalAltResult { | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorNamespaceNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) @@ -303,6 +308,7 @@ impl EvalAltResult { | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorNamespaceNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) diff --git a/src/scope.rs b/src/scope.rs index 937efe1b..d4ad0cb8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Dynamic, Variant}; +use crate::engine::Map; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; @@ -13,8 +14,8 @@ pub enum EntryType { Normal, /// Immutable constant value. Constant, - /// Name of a subscope, allowing member access with the :: operator. - Subscope, + /// Name of a sub-scope, allowing member access with the :: operator. + SubScope, } /// An entry in the Scope. @@ -167,22 +168,27 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, value, false); } - /// Add (push) a new subscope to the Scope. + /// Add (push) a new sub-scope to the Scope. /// - /// Subscopes are used for access to members in modules and plugins. + /// Sub-scopes are used for accessing members in modules and plugins under a namespace. /// /// # Examples /// /// ``` - /// use rhai::Scope; + /// use rhai::{Scope, Map}; /// /// let mut my_scope = Scope::new(); /// - /// my_scope.push_subscope("x".to_string(), "My Plugin".to_string()); - /// assert_eq!(my_scope.get_subscope("x").unwrap(), "My Plugin"); + /// let mut sub_scope = Map::new(); + /// sub_scope.insert("x".to_string(), 42_i64.into()); + /// + /// my_scope.push_sub_scope("my_plugin", sub_scope); + /// + /// let s = my_scope.find_sub_scope("my_plugin").unwrap(); + /// assert_eq!(*s.get("x").unwrap().downcast_ref::().unwrap(), 42); /// ``` - pub fn push_subscope(&mut self, name: String, value: String) { - self.push_dynamic_value(name, EntryType::Subscope, Dynamic::from(value), true); + pub fn push_sub_scope>>(&mut self, name: K, value: Map) { + self.push_dynamic_value(name, EntryType::SubScope, value.into(), true); } /// Add (push) a new constant to the Scope. @@ -284,6 +290,8 @@ impl<'a> Scope<'a> { /// Does the scope contain the entry? /// + /// Sub-scopes are ignored. + /// /// # Examples /// /// ``` @@ -299,37 +307,45 @@ impl<'a> Scope<'a> { self.0 .iter() .rev() // Always search a Scope in reverse order - .any(|Entry { name: key, .. }| name == key) + .any(|Entry { name: key, typ, .. }| match typ { + EntryType::Normal | EntryType::Constant => name == key, + EntryType::SubScope => false, + }) } /// Find an entry in the Scope, starting from the last. + /// + /// Sub-scopes are ignored. pub(crate) fn get(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find_map(|(index, Entry { name: key, typ, .. })| { - if name == key { - Some((index, *typ)) - } else { - None + .find_map(|(index, Entry { name: key, typ, .. })| match typ { + EntryType::Normal | EntryType::Constant => { + if name == key { + Some((index, *typ)) + } else { + None + } } + EntryType::SubScope => None, }) } - /// Get the subscope of an entry in the Scope, starting from the last. - /// - pub fn get_subscope(&self, name: &str) -> Option { + /// Find a sub-scope in the Scope, starting from the last entry. + pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut Map> { self.0 - .iter() + .iter_mut() .rev() - .find(|Entry { name: key, typ, .. }| name == key && - std::mem::discriminant(typ) == std::mem::discriminant(&EntryType::Subscope)) - .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) + .find(|Entry { name: key, typ, .. }| name == key && *typ == EntryType::SubScope) + .and_then(|Entry { value, .. }| value.downcast_mut::()) } /// Get the value of an entry in the Scope, starting from the last. /// + /// Sub-scopes are ignored. + /// /// # Examples /// /// ``` @@ -344,7 +360,10 @@ impl<'a> Scope<'a> { self.0 .iter() .rev() - .find(|Entry { name: key, .. }| name == key) + .find(|Entry { name: key, typ, .. }| match typ { + EntryType::Normal | EntryType::Constant => name == key, + EntryType::SubScope => false, + }) .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } @@ -371,14 +390,13 @@ impl<'a> Scope<'a> { /// ``` pub fn set_value(&mut self, name: &'a str, value: T) { match self.get(name) { + None => self.push(name, value), Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { self.0.get_mut(index).unwrap().value = Dynamic::from(value) } - Some((index, EntryType::Subscope)) => { - self.0.get_mut(index).unwrap().value = Dynamic::from(value) - } - None => self.push(name, value), + // Sub-scopes cannot be modified + Some((_, EntryType::SubScope)) => unreachable!(), } } diff --git a/src/token.rs b/src/token.rs index d136d172..698cb4f4 100644 --- a/src/token.rs +++ b/src/token.rs @@ -153,6 +153,7 @@ pub enum Token { RightShift, SemiColon, Colon, + #[cfg(not(feature = "no_import"))] DoubleColon, Comma, Period, @@ -198,6 +199,12 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, + #[cfg(not(feature = "no_import"))] + Import, + #[cfg(not(feature = "no_import"))] + Export, + #[cfg(not(feature = "no_import"))] + As, LexError(Box), EOF, } @@ -231,6 +238,7 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", + #[cfg(not(feature = "no_import"))] DoubleColon => "::", Comma => ",", Period => ".", @@ -245,6 +253,8 @@ impl Token { Else => "else", While => "while", Loop => "loop", + For => "for", + In => "in", LessThan => "<", GreaterThan => ">", Bang => "!", @@ -278,8 +288,12 @@ impl Token { ModuloAssign => "%=", PowerOf => "~", PowerOfAssign => "~=", - For => "for", - In => "in", + #[cfg(not(feature = "no_import"))] + Import => "import", + #[cfg(not(feature = "no_import"))] + Export => "export", + #[cfg(not(feature = "no_import"))] + As => "as", EOF => "{EOF}", _ => panic!("operator should be match in outer scope"), }) @@ -743,6 +757,13 @@ impl<'a> TokenIterator<'a> { "for" => Token::For, "in" => Token::In, + #[cfg(not(feature = "no_import"))] + "import" => Token::Import, + #[cfg(not(feature = "no_import"))] + "export" => Token::Export, + #[cfg(not(feature = "no_import"))] + "as" => Token::As, + #[cfg(not(feature = "no_function"))] "fn" => Token::Fn, @@ -897,6 +918,7 @@ impl<'a> TokenIterator<'a> { } ('=', _) => return Some((Token::Equals, pos)), + #[cfg(not(feature = "no_import"))] (':', ':') => { self.eat_next(); return Some((Token::DoubleColon, pos)); From 798e1df29870cd65996f3b7cdfaeb55eba1eb165 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 May 2020 18:06:09 +0800 Subject: [PATCH 08/23] Do not error when a property is read-only. --- src/engine.rs | 6 +++++- tests/get_set.rs | 11 ++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index e4cdfa7c..7e849c7a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -885,7 +885,11 @@ impl Engine { if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_setter(id); args[1] = indexed_val; - self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0)?; + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { + // If there is no setter, no need to feed it back because the property is read-only + EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), + err => Err(Box::new(err)) + })?; } } diff --git a/tests/get_set.rs b/tests/get_set.rs index 5859ea2e..633192ad 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -7,6 +7,7 @@ fn test_get_set() -> Result<(), Box> { #[derive(Clone)] struct TestStruct { x: INT, + y: INT, } impl TestStruct { @@ -18,8 +19,12 @@ fn test_get_set() -> Result<(), Box> { self.x = new_x; } + fn get_y(&mut self) -> INT { + self.y + } + fn new() -> Self { - TestStruct { x: 1 } + TestStruct { x: 1, y: 0 } } } @@ -28,9 +33,13 @@ fn test_get_set() -> Result<(), Box> { engine.register_type::(); engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); + engine.register_get("y", TestStruct::get_y); + engine.register_fn("add", |value: &mut INT| *value += 41); engine.register_fn("new_ts", TestStruct::new); assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); + assert_eq!(engine.eval::("let a = new_ts(); a.x.add(); a.x")?, 42); + assert_eq!(engine.eval::("let a = new_ts(); a.y.add(); a.y")?, 0); Ok(()) } From 2bdd174f16bf17cd216d19950ee6a5a5fc8e14fc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 May 2020 19:36:58 +0800 Subject: [PATCH 09/23] Add import statement. --- Cargo.toml | 2 +- README.md | 19 +++++ src/engine.rs | 60 ++++++++------ src/optimize.rs | 4 +- src/parser.rs | 206 ++++++++++++++++++++++++++++++++---------------- src/result.rs | 18 +++-- src/scope.rs | 29 +++++-- src/token.rs | 6 ++ 8 files changed, 239 insertions(+), 105 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2330d24..3148eb3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ no_float = [] # no floating-point no_function = [] # no script-defined functions no_object = [] # no custom objects no_optimize = [] # no script optimizer -no_import = [] # no namespaces/modules +no_import = [] # no modules only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types sync = [] # restrict to only types that implement Send + Sync diff --git a/README.md b/README.md index b453b04c..36337932 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Rhai's current features set: * [`no-std`](#optional-features) support * Support for [function overloading](#function-overloading) * Support for [operator overloading](#operator-overloading) +* Support for loading external [modules] * Compiled script is [optimized](#script-optimization) for repeat evaluations * Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) @@ -69,6 +70,7 @@ Optional features | `no_object` | Disable support for custom types and objects. | | `no_float` | Disable floating-point numbers and math if not needed. | | `no_optimize` | Disable the script optimizer. | +| `no_import` | Disable modules. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | @@ -84,6 +86,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_function`]: #optional-features [`no_object`]: #optional-features [`no_optimize`]: #optional-features +[`no_import`]: #optional-features [`only_i32`]: #optional-features [`only_i64`]: #optional-features [`no_std`]: #optional-features @@ -1998,6 +2001,22 @@ for entry in logbook.read().unwrap().iter() { } ``` +Using external modules +---------------------- + +[module]: #using-external-modules +[modules]: #using-external-modules + +```rust +import "crypto" as crypto; // Import an external script file as a module + +crypto::encrypt(secret); // Use functions defined under the module via '::' + +print(crypto::status); // Module variables are constants + +crypto::hash::sha256(key); // Sub-modules are also supported +``` + Script optimization =================== diff --git a/src/engine.rs b/src/engine.rs index 6af55b69..8ec80117 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -476,25 +476,23 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - namespaces: Option<&Box>>, + modules: Option<&Box>>, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - if let Some(namespaces) = namespaces { - let (id, root_pos) = namespaces.get(0); // First namespace + if let Some(modules) = modules { + let (id, root_pos) = modules.get(0); // First module let mut sub_scope = scope .find_sub_scope(id) - .ok_or_else(|| Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *root_pos)))?; + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?; - for x in 1..namespaces.len() { - let (id, id_pos) = namespaces.get(x); + for x in 1..modules.len() { + let (id, id_pos) = modules.get(x); sub_scope = sub_scope .get_mut(id) .unwrap() .downcast_mut::() - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorNamespaceNotFound(id.into(), *id_pos)) - })?; + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; } sub_scope @@ -503,7 +501,7 @@ fn search_scope<'a>( .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos))) } else { let (index, _) = scope - .get(name) + .get_index(name) .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; Ok(scope.get_mut(index)) @@ -863,7 +861,7 @@ impl Engine { // TODO - Remove assumption of side effects by checking whether the first parameter is &mut self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) } - // xxx.namespace::fn_name(...) - syntax error + // xxx.module::fn_name(...) - syntax error Expr::FnCall(_,_,_,_,_) => unreachable!(), // {xxx:map}.id = ??? Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { @@ -934,7 +932,7 @@ impl Engine { if may_be_changed { if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_setter(id); - // Reuse args because the first &mut parameter will not be consumed + // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only @@ -974,10 +972,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] - Expr::Variable(id, namespaces, index, pos) => { + Expr::Variable(id, modules, index, pos) => { let (target, typ) = match index { Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), - _ => search_scope(scope, id, namespaces.as_ref(), *pos)?, + _ => search_scope(scope, id, modules.as_ref(), *pos)?, }; // Constants cannot be modified @@ -1211,8 +1209,8 @@ impl Engine { Expr::Variable(_, None, Some(index), _) if !state.always_search => { Ok(scope.get_mut(scope.len() - index.get()).0.clone()) } - Expr::Variable(id, namespaces, _, pos) => { - search_scope(scope, id, namespaces.as_ref(), *pos).map(|(v, _)| v.clone()) + Expr::Variable(id, modules, _, pos) => { + search_scope(scope, id, modules.as_ref(), *pos).map(|(v, _)| v.clone()) } Expr::Property(_, _) => unreachable!(), @@ -1225,8 +1223,8 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, namespaces, _, pos) => { - match search_scope(scope, name, namespaces.as_ref(), *pos)? { + Expr::Variable(name, modules, _, pos) => { + match search_scope(scope, name, modules.as_ref(), *pos)? { (_, ScopeEntryType::Constant) => Err(Box::new( EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), )), @@ -1299,8 +1297,8 @@ impl Engine { .collect::, _>>()?, )))), - // TODO - handle namespaced function call - Expr::FnCall(fn_name, namespaces, arg_exprs, def_val, pos) => { + // TODO - handle moduled function call + Expr::FnCall(fn_name, modules, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) @@ -1546,9 +1544,6 @@ impl Engine { Ok(Default::default()) } - // Import statement - Stmt::Import(_name_expr, _alias) => unimplemented!(), - // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { let val = self.eval_expr(scope, state, fn_lib, expr, level)?; @@ -1560,6 +1555,25 @@ impl Engine { // Const expression not constant Stmt::Const(_, _, _) => unreachable!(), + + // Import statement + Stmt::Import(expr, name, _) => { + if let Some(path) = self + .eval_expr(scope, state, fn_lib, expr, level)? + .try_cast::() + { + let mut module = Map::new(); + module.insert("kitty".to_string(), "foo".to_string().into()); + module.insert("path".to_string(), path.into()); + + // TODO - avoid copying module name in inner block? + let mod_name = name.as_ref().clone(); + scope.push_sub_scope(mod_name, module); + Ok(Default::default()) + } else { + Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) + } + } } } diff --git a/src/optimize.rs b/src/optimize.rs index 5143c480..57425c9a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -596,8 +596,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { } // id(args ..) -> optimize function call arguments - Expr::FnCall(id, namespaces, args, def_value, pos) => - Expr::FnCall(id, namespaces, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, modules, args, def_value, pos) => + Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // constant-name Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { diff --git a/src/parser.rs b/src/parser.rs index 782fcb66..7dcb4d65 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -186,7 +186,7 @@ pub enum ReturnType { /// A type that encapsulates a local stack with variable names to simulate an actual runtime scope. #[derive(Debug, Clone)] -struct Stack(Vec); +struct Stack(Vec<(String, ScopeEntryType)>); impl Stack { /// Create a new `Stack`. @@ -202,13 +202,31 @@ impl Stack { .iter() .rev() .enumerate() - .find(|(_, n)| *n == name) + .find(|(_, (n, typ))| match typ { + ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name, + ScopeEntryType::SubScope => false, + }) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + } + /// Find a sub-scope by name in the `Stack`, searching in reverse. + /// The return value is the offset to be deducted from `Stack::len`, + /// i.e. the top element of the `Stack` is offset 1. + /// Return zero when the variable name is not found in the `Stack`. + pub fn find_sub_scope(&self, name: &str) -> Option { + self.0 + .iter() + .rev() + .enumerate() + .find(|(_, (n, typ))| match typ { + ScopeEntryType::SubScope => *n == name, + ScopeEntryType::Normal | ScopeEntryType::Constant => false, + }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } } impl Deref for Stack { - type Target = Vec; + type Target = Vec<(String, ScopeEntryType)>; fn deref(&self) -> &Self::Target { &self.0 @@ -248,8 +266,8 @@ pub enum Stmt { Break(Position), /// return/throw ReturnWithVal(Option>, ReturnType, Position), - /// import expr - Import(Box, Option), + /// import expr as module + Import(Box, Box, Position), } impl Stmt { @@ -259,14 +277,13 @@ impl Stmt { Stmt::Noop(pos) | Stmt::Let(_, _, pos) | Stmt::Const(_, _, pos) + | Stmt::Import(_, _, pos) | Stmt::Block(_, pos) | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) | Stmt::Import(expr, _) => { - expr.position() - } + Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), } @@ -279,14 +296,14 @@ impl Stmt { | Stmt::While(_, _) | Stmt::Loop(_) | Stmt::For(_, _, _) - | Stmt::Block(_, _) - | Stmt::Import(_, _) => true, + | Stmt::Block(_, _) => true, // A No-op requires a semicolon in order to know it is an empty statement! Stmt::Noop(_) => false, Stmt::Let(_, _, _) | Stmt::Const(_, _, _) + | Stmt::Import(_, _, _) | Stmt::Expr(_) | Stmt::Continue(_) | Stmt::Break(_) @@ -310,7 +327,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, - Stmt::Import(_, _) => false, + Stmt::Import(_, _, _) => false, } } } @@ -326,7 +343,7 @@ pub enum Expr { CharConstant(char, Position), /// String constant. StringConstant(String, Position), - /// Variable access - (variable name, optional namespaces, optional index, position) + /// Variable access - (variable name, optional modules, optional index, position) Variable( Box, Option>>, @@ -337,7 +354,7 @@ pub enum Expr { Property(String, Position), /// { stmt } Stmt(Box, Position), - /// func(expr, ... ) - (function name, optional namespaces, arguments, optional default value, position) + /// func(expr, ... ) - (function name, optional modules, arguments, optional default value, position) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( @@ -614,11 +631,11 @@ fn match_token(input: &mut Peekable, token: Token) -> Result( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { if match_token(input, Token::RightParen)? { - return Ok(Expr::Unit(begin)); + return Ok(Expr::Unit(pos)); } let expr = parse_expr(input, stack, allow_stmt_expr)?; @@ -630,7 +647,7 @@ fn parse_paren_expr<'a>( (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), "for a matching ( in this expression".into(), ) .into_err(pos)), @@ -642,7 +659,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, - namespaces: Option>>, + modules: Option>>, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -652,7 +669,7 @@ fn parse_call_expr<'a>( // id (Token::EOF, pos) => { return Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) @@ -664,7 +681,7 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); return Ok(Expr::FnCall( Box::new(id.into()), - namespaces, + modules, Box::new(args), None, begin, @@ -683,7 +700,7 @@ fn parse_call_expr<'a>( return Ok(Expr::FnCall( Box::new(id.into()), - namespaces, + modules, Box::new(args), None, begin, @@ -694,7 +711,7 @@ fn parse_call_expr<'a>( } (Token::EOF, pos) => { return Err(PERR::MissingToken( - ")".into(), + Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) .into_err(*pos)) @@ -704,7 +721,7 @@ fn parse_call_expr<'a>( } (_, pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), format!("to separate the arguments to function call '{}'", id), ) .into_err(*pos)) @@ -848,7 +865,7 @@ fn parse_index_chain<'a>( } (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), (_, pos) => Err(PERR::MissingToken( - "]".into(), + Token::RightBracket.into(), "for a matching [ in this index expression".into(), ) .into_err(*pos)), @@ -859,7 +876,7 @@ fn parse_index_chain<'a>( fn parse_array_literal<'a>( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { let mut arr = Vec::new(); @@ -875,17 +892,18 @@ fn parse_array_literal<'a>( break; } (Token::EOF, pos) => { - return Err( - PERR::MissingToken("]".into(), "to end this array literal".into()) - .into_err(*pos), + return Err(PERR::MissingToken( + Token::RightBracket.into(), + "to end this array literal".into(), ) + .into_err(*pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) @@ -894,14 +912,14 @@ fn parse_array_literal<'a>( } } - Ok(Expr::Array(arr, begin)) + Ok(Expr::Array(arr, pos)) } /// Parse a map literal. fn parse_map_literal<'a>( input: &mut Peekable>, stack: &mut Stack, - begin: Position, + pos: Position, allow_stmt_expr: bool, ) -> Result> { let mut map = Vec::new(); @@ -917,10 +935,16 @@ fn parse_map_literal<'a>( return Err(PERR::BadInput(err.to_string()).into_err(pos)) } (_, pos) if map.is_empty() => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(pos), + ) } (Token::EOF, pos) => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(pos), + ) } (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; @@ -932,7 +956,7 @@ fn parse_map_literal<'a>( } (_, pos) => { return Err(PERR::MissingToken( - ":".into(), + Token::Colon.into(), format!( "to follow the property '{}' in this object map literal", name @@ -956,7 +980,7 @@ fn parse_map_literal<'a>( } (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( - ",".into(), + Token::Comma.into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) @@ -965,7 +989,10 @@ fn parse_map_literal<'a>( return Err(PERR::BadInput(err.to_string()).into_err(*pos)) } (_, pos) => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) + return Err( + PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) + .into_err(*pos), + ) } } } @@ -982,7 +1009,7 @@ fn parse_map_literal<'a>( }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; - Ok(Expr::Map(map, begin)) + Ok(Expr::Map(map, pos)) } /// Parse a primary expression. @@ -1037,25 +1064,28 @@ fn parse_primary<'a>( root_expr = match (root_expr, token) { // Function call - (Expr::Variable(id, namespaces, _, pos), Token::LeftParen) => { - parse_call_expr(input, stack, *id, namespaces, pos, allow_stmt_expr)? + (Expr::Variable(id, modules, _, pos), Token::LeftParen) => { + parse_call_expr(input, stack, *id, modules, pos, allow_stmt_expr)? } (Expr::Property(id, pos), Token::LeftParen) => { parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? } - // Namespaced + // moduled #[cfg(not(feature = "no_import"))] - (Expr::Variable(id, mut namespaces, _, pos), Token::DoubleColon) => { + (Expr::Variable(id, mut modules, _, pos), Token::DoubleColon) => { match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - if let Some(ref mut namespaces) = namespaces { - namespaces.push((*id, pos)); + if let Some(ref mut modules) = modules { + modules.push((*id, pos)); } else { let mut vec = StaticVec::new(); vec.push((*id, pos)); - namespaces = Some(Box::new(vec)); + modules = Some(Box::new(vec)); } - Expr::Variable(Box::new(id2), namespaces, None, pos2) + + let root = modules.as_ref().unwrap().get(0); + let index = stack.find_sub_scope(&root.0); + Expr::Variable(Box::new(id2), modules, index, pos2) } (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), } @@ -1213,9 +1243,9 @@ fn make_dot_expr( let lhs = if is_index { lhs.into_property() } else { lhs }; Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos) } - // lhs.namespace::id - syntax error - (_, Expr::Variable(_, Some(namespaces), _, _)) => { - return Err(PERR::PropertyExpected.into_err(namespaces.get(0).1)) + // lhs.module::id - syntax error + (_, Expr::Variable(_, Some(modules), _, _)) => { + return Err(PERR::PropertyExpected.into_err(modules.get(0).1)) } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( @@ -1546,7 +1576,7 @@ fn parse_binary_op<'a>( #[cfg(not(feature = "no_object"))] Token::Period => make_dot_expr(current_lhs, rhs, pos, false)?, - token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), + token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)), }; } } @@ -1701,7 +1731,7 @@ fn parse_for<'a>( (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { return Err( - PERR::MissingToken("in".into(), "after the iteration variable".into()) + PERR::MissingToken(Token::In.into(), "after the iteration variable".into()) .into_err(pos), ) } @@ -1712,7 +1742,7 @@ fn parse_for<'a>( let expr = parse_expr(input, stack, allow_stmt_expr)?; let prev_len = stack.len(); - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Normal)); let body = parse_block(input, stack, true, allow_stmt_expr)?; @@ -1746,12 +1776,12 @@ fn parse_let<'a>( match var_type { // let name = expr ScopeEntryType::Normal => { - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Normal)); Ok(Stmt::Let(Box::new(name), Some(Box::new(init_value)), pos)) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { - stack.push(name.clone()); + stack.push((name.clone(), ScopeEntryType::Constant)); Ok(Stmt::Const(Box::new(name), Box::new(init_value), pos)) } // const name = expr - error @@ -1767,6 +1797,40 @@ fn parse_let<'a>( } } +/// Parse an import statement. +#[cfg(not(feature = "no_import"))] +fn parse_import<'a>( + input: &mut Peekable>, + stack: &mut Stack, + allow_stmt_expr: bool, +) -> Result> { + // import ... + let pos = eat_token(input, Token::Import); + + // import expr ... + let expr = parse_expr(input, stack, allow_stmt_expr)?; + + // import expr as ... + match input.next().unwrap() { + (Token::As, _) => (), + (_, pos) => { + return Err( + PERR::MissingToken(Token::As.into(), "in this import statement".into()) + .into_err(pos), + ) + } + } + + // import expr as name ... + let (name, _) = match input.next().unwrap() { + (Token::Identifier(s), pos) => (s, pos), + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), + }; + + Ok(Stmt::Import(Box::new(expr), Box::new(name), pos)) +} + /// Parse a statement block. fn parse_block<'a>( input: &mut Peekable>, @@ -1779,9 +1843,11 @@ fn parse_block<'a>( (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => { - return Err( - PERR::MissingToken("{".into(), "to start a statement block".into()).into_err(pos), + return Err(PERR::MissingToken( + Token::LeftBrace.into(), + "to start a statement block".into(), ) + .into_err(pos)) } }; @@ -1818,10 +1884,11 @@ fn parse_block<'a>( // { ... stmt ??? (_, pos) => { // Semicolons are not optional between statements - return Err( - PERR::MissingToken(";".into(), "to terminate this statement".into()) - .into_err(*pos), - ); + return Err(PERR::MissingToken( + Token::SemiColon.into(), + "to terminate this statement".into(), + ) + .into_err(*pos)); } } } @@ -1905,6 +1972,8 @@ fn parse_stmt<'a>( Token::Let => parse_let(input, stack, ScopeEntryType::Normal, allow_stmt_expr), Token::Const => parse_let(input, stack, ScopeEntryType::Constant, allow_stmt_expr), + Token::Import => parse_import(input, stack, allow_stmt_expr), + _ => parse_expr_stmt(input, stack, allow_stmt_expr), } } @@ -1936,25 +2005,29 @@ fn parse_fn<'a>( loop { match input.next().unwrap() { (Token::Identifier(s), pos) => { - stack.push(s.clone()); + stack.push((s.clone(), ScopeEntryType::Normal)); params.push((s, pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos)) + } } match input.next().unwrap() { (Token::RightParen, _) => break, (Token::Comma, _) => (), (Token::Identifier(_), pos) => { - return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)) + return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } (Token::LexError(err), pos) => { return Err(PERR::BadInput(err.to_string()).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) + } } } } @@ -2064,10 +2137,11 @@ fn parse_global_level<'a>( // stmt ??? (_, pos) => { // Semicolons are not optional between statements - return Err( - PERR::MissingToken(";".into(), "to terminate this statement".into()) - .into_err(*pos), - ); + return Err(PERR::MissingToken( + Token::SemiColon.into(), + "to terminate this statement".into(), + ) + .into_err(*pos)); } } } diff --git a/src/result.rs b/src/result.rs index dbb5ec59..645432b3 100644 --- a/src/result.rs +++ b/src/result.rs @@ -53,6 +53,8 @@ pub enum EvalAltResult { ErrorNumericIndexExpr(Position), /// Trying to index into a map with an index that is not `String`. ErrorStringIndexExpr(Position), + /// Trying to import with an expression that is not `String`. + ErrorImportExpr(Position), /// Invalid arguments for `in` operator. ErrorInExpr(Position), /// The guard expression in an `if` or `while` statement does not return a boolean value. @@ -61,8 +63,8 @@ pub enum EvalAltResult { ErrorFor(Position), /// Usage of an unknown variable. Wrapped value is the name of the variable. ErrorVariableNotFound(String, Position), - /// Usage of an unknown namespace. Wrapped value is the name of the namespace. - ErrorNamespaceNotFound(String, Position), + /// Usage of an unknown module. Wrapped value is the name of the module. + ErrorModuleNotFound(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -108,6 +110,7 @@ impl EvalAltResult { Self::ErrorIndexingType(_, _) => { "Indexing can only be performed on an array, an object map, or a string" } + Self::ErrorImportExpr(_) => "Importing a module expects a string path", Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } @@ -121,7 +124,7 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", - Self::ErrorNamespaceNotFound(_, _) => "Namespace not found", + Self::ErrorModuleNotFound(_, _) => "module not found", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -155,13 +158,14 @@ impl fmt::Display for EvalAltResult { Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) - | Self::ErrorNamespaceNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + | Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorAssignmentToUnknownLHS(pos) @@ -270,10 +274,11 @@ impl EvalAltResult { | Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) - | Self::ErrorNamespaceNotFound(_, pos) + | Self::ErrorModuleNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) @@ -305,10 +310,11 @@ impl EvalAltResult { | Self::ErrorIndexingType(_, pos) | Self::ErrorNumericIndexExpr(pos) | Self::ErrorStringIndexExpr(pos) + | Self::ErrorImportExpr(pos) | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) - | Self::ErrorNamespaceNotFound(_, pos) + | Self::ErrorModuleNotFound(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) diff --git a/src/scope.rs b/src/scope.rs index d4ad0cb8..3f55aeaa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -316,7 +316,7 @@ impl<'a> Scope<'a> { /// Find an entry in the Scope, starting from the last. /// /// Sub-scopes are ignored. - pub(crate) fn get(&self, name: &str) -> Option<(usize, EntryType)> { + pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() .enumerate() @@ -333,13 +333,28 @@ impl<'a> Scope<'a> { }) } + /// Find a sub-scope in the Scope, starting from the last. + pub(crate) fn get_sub_scope_index(&self, name: &str) -> Option { + self.0 + .iter() + .enumerate() + .rev() // Always search a Scope in reverse order + .find_map(|(index, Entry { name: key, typ, .. })| match typ { + EntryType::SubScope => { + if name == key { + Some(index) + } else { + None + } + } + EntryType::Normal | EntryType::Constant => None, + }) + } + /// Find a sub-scope in the Scope, starting from the last entry. pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut Map> { - self.0 - .iter_mut() - .rev() - .find(|Entry { name: key, typ, .. }| name == key && *typ == EntryType::SubScope) - .and_then(|Entry { value, .. }| value.downcast_mut::()) + let index = self.get_sub_scope_index(name)?; + self.get_mut(index).0.downcast_mut() } /// Get the value of an entry in the Scope, starting from the last. @@ -389,7 +404,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` pub fn set_value(&mut self, name: &'a str, value: T) { - match self.get(name) { + match self.get_index(name) { None => self.push(name, value), Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { diff --git a/src/token.rs b/src/token.rs index 698cb4f4..86a3b820 100644 --- a/src/token.rs +++ b/src/token.rs @@ -422,6 +422,12 @@ impl Token { } } +impl From for String { + fn from(token: Token) -> Self { + token.syntax().into() + } +} + /// An iterator on a `Token` stream. pub struct TokenIterator<'a> { /// Can the next token be a unary operator? From 64036f69ca19773b7658140be868ee22712d0664 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 4 May 2020 23:07:42 +0800 Subject: [PATCH 10/23] Refine modules. --- README.md | 34 +++++++++-- src/any.rs | 12 +++- src/engine.rs | 148 +++++++++++++++++++++++++++++++++++------------ src/lib.rs | 3 + src/parser.rs | 10 ++-- src/scope.rs | 21 ++++--- tests/modules.rs | 15 +++++ 7 files changed, 186 insertions(+), 57 deletions(-) create mode 100644 tests/modules.rs diff --git a/README.md b/README.md index 36337932..7122feb6 100644 --- a/README.md +++ b/README.md @@ -1977,8 +1977,8 @@ When embedding Rhai into an application, it is usually necessary to trap `print` (for logging into a tracking log, for example). ```rust -// Any function or closure that takes an &str argument can be used to override -// print and debug +// Any function or closure that takes an '&str' argument can be used to override +// 'print' and 'debug' engine.on_print(|x| println!("hello: {}", x)); engine.on_debug(|x| println!("DEBUG: {}", x)); @@ -2007,14 +2007,36 @@ Using external modules [module]: #using-external-modules [modules]: #using-external-modules +Rhai allows organizing code (functions and variables) into _modules_. A module is a single script file +with `export` statements that _exports_ certain global variables and functions as contents of the module. + +Everything exported as part of a module is constant and read-only. + +A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++. + ```rust -import "crypto" as crypto; // Import an external script file as a module +import "crypto" as crypto; // import the script file 'crypto.rhai' as a module -crypto::encrypt(secret); // Use functions defined under the module via '::' +crypto::encrypt(secret); // use functions defined under the module via '::' -print(crypto::status); // Module variables are constants +print(crypto::status); // module variables are constants -crypto::hash::sha256(key); // Sub-modules are also supported +crypto::hash::sha256(key); // sub-modules are also supported +``` + +`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported. + +```rust +let mod = "crypto"; + +if secured { // new block scope + import mod as crypto; // import module (the path needs not be a constant string) + + crypto::encrypt(key); // use a function in the module +} // the module disappears at the end of the block scope + +crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module + // is no longer available! ``` Script optimization diff --git a/src/any.rs b/src/any.rs index 28c462d1..e07d584e 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,6 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::engine::{Array, Map}; +use crate::engine::{Array, Map, SubScope}; use crate::parser::INT; #[cfg(not(feature = "no_float"))] @@ -135,6 +135,7 @@ pub enum Union { Float(FLOAT), Array(Box), Map(Box), + SubScope(Box), Variant(Box>), } @@ -165,6 +166,7 @@ impl Dynamic { Union::Float(_) => TypeId::of::(), Union::Array(_) => TypeId::of::(), Union::Map(_) => TypeId::of::(), + Union::SubScope(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), } } @@ -181,6 +183,7 @@ impl Dynamic { Union::Float(_) => type_name::(), Union::Array(_) => "array", Union::Map(_) => "map", + Union::SubScope(_) => "sub-scope", #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", @@ -201,6 +204,7 @@ impl fmt::Display for Dynamic { Union::Float(value) => write!(f, "{}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), + Union::SubScope(value) => write!(f, "#{:?}", value), Union::Variant(_) => write!(f, "?"), } } @@ -218,6 +222,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::SubScope(value) => write!(f, "#{:?}", value), Union::Variant(_) => write!(f, ""), } } @@ -235,6 +240,7 @@ impl Clone for Dynamic { Union::Float(value) => Self(Union::Float(value)), Union::Array(ref value) => Self(Union::Array(value.clone())), Union::Map(ref value) => Self(Union::Map(value.clone())), + Union::SubScope(ref value) => Self(Union::SubScope(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), } } @@ -363,6 +369,7 @@ impl Dynamic { Union::Float(ref value) => (value as &dyn Any).downcast_ref::().cloned(), Union::Array(value) => cast_box::<_, T>(value).ok(), Union::Map(value) => cast_box::<_, T>(value).ok(), + Union::SubScope(value) => cast_box::<_, T>(value).ok(), Union::Variant(value) => value.as_any().downcast_ref::().cloned(), } } @@ -400,6 +407,7 @@ impl Dynamic { Union::Float(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), Union::Array(value) => cast_box::<_, T>(value).unwrap(), Union::Map(value) => cast_box::<_, T>(value).unwrap(), + Union::SubScope(value) => cast_box::<_, T>(value).unwrap(), Union::Variant(value) => value.as_any().downcast_ref::().unwrap().clone(), } } @@ -422,6 +430,7 @@ impl Dynamic { Union::Float(value) => (value as &dyn Any).downcast_ref::(), Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::(), Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::SubScope(value) => (value.as_ref() as &dyn Any).downcast_ref::(), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), } } @@ -444,6 +453,7 @@ impl Dynamic { Union::Float(value) => (value as &mut dyn Any).downcast_mut::(), Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::SubScope(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), } } diff --git a/src/engine.rs b/src/engine.rs index 8ec80117..884e757b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,6 +18,7 @@ use crate::stdlib::{ hash::{Hash, Hasher}, iter::once, mem, + num::NonZeroUsize, ops::{Deref, DerefMut}, rc::Rc, string::{String, ToString}, @@ -41,6 +42,32 @@ pub type Array = Vec; /// Not available under the `no_object` feature. pub type Map = HashMap; +/// A sub-scope - basically an imported module namespace. +/// +/// Not available under the `no_import` feature. +#[derive(Debug, Clone)] +pub struct SubScope(HashMap); + +impl SubScope { + /// Create a new sub-scope. + pub fn new() -> Self { + Self(HashMap::new()) + } +} + +impl Deref for SubScope { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SubScope { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + pub type FnCallArgs<'a> = [&'a mut Dynamic]; #[cfg(feature = "sync")] @@ -472,39 +499,68 @@ fn default_print(s: &str) { println!("{}", s); } -/// Search for a variable within the scope, returning its value and index inside the Scope -fn search_scope<'a>( +/// Search for a variable within the scope +fn search_scope_variables<'a>( scope: &'a mut Scope, name: &str, - modules: Option<&Box>>, + index: Option, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - if let Some(modules) = modules { - let (id, root_pos) = modules.get(0); // First module - let mut sub_scope = scope - .find_sub_scope(id) - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?; - - for x in 1..modules.len() { - let (id, id_pos) = modules.get(x); - - sub_scope = sub_scope - .get_mut(id) - .unwrap() - .downcast_mut::() - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; - } - - sub_scope - .get_mut(name) - .map(|v| (v, ScopeEntryType::Constant)) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos))) + let index = if let Some(index) = index { + scope.len() - index.get() } else { - let (index, _) = scope + scope .get_index(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))? + .0 + }; - Ok(scope.get_mut(index)) + Ok(scope.get_mut(index)) +} + +/// Search for a sub-scope within the scope +fn search_scope_modules<'a>( + scope: &'a mut Scope, + name: &str, + modules: &Box>, + index: Option, + pos: Position, +) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { + let (id, root_pos) = modules.get(0); // First module + + let mut sub_scope = if let Some(index) = index { + scope + .get_mut(scope.len() - index.get()) + .0 + .downcast_mut::() + .unwrap() + } else { + scope + .find_sub_scope(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? + }; + + for x in 1..modules.len() { + let (id, id_pos) = modules.get(x); + + sub_scope = sub_scope + .get_mut(id) + .and_then(|v| v.downcast_mut::()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; + } + + let result = sub_scope + .get_mut(name) + .map(|v| (v, ScopeEntryType::Constant)) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; + + if result.0.is::() { + Err(Box::new(EvalAltResult::ErrorVariableNotFound( + name.into(), + pos, + ))) + } else { + Ok(result) } } @@ -973,20 +1029,24 @@ impl Engine { match dot_lhs { // id.??? or id[???] Expr::Variable(id, modules, index, pos) => { - let (target, typ) = match index { - Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), - _ => search_scope(scope, id, modules.as_ref(), *pos)?, + let index = if state.always_search { None } else { *index }; + + let (target, typ) = if let Some(modules) = modules { + search_scope_modules(scope, id, modules, index, *pos)? + } else { + search_scope_variables(scope, id, index, *pos)? }; // Constants cannot be modified match typ { + ScopeEntryType::SubScope => unreachable!(), ScopeEntryType::Constant if new_val.is_some() => { return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( id.to_string(), *pos, ))); } - _ => (), + ScopeEntryType::Constant | ScopeEntryType::Normal => (), } let this_ptr = target.into(); @@ -1206,11 +1266,16 @@ impl Engine { Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::CharConstant(c, _) => Ok((*c).into()), - Expr::Variable(_, None, Some(index), _) if !state.always_search => { - Ok(scope.get_mut(scope.len() - index.get()).0.clone()) - } - Expr::Variable(id, modules, _, pos) => { - search_scope(scope, id, modules.as_ref(), *pos).map(|(v, _)| v.clone()) + Expr::Variable(id, modules, index, pos) => { + let index = if state.always_search { None } else { *index }; + + let val = if let Some(modules) = modules { + search_scope_modules(scope, id, modules, index, *pos)? + } else { + search_scope_variables(scope, id, index, *pos)? + }; + + Ok(val.0.clone()) } Expr::Property(_, _) => unreachable!(), @@ -1223,8 +1288,15 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, modules, _, pos) => { - match search_scope(scope, name, modules.as_ref(), *pos)? { + Expr::Variable(name, modules, index, pos) => { + let index = if state.always_search { None } else { *index }; + let val = if let Some(modules) = modules { + search_scope_modules(scope, name, modules, index, *pos)? + } else { + search_scope_variables(scope, name, index, *pos)? + }; + + match val { (_, ScopeEntryType::Constant) => Err(Box::new( EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), )), @@ -1562,7 +1634,7 @@ impl Engine { .eval_expr(scope, state, fn_lib, expr, level)? .try_cast::() { - let mut module = Map::new(); + let mut module = SubScope::new(); module.insert("kitty".to_string(), "foo".to_string().into()); module.insert("path".to_string(), path.into()); diff --git a/src/lib.rs b/src/lib.rs index e13eaf0f..beeafb46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,9 @@ pub use engine::Array; #[cfg(not(feature = "no_object"))] pub use engine::Map; +#[cfg(not(feature = "no_import"))] +pub use engine::SubScope; + #[cfg(not(feature = "no_float"))] pub use parser::FLOAT; diff --git a/src/parser.rs b/src/parser.rs index 7dcb4d65..8f242e0f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1070,9 +1070,9 @@ fn parse_primary<'a>( (Expr::Property(id, pos), Token::LeftParen) => { parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? } - // moduled + // module access #[cfg(not(feature = "no_import"))] - (Expr::Variable(id, mut modules, _, pos), Token::DoubleColon) => { + (Expr::Variable(id, mut modules, mut index, pos), Token::DoubleColon) => { match input.next().unwrap() { (Token::Identifier(id2), pos2) => { if let Some(ref mut modules) = modules { @@ -1081,10 +1081,11 @@ fn parse_primary<'a>( let mut vec = StaticVec::new(); vec.push((*id, pos)); modules = Some(Box::new(vec)); + + let root = modules.as_ref().unwrap().get(0); + index = stack.find_sub_scope(&root.0); } - let root = modules.as_ref().unwrap().get(0); - let index = stack.find_sub_scope(&root.0); Expr::Variable(Box::new(id2), modules, index, pos2) } (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), @@ -1828,6 +1829,7 @@ fn parse_import<'a>( (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; + stack.push((name.clone(), ScopeEntryType::SubScope)); Ok(Stmt::Import(Box::new(expr), Box::new(name), pos)) } diff --git a/src/scope.rs b/src/scope.rs index 3f55aeaa..a965930f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,7 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. -use crate::any::{Dynamic, Variant}; -use crate::engine::Map; +use crate::any::{Dynamic, Union, Variant}; +use crate::engine::SubScope; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; @@ -175,11 +175,11 @@ impl<'a> Scope<'a> { /// # Examples /// /// ``` - /// use rhai::{Scope, Map}; + /// use rhai::{Scope, SubScope}; /// /// let mut my_scope = Scope::new(); /// - /// let mut sub_scope = Map::new(); + /// let mut sub_scope = SubScope::new(); /// sub_scope.insert("x".to_string(), 42_i64.into()); /// /// my_scope.push_sub_scope("my_plugin", sub_scope); @@ -187,8 +187,13 @@ impl<'a> Scope<'a> { /// let s = my_scope.find_sub_scope("my_plugin").unwrap(); /// assert_eq!(*s.get("x").unwrap().downcast_ref::().unwrap(), 42); /// ``` - pub fn push_sub_scope>>(&mut self, name: K, value: Map) { - self.push_dynamic_value(name, EntryType::SubScope, value.into(), true); + pub fn push_sub_scope>>(&mut self, name: K, value: SubScope) { + self.push_dynamic_value( + name, + EntryType::SubScope, + Dynamic(Union::SubScope(Box::new(value))), + true, + ); } /// Add (push) a new constant to the Scope. @@ -352,9 +357,9 @@ impl<'a> Scope<'a> { } /// Find a sub-scope in the Scope, starting from the last entry. - pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut Map> { + pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut SubScope> { let index = self.get_sub_scope_index(name)?; - self.get_mut(index).0.downcast_mut() + self.get_mut(index).0.downcast_mut::() } /// Get the value of an entry in the Scope, starting from the last. diff --git a/tests/modules.rs b/tests/modules.rs new file mode 100644 index 00000000..9052edb3 --- /dev/null +++ b/tests/modules.rs @@ -0,0 +1,15 @@ +use rhai::{EvalAltResult, Scope, SubScope, INT}; + +#[test] +#[cfg(not(feature = "no_import"))] +fn test_sub_scope() { + let mut my_scope = Scope::new(); + + let mut sub_scope = SubScope::new(); + sub_scope.insert("x".to_string(), (42 as INT).into()); + + my_scope.push_sub_scope("my_plugin", sub_scope); + + let s = my_scope.find_sub_scope("my_plugin").unwrap(); + assert_eq!(*s.get("x").unwrap().downcast_ref::().unwrap(), 42); +} From 143861747d91ab5381609b3dca594b43ce6304b3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 10:39:12 +0800 Subject: [PATCH 11/23] Rename sub-scope/SubScope to module. --- Cargo.toml | 2 +- README.md | 4 +- src/any.rs | 22 +++--- src/engine.rs | 186 ++++++++++++++++++++++------------------------- src/lib.rs | 3 - src/optimize.rs | 14 +++- src/parser.rs | 29 ++++---- src/scope.rs | 63 ++++++---------- src/token.rs | 24 +++--- tests/modules.rs | 16 +--- 10 files changed, 162 insertions(+), 201 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3148eb3e..2afe38b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ no_float = [] # no floating-point no_function = [] # no script-defined functions no_object = [] # no custom objects no_optimize = [] # no script optimizer -no_import = [] # no modules +no_module = [] # no modules only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types sync = [] # restrict to only types that implement Send + Sync diff --git a/README.md b/README.md index 7122feb6..bde4bbcd 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Optional features | `no_object` | Disable support for custom types and objects. | | `no_float` | Disable floating-point numbers and math if not needed. | | `no_optimize` | Disable the script optimizer. | -| `no_import` | Disable modules. | +| `no_module` | Disable modules. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | @@ -86,7 +86,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_function`]: #optional-features [`no_object`]: #optional-features [`no_optimize`]: #optional-features -[`no_import`]: #optional-features +[`no_module`]: #optional-features [`only_i32`]: #optional-features [`only_i64`]: #optional-features [`no_std`]: #optional-features diff --git a/src/any.rs b/src/any.rs index e07d584e..aee3da60 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,6 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::engine::{Array, Map, SubScope}; +use crate::engine::{Array, Map, Module}; use crate::parser::INT; #[cfg(not(feature = "no_float"))] @@ -135,7 +135,7 @@ pub enum Union { Float(FLOAT), Array(Box), Map(Box), - SubScope(Box), + Module(Box), Variant(Box>), } @@ -166,7 +166,7 @@ impl Dynamic { Union::Float(_) => TypeId::of::(), Union::Array(_) => TypeId::of::(), Union::Map(_) => TypeId::of::(), - Union::SubScope(_) => TypeId::of::(), + Union::Module(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), } } @@ -183,7 +183,7 @@ impl Dynamic { Union::Float(_) => type_name::(), Union::Array(_) => "array", Union::Map(_) => "map", - Union::SubScope(_) => "sub-scope", + Union::Module(_) => "sub-scope", #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", @@ -204,7 +204,7 @@ impl fmt::Display for Dynamic { Union::Float(value) => write!(f, "{}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), - Union::SubScope(value) => write!(f, "#{:?}", value), + Union::Module(value) => write!(f, "#{:?}", value), Union::Variant(_) => write!(f, "?"), } } @@ -222,7 +222,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::SubScope(value) => write!(f, "#{:?}", value), + Union::Module(value) => write!(f, "#{:?}", value), Union::Variant(_) => write!(f, ""), } } @@ -240,7 +240,7 @@ impl Clone for Dynamic { Union::Float(value) => Self(Union::Float(value)), Union::Array(ref value) => Self(Union::Array(value.clone())), Union::Map(ref value) => Self(Union::Map(value.clone())), - Union::SubScope(ref value) => Self(Union::SubScope(value.clone())), + Union::Module(ref value) => Self(Union::Module(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), } } @@ -369,7 +369,7 @@ impl Dynamic { Union::Float(ref value) => (value as &dyn Any).downcast_ref::().cloned(), Union::Array(value) => cast_box::<_, T>(value).ok(), Union::Map(value) => cast_box::<_, T>(value).ok(), - Union::SubScope(value) => cast_box::<_, T>(value).ok(), + Union::Module(value) => cast_box::<_, T>(value).ok(), Union::Variant(value) => value.as_any().downcast_ref::().cloned(), } } @@ -407,7 +407,7 @@ impl Dynamic { Union::Float(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), Union::Array(value) => cast_box::<_, T>(value).unwrap(), Union::Map(value) => cast_box::<_, T>(value).unwrap(), - Union::SubScope(value) => cast_box::<_, T>(value).unwrap(), + Union::Module(value) => cast_box::<_, T>(value).unwrap(), Union::Variant(value) => value.as_any().downcast_ref::().unwrap().clone(), } } @@ -430,7 +430,7 @@ impl Dynamic { Union::Float(value) => (value as &dyn Any).downcast_ref::(), Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::(), Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::(), - Union::SubScope(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::(), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), } } @@ -453,7 +453,7 @@ impl Dynamic { Union::Float(value) => (value as &mut dyn Any).downcast_mut::(), Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), - Union::SubScope(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), } } diff --git a/src/engine.rs b/src/engine.rs index 884e757b..cdcdbc72 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -5,7 +5,7 @@ use crate::calc_fn_hash; use crate::error::ParseErrorType; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; -use crate::parser::{Expr, FnDef, ReturnType, Stmt}; +use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -42,27 +42,27 @@ pub type Array = Vec; /// Not available under the `no_object` feature. pub type Map = HashMap; -/// A sub-scope - basically an imported module namespace. +/// An imported module. /// -/// Not available under the `no_import` feature. +/// Not available under the `no_module` feature. #[derive(Debug, Clone)] -pub struct SubScope(HashMap); +pub struct Module(HashMap); -impl SubScope { - /// Create a new sub-scope. +impl Module { + /// Create a new module. pub fn new() -> Self { Self(HashMap::new()) } } -impl Deref for SubScope { +impl Deref for Module { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for SubScope { +impl DerefMut for Module { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -500,67 +500,61 @@ fn default_print(s: &str) { } /// Search for a variable within the scope -fn search_scope_variables<'a>( +fn search_scope<'a>( scope: &'a mut Scope, name: &str, + modules: &Option>>, index: Option, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - let index = if let Some(index) = index { - scope.len() - index.get() + if let Some(modules) = modules { + let (id, root_pos) = modules.get(0); // First module + + let mut module = if let Some(index) = index { + scope + .get_mut(scope.len() - index.get()) + .0 + .downcast_mut::() + .unwrap() + } else { + scope + .find_module(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? + }; + + for x in 1..modules.len() { + let (id, id_pos) = modules.get(x); + + module = module + .get_mut(id) + .and_then(|v| v.downcast_mut::()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; + } + + let result = module + .get_mut(name) + .map(|v| (v, ScopeEntryType::Constant)) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; + + if result.0.is::() { + Err(Box::new(EvalAltResult::ErrorVariableNotFound( + name.into(), + pos, + ))) + } else { + Ok(result) + } } else { - scope - .get_index(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))? - .0 - }; + let index = if let Some(index) = index { + scope.len() - index.get() + } else { + scope + .get_index(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))? + .0 + }; - Ok(scope.get_mut(index)) -} - -/// Search for a sub-scope within the scope -fn search_scope_modules<'a>( - scope: &'a mut Scope, - name: &str, - modules: &Box>, - index: Option, - pos: Position, -) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - let (id, root_pos) = modules.get(0); // First module - - let mut sub_scope = if let Some(index) = index { - scope - .get_mut(scope.len() - index.get()) - .0 - .downcast_mut::() - .unwrap() - } else { - scope - .find_sub_scope(id) - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? - }; - - for x in 1..modules.len() { - let (id, id_pos) = modules.get(x); - - sub_scope = sub_scope - .get_mut(id) - .and_then(|v| v.downcast_mut::()) - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; - } - - let result = sub_scope - .get_mut(name) - .map(|v| (v, ScopeEntryType::Constant)) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; - - if result.0.is::() { - Err(Box::new(EvalAltResult::ErrorVariableNotFound( - name.into(), - pos, - ))) - } else { - Ok(result) + Ok(scope.get_mut(index)) } } @@ -620,11 +614,13 @@ impl Engine { } /// Universal method for calling functions either registered with the `Engine` or written in Rhai + // TODO - handle moduled function call pub(crate) fn call_fn_raw( &self, scope: Option<&mut Scope>, fn_lib: &FunctionsLib, fn_name: &str, + modules: &ModuleRef, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -794,6 +790,7 @@ impl Engine { &self, fn_lib: &FunctionsLib, fn_name: &str, + modules: &ModuleRef, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -801,12 +798,20 @@ impl Engine { ) -> Result> { match fn_name { // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => { + KEYWORD_TYPE_OF + if modules.is_none() + && args.len() == 1 + && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => + { Ok(self.map_type_name(args[0].type_name()).to_string().into()) } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) => { + KEYWORD_EVAL + if modules.is_none() + && args.len() == 1 + && !self.has_override(fn_lib, KEYWORD_EVAL) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), pos, @@ -814,7 +819,7 @@ impl Engine { } // Normal method call - _ => self.call_fn_raw(None, fn_lib, fn_name, args, def_val, pos, level), + _ => self.call_fn_raw(None, fn_lib, fn_name, modules, args, def_val, pos, level), } } @@ -915,7 +920,7 @@ impl Engine { let def_val = def_val.as_deref(); // A function call is assumed to have side effects, so the value is changed // TODO - Remove assumption of side effects by checking whether the first parameter is &mut - self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(fn_lib, fn_name, &None, &mut args, def_val, *pos, 0).map(|v| (v, true)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_,_,_,_,_) => unreachable!(), @@ -936,13 +941,13 @@ impl Engine { Expr::Property(id, pos) if new_val.is_some() => { let fn_name = make_setter(id); let mut args = [obj, new_val.as_mut().unwrap()]; - self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, true)) } // xxx.id Expr::Property(id, pos) => { let fn_name = make_getter(id); let mut args = [obj]; - self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) + self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, false)) } // {xxx:map}.idx_lhs[idx_expr] Expr::Index(dot_lhs, dot_rhs, pos) | @@ -972,7 +977,7 @@ impl Engine { let indexed_val = &mut (if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_getter(id); - self.exec_fn_call(fn_lib, &fn_name, &mut args[..1], None, *pos, 0)? + self.exec_fn_call(fn_lib, &fn_name, &None, &mut args[..1], None, *pos, 0)? } else { // Syntax error return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -990,7 +995,7 @@ impl Engine { let fn_name = make_setter(id); // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; - self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { + self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), err => Err(Box::new(err)) @@ -1030,16 +1035,11 @@ impl Engine { // id.??? or id[???] Expr::Variable(id, modules, index, pos) => { let index = if state.always_search { None } else { *index }; - - let (target, typ) = if let Some(modules) = modules { - search_scope_modules(scope, id, modules, index, *pos)? - } else { - search_scope_variables(scope, id, index, *pos)? - }; + let (target, typ) = search_scope(scope, id, modules, index, *pos)?; // Constants cannot be modified match typ { - ScopeEntryType::SubScope => unreachable!(), + ScopeEntryType::Module => unreachable!(), ScopeEntryType::Constant if new_val.is_some() => { return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( id.to_string(), @@ -1223,9 +1223,10 @@ impl Engine { for value in rhs_value.iter_mut() { let args = &mut [&mut lhs_value, value]; let def_value = Some(&def_value); + let pos = rhs.position(); if self - .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? + .call_fn_raw(None, fn_lib, "==", &None, args, def_value, pos, level)? .as_bool() .unwrap_or(false) { @@ -1268,13 +1269,7 @@ impl Engine { Expr::CharConstant(c, _) => Ok((*c).into()), Expr::Variable(id, modules, index, pos) => { let index = if state.always_search { None } else { *index }; - - let val = if let Some(modules) = modules { - search_scope_modules(scope, id, modules, index, *pos)? - } else { - search_scope_variables(scope, id, index, *pos)? - }; - + let val = search_scope(scope, id, modules, index, *pos)?; Ok(val.0.clone()) } Expr::Property(_, _) => unreachable!(), @@ -1290,13 +1285,7 @@ impl Engine { // name = rhs Expr::Variable(name, modules, index, pos) => { let index = if state.always_search { None } else { *index }; - let val = if let Some(modules) = modules { - search_scope_modules(scope, name, modules, index, *pos)? - } else { - search_scope_variables(scope, name, index, *pos)? - }; - - match val { + match search_scope(scope, name, modules, index, *pos)? { (_, ScopeEntryType::Constant) => Err(Box::new( EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), )), @@ -1304,8 +1293,8 @@ impl Engine { *value_ptr = rhs_val; Ok(Default::default()) } - // End variable cannot be a sub-scope - (_, ScopeEntryType::SubScope) => unreachable!(), + // End variable cannot be a module + (_, ScopeEntryType::Module) => unreachable!(), } } // idx_lhs[idx_expr] = rhs @@ -1369,7 +1358,6 @@ impl Engine { .collect::, _>>()?, )))), - // TODO - handle moduled function call Expr::FnCall(fn_name, modules, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() @@ -1380,6 +1368,7 @@ impl Engine { // eval - only in function call style if fn_name.as_ref() == KEYWORD_EVAL + && modules.is_none() && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { @@ -1399,7 +1388,8 @@ impl Engine { } // Normal function call - except for eval (handled above) - self.exec_fn_call(fn_lib, fn_name, &mut args, def_val.as_deref(), *pos, level) + let def_value = def_val.as_deref(); + self.exec_fn_call(fn_lib, fn_name, modules, &mut args, def_value, *pos, level) } Expr::In(lhs, rhs, _) => { @@ -1634,13 +1624,13 @@ impl Engine { .eval_expr(scope, state, fn_lib, expr, level)? .try_cast::() { - let mut module = SubScope::new(); + let mut module = Module::new(); module.insert("kitty".to_string(), "foo".to_string().into()); module.insert("path".to_string(), path.into()); // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); - scope.push_sub_scope(mod_name, module); + scope.push_module(mod_name, module); Ok(Default::default()) } else { Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) diff --git a/src/lib.rs b/src/lib.rs index beeafb46..e13eaf0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,9 +103,6 @@ pub use engine::Array; #[cfg(not(feature = "no_object"))] pub use engine::Map; -#[cfg(not(feature = "no_import"))] -pub use engine::SubScope; - #[cfg(not(feature = "no_float"))] pub use parser::FLOAT; diff --git a/src/optimize.rs b/src/optimize.rs index 57425c9a..29d2f7fe 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -231,6 +231,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - } // let id; Stmt::Let(_, None, _) => stmt, + // import expr as id; + Stmt::Import(expr, id, pos) => Stmt::Import(Box::new(optimize_expr(*expr, state)), id, pos), // { block } Stmt::Block(block, pos) => { let orig_len = block.len(); // Original number of statements in the block, for change detection @@ -260,7 +262,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - result.push(stmt); } - // Remove all let statements at the end of a block - the new variables will go away anyway. + // Remove all let/import statements at the end of a block - the new variables will go away anyway. // But be careful only remove ones that have no initial values or have values that are pure expressions, // otherwise there may be side effects. let mut removed = false; @@ -268,7 +270,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - while let Some(expr) = result.pop() { match expr { Stmt::Let(_, None, _) => removed = true, - Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true, + Stmt::Let(_, Some(val_expr), _) => removed = val_expr.is_pure(), + Stmt::Import(expr, _, _) => removed = expr.is_pure(), _ => { result.push(expr); break; @@ -323,6 +326,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - state.set_dirty(); Stmt::Noop(pos) } + // Only one let/import statement - leave it alone + [Stmt::Let(_, _, _)] | [Stmt::Import(_, _, _)] => Stmt::Block(result, pos), // Only one statement - promote [_] => { state.set_dirty(); @@ -666,7 +671,10 @@ fn optimize<'a>( _ => { // Keep all variable declarations at this level // and always keep the last return value - let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; + let keep = match stmt { + Stmt::Let(_, _, _) | Stmt::Import(_, _, _) => true, + _ => i == num_statements - 1, + }; optimize_stmt(stmt, &mut state, keep) } } diff --git a/src/parser.rs b/src/parser.rs index 8f242e0f..ef31c3f9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -42,6 +42,8 @@ pub type FLOAT = f64; type PERR = ParseErrorType; +pub type ModuleRef = Option>>; + /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. @@ -204,7 +206,7 @@ impl Stack { .enumerate() .find(|(_, (n, typ))| match typ { ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name, - ScopeEntryType::SubScope => false, + ScopeEntryType::Module => false, }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } @@ -218,7 +220,7 @@ impl Stack { .rev() .enumerate() .find(|(_, (n, typ))| match typ { - ScopeEntryType::SubScope => *n == name, + ScopeEntryType::Module => *n == name, ScopeEntryType::Normal | ScopeEntryType::Constant => false, }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)) @@ -344,12 +346,7 @@ pub enum Expr { /// String constant. StringConstant(String, Position), /// Variable access - (variable name, optional modules, optional index, position) - Variable( - Box, - Option>>, - Option, - Position, - ), + Variable(Box, ModuleRef, Option, Position), /// Property access. Property(String, Position), /// { stmt } @@ -359,7 +356,7 @@ pub enum Expr { /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box>, - Option>>, + ModuleRef, Box>, Option>, Position, @@ -575,12 +572,12 @@ impl Expr { Self::Variable(_, None, _, _) => match token { Token::LeftBracket | Token::LeftParen => true, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] Token::DoubleColon => true, _ => false, }, Self::Variable(_, _, _, _) => match token { - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] Token::DoubleColon => true, _ => false, }, @@ -659,7 +656,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, - modules: Option>>, + modules: ModuleRef, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -1071,7 +1068,7 @@ fn parse_primary<'a>( parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? } // module access - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] (Expr::Variable(id, mut modules, mut index, pos), Token::DoubleColon) => { match input.next().unwrap() { (Token::Identifier(id2), pos2) => { @@ -1790,7 +1787,7 @@ fn parse_let<'a>( Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) } // Variable cannot be a sub-scope - ScopeEntryType::SubScope => unreachable!(), + ScopeEntryType::Module => unreachable!(), } } else { // let name @@ -1799,7 +1796,7 @@ fn parse_let<'a>( } /// Parse an import statement. -#[cfg(not(feature = "no_import"))] +#[cfg(not(feature = "no_module"))] fn parse_import<'a>( input: &mut Peekable>, stack: &mut Stack, @@ -1829,7 +1826,7 @@ fn parse_import<'a>( (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; - stack.push((name.clone(), ScopeEntryType::SubScope)); + stack.push((name.clone(), ScopeEntryType::Module)); Ok(Stmt::Import(Box::new(expr), Box::new(name), pos)) } diff --git a/src/scope.rs b/src/scope.rs index a965930f..394997bf 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,7 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Dynamic, Union, Variant}; -use crate::engine::SubScope; +use crate::engine::Module; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; @@ -14,8 +14,9 @@ pub enum EntryType { Normal, /// Immutable constant value. Constant, - /// Name of a sub-scope, allowing member access with the :: operator. - SubScope, + /// Name of a module, allowing member access with the :: operator. + /// This is for internal use only. + Module, } /// An entry in the Scope. @@ -168,30 +169,14 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, EntryType::Normal, value, false); } - /// Add (push) a new sub-scope to the Scope. + /// Add (push) a new module to the Scope. /// - /// Sub-scopes are used for accessing members in modules and plugins under a namespace. - /// - /// # Examples - /// - /// ``` - /// use rhai::{Scope, SubScope}; - /// - /// let mut my_scope = Scope::new(); - /// - /// let mut sub_scope = SubScope::new(); - /// sub_scope.insert("x".to_string(), 42_i64.into()); - /// - /// my_scope.push_sub_scope("my_plugin", sub_scope); - /// - /// let s = my_scope.find_sub_scope("my_plugin").unwrap(); - /// assert_eq!(*s.get("x").unwrap().downcast_ref::().unwrap(), 42); - /// ``` - pub fn push_sub_scope>>(&mut self, name: K, value: SubScope) { + /// Modules are used for accessing member variables, functions and plugins under a namespace. + pub(crate) fn push_module>>(&mut self, name: K, value: Module) { self.push_dynamic_value( name, - EntryType::SubScope, - Dynamic(Union::SubScope(Box::new(value))), + EntryType::Module, + Dynamic(Union::Module(Box::new(value))), true, ); } @@ -295,8 +280,6 @@ impl<'a> Scope<'a> { /// Does the scope contain the entry? /// - /// Sub-scopes are ignored. - /// /// # Examples /// /// ``` @@ -314,13 +297,13 @@ impl<'a> Scope<'a> { .rev() // Always search a Scope in reverse order .any(|Entry { name: key, typ, .. }| match typ { EntryType::Normal | EntryType::Constant => name == key, - EntryType::SubScope => false, + EntryType::Module => false, }) } /// Find an entry in the Scope, starting from the last. /// - /// Sub-scopes are ignored. + /// modules are ignored. pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { self.0 .iter() @@ -334,18 +317,18 @@ impl<'a> Scope<'a> { None } } - EntryType::SubScope => None, + EntryType::Module => None, }) } - /// Find a sub-scope in the Scope, starting from the last. - pub(crate) fn get_sub_scope_index(&self, name: &str) -> Option { + /// Find a module in the Scope, starting from the last. + pub(crate) fn get_module_index(&self, name: &str) -> Option { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order .find_map(|(index, Entry { name: key, typ, .. })| match typ { - EntryType::SubScope => { + EntryType::Module => { if name == key { Some(index) } else { @@ -356,15 +339,15 @@ impl<'a> Scope<'a> { }) } - /// Find a sub-scope in the Scope, starting from the last entry. - pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut SubScope> { - let index = self.get_sub_scope_index(name)?; - self.get_mut(index).0.downcast_mut::() + /// Find a module in the Scope, starting from the last entry. + pub fn find_module(&mut self, name: &str) -> Option<&mut Module> { + let index = self.get_module_index(name)?; + self.get_mut(index).0.downcast_mut::() } /// Get the value of an entry in the Scope, starting from the last. /// - /// Sub-scopes are ignored. + /// modules are ignored. /// /// # Examples /// @@ -382,7 +365,7 @@ impl<'a> Scope<'a> { .rev() .find(|Entry { name: key, typ, .. }| match typ { EntryType::Normal | EntryType::Constant => name == key, - EntryType::SubScope => false, + EntryType::Module => false, }) .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } @@ -415,8 +398,8 @@ impl<'a> Scope<'a> { Some((index, EntryType::Normal)) => { self.0.get_mut(index).unwrap().value = Dynamic::from(value) } - // Sub-scopes cannot be modified - Some((_, EntryType::SubScope)) => unreachable!(), + // modules cannot be modified + Some((_, EntryType::Module)) => unreachable!(), } } diff --git a/src/token.rs b/src/token.rs index 86a3b820..1a303810 100644 --- a/src/token.rs +++ b/src/token.rs @@ -153,7 +153,7 @@ pub enum Token { RightShift, SemiColon, Colon, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] DoubleColon, Comma, Period, @@ -199,11 +199,11 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] Import, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] Export, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] As, LexError(Box), EOF, @@ -238,7 +238,7 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] DoubleColon => "::", Comma => ",", Period => ".", @@ -288,11 +288,11 @@ impl Token { ModuloAssign => "%=", PowerOf => "~", PowerOfAssign => "~=", - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] Import => "import", - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] Export => "export", - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] As => "as", EOF => "{EOF}", _ => panic!("operator should be match in outer scope"), @@ -763,11 +763,11 @@ impl<'a> TokenIterator<'a> { "for" => Token::For, "in" => Token::In, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] "import" => Token::Import, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] "export" => Token::Export, - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] "as" => Token::As, #[cfg(not(feature = "no_function"))] @@ -924,7 +924,7 @@ impl<'a> TokenIterator<'a> { } ('=', _) => return Some((Token::Equals, pos)), - #[cfg(not(feature = "no_import"))] + #[cfg(not(feature = "no_module"))] (':', ':') => { self.eat_next(); return Some((Token::DoubleColon, pos)); diff --git a/tests/modules.rs b/tests/modules.rs index 9052edb3..5046791d 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,15 +1 @@ -use rhai::{EvalAltResult, Scope, SubScope, INT}; - -#[test] -#[cfg(not(feature = "no_import"))] -fn test_sub_scope() { - let mut my_scope = Scope::new(); - - let mut sub_scope = SubScope::new(); - sub_scope.insert("x".to_string(), (42 as INT).into()); - - my_scope.push_sub_scope("my_plugin", sub_scope); - - let s = my_scope.find_sub_scope("my_plugin").unwrap(); - assert_eq!(*s.get("x").unwrap().downcast_ref::().unwrap(), 42); -} +use rhai::{EvalAltResult, Scope, INT}; From c03b162b7e01f467d34482b414b0a34585414ea7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 12:24:13 +0800 Subject: [PATCH 12/23] Refactor code base and split into more module files. --- src/any.rs | 3 +- src/engine.rs | 143 +++------------------------------------------ src/fn_register.rs | 3 +- src/lib.rs | 5 +- src/module.rs | 35 +++++++++++ src/parser.rs | 10 +++- src/scope.rs | 2 +- src/utils.rs | 128 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 186 insertions(+), 143 deletions(-) create mode 100644 src/module.rs create mode 100644 src/utils.rs diff --git a/src/any.rs b/src/any.rs index aee3da60..b87f2d50 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,7 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::engine::{Array, Map, Module}; +use crate::engine::{Array, Map}; +use crate::module::Module; use crate::parser::INT; #[cfg(not(feature = "no_float"))] diff --git a/src/engine.rs b/src/engine.rs index cdcdbc72..6a1853d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,19 +3,20 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::error::ParseErrorType; +use crate::module::Module; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; +use crate::utils::{calc_fn_def, StaticVec}; use crate::stdlib::{ any::TypeId, boxed::Box, collections::HashMap, format, - hash::{Hash, Hasher}, iter::once, mem, num::NonZeroUsize, @@ -26,12 +27,6 @@ use crate::stdlib::{ vec::Vec, }; -#[cfg(not(feature = "no_std"))] -use crate::stdlib::collections::hash_map::DefaultHasher; - -#[cfg(feature = "no_std")] -use ahash::AHasher; - /// An dynamic array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -42,32 +37,6 @@ pub type Array = Vec; /// Not available under the `no_object` feature. pub type Map = HashMap; -/// An imported module. -/// -/// Not available under the `no_module` feature. -#[derive(Debug, Clone)] -pub struct Module(HashMap); - -impl Module { - /// Create a new module. - pub fn new() -> Self { - Self(HashMap::new()) - } -} - -impl Deref for Module { - type Target = HashMap; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Module { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - pub type FnCallArgs<'a> = [&'a mut Dynamic]; #[cfg(feature = "sync")] @@ -166,78 +135,8 @@ impl> From for Target<'_> { } } -/// A type to hold a number of values in static storage for speed, -/// and any spill-overs in a `Vec`. -#[derive(Debug, Clone)] -pub struct StaticVec { - /// Total number of values held. - len: usize, - /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. - list: [T; 4], - /// Dynamic storage. For spill-overs. - more: Vec, -} - -impl StaticVec { - /// Create a new `StaticVec`. - pub fn new() -> Self { - Self { - len: 0, - list: [ - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ], - more: Vec::new(), - } - } - /// Push a new value to the end of this `StaticVec`. - pub fn push>(&mut self, value: X) { - if self.len >= self.list.len() { - self.more.push(value.into()); - } else { - self.list[self.len] = value.into(); - } - self.len += 1; - } - /// Pop a value from the end of this `StaticVec`. - /// - /// # Panics - /// - /// Panics if the `StaticVec` is empty. - pub fn pop(&mut self) -> T { - let result = if self.len <= 0 { - panic!("nothing to pop!") - } else if self.len <= self.list.len() { - mem::replace(self.list.get_mut(self.len - 1).unwrap(), Default::default()) - } else { - self.more.pop().unwrap() - }; - - self.len -= 1; - - result - } - /// Get the number of items in this `StaticVec`. - pub fn len(&self) -> usize { - self.len - } - pub fn get(&self, index: usize) -> &T { - if index >= self.len { - panic!("index OOB in StaticVec"); - } - - if index < self.list.len() { - self.list.get(index).unwrap() - } else { - self.more.get(index - self.list.len()).unwrap() - } - } -} - /// A type that holds all the current states of the Engine. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct State { /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned. @@ -466,33 +365,6 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { } } -/// Calculate a `u64` hash key from a function name and parameter types. -/// -/// Parameter types are passed in via `TypeId` values from an iterator -/// which can come from any source. -pub fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { - #[cfg(feature = "no_std")] - let mut s: AHasher = Default::default(); - #[cfg(not(feature = "no_std"))] - let mut s = DefaultHasher::new(); - - s.write(fn_name.as_bytes()); - params.for_each(|t| t.hash(&mut s)); - s.finish() -} - -/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types). -pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 { - #[cfg(feature = "no_std")] - let mut s: AHasher = Default::default(); - #[cfg(not(feature = "no_std"))] - let mut s = DefaultHasher::new(); - - s.write(fn_name.as_bytes()); - s.write_usize(params); - s.finish() -} - /// Print/debug to stdout fn default_print(s: &str) { #[cfg(not(feature = "no_std"))] @@ -503,12 +375,13 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - modules: &Option>>, + modules: &ModuleRef, index: Option, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { if let Some(modules) = modules { - let (id, root_pos) = modules.get(0); // First module + let mut drain = modules.iter(); + let (id, root_pos) = drain.next().unwrap(); // First module let mut module = if let Some(index) = index { scope @@ -522,9 +395,7 @@ fn search_scope<'a>( .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? }; - for x in 1..modules.len() { - let (id, id_pos) = modules.get(x); - + for (id, id_pos) in drain { module = module .get_mut(id) .and_then(|v| v.downcast_mut::()) diff --git a/src/fn_register.rs b/src/fn_register.rs index 9368d6a3..197cf6cc 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -3,9 +3,10 @@ #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; -use crate::engine::{calc_fn_spec, Engine, FnCallArgs}; +use crate::engine::{Engine, FnCallArgs}; use crate::result::EvalAltResult; use crate::token::Position; +use crate::utils::calc_fn_spec; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; diff --git a/src/lib.rs b/src/lib.rs index e13eaf0f..04e4ebb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,7 @@ mod error; mod fn_call; mod fn_func; mod fn_register; +mod module; mod optimize; pub mod packages; mod parser; @@ -83,9 +84,10 @@ mod result; mod scope; mod stdlib; mod token; +mod utils; pub use any::Dynamic; -pub use engine::{calc_fn_spec as calc_fn_hash, Engine}; +pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; @@ -93,6 +95,7 @@ pub use parser::{AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; pub use token::Position; +pub use utils::calc_fn_spec as calc_fn_hash; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; diff --git a/src/module.rs b/src/module.rs new file mode 100644 index 00000000..4b62097b --- /dev/null +++ b/src/module.rs @@ -0,0 +1,35 @@ +//! Module defining external-loaded modules for Rhai. + +use crate::any::Dynamic; + +use crate::stdlib::{ + collections::HashMap, + ops::{Deref, DerefMut}, + string::String, +}; + +/// An imported module. +/// +/// Not available under the `no_module` feature. +#[derive(Debug, Clone)] +pub struct Module(HashMap); + +impl Module { + /// Create a new module. + pub fn new() -> Self { + Self(HashMap::new()) + } +} + +impl Deref for Module { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Module { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/src/parser.rs b/src/parser.rs index ef31c3f9..707b9809 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,11 +1,12 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{calc_fn_def, Engine, FunctionsLib, StaticVec}; +use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; +use crate::utils::{calc_fn_def, StaticVec}; use crate::stdlib::{ borrow::Cow, @@ -42,6 +43,9 @@ pub type FLOAT = f64; type PERR = ParseErrorType; +/// A chain of module names to qualify a variable or function call. +/// A `StaticVec` is used because most module-level access contains only one level, +/// and it is wasteful to always allocate a `Vec` with one element. pub type ModuleRef = Option>>; /// Compiled AST (abstract syntax tree) of a Rhai script. @@ -1079,7 +1083,7 @@ fn parse_primary<'a>( vec.push((*id, pos)); modules = Some(Box::new(vec)); - let root = modules.as_ref().unwrap().get(0); + let root = modules.as_ref().unwrap().iter().next().unwrap(); index = stack.find_sub_scope(&root.0); } @@ -1243,7 +1247,7 @@ fn make_dot_expr( } // lhs.module::id - syntax error (_, Expr::Variable(_, Some(modules), _, _)) => { - return Err(PERR::PropertyExpected.into_err(modules.get(0).1)) + return Err(PERR::PropertyExpected.into_err(modules.iter().next().unwrap().1)) } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( diff --git a/src/scope.rs b/src/scope.rs index 394997bf..da90f72a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,7 +1,7 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Dynamic, Union, Variant}; -use crate::engine::Module; +use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..4f51c7aa --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,128 @@ +//! Module containing various utility types and functions. + +use crate::stdlib::{ + any::TypeId, + hash::{Hash, Hasher}, + mem, + vec::Vec, +}; + +#[cfg(not(feature = "no_std"))] +use crate::stdlib::collections::hash_map::DefaultHasher; + +#[cfg(feature = "no_std")] +use ahash::AHasher; + +/// Calculate a `u64` hash key from a function name and parameter types. +/// +/// Parameter types are passed in via `TypeId` values from an iterator +/// which can come from any source. +pub fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] + let mut s = DefaultHasher::new(); + + s.write(fn_name.as_bytes()); + params.for_each(|t| t.hash(&mut s)); + s.finish() +} + +/// Calculate a `u64` hash key from a function name and number of parameters (without regard to types). +pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 { + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] + let mut s = DefaultHasher::new(); + + s.write(fn_name.as_bytes()); + s.write_usize(num_params); + s.finish() +} + +/// A type to hold a number of values in static storage for speed, and any spill-overs in a `Vec`. +/// +/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. +/// This simplified implementation here is to avoid pulling in another crate. +#[derive(Debug, Clone)] +pub struct StaticVec { + /// Total number of values held. + len: usize, + /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. + list: [T; 4], + /// Dynamic storage. For spill-overs. + more: Vec, +} + +impl StaticVec { + /// Create a new `StaticVec`. + pub fn new() -> Self { + Self { + len: 0, + list: [ + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ], + more: Vec::new(), + } + } + /// Push a new value to the end of this `StaticVec`. + pub fn push>(&mut self, value: X) { + if self.len >= self.list.len() { + self.more.push(value.into()); + } else { + self.list[self.len] = value.into(); + } + self.len += 1; + } + /// Pop a value from the end of this `StaticVec`. + /// + /// # Panics + /// + /// Panics if the `StaticVec` is empty. + pub fn pop(&mut self) -> T { + let result = if self.len <= 0 { + panic!("nothing to pop!") + } else if self.len <= self.list.len() { + mem::replace(self.list.get_mut(self.len - 1).unwrap(), Default::default()) + } else { + self.more.pop().unwrap() + }; + + self.len -= 1; + + result + } + /// Get the number of items in this `StaticVec`. + pub fn len(&self) -> usize { + self.len + } + /// Get an item at a particular index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + pub fn get(&self, index: usize) -> &T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + if index < self.list.len() { + self.list.get(index).unwrap() + } else { + self.more.get(index - self.list.len()).unwrap() + } + } + /// Get an iterator to entries in the `StaticVec`. + pub fn iter(&self) -> impl Iterator { + let num = if self.len >= self.list.len() { + self.list.len() + } else { + self.len + }; + + self.list[..num].iter().chain(self.more.iter()) + } +} From 38e717a838635169db511e2851baeb1cc289cbb5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 15:00:10 +0800 Subject: [PATCH 13/23] Build Module type plus engine hooks. --- examples/repl.rs | 6 +- src/any.rs | 4 +- src/api.rs | 19 +++--- src/engine.rs | 70 ++++++++-------------- src/fn_register.rs | 2 +- src/lib.rs | 3 + src/module.rs | 137 ++++++++++++++++++++++++++++++++++++++------ src/optimize.rs | 9 +-- src/packages/mod.rs | 6 +- src/parser.rs | 55 +++++++++++------- src/scope.rs | 12 +--- src/token.rs | 2 +- src/utils.rs | 24 ++++---- tests/modules.rs | 34 ++++++++++- 14 files changed, 249 insertions(+), 134 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index d2281b6a..af26d593 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -68,9 +68,9 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); - let mut main_ast = AST::new(); - let mut ast_u = AST::new(); - let mut ast = AST::new(); + let mut main_ast: AST = Default::default(); + let mut ast_u: AST = Default::default(); + let mut ast: AST = Default::default(); println!("Rhai REPL tool"); println!("=============="); diff --git a/src/any.rs b/src/any.rs index b87f2d50..4ae08f38 100644 --- a/src/any.rs +++ b/src/any.rs @@ -205,7 +205,7 @@ impl fmt::Display for Dynamic { Union::Float(value) => write!(f, "{}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), - Union::Module(value) => write!(f, "#{:?}", value), + Union::Module(value) => write!(f, "{:?}", value), Union::Variant(_) => write!(f, "?"), } } @@ -223,7 +223,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::Module(value) => write!(f, "#{:?}", value), + Union::Module(value) => write!(f, "{:?}", value), Union::Variant(_) => write!(f, ""), } } diff --git a/src/api.rs b/src/api.rs index 9377bc68..03da48cb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,6 +15,7 @@ use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, collections::HashMap, + mem, string::{String, ToString}, vec::Vec, }; @@ -797,10 +798,10 @@ impl Engine { ) -> Result> { let mut state = State::new(); - ast.0 + ast.statements() .iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0) + self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) }) .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), @@ -862,10 +863,10 @@ impl Engine { ) -> Result<(), Box> { let mut state = State::new(); - ast.0 + ast.statements() .iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0) + self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) }) .map_or_else( |err| match *err { @@ -921,7 +922,7 @@ impl Engine { ) -> Result> { let mut arg_values = args.into_vec(); let mut args: Vec<_> = arg_values.iter_mut().collect(); - let fn_lib = ast.1.as_ref(); + let fn_lib = ast.fn_lib(); let pos = Position::none(); let fn_def = fn_lib @@ -955,15 +956,17 @@ impl Engine { pub fn optimize_ast( &self, scope: &Scope, - ast: AST, + mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { let fn_lib = ast - .1 + .fn_lib() .iter() .map(|(_, fn_def)| fn_def.as_ref().clone()) .collect(); - optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) + + let stmt = mem::take(ast.statements_mut()); + optimize_into_ast(self, scope, stmt, fn_lib, optimization_level) } /// Override default action of `print` (print to stdout using `println!`) diff --git a/src/engine.rs b/src/engine.rs index 6a1853d2..5be305ac 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,7 +6,7 @@ use crate::error::ParseErrorType; use crate::module::Module; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; -use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt}; +use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -159,7 +159,7 @@ impl State { /// and number of parameters are considered equivalent. /// /// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_def`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct FunctionsLib( #[cfg(feature = "sync")] HashMap>, #[cfg(not(feature = "sync"))] HashMap>, @@ -168,7 +168,7 @@ pub struct FunctionsLib( impl FunctionsLib { /// Create a new `FunctionsLib`. pub fn new() -> Self { - FunctionsLib(HashMap::new()) + Default::default() } /// Create a new `FunctionsLib` from a collection of `FnDef`. pub fn from_vec(vec: Vec) -> Self { @@ -289,10 +289,10 @@ impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Self { - packages: Vec::new(), + packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT), - type_iterators: HashMap::new(), - type_names: HashMap::new(), + type_iterators: Default::default(), + type_names: Default::default(), // default print/debug implementations print: Box::new(default_print), @@ -380,10 +380,9 @@ fn search_scope<'a>( pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { if let Some(modules) = modules { - let mut drain = modules.iter(); - let (id, root_pos) = drain.next().unwrap(); // First module + let (id, root_pos) = modules.get(0); // First module - let mut module = if let Some(index) = index { + let module = if let Some(index) = index { scope .get_mut(scope.len() - index.get()) .0 @@ -395,26 +394,11 @@ fn search_scope<'a>( .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? }; - for (id, id_pos) in drain { - module = module - .get_mut(id) - .and_then(|v| v.downcast_mut::()) - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; - } - - let result = module - .get_mut(name) - .map(|v| (v, ScopeEntryType::Constant)) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; - - if result.0.is::() { - Err(Box::new(EvalAltResult::ErrorVariableNotFound( - name.into(), - pos, - ))) - } else { - Ok(result) - } + Ok(( + module.get_qualified_variable_mut(name, modules.as_ref(), pos)?, + // Module variables are constant + ScopeEntryType::Constant, + )) } else { let index = if let Some(index) = index { scope.len() - index.get() @@ -439,10 +423,10 @@ impl Engine { /// Use the `load_package` method to load packages of functions. pub fn new_raw() -> Self { Self { - packages: Vec::new(), + packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), - type_iterators: HashMap::new(), - type_names: HashMap::new(), + type_iterators: Default::default(), + type_names: Default::default(), print: Box::new(|_| {}), debug: Box::new(|_| {}), @@ -595,8 +579,7 @@ impl Engine { .iter() .zip( // Actually consume the arguments instead of cloning them - args.into_iter() - .map(|v| mem::replace(*v, Default::default())), + args.into_iter().map(|v| mem::take(*v)), ) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); @@ -626,8 +609,7 @@ impl Engine { .iter() .zip( // Actually consume the arguments instead of cloning them - args.into_iter() - .map(|v| mem::replace(*v, Default::default())), + args.into_iter().map(|v| mem::take(*v)), ) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); @@ -715,20 +697,14 @@ impl Engine { )?; // If new functions are defined within the eval string, it is an error - if ast.1.len() > 0 { + if ast.fn_lib().len() > 0 { return Err(Box::new(EvalAltResult::ErrorParsing( ParseErrorType::WrongFnDefinition.into_err(pos), ))); } - #[cfg(feature = "sync")] - { - ast.1 = Arc::new(fn_lib.clone()); - } - #[cfg(not(feature = "sync"))] - { - ast.1 = Rc::new(fn_lib.clone()); - } + let statements = mem::take(ast.statements_mut()); + ast = AST::new(statements, fn_lib.clone()); // Evaluate the AST self.eval_ast_with_scope_raw(scope, &ast) @@ -1496,8 +1472,8 @@ impl Engine { .try_cast::() { let mut module = Module::new(); - module.insert("kitty".to_string(), "foo".to_string().into()); - module.insert("path".to_string(), path.into()); + module.set_variable("kitty", "foo".to_string()); + module.set_variable("path", path); // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); diff --git a/src/fn_register.rs b/src/fn_register.rs index 197cf6cc..9f798af1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -128,7 +128,7 @@ pub fn by_ref(data: &mut Dynamic) -> &mut T { pub fn by_value(data: &mut Dynamic) -> T { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. - mem::replace(data, Default::default()).cast::() + mem::take(data).cast::() } /// This macro counts the number of arguments via recursion. diff --git a/src/lib.rs b/src/lib.rs index 04e4ebb5..ddcdee28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,5 +109,8 @@ pub use engine::Map; #[cfg(not(feature = "no_float"))] pub use parser::FLOAT; +#[cfg(not(feature = "no_module"))] +pub use module::Module; + #[cfg(not(feature = "no_optimize"))] pub use optimize::OptimizationLevel; diff --git a/src/module.rs b/src/module.rs index 4b62097b..d7c48d3c 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,35 +1,134 @@ //! Module defining external-loaded modules for Rhai. -use crate::any::Dynamic; +use crate::any::{Dynamic, Variant}; +use crate::engine::{FnAny, FunctionsLib}; +use crate::result::EvalAltResult; +use crate::token::Position; +use crate::utils::StaticVec; -use crate::stdlib::{ - collections::HashMap, - ops::{Deref, DerefMut}, - string::String, -}; +use crate::stdlib::{collections::HashMap, fmt, string::String}; -/// An imported module. +/// An imported module, which may contain variables, sub-modules, +/// external Rust functions, and script-defined functions. /// /// Not available under the `no_module` feature. -#[derive(Debug, Clone)] -pub struct Module(HashMap); +#[derive(Default)] +pub struct Module { + /// Sub-modules. + modules: HashMap, + /// Module variables, including sub-modules. + variables: HashMap, + /// External Rust functions. + functions: HashMap>, + /// Script-defined functions. + lib: FunctionsLib, +} + +impl fmt::Debug for Module { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "", + self.variables, + self.functions.len(), + self.lib.len() + ) + } +} + +impl Clone for Module { + fn clone(&self) -> Self { + // `Module` implements `Clone` so it can fit inside a `Dynamic` + // but we should never actually clone it. + unimplemented!() + } +} impl Module { /// Create a new module. pub fn new() -> Self { - Self(HashMap::new()) + Default::default() } -} -impl Deref for Module { - type Target = HashMap; - fn deref(&self) -> &Self::Target { - &self.0 + /// Does a variable exist in the module? + pub fn contains_variable(&self, name: &str) -> bool { + self.variables.contains_key(name) } -} -impl DerefMut for Module { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + /// Get the value of a module variable. + pub fn get_variable_value(&self, name: &str) -> Option { + self.get_variable(name).and_then(|v| v.try_cast::()) + } + + /// Get a module variable. + pub fn get_variable(&self, name: &str) -> Option { + self.variables.get(name).cloned() + } + + /// Get a mutable reference to a module variable. + pub fn get_variable_mut(&mut self, name: &str) -> Option<&mut Dynamic> { + self.variables.get_mut(name) + } + + /// Set a variable into the module. + /// + /// If there is an existing variable of the same name, it is replaced. + pub fn set_variable, T: Into>(&mut self, name: K, value: T) { + self.variables.insert(name.into(), value.into()); + } + + /// Get a mutable reference to a modules-qualified variable. + pub(crate) fn get_qualified_variable_mut( + &mut self, + name: &str, + modules: &StaticVec<(String, Position)>, + pos: Position, + ) -> Result<&mut Dynamic, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .get_variable_mut(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?) + } + + /// Does a sub-module exist in the module? + pub fn contains_sub_module(&self, name: &str) -> bool { + self.modules.contains_key(name) + } + + /// Get a sub-module. + pub fn get_sub_module(&self, name: &str) -> Option<&Module> { + self.modules.get(name) + } + + /// Get a mutable reference to a sub-module. + pub fn get_sub_module_mut(&mut self, name: &str) -> Option<&mut Module> { + self.modules.get_mut(name) + } + + /// Set a sub-module into the module. + /// + /// If there is an existing sub-module of the same name, it is replaced. + pub fn set_sub_module>(&mut self, name: K, sub_module: Module) { + self.modules.insert(name.into(), sub_module.into()); + } + + /// Get a mutable reference to a modules chain. + /// The first module is always skipped and assumed to be the same as `self`. + pub(crate) fn get_qualified_module_mut( + &mut self, + modules: &StaticVec<(String, Position)>, + ) -> Result<&mut Module, Box> { + let mut drain = modules.iter(); + drain.next().unwrap(); // Skip first module + + let mut module = self; + + for (id, id_pos) in drain { + module = module + .get_sub_module_mut(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; + } + + Ok(module) } } diff --git a/src/optimize.rs b/src/optimize.rs index 29d2f7fe..a2dc0048 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -13,9 +13,7 @@ use crate::token::Position; use crate::stdlib::{ boxed::Box, collections::HashMap, - rc::Rc, string::{String, ToString}, - sync::Arc, vec, vec::Vec, }; @@ -747,16 +745,13 @@ pub fn optimize_into_ast( .collect(), ); - AST( + AST::new( match level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { optimize(statements, engine, &scope, &fn_lib, level) } }, - #[cfg(feature = "sync")] - Arc::new(lib), - #[cfg(not(feature = "sync"))] - Rc::new(lib), + lib, ) } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 54c3f177..3734bd25 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -47,6 +47,7 @@ pub trait Package { } /// Type to store all functions in the package. +#[derive(Default)] pub struct PackageStore { /// All functions, keyed by a hash created from the function name and parameter types. pub functions: HashMap>, @@ -58,10 +59,7 @@ pub struct PackageStore { impl PackageStore { /// Create a new `PackageStore`. pub fn new() -> Self { - Self { - functions: HashMap::new(), - type_iterators: HashMap::new(), - } + Default::default() } } diff --git a/src/parser.rs b/src/parser.rs index 707b9809..9cac3bef 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -51,17 +51,44 @@ pub type ModuleRef = Option>>; /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct AST( - pub(crate) Vec, - #[cfg(feature = "sync")] pub(crate) Arc, - #[cfg(not(feature = "sync"))] pub(crate) Rc, + /// Global statements. + Vec, + /// Script-defined functions, wrapped in an `Arc` for shared access. + #[cfg(feature = "sync")] + Arc, + /// Script-defined functions, wrapped in an `Rc` for shared access. + #[cfg(not(feature = "sync"))] + Rc, ); impl AST { /// Create a new `AST`. - pub fn new() -> Self { - Default::default() + pub fn new(statements: Vec, fn_lib: FunctionsLib) -> Self { + #[cfg(feature = "sync")] + { + Self(statements, Arc::new(fn_lib)) + } + #[cfg(not(feature = "sync"))] + { + Self(statements, Rc::new(fn_lib)) + } + } + + /// Get the statements. + pub(crate) fn statements(&self) -> &Vec { + &self.0 + } + + /// Get a mutable reference to the statements. + pub(crate) fn statements_mut(&mut self) -> &mut Vec { + &mut self.0 + } + + /// Get the script-defined functions. + pub(crate) fn fn_lib(&self) -> &FunctionsLib { + self.1.as_ref() } /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version @@ -148,18 +175,6 @@ impl AST { } } -impl Default for AST { - fn default() -> Self { - #[cfg(feature = "sync")] - { - Self(vec![], Arc::new(FunctionsLib::new())) - } - #[cfg(not(feature = "sync"))] - { - Self(vec![], Rc::new(FunctionsLib::new())) - } - } -} impl Add for &AST { type Output = AST; @@ -191,13 +206,13 @@ pub enum ReturnType { } /// A type that encapsulates a local stack with variable names to simulate an actual runtime scope. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct Stack(Vec<(String, ScopeEntryType)>); impl Stack { /// Create a new `Stack`. pub fn new() -> Self { - Self(Vec::new()) + Default::default() } /// Find a variable by name in the `Stack`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, diff --git a/src/scope.rs b/src/scope.rs index da90f72a..56a0e6fb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -60,7 +60,7 @@ pub struct Entry<'a> { /// allowing for automatic _shadowing_. /// /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { @@ -77,7 +77,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn new() -> Self { - Self(Vec::new()) + Default::default() } /// Empty the Scope. @@ -177,7 +177,7 @@ impl<'a> Scope<'a> { name, EntryType::Module, Dynamic(Union::Module(Box::new(value))), - true, + false, ); } @@ -422,12 +422,6 @@ impl<'a> Scope<'a> { } } -impl Default for Scope<'_> { - fn default() -> Self { - Scope::new() - } -} - impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { fn extend>(&mut self, iter: T) { self.0 diff --git a/src/token.rs b/src/token.rs index 1a303810..29e7ce87 100644 --- a/src/token.rs +++ b/src/token.rs @@ -122,7 +122,7 @@ impl fmt::Display for Position { impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({}:{})", self.line, self.pos) + write!(f, "{}:{}", self.line, self.pos) } } diff --git a/src/utils.rs b/src/utils.rs index 4f51c7aa..4a9479f1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ use crate::stdlib::{ any::TypeId, + fmt, hash::{Hash, Hasher}, mem, vec::Vec, @@ -44,7 +45,7 @@ pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 { /// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This simplified implementation here is to avoid pulling in another crate. -#[derive(Debug, Clone)] +#[derive(Clone, Default)] pub struct StaticVec { /// Total number of values held. len: usize, @@ -57,16 +58,7 @@ pub struct StaticVec { impl StaticVec { /// Create a new `StaticVec`. pub fn new() -> Self { - Self { - len: 0, - list: [ - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ], - more: Vec::new(), - } + Default::default() } /// Push a new value to the end of this `StaticVec`. pub fn push>(&mut self, value: X) { @@ -86,7 +78,7 @@ impl StaticVec { let result = if self.len <= 0 { panic!("nothing to pop!") } else if self.len <= self.list.len() { - mem::replace(self.list.get_mut(self.len - 1).unwrap(), Default::default()) + mem::take(self.list.get_mut(self.len - 1).unwrap()) } else { self.more.pop().unwrap() }; @@ -126,3 +118,11 @@ impl StaticVec { self.list[..num].iter().chain(self.more.iter()) } } + +impl fmt::Debug for StaticVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ ")?; + self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; + write!(f, "]") + } +} diff --git a/tests/modules.rs b/tests/modules.rs index 5046791d..4f17d635 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1 +1,33 @@ -use rhai::{EvalAltResult, Scope, INT}; +#![cfg(not(feature = "no_module"))] +use rhai::{EvalAltResult, Module, Scope, INT}; + +#[test] +fn test_module() { + let mut module = Module::new(); + module.set_variable("kitty", 42 as INT); + + assert!(module.contains_variable("kitty")); + assert_eq!(module.get_variable_value::("kitty").unwrap(), 42); +} + +#[test] +fn test_sub_module() { + let mut module = Module::new(); + + let mut sub_module = Module::new(); + + let mut sub_module2 = Module::new(); + sub_module2.set_variable("kitty", 42 as INT); + + sub_module.set_sub_module("world", sub_module2); + module.set_sub_module("hello", sub_module); + + assert!(module.contains_sub_module("hello")); + let m = module.get_sub_module("hello").unwrap(); + + assert!(m.contains_sub_module("world")); + let m2 = m.get_sub_module("world").unwrap(); + + assert!(m2.contains_variable("kitty")); + assert_eq!(m2.get_variable_value::("kitty").unwrap(), 42); +} From aae9e43109409f5e9ae88be543e98e2f862b0a3a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 17:51:40 +0800 Subject: [PATCH 14/23] Implement module-qualified functions. --- src/engine.rs | 67 ++++++------ src/module.rs | 263 +++++++++++++++++++++++++++++++++++++++++++++-- src/parser.rs | 7 +- src/scope.rs | 2 +- tests/modules.rs | 53 +++++++--- 5 files changed, 329 insertions(+), 63 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 5be305ac..030fc85e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -395,7 +395,7 @@ fn search_scope<'a>( }; Ok(( - module.get_qualified_variable_mut(name, modules.as_ref(), pos)?, + module.get_qualified_var_mut(name, modules.as_ref(), pos)?, // Module variables are constant ScopeEntryType::Constant, )) @@ -469,13 +469,11 @@ impl Engine { } /// Universal method for calling functions either registered with the `Engine` or written in Rhai - // TODO - handle moduled function call pub(crate) fn call_fn_raw( &self, scope: Option<&mut Scope>, fn_lib: &FunctionsLib, fn_name: &str, - modules: &ModuleRef, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -643,7 +641,6 @@ impl Engine { &self, fn_lib: &FunctionsLib, fn_name: &str, - modules: &ModuleRef, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -651,28 +648,19 @@ impl Engine { ) -> Result> { match fn_name { // type_of - KEYWORD_TYPE_OF - if modules.is_none() - && args.len() == 1 - && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => - { + KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => { Ok(self.map_type_name(args[0].type_name()).to_string().into()) } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL - if modules.is_none() - && args.len() == 1 - && !self.has_override(fn_lib, KEYWORD_EVAL) => - { + KEYWORD_EVAL if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), pos, ))) } - // Normal method call - _ => self.call_fn_raw(None, fn_lib, fn_name, modules, args, def_val, pos, level), + _ => self.call_fn_raw(None, fn_lib, fn_name, args, def_val, pos, level), } } @@ -767,7 +755,7 @@ impl Engine { let def_val = def_val.as_deref(); // A function call is assumed to have side effects, so the value is changed // TODO - Remove assumption of side effects by checking whether the first parameter is &mut - self.exec_fn_call(fn_lib, fn_name, &None, &mut args, def_val, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_,_,_,_,_) => unreachable!(), @@ -788,13 +776,13 @@ impl Engine { Expr::Property(id, pos) if new_val.is_some() => { let fn_name = make_setter(id); let mut args = [obj, new_val.as_mut().unwrap()]; - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, true)) } // xxx.id Expr::Property(id, pos) => { let fn_name = make_getter(id); let mut args = [obj]; - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, false)) + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) } // {xxx:map}.idx_lhs[idx_expr] Expr::Index(dot_lhs, dot_rhs, pos) | @@ -824,7 +812,7 @@ impl Engine { let indexed_val = &mut (if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_getter(id); - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args[..1], None, *pos, 0)? + self.exec_fn_call(fn_lib, &fn_name, &mut args[..1], None, *pos, 0)? } else { // Syntax error return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -842,7 +830,7 @@ impl Engine { let fn_name = make_setter(id); // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).or_else(|err| match *err { + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), err => Err(Box::new(err)) @@ -1073,7 +1061,7 @@ impl Engine { let pos = rhs.position(); if self - .call_fn_raw(None, fn_lib, "==", &None, args, def_value, pos, level)? + .call_fn_raw(None, fn_lib, "==", args, def_value, pos, level)? .as_bool() .unwrap_or(false) { @@ -1213,12 +1201,26 @@ impl Engine { let mut args: Vec<_> = arg_values.iter_mut().collect(); - // eval - only in function call style - if fn_name.as_ref() == KEYWORD_EVAL - && modules.is_none() + if let Some(modules) = modules { + // Module-qualified function call + let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + + let (id, root_pos) = modules.get(0); // First module + + let module = scope.find_module(id).ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + })?; + match module.get_qualified_fn(fn_name, hash, modules.as_ref(), *pos) { + Ok(func) => func(&mut args, *pos) + .map_err(|err| EvalAltResult::set_position(err, *pos)), + Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), + Err(err) => Err(err), + } + } else if fn_name.as_ref() == KEYWORD_EVAL && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { + // eval - only in function call style let prev_len = scope.len(); // Evaluate the text string as a script @@ -1231,12 +1233,12 @@ impl Engine { state.always_search = true; } - return result; + result + } else { + // Normal function call - except for eval (handled above) + let def_value = def_val.as_deref(); + self.exec_fn_call(fn_lib, fn_name, &mut args, def_value, *pos, level) } - - // Normal function call - except for eval (handled above) - let def_value = def_val.as_deref(); - self.exec_fn_call(fn_lib, fn_name, modules, &mut args, def_value, *pos, level) } Expr::In(lhs, rhs, _) => { @@ -1472,8 +1474,9 @@ impl Engine { .try_cast::() { let mut module = Module::new(); - module.set_variable("kitty", "foo".to_string()); - module.set_variable("path", path); + module.set_var("kitty", "foo".to_string()); + module.set_var("path", path); + module.set_fn_1_mut("calc", |x: &mut String| Ok(x.len() as crate::parser::INT)); // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); diff --git a/src/module.rs b/src/module.rs index d7c48d3c..e7b2a51b 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,12 +1,14 @@ //! Module defining external-loaded modules for Rhai. use crate::any::{Dynamic, Variant}; -use crate::engine::{FnAny, FunctionsLib}; +use crate::calc_fn_hash; +use crate::engine::{FnAny, FnCallArgs, FunctionsLib}; use crate::result::EvalAltResult; use crate::token::Position; +use crate::token::Token; use crate::utils::StaticVec; -use crate::stdlib::{collections::HashMap, fmt, string::String}; +use crate::stdlib::{any::TypeId, collections::HashMap, fmt, iter::empty, mem, string::String}; /// An imported module, which may contain variables, sub-modules, /// external Rust functions, and script-defined functions. @@ -51,34 +53,34 @@ impl Module { } /// Does a variable exist in the module? - pub fn contains_variable(&self, name: &str) -> bool { + pub fn contains_var(&self, name: &str) -> bool { self.variables.contains_key(name) } /// Get the value of a module variable. - pub fn get_variable_value(&self, name: &str) -> Option { - self.get_variable(name).and_then(|v| v.try_cast::()) + pub fn get_var_value(&self, name: &str) -> Option { + self.get_var(name).and_then(|v| v.try_cast::()) } /// Get a module variable. - pub fn get_variable(&self, name: &str) -> Option { + pub fn get_var(&self, name: &str) -> Option { self.variables.get(name).cloned() } /// Get a mutable reference to a module variable. - pub fn get_variable_mut(&mut self, name: &str) -> Option<&mut Dynamic> { + pub fn get_var_mut(&mut self, name: &str) -> Option<&mut Dynamic> { self.variables.get_mut(name) } /// Set a variable into the module. /// /// If there is an existing variable of the same name, it is replaced. - pub fn set_variable, T: Into>(&mut self, name: K, value: T) { + pub fn set_var, T: Into>(&mut self, name: K, value: T) { self.variables.insert(name.into(), value.into()); } /// Get a mutable reference to a modules-qualified variable. - pub(crate) fn get_qualified_variable_mut( + pub(crate) fn get_qualified_var_mut( &mut self, name: &str, modules: &StaticVec<(String, Position)>, @@ -86,7 +88,7 @@ impl Module { ) -> Result<&mut Dynamic, Box> { Ok(self .get_qualified_module_mut(modules)? - .get_variable_mut(name) + .get_var_mut(name) .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?) } @@ -131,4 +133,245 @@ impl Module { Ok(module) } + + /// Does the particular Rust function exist in the module? + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + pub fn contains_fn(&self, hash: u64) -> bool { + self.functions.contains_key(&hash) + } + + /// Set a Rust function into the module, returning a hash key. + /// + /// If there is an existing Rust function of the same hash, it is replaced. + pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box) -> u64 { + let hash = calc_fn_hash(fn_name, params.iter().cloned()); + self.functions.insert(hash, func); + hash + } + + /// Set a Rust function taking no parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_0>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn() -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn() -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash(fn_name, empty()); + let f = move |_: &mut FnCallArgs, _: Position| func().map(|v| v.into()); + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking one parameter into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_1>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(A) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); + + let f = move |args: &mut FnCallArgs, _: Position| { + func(mem::take(args[0]).cast::()).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking one mutable parameter into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_1_mut>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); + + let f = move |args: &mut FnCallArgs, _: Position| { + func(args[0].downcast_mut::().unwrap()).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking two parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_2>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::()].iter().cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let a = mem::take(args[0]).cast::(); + let b = mem::take(args[1]).cast::(); + + func(a, b).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking two parameters (the first one mutable) into the module, + /// returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_2_mut>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> Result> + + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::()].iter().cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let b = mem::take(args[1]).cast::(); + let a = args[0].downcast_mut::().unwrap(); + + func(a, b).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking three parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_3< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Into, + >( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::(), TypeId::of::()] + .iter() + .cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let a = mem::take(args[0]).cast::(); + let b = mem::take(args[1]).cast::(); + let c = mem::take(args[2]).cast::(); + + func(a, b, c).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking three parameters (the first one mutable) into the module, + /// returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_3_mut< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Into, + >( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> Result> + + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::(), TypeId::of::()] + .iter() + .cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let b = mem::take(args[1]).cast::(); + let c = mem::take(args[2]).cast::(); + let a = args[0].downcast_mut::().unwrap(); + + func(a, b, c).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Get a Rust function. + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + pub fn get_fn(&self, hash: u64) -> Option<&Box> { + self.functions.get(&hash) + } + + /// Get a modules-qualified function. + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + pub(crate) fn get_qualified_fn( + &mut self, + name: &str, + hash: u64, + modules: &StaticVec<(String, Position)>, + pos: Position, + ) -> Result<&Box, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .get_fn(hash) + .ok_or_else(|| { + let mut fn_name: String = Default::default(); + + modules.iter().for_each(|(n, _)| { + fn_name.push_str(n); + fn_name.push_str(Token::DoubleColon.syntax().as_ref()); + }); + + fn_name.push_str(name); + + Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos)) + })?) + } } diff --git a/src/parser.rs b/src/parser.rs index 9cac3bef..4cb663d8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -589,13 +589,8 @@ impl Expr { _ => false, }, - Self::Variable(_, None, _, _) => match token { - Token::LeftBracket | Token::LeftParen => true, - #[cfg(not(feature = "no_module"))] - Token::DoubleColon => true, - _ => false, - }, Self::Variable(_, _, _, _) => match token { + Token::LeftBracket | Token::LeftParen => true, #[cfg(not(feature = "no_module"))] Token::DoubleColon => true, _ => false, diff --git a/src/scope.rs b/src/scope.rs index 56a0e6fb..a20afd44 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -172,7 +172,7 @@ impl<'a> Scope<'a> { /// Add (push) a new module to the Scope. /// /// Modules are used for accessing member variables, functions and plugins under a namespace. - pub(crate) fn push_module>>(&mut self, name: K, value: Module) { + pub fn push_module>>(&mut self, name: K, value: Module) { self.push_dynamic_value( name, EntryType::Module, diff --git a/tests/modules.rs b/tests/modules.rs index 4f17d635..5a62d942 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,33 +1,58 @@ #![cfg(not(feature = "no_module"))] -use rhai::{EvalAltResult, Module, Scope, INT}; +use rhai::{Engine, EvalAltResult, Module, Scope, INT}; #[test] fn test_module() { let mut module = Module::new(); - module.set_variable("kitty", 42 as INT); + module.set_var("answer", 42 as INT); - assert!(module.contains_variable("kitty")); - assert_eq!(module.get_variable_value::("kitty").unwrap(), 42); + assert!(module.contains_var("answer")); + assert_eq!(module.get_var_value::("answer").unwrap(), 42); } #[test] -fn test_sub_module() { +fn test_sub_module() -> Result<(), Box> { let mut module = Module::new(); let mut sub_module = Module::new(); let mut sub_module2 = Module::new(); - sub_module2.set_variable("kitty", 42 as INT); + sub_module2.set_var("answer", 41 as INT); + let hash = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); - sub_module.set_sub_module("world", sub_module2); - module.set_sub_module("hello", sub_module); + sub_module.set_sub_module("universe", sub_module2); + module.set_sub_module("life", sub_module); - assert!(module.contains_sub_module("hello")); - let m = module.get_sub_module("hello").unwrap(); + assert!(module.contains_sub_module("life")); + let m = module.get_sub_module("life").unwrap(); - assert!(m.contains_sub_module("world")); - let m2 = m.get_sub_module("world").unwrap(); + assert!(m.contains_sub_module("universe")); + let m2 = m.get_sub_module("universe").unwrap(); - assert!(m2.contains_variable("kitty")); - assert_eq!(m2.get_variable_value::("kitty").unwrap(), 42); + assert!(m2.contains_var("answer")); + assert!(m2.contains_fn(hash)); + + assert_eq!(m2.get_var_value::("answer").unwrap(), 41); + + let mut engine = Engine::new(); + let mut scope = Scope::new(); + + scope.push_module("question", module); + + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "question::life::universe::answer + 1" + )?, + 42 + ); + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "question::life::universe::inc(question::life::universe::answer)" + )?, + 42 + ); + + Ok(()) } From ff86ae1bd8dea2a18dda6403940c09f4bd736ca0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 18:26:08 +0800 Subject: [PATCH 15/23] Update README with module functions API. --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index bde4bbcd..72011fea 100644 --- a/README.md +++ b/README.md @@ -2012,6 +2012,8 @@ with `export` statements that _exports_ certain global variables and functions a Everything exported as part of a module is constant and read-only. +### Importing modules + A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++. ```rust @@ -2039,6 +2041,33 @@ crypto::encrypt(others); // <- this causes a run-time error because the 'cryp // is no longer available! ``` +### Creating custom modules from Rust + +To load a custom module into an [`Engine`], first create a `Module` type, add variables/functions into it, +then finally push it into a custom [`Scope`]. This has the equivalent effect of putting an `import` statement +at the beginning of any script run. + +```rust +use rhai::{Engine, Scope, Module, i64}; + +let mut engine = Engine::new(); +let mut scope = Scope::new(); + +let mut module = Module::new(); // new module +module.set_var("answer", 41_i64); // variable 'answer' under module +module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions + +// Push the module into the custom scope under the name 'question' +// This is equivalent to 'import "..." as question;' +scope.push_module("question", module); + +// Use module-qualified variables +engine.eval_expression_with_scope::(&scope, "question::answer + 1")? == 42; + +// Call module-qualified functions +engine.eval_expression_with_scope::(&scope, "question::inc(question::answer)")? == 42; +``` + Script optimization =================== From f081040767ef95176fa7dab8901ac2ec562853e2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 20:38:48 +0800 Subject: [PATCH 16/23] Add support for custom type indexers. --- README.md | 37 ++++++++++++++++++++++++++++- src/api.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++- src/engine.rs | 53 +++++++++++++++++++++++++++--------------- src/result.rs | 4 ++-- tests/get_set.rs | 13 ++++++++++- 5 files changed, 144 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b453b04c..55b7b1c3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Rhai's current features set: * Easy-to-use language similar to JS+Rust * Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods), - including [getter/setter](#getters-and-setters)/[methods](#members-and-methods) + including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers) * Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust * Freely pass variables/constants into a script via an external [`Scope`] * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) @@ -923,6 +923,41 @@ let result = engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?; println!("Answer: {}", result); // prints 42 ``` +Indexers +-------- + +Custom types can also expose an _indexer_ by registering an indexer function. +A custom with an indexer function defined can use the bracket '`[]`' notation to get a property value +(but not update it - indexers are read-only). + +```rust +#[derive(Clone)] +struct TestStruct { + fields: Vec +} + +impl TestStruct { + fn get_field(&mut self, index: i64) -> i64 { + self.field + } + + fn new() -> Self { + TestStruct { field: vec![1, 2, 42, 4, 5] } + } +} + +let engine = Engine::new(); + +engine.register_type::(); + +engine.register_fn("new_ts", TestStruct::new); +engine.register_indexer(TestStruct::get_field); + +let result = engine.eval::("let a = new_ts(); a[2]")?; + +println!("Answer: {}", result); // prints 42 +``` + Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set` are not available when the [`no_object`] feature is turned on. diff --git a/src/api.rs b/src/api.rs index 9377bc68..addcbe45 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,7 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{make_getter, make_setter, Engine, Map, State}; +use crate::engine::{make_getter, make_setter, Engine, Map, State, FUNC_INDEXER}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; @@ -42,6 +42,16 @@ pub trait ObjectSetCallback: Fn(&mut T, U) + 'static {} #[cfg(not(feature = "sync"))] impl ObjectSetCallback for F {} +#[cfg(feature = "sync")] +pub trait ObjectIndexerCallback: Fn(&mut T, X) -> U + Send + Sync + 'static {} +#[cfg(feature = "sync")] +impl U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback for F {} + +#[cfg(not(feature = "sync"))] +pub trait ObjectIndexerCallback: Fn(&mut T, X) -> U + 'static {} +#[cfg(not(feature = "sync"))] +impl U + 'static, T, X, U> ObjectIndexerCallback for F {} + #[cfg(feature = "sync")] pub trait IteratorCallback: Fn(Dynamic) -> Box> + Send + Sync + 'static @@ -299,6 +309,54 @@ impl Engine { self.register_set(name, set_fn); } + /// Register an indexer function for a registered type with the `Engine`. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, RegisterFn}; + /// + /// let mut engine = Engine::new(); + /// + /// // Register the custom type. + /// engine.register_type::(); + /// + /// engine.register_fn("new_ts", TestStruct::new); + /// + /// // Register an indexer. + /// engine.register_indexer(TestStruct::get_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_index"))] + pub fn register_indexer(&mut self, callback: F) + where + T: Variant + Clone, + U: Variant + Clone, + X: Variant + Clone, + F: ObjectIndexerCallback, + { + self.register_fn(FUNC_INDEXER, callback); + } + /// Compile a string into an `AST`, which can be used later for evaluation. /// /// # Example diff --git a/src/engine.rs b/src/engine.rs index 7e849c7a..ae9cd8d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -74,6 +74,7 @@ pub const KEYWORD_EVAL: &str = "eval"; pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_GETTER: &str = "get$"; pub const FUNC_SETTER: &str = "set$"; +pub const FUNC_INDEXER: &str = "$index$"; /// A type that encapsulates a mutation target for an expression with side effects. enum Target<'a> { @@ -577,6 +578,11 @@ impl Engine { }); } + // Return default value (if any) + if let Some(val) = def_val { + return Ok(val.clone()); + } + // Getter function not found? if let Some(prop) = extract_prop_from_getter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -593,17 +599,20 @@ impl Engine { ))); } - // Return default value (if any) - if let Some(val) = def_val { - return Ok(val.clone()); - } - - // Raise error let types_list: Vec<_> = args .iter() .map(|name| self.map_type_name(name.type_name())) .collect(); + // Getter function not found? + if fn_name == FUNC_INDEXER { + return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!("[]({})", types_list.join(", ")), + pos, + ))); + } + + // Raise error Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!("{} ({})", fn_name, types_list.join(", ")), pos, @@ -787,20 +796,20 @@ impl Engine { Expr::Index(idx, idx_rhs, pos) => { let is_index = matches!(rhs, Expr::Index(_,_,_)); - let indexed_val = self.get_indexed_mut(obj, idx_val, idx.position(), op_pos, false)?; + let indexed_val = self.get_indexed_mut(fn_lib, obj, idx_val, idx.position(), op_pos, false)?; self.eval_dot_index_chain_helper( fn_lib, indexed_val, idx_rhs.as_ref(), idx_values, is_index, *pos, level, new_val ) } // xxx[rhs] = new_val _ if new_val.is_some() => { - let mut indexed_val = self.get_indexed_mut(obj, idx_val, rhs.position(), op_pos, true)?; + let mut indexed_val = self.get_indexed_mut(fn_lib, obj, idx_val, rhs.position(), op_pos, true)?; indexed_val.set_value(new_val.unwrap(), rhs.position())?; Ok((Default::default(), true)) } // xxx[rhs] _ => self - .get_indexed_mut(obj, idx_val, rhs.position(), op_pos, false) + .get_indexed_mut(fn_lib, obj, idx_val, rhs.position(), op_pos, false) .map(|v| (v.clone_into_dynamic(), false)) } } else { @@ -818,14 +827,14 @@ impl Engine { // {xxx:map}.id = ??? Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { let mut indexed_val = - self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, true)?; + self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, true)?; indexed_val.set_value(new_val.unwrap(), rhs.position())?; Ok((Default::default(), true)) } // {xxx:map}.id Expr::Property(id, pos) if obj.is::() => { let indexed_val = - self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)?; + self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)?; Ok((indexed_val.clone_into_dynamic(), false)) } // xxx.id = ??? @@ -847,7 +856,7 @@ impl Engine { let is_index = matches!(rhs, Expr::Index(_,_,_)); let indexed_val = if let Expr::Property(id, pos) = dot_lhs.as_ref() { - self.get_indexed_mut(obj, id.to_string().into(), *pos, op_pos, false)? + self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)? } else { // Syntax error return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -1010,8 +1019,9 @@ impl Engine { /// Get the value at the indexed position of a base type fn get_indexed_mut<'a>( &self, + fn_lib: &FunctionsLib, val: &'a mut Dynamic, - idx: Dynamic, + mut idx: Dynamic, idx_pos: Position, op_pos: Position, create: bool, @@ -1084,11 +1094,18 @@ impl Engine { } } - // Error - cannot be indexed - _ => Err(Box::new(EvalAltResult::ErrorIndexingType( - type_name.to_string(), - op_pos, - ))), + _ => { + let args = &mut [val, &mut idx]; + self.exec_fn_call(fn_lib, FUNC_INDEXER, args, None, op_pos, 0) + .map(|v| v.into()) + .map_err(|_| { + Box::new(EvalAltResult::ErrorIndexingType( + // Error - cannot be indexed + type_name.to_string(), + op_pos, + )) + }) + } } } diff --git a/src/result.rs b/src/result.rs index 17fe389a..c88342ee 100644 --- a/src/result.rs +++ b/src/result.rs @@ -47,7 +47,7 @@ pub enum EvalAltResult { /// String indexing out-of-bounds. /// Wrapped values are the current number of characters in the string and the index number. ErrorStringBounds(usize, INT, Position), - /// Trying to index into a type that is not an array, an object map, or a string. + /// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined. ErrorIndexingType(String, Position), /// Trying to index into an array or string with an index that is not `i64`. ErrorNumericIndexExpr(Position), @@ -104,7 +104,7 @@ impl EvalAltResult { } Self::ErrorStringIndexExpr(_) => "Indexing into an object map expects a string index", Self::ErrorIndexingType(_, _) => { - "Indexing can only be performed on an array, an object map, or a string" + "Indexing can only be performed on an array, an object map, a string, or a type with an indexer function defined" } Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" diff --git a/tests/get_set.rs b/tests/get_set.rs index 633192ad..57712304 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -8,6 +8,7 @@ fn test_get_set() -> Result<(), Box> { struct TestStruct { x: INT, y: INT, + array: Vec, } impl TestStruct { @@ -24,7 +25,11 @@ fn test_get_set() -> Result<(), Box> { } fn new() -> Self { - TestStruct { x: 1, y: 0 } + TestStruct { + x: 1, + y: 0, + array: vec![1, 2, 3, 4, 5], + } } } @@ -37,10 +42,16 @@ fn test_get_set() -> Result<(), Box> { engine.register_fn("add", |value: &mut INT| *value += 41); engine.register_fn("new_ts", TestStruct::new); + #[cfg(not(feature = "no_index"))] + engine.register_indexer(|value: &mut TestStruct, index: INT| value.array[index as usize]); + assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); assert_eq!(engine.eval::("let a = new_ts(); a.x.add(); a.x")?, 42); assert_eq!(engine.eval::("let a = new_ts(); a.y.add(); a.y")?, 0); + #[cfg(not(feature = "no_index"))] + assert_eq!(engine.eval::("let a = new_ts(); a[3]")?, 4); + Ok(()) } From 87e0e783bc5ea2a45fb763f48374b85d423b727c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 20:54:56 +0800 Subject: [PATCH 17/23] Display timestamp. --- src/any.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/any.rs b/src/any.rs index 4ae08f38..78fa76d9 100644 --- a/src/any.rs +++ b/src/any.rs @@ -206,6 +206,9 @@ impl fmt::Display for Dynamic { Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), Union::Module(value) => write!(f, "{:?}", value), + + #[cfg(not(feature = "no_std"))] + Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(_) => write!(f, "?"), } } @@ -224,6 +227,9 @@ impl fmt::Debug for Dynamic { Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), Union::Module(value) => write!(f, "{:?}", value), + + #[cfg(not(feature = "no_std"))] + Union::Variant(value) if value.is::() => write!(f, ""), Union::Variant(_) => write!(f, ""), } } From 036c054ba86147ebe59090468d3c3b38b03f6dc6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 20:55:06 +0800 Subject: [PATCH 18/23] Fix indexer example. --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab692bd1..ae2807b5 100644 --- a/README.md +++ b/README.md @@ -941,11 +941,11 @@ struct TestStruct { impl TestStruct { fn get_field(&mut self, index: i64) -> i64 { - self.field + self.fields[index as usize] } fn new() -> Self { - TestStruct { field: vec![1, 2, 42, 4, 5] } + TestStruct { fields: vec![1, 2, 42, 4, 5] } } } @@ -961,8 +961,9 @@ let result = engine.eval::("let a = new_ts(); a[2]")?; println!("Answer: {}", result); // prints 42 ``` -Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set` -are not available when the [`no_object`] feature is turned on. +Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set` +and `register_indexer` are not available when the [`no_object`] feature is turned on. +`register_indexer` is also not available when the [`no_index`] feature is turned on. `Scope` - Initializing and maintaining state ------------------------------------------- From 82e1af7acd3d0d190efca99a6c158a62937e436a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 23:57:25 +0800 Subject: [PATCH 19/23] Implement module resolvers. --- README.md | 16 +++ src/engine.rs | 63 +++++++++--- src/lib.rs | 7 +- src/module.rs | 249 ++++++++++++++++++++++++++++++++++++----------- src/scope.rs | 7 +- tests/modules.rs | 29 +++++- 6 files changed, 295 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index ae2807b5..5de326f1 100644 --- a/README.md +++ b/README.md @@ -579,6 +579,7 @@ A number of traits, under the `rhai::` module namespace, provide additional func | `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | | `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, Box>` | `register_result_fn` | | `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | +| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | Working with functions ---------------------- @@ -2104,6 +2105,21 @@ engine.eval_expression_with_scope::(&scope, "question::answer + 1")? == 42; engine.eval_expression_with_scope::(&scope, "question::inc(question::answer)")? == 42; ``` +### Module resolvers + +When encountering an `import` statement, Rhai attempts to _resolve_ the module based on the path string. +_Module Resolvers_ are service types that implement the [`ModuleResolver`](#traits) trait. +There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` +which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. + +Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. + +| Module Resolver | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function. | +| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | +| `NullModuleResolver` | The default module resolution service under the [`no_std`] feature. Always returns an `EvalAltResult::ErrorModuleNotFound` error. | + Script optimization =================== diff --git a/src/engine.rs b/src/engine.rs index d3702339..c68efd95 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::error::ParseErrorType; -use crate::module::Module; +use crate::module::{resolvers, Module, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST}; @@ -260,6 +260,10 @@ pub struct Engine { /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: HashMap>, + + /// A module resolution service. + pub(crate) module_resolver: Box, + /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, @@ -293,6 +297,13 @@ impl Default for Engine { packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT), type_iterators: Default::default(), + + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_std"))] + module_resolver: Box::new(resolvers::FileModuleResolver::new()), + #[cfg(any(feature = "no_std", feature = "no_module"))] + module_resolver: Box::new(resolvers::NullModuleResolver::new()), + type_names: Default::default(), // default print/debug implementations @@ -427,6 +438,13 @@ impl Engine { packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), type_iterators: Default::default(), + + #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_std"))] + module_resolver: Box::new(resolvers::FileModuleResolver::new()), + #[cfg(any(feature = "no_std", feature = "no_module"))] + module_resolver: Box::new(resolvers::NullModuleResolver::new()), + type_names: Default::default(), print: Box::new(|_| {}), debug: Box::new(|_| {}), @@ -455,7 +473,7 @@ impl Engine { self.packages.insert(0, package); } - /// Control whether and how the `Engine` will optimize an AST after compilation + /// Control whether and how the `Engine` will optimize an AST after compilation. /// /// Not available under the `no_optimize` feature. #[cfg(not(feature = "no_optimize"))] @@ -469,7 +487,15 @@ impl Engine { self.max_call_stack_depth = levels } - /// Universal method for calling functions either registered with the `Engine` or written in Rhai + /// Set the module resolution service used by the `Engine`. + /// + /// Not available under the `no_module` feature. + #[cfg(not(feature = "no_module"))] + pub fn set_module_resolver(&mut self, resolver: impl ModuleResolver + 'static) { + self.module_resolver = Box::new(resolver); + } + + /// Universal method for calling functions either registered with the `Engine` or written in Rhai. pub(crate) fn call_fn_raw( &self, scope: Option<&mut Scope>, @@ -1220,18 +1246,29 @@ impl Engine { if let Some(modules) = modules { // Module-qualified function call - let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + let modules = modules.as_ref(); let (id, root_pos) = modules.get(0); // First module let module = scope.find_module(id).ok_or_else(|| { Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) })?; - match module.get_qualified_fn(fn_name, hash, modules.as_ref(), *pos) { - Ok(func) => func(&mut args, *pos) - .map_err(|err| EvalAltResult::set_position(err, *pos)), - Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), - Err(err) => Err(err), + + // First search in script-defined functions (can override built-in) + if let Some(fn_def) = + module.get_qualified_fn_lib(fn_name, args.len(), modules)? + { + self.call_fn_from_lib(None, fn_lib, fn_def, &mut args, *pos, level) + } else { + // Then search in Rust functions + let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + + match module.get_qualified_fn(fn_name, hash, modules, *pos) { + Ok(func) => func(&mut args, *pos) + .map_err(|err| EvalAltResult::set_position(err, *pos)), + Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), + Err(err) => Err(err), + } } } else if fn_name.as_ref() == KEYWORD_EVAL && args.len() == 1 @@ -1486,14 +1523,14 @@ impl Engine { // Import statement Stmt::Import(expr, name, _) => { + #[cfg(feature = "no_module")] + unreachable!(); + if let Some(path) = self .eval_expr(scope, state, fn_lib, expr, level)? .try_cast::() { - let mut module = Module::new(); - module.set_var("kitty", "foo".to_string()); - module.set_var("path", path); - module.set_fn_1_mut("calc", |x: &mut String| Ok(x.len() as crate::parser::INT)); + let module = self.module_resolver.resolve(self, &path)?; // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); diff --git a/src/lib.rs b/src/lib.rs index ddcdee28..1bdf8fd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,12 @@ pub use engine::Map; pub use parser::FLOAT; #[cfg(not(feature = "no_module"))] -pub use module::Module; +pub use module::{Module, ModuleResolver}; + +#[cfg(not(feature = "no_module"))] +pub mod module_resolvers { + pub use crate::module::resolvers::*; +} #[cfg(not(feature = "no_optimize"))] pub use optimize::OptimizationLevel; diff --git a/src/module.rs b/src/module.rs index e7b2a51b..32d27395 100644 --- a/src/module.rs +++ b/src/module.rs @@ -2,28 +2,44 @@ use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; -use crate::engine::{FnAny, FnCallArgs, FunctionsLib}; +use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib}; +use crate::parser::FnDef; use crate::result::EvalAltResult; +use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; use crate::token::Token; use crate::utils::StaticVec; -use crate::stdlib::{any::TypeId, collections::HashMap, fmt, iter::empty, mem, string::String}; +use crate::stdlib::{ + any::TypeId, collections::HashMap, fmt, iter::empty, mem, rc::Rc, string::String, sync::Arc, +}; + +/// A trait that encapsulates a module resolution service. +pub trait ModuleResolver { + /// Resolve a module based on a path string. + fn resolve(&self, engine: &Engine, path: &str) -> Result>; +} /// An imported module, which may contain variables, sub-modules, /// external Rust functions, and script-defined functions. /// /// Not available under the `no_module` feature. -#[derive(Default)] +#[derive(Default, Clone)] pub struct Module { /// Sub-modules. modules: HashMap, /// Module variables, including sub-modules. variables: HashMap, + /// External Rust functions. - functions: HashMap>, + #[cfg(not(feature = "sync"))] + functions: HashMap>>, + /// External Rust functions. + #[cfg(feature = "sync")] + functions: HashMap>>, + /// Script-defined functions. - lib: FunctionsLib, + fn_lib: FunctionsLib, } impl fmt::Debug for Module { @@ -33,19 +49,11 @@ impl fmt::Debug for Module { "", self.variables, self.functions.len(), - self.lib.len() + self.fn_lib.len() ) } } -impl Clone for Module { - fn clone(&self) -> Self { - // `Module` implements `Clone` so it can fit inside a `Dynamic` - // but we should never actually clone it. - unimplemented!() - } -} - impl Module { /// Create a new module. pub fn new() -> Self { @@ -147,7 +155,13 @@ impl Module { /// If there is an existing Rust function of the same hash, it is replaced. pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box) -> u64 { let hash = calc_fn_hash(fn_name, params.iter().cloned()); - self.functions.insert(hash, func); + + #[cfg(not(feature = "sync"))] + self.functions.insert(hash, Rc::new(func)); + + #[cfg(feature = "sync")] + self.functions.insert(hash, Arc::new(func)); + hash } @@ -163,10 +177,8 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash(fn_name, empty()); let f = move |_: &mut FnCallArgs, _: Position| func().map(|v| v.into()); - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn(fn_name, &[], Box::new(f)) } /// Set a Rust function taking one parameter into the module, returning a hash key. @@ -181,13 +193,10 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash(fn_name, [TypeId::of::
()].iter().cloned()); - let f = move |args: &mut FnCallArgs, _: Position| { func(mem::take(args[0]).cast::()).map(|v| v.into()) }; - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn(fn_name, &[TypeId::of::()], Box::new(f)) } /// Set a Rust function taking one mutable parameter into the module, returning a hash key. @@ -202,13 +211,10 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); - let f = move |args: &mut FnCallArgs, _: Position| { func(args[0].downcast_mut::().unwrap()).map(|v| v.into()) }; - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn(fn_name, &[TypeId::of::()], Box::new(f)) } /// Set a Rust function taking two parameters into the module, returning a hash key. @@ -223,19 +229,17 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash( - fn_name, - [TypeId::of::(), TypeId::of::()].iter().cloned(), - ); - let f = move |args: &mut FnCallArgs, _: Position| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); func(a, b).map(|v| v.into()) }; - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn( + fn_name, + &[TypeId::of::(), TypeId::of::()], + Box::new(f), + ) } /// Set a Rust function taking two parameters (the first one mutable) into the module, @@ -252,19 +256,17 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash( - fn_name, - [TypeId::of::(), TypeId::of::()].iter().cloned(), - ); - let f = move |args: &mut FnCallArgs, _: Position| { let b = mem::take(args[1]).cast::(); let a = args[0].downcast_mut::().unwrap(); func(a, b).map(|v| v.into()) }; - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn( + fn_name, + &[TypeId::of::(), TypeId::of::()], + Box::new(f), + ) } /// Set a Rust function taking three parameters into the module, returning a hash key. @@ -284,13 +286,6 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash( - fn_name, - [TypeId::of::(), TypeId::of::(), TypeId::of::()] - .iter() - .cloned(), - ); - let f = move |args: &mut FnCallArgs, _: Position| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); @@ -298,8 +293,11 @@ impl Module { func(a, b, c).map(|v| v.into()) }; - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn( + fn_name, + &[TypeId::of::(), TypeId::of::(), TypeId::of::()], + Box::new(f), + ) } /// Set a Rust function taking three parameters (the first one mutable) into the module, @@ -321,13 +319,6 @@ impl Module { + Sync + 'static, ) -> u64 { - let hash = calc_fn_hash( - fn_name, - [TypeId::of::(), TypeId::of::(), TypeId::of::()] - .iter() - .cloned(), - ); - let f = move |args: &mut FnCallArgs, _: Position| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); @@ -335,8 +326,11 @@ impl Module { func(a, b, c).map(|v| v.into()) }; - self.functions.insert(hash, Box::new(f)); - hash + self.set_fn( + fn_name, + &[TypeId::of::(), TypeId::of::(), TypeId::of::()], + Box::new(f), + ) } /// Get a Rust function. @@ -344,7 +338,7 @@ impl Module { /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. pub fn get_fn(&self, hash: u64) -> Option<&Box> { - self.functions.get(&hash) + self.functions.get(&hash).map(|v| v.as_ref()) } /// Get a modules-qualified function. @@ -374,4 +368,141 @@ impl Module { Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos)) })?) } + + /// Get a script-defined function. + pub fn get_fn_lib(&self) -> &FunctionsLib { + &self.fn_lib + } + + /// Get a modules-qualified functions library. + pub(crate) fn get_qualified_fn_lib( + &mut self, + name: &str, + args: usize, + modules: &StaticVec<(String, Position)>, + ) -> Result, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .fn_lib + .get_function(name, args)) + } +} + +pub mod resolvers { + use super::*; + + #[cfg(not(feature = "no_std"))] + use crate::stdlib::path::PathBuf; + + /// A module resolution service that loads module script files (assumed `.rhai` extension). + #[cfg(not(feature = "no_std"))] + #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] + pub struct FileModuleResolver(PathBuf); + + #[cfg(not(feature = "no_std"))] + impl FileModuleResolver { + /// Create a new `FileModuleResolver` with a specific base path. + pub fn new_with_path(path: PathBuf) -> Self { + Self(path) + } + /// Create a new `FileModuleResolver` with the current directory as base path. + pub fn new() -> Self { + Default::default() + } + } + + #[cfg(not(feature = "no_std"))] + impl Default for FileModuleResolver { + fn default() -> Self { + Self::new_with_path(".".into()) + } + } + + #[cfg(not(feature = "no_std"))] + impl ModuleResolver for FileModuleResolver { + fn resolve(&self, engine: &Engine, path: &str) -> Result> { + // Load the script file (attaching `.rhai`) + let mut file_path = self.0.clone(); + file_path.push(path); + file_path.set_extension("rhai"); + + // Compile it + let ast = engine.compile_file(file_path)?; + + // Use new scope + let mut scope = Scope::new(); + + // Run the script + engine.eval_ast_with_scope_raw(&mut scope, &ast)?; + + // Create new module + let mut module = Module::new(); + + // Variables left in the scope become module variables + for entry in scope.into_iter() { + match entry.typ { + ScopeEntryType::Normal | ScopeEntryType::Constant => { + module + .variables + .insert(entry.name.into_owned(), entry.value); + } + ScopeEntryType::Module => { + module + .modules + .insert(entry.name.into_owned(), entry.value.cast::()); + } + } + } + + module.fn_lib = FunctionsLib::new().merge(ast.fn_lib()); + + Ok(module) + } + } + + /// A module resolution service that serves modules added into it. + #[derive(Debug, Clone, Default)] + pub struct StaticModuleResolver(HashMap); + + impl StaticModuleResolver { + /// Create a new `StaticModuleResolver`. + pub fn new() -> Self { + Default::default() + } + /// Add a named module. + pub fn add_module(&mut self, name: &str, module: Module) { + self.0.insert(name.to_string(), module); + } + } + + impl ModuleResolver for StaticModuleResolver { + fn resolve(&self, _: &Engine, path: &str) -> Result> { + self.0.get(path).cloned().ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound( + path.to_string(), + Position::none(), + )) + }) + } + } + + /// A module resolution service that always returns a not-found error. + #[derive(Debug, Clone, PartialEq, Eq, Copy, Default)] + pub struct NullModuleResolver; + + impl NullModuleResolver { + /// Create a new `NullModuleResolver`. + pub fn new() -> Self { + Default::default() + } + } + + impl ModuleResolver for NullModuleResolver { + fn resolve(&self, _: &Engine, path: &str) -> Result> { + Err(Box::new(EvalAltResult::ErrorModuleNotFound( + path.to_string(), + Position::none(), + ))) + } + } } diff --git a/src/scope.rs b/src/scope.rs index a20afd44..94035dbb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -5,7 +5,7 @@ use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; -use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; +use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -416,6 +416,11 @@ impl<'a> Scope<'a> { (&mut entry.value, entry.typ) } + /// Get an iterator to entries in the Scope. + pub(crate) fn into_iter(self) -> impl Iterator> { + self.0.into_iter() + } + /// Get an iterator to entries in the Scope. pub(crate) fn iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order diff --git a/tests/modules.rs b/tests/modules.rs index 5a62d942..e1382764 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_module"))] -use rhai::{Engine, EvalAltResult, Module, Scope, INT}; +use rhai::{module_resolvers, Engine, EvalAltResult, Module, Scope, INT}; #[test] fn test_module() { @@ -11,7 +11,7 @@ fn test_module() { } #[test] -fn test_sub_module() -> Result<(), Box> { +fn test_module_sub_module() -> Result<(), Box> { let mut module = Module::new(); let mut sub_module = Module::new(); @@ -56,3 +56,28 @@ fn test_sub_module() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_module_resolver() -> Result<(), Box> { + let mut resolver = module_resolvers::StaticModuleResolver::new(); + + let mut module = Module::new(); + module.set_var("answer", 42 as INT); + + resolver.add_module("hello", module); + + let mut engine = Engine::new(); + engine.set_module_resolver(resolver); + + assert_eq!( + engine.eval::( + r#" + import "hello" as h; + h::answer + "# + )?, + 42 + ); + + Ok(()) +} From c9571d375ad3c3a1fca017397e6ccc3b4c305532 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 May 2020 00:09:04 +0800 Subject: [PATCH 20/23] Add position to script error. --- README.md | 7 +++++++ src/api.rs | 18 ++++++++++++++---- src/engine.rs | 5 ++++- src/result.rs | 14 +++++++++----- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5de326f1..d8c70be3 100644 --- a/README.md +++ b/README.md @@ -2120,6 +2120,13 @@ Built-in module resolvers are grouped under the `rhai::module_resolvers` module | `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | | `NullModuleResolver` | The default module resolution service under the [`no_std`] feature. Always returns an `EvalAltResult::ErrorModuleNotFound` error. | +An [`Engine`]'s module resolver is set via a call to `set_module_resolver`: + +```rust +// Use the 'NullModuleResolver' +engine.set_module_resolver(rhai::module_resolvers::NullModuleResolver::new()); +``` + Script optimization =================== diff --git a/src/api.rs b/src/api.rs index 13c3a399..abc4d37f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -437,13 +437,23 @@ impl Engine { /// Read the contents of a file into a string. #[cfg(not(feature = "no_std"))] fn read_file(path: PathBuf) -> Result> { - let mut f = File::open(path.clone()) - .map_err(|err| Box::new(EvalAltResult::ErrorReadingScriptFile(path.clone(), err)))?; + let mut f = File::open(path.clone()).map_err(|err| { + Box::new(EvalAltResult::ErrorReadingScriptFile( + path.clone(), + Position::none(), + err, + )) + })?; let mut contents = String::new(); - f.read_to_string(&mut contents) - .map_err(|err| Box::new(EvalAltResult::ErrorReadingScriptFile(path.clone(), err)))?; + f.read_to_string(&mut contents).map_err(|err| { + Box::new(EvalAltResult::ErrorReadingScriptFile( + path.clone(), + Position::none(), + err, + )) + })?; Ok(contents) } diff --git a/src/engine.rs b/src/engine.rs index c68efd95..a7beada9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1530,7 +1530,10 @@ impl Engine { .eval_expr(scope, state, fn_lib, expr, level)? .try_cast::() { - let module = self.module_resolver.resolve(self, &path)?; + let module = self + .module_resolver + .resolve(self, &path) + .map_err(|err| EvalAltResult::set_position(err, expr.position()))?; // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); diff --git a/src/result.rs b/src/result.rs index 93846eb0..5e38f8d9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -29,7 +29,7 @@ pub enum EvalAltResult { /// /// Never appears under the `no_std` feature. #[cfg(not(feature = "no_std"))] - ErrorReadingScriptFile(PathBuf, std::io::Error), + ErrorReadingScriptFile(PathBuf, Position, std::io::Error), /// Call to an unknown function. Wrapped value is the name of the function. ErrorFunctionNotFound(String, Position), @@ -94,7 +94,7 @@ impl EvalAltResult { pub(crate) fn desc(&self) -> &str { match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", + Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", Self::ErrorParsing(p) => p.desc(), Self::ErrorFunctionNotFound(_, _) => "Function not found", @@ -150,9 +150,13 @@ impl fmt::Display for EvalAltResult { match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(path, err) => { + Self::ErrorReadingScriptFile(path, pos, err) if pos.is_none() => { write!(f, "{} '{}': {}", desc, path.display(), err) } + #[cfg(not(feature = "no_std"))] + Self::ErrorReadingScriptFile(path, pos, err) => { + write!(f, "{} '{}': {} ({})", desc, path.display(), err, pos) + } Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), @@ -261,7 +265,7 @@ impl EvalAltResult { pub fn position(&self) -> Position { match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(_, _) => Position::none(), + Self::ErrorReadingScriptFile(_, pos, _) => *pos, Self::ErrorParsing(err) => err.position(), @@ -297,7 +301,7 @@ impl EvalAltResult { pub(crate) fn set_position(mut err: Box, new_position: Position) -> Box { match err.as_mut() { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(_, _) => (), + Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, Self::ErrorParsing(err) => err.1 = new_position, From 88fec57394d38236d2a87573f6b4c37444f9ce87 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 May 2020 16:09:44 +0800 Subject: [PATCH 21/23] Make module_resolver optional and remove NullModuleResolver. --- Cargo.toml | 2 +- README.md | 9 +- examples/repl.rs | 3 +- src/engine.rs | 41 +++---- src/module.rs | 305 +++++++++++++++++++++++++---------------------- src/parser.rs | 31 ++--- src/token.rs | 12 -- tests/modules.rs | 4 +- 8 files changed, 200 insertions(+), 207 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2afe38b0..5cf15e1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ categories = [ "no-std", "embedded", "parser-implementations" ] num-traits = { version = "0.2.11", default-features = false } [features] -#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] +#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] default = [] unchecked = [] # unchecked arithmetic no_index = [] # no arrays and indexing diff --git a/README.md b/README.md index d8c70be3..e27bf079 100644 --- a/README.md +++ b/README.md @@ -2118,13 +2118,16 @@ Built-in module resolvers are grouped under the `rhai::module_resolvers` module | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function. | | `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. | -| `NullModuleResolver` | The default module resolution service under the [`no_std`] feature. Always returns an `EvalAltResult::ErrorModuleNotFound` error. | An [`Engine`]'s module resolver is set via a call to `set_module_resolver`: ```rust -// Use the 'NullModuleResolver' -engine.set_module_resolver(rhai::module_resolvers::NullModuleResolver::new()); +// Use the 'StaticModuleResolver' +let resolver = rhai::module_resolvers::StaticModuleResolver::new(); +engine.set_module_resolver(Some(resolver)); + +// Effectively disable 'import' statements by setting module resolver to 'None' +engine.set_module_resolver(None); ``` Script optimization diff --git a/examples/repl.rs b/examples/repl.rs index af26d593..a8763e28 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Map, Scope, AST, INT}; +use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST, INT}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -163,6 +163,7 @@ fn main() { } // Throw away all the statements, leaving only the functions + #[cfg(not(feature = "no_function"))] main_ast.retain_functions(); } } diff --git a/src/engine.rs b/src/engine.rs index a7beada9..e9e14e1a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -262,7 +262,7 @@ pub struct Engine { pub(crate) type_iterators: HashMap>, /// A module resolution service. - pub(crate) module_resolver: Box, + pub(crate) module_resolver: Option>, /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, @@ -300,9 +300,9 @@ impl Default for Engine { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] - module_resolver: Box::new(resolvers::FileModuleResolver::new()), + module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())), #[cfg(any(feature = "no_std", feature = "no_module"))] - module_resolver: Box::new(resolvers::NullModuleResolver::new()), + module_resolver: None, type_names: Default::default(), @@ -438,13 +438,7 @@ impl Engine { packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), type_iterators: Default::default(), - - #[cfg(not(feature = "no_module"))] - #[cfg(not(feature = "no_std"))] - module_resolver: Box::new(resolvers::FileModuleResolver::new()), - #[cfg(any(feature = "no_std", feature = "no_module"))] - module_resolver: Box::new(resolvers::NullModuleResolver::new()), - + module_resolver: None, type_names: Default::default(), print: Box::new(|_| {}), debug: Box::new(|_| {}), @@ -491,8 +485,8 @@ impl Engine { /// /// Not available under the `no_module` feature. #[cfg(not(feature = "no_module"))] - pub fn set_module_resolver(&mut self, resolver: impl ModuleResolver + 'static) { - self.module_resolver = Box::new(resolver); + pub fn set_module_resolver(&mut self, resolver: Option) { + self.module_resolver = resolver.map(|f| Box::new(f) as Box); } /// Universal method for calling functions either registered with the `Engine` or written in Rhai. @@ -1264,8 +1258,7 @@ impl Engine { let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); match module.get_qualified_fn(fn_name, hash, modules, *pos) { - Ok(func) => func(&mut args, *pos) - .map_err(|err| EvalAltResult::set_position(err, *pos)), + Ok(func) => func(&mut args, *pos), Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), Err(err) => Err(err), } @@ -1530,15 +1523,19 @@ impl Engine { .eval_expr(scope, state, fn_lib, expr, level)? .try_cast::() { - let module = self - .module_resolver - .resolve(self, &path) - .map_err(|err| EvalAltResult::set_position(err, expr.position()))?; + if let Some(resolver) = self.module_resolver.as_ref() { + let module = resolver.resolve(self, &path, expr.position())?; - // TODO - avoid copying module name in inner block? - let mod_name = name.as_ref().clone(); - scope.push_module(mod_name, module); - Ok(Default::default()) + // TODO - avoid copying module name in inner block? + let mod_name = name.as_ref().clone(); + scope.push_module(mod_name, module); + Ok(Default::default()) + } else { + Err(Box::new(EvalAltResult::ErrorModuleNotFound( + path, + expr.position(), + ))) + } } else { Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) } diff --git a/src/module.rs b/src/module.rs index 32d27395..0c329f27 100644 --- a/src/module.rs +++ b/src/module.rs @@ -5,21 +5,35 @@ use crate::calc_fn_hash; use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib}; use crate::parser::FnDef; use crate::result::EvalAltResult; -use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::token::Position; use crate::token::Token; use crate::utils::StaticVec; use crate::stdlib::{ - any::TypeId, collections::HashMap, fmt, iter::empty, mem, rc::Rc, string::String, sync::Arc, + any::TypeId, + collections::HashMap, + fmt, mem, + ops::{Deref, DerefMut}, + rc::Rc, + string::String, + sync::Arc, }; /// A trait that encapsulates a module resolution service. pub trait ModuleResolver { /// Resolve a module based on a path string. - fn resolve(&self, engine: &Engine, path: &str) -> Result>; + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result>; } +/// Return type of module-level Rust function. +type FuncReturn = Result>; + /// An imported module, which may contain variables, sub-modules, /// external Rust functions, and script-defined functions. /// @@ -158,7 +172,6 @@ impl Module { #[cfg(not(feature = "sync"))] self.functions.insert(hash, Rc::new(func)); - #[cfg(feature = "sync")] self.functions.insert(hash, Arc::new(func)); @@ -171,14 +184,16 @@ impl Module { pub fn set_fn_0>( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn() -> Result> + 'static, - #[cfg(feature = "sync")] func: impl Fn() -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |_: &mut FnCallArgs, _: Position| func().map(|v| v.into()); - self.set_fn(fn_name, &[], Box::new(f)) + let f = move |_: &mut FnCallArgs, pos| { + func() + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) + }; + let arg_types = &[]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Set a Rust function taking one parameter into the module, returning a hash key. @@ -187,16 +202,16 @@ impl Module { pub fn set_fn_1>( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn(A) -> Result> + 'static, - #[cfg(feature = "sync")] func: impl Fn(A) -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs, _: Position| { - func(mem::take(args[0]).cast::
()).map(|v| v.into()) + let f = move |args: &mut FnCallArgs, pos| { + func(mem::take(args[0]).cast::()) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) }; - self.set_fn(fn_name, &[TypeId::of::()], Box::new(f)) + let arg_types = &[TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Set a Rust function taking one mutable parameter into the module, returning a hash key. @@ -205,16 +220,16 @@ impl Module { pub fn set_fn_1_mut>( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> Result> + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A) -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs, _: Position| { - func(args[0].downcast_mut::().unwrap()).map(|v| v.into()) + let f = move |args: &mut FnCallArgs, pos| { + func(args[0].downcast_mut::().unwrap()) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) }; - self.set_fn(fn_name, &[TypeId::of::()], Box::new(f)) + let arg_types = &[TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Set a Rust function taking two parameters into the module, returning a hash key. @@ -223,23 +238,19 @@ impl Module { pub fn set_fn_2>( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> Result> + 'static, - #[cfg(feature = "sync")] func: impl Fn(A, B) -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs, _: Position| { + let f = move |args: &mut FnCallArgs, pos| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); - func(a, b).map(|v| v.into()) + func(a, b) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) }; - self.set_fn( - fn_name, - &[TypeId::of::(), TypeId::of::()], - Box::new(f), - ) + let arg_types = &[TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Set a Rust function taking two parameters (the first one mutable) into the module, @@ -249,24 +260,19 @@ impl Module { pub fn set_fn_2_mut>( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> Result> - + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs, _: Position| { + let f = move |args: &mut FnCallArgs, pos| { let b = mem::take(args[1]).cast::(); let a = args[0].downcast_mut::().unwrap(); - func(a, b).map(|v| v.into()) + func(a, b) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) }; - self.set_fn( - fn_name, - &[TypeId::of::(), TypeId::of::()], - Box::new(f), - ) + let arg_types = &[TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Set a Rust function taking three parameters into the module, returning a hash key. @@ -280,24 +286,20 @@ impl Module { >( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> Result> + 'static, - #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs, _: Position| { + let f = move |args: &mut FnCallArgs, pos| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - func(a, b, c).map(|v| v.into()) + func(a, b, c) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) }; - self.set_fn( - fn_name, - &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - Box::new(f), - ) + let arg_types = &[TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Set a Rust function taking three parameters (the first one mutable) into the module, @@ -312,25 +314,20 @@ impl Module { >( &mut self, fn_name: &str, - #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> Result> - + 'static, - #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> Result> - + Send - + Sync - + 'static, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn + Send + Sync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs, _: Position| { + let f = move |args: &mut FnCallArgs, pos| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); - func(a, b, c).map(|v| v.into()) + func(a, b, c) + .map(|v| v.into()) + .map_err(|err| EvalAltResult::set_position(err, pos)) }; - self.set_fn( - fn_name, - &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - Box::new(f), - ) + let arg_types = &[TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name, arg_types, Box::new(f)) } /// Get a Rust function. @@ -388,22 +385,40 @@ impl Module { } } +/// Re-export module resolvers. pub mod resolvers { - use super::*; + pub use super::file::FileModuleResolver; + pub use super::stat::StaticModuleResolver; +} - #[cfg(not(feature = "no_std"))] +/// Script file-based module resolver. +#[cfg(not(feature = "no_std"))] +mod file { + use super::*; use crate::stdlib::path::PathBuf; - /// A module resolution service that loads module script files (assumed `.rhai` extension). - #[cfg(not(feature = "no_std"))] - #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] - pub struct FileModuleResolver(PathBuf); + /// A module resolution service that loads module script files from the file system. + /// + /// The `new_with_path` and `new_with_path_and_extension` constructor functions + /// allow specification of a base directory with module path used as a relative path offset + /// to the base directory. The script file is then forced to be in a specified extension + /// (default `.rhai`). + #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] + pub struct FileModuleResolver { + path: PathBuf, + extension: String, + } - #[cfg(not(feature = "no_std"))] impl FileModuleResolver { /// Create a new `FileModuleResolver` with a specific base path. pub fn new_with_path(path: PathBuf) -> Self { - Self(path) + Self::new_with_path_and_extension(path, "rhai".to_string()) + } + /// Create a new `FileModuleResolver` with a specific base path and file extension. + /// + /// The default extension is `.rhai`. + pub fn new_with_path_and_extension(path: PathBuf, extension: String) -> Self { + Self { path, extension } } /// Create a new `FileModuleResolver` with the current directory as base path. pub fn new() -> Self { @@ -411,54 +426,63 @@ pub mod resolvers { } } - #[cfg(not(feature = "no_std"))] - impl Default for FileModuleResolver { - fn default() -> Self { - Self::new_with_path(".".into()) - } - } - - #[cfg(not(feature = "no_std"))] impl ModuleResolver for FileModuleResolver { - fn resolve(&self, engine: &Engine, path: &str) -> Result> { - // Load the script file (attaching `.rhai`) - let mut file_path = self.0.clone(); + fn resolve( + &self, + engine: &Engine, + path: &str, + pos: Position, + ) -> Result> { + // Construct the script file path + let mut file_path = self.path.clone(); file_path.push(path); - file_path.set_extension("rhai"); + file_path.set_extension(&self.extension); // Force extension // Compile it - let ast = engine.compile_file(file_path)?; + let ast = engine + .compile_file(file_path) + .map_err(|err| EvalAltResult::set_position(err, pos))?; // Use new scope let mut scope = Scope::new(); // Run the script - engine.eval_ast_with_scope_raw(&mut scope, &ast)?; + engine + .eval_ast_with_scope_raw(&mut scope, &ast) + .map_err(|err| EvalAltResult::set_position(err, pos))?; // Create new module let mut module = Module::new(); - // Variables left in the scope become module variables - for entry in scope.into_iter() { - match entry.typ { - ScopeEntryType::Normal | ScopeEntryType::Constant => { - module - .variables - .insert(entry.name.into_owned(), entry.value); + scope.into_iter().for_each( + |ScopeEntry { + name, typ, value, .. + }| { + match typ { + // Variables left in the scope become module variables + ScopeEntryType::Normal | ScopeEntryType::Constant => { + module.variables.insert(name.into_owned(), value); + } + // Modules left in the scope become sub-modules + ScopeEntryType::Module => { + module + .modules + .insert(name.into_owned(), value.cast::()); + } } - ScopeEntryType::Module => { - module - .modules - .insert(entry.name.into_owned(), entry.value.cast::()); - } - } - } + }, + ); - module.fn_lib = FunctionsLib::new().merge(ast.fn_lib()); + module.fn_lib = module.fn_lib.merge(ast.fn_lib()); Ok(module) } } +} + +/// Static module resolver. +mod stat { + use super::*; /// A module resolution service that serves modules added into it. #[derive(Debug, Clone, Default)] @@ -469,40 +493,33 @@ pub mod resolvers { pub fn new() -> Self { Default::default() } - /// Add a named module. - pub fn add_module(&mut self, name: &str, module: Module) { - self.0.insert(name.to_string(), module); + } + + impl Deref for StaticModuleResolver { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for StaticModuleResolver { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } impl ModuleResolver for StaticModuleResolver { - fn resolve(&self, _: &Engine, path: &str) -> Result> { - self.0.get(path).cloned().ok_or_else(|| { - Box::new(EvalAltResult::ErrorModuleNotFound( - path.to_string(), - Position::none(), - )) - }) - } - } - - /// A module resolution service that always returns a not-found error. - #[derive(Debug, Clone, PartialEq, Eq, Copy, Default)] - pub struct NullModuleResolver; - - impl NullModuleResolver { - /// Create a new `NullModuleResolver`. - pub fn new() -> Self { - Default::default() - } - } - - impl ModuleResolver for NullModuleResolver { - fn resolve(&self, _: &Engine, path: &str) -> Result> { - Err(Box::new(EvalAltResult::ErrorModuleNotFound( - path.to_string(), - Position::none(), - ))) + fn resolve( + &self, + _: &Engine, + path: &str, + pos: Position, + ) -> Result> { + self.0 + .get(path) + .cloned() + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))) } } } diff --git a/src/parser.rs b/src/parser.rs index 4cb663d8..b1e91eb0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -67,13 +67,9 @@ impl AST { /// Create a new `AST`. pub fn new(statements: Vec, fn_lib: FunctionsLib) -> Self { #[cfg(feature = "sync")] - { - Self(statements, Arc::new(fn_lib)) - } + return Self(statements, Arc::new(fn_lib)); #[cfg(not(feature = "sync"))] - { - Self(statements, Rc::new(fn_lib)) - } + return Self(statements, Rc::new(fn_lib)); } /// Get the statements. @@ -148,13 +144,9 @@ impl AST { }; #[cfg(feature = "sync")] - { - Self(ast, Arc::new(functions.merge(other.1.as_ref()))) - } + return Self(ast, Arc::new(functions.merge(other.1.as_ref()))); #[cfg(not(feature = "sync"))] - { - Self(ast, Rc::new(functions.merge(other.1.as_ref()))) - } + return Self(ast, Rc::new(functions.merge(other.1.as_ref()))); } /// Clear all function definitions in the `AST`. @@ -1810,7 +1802,6 @@ fn parse_let<'a>( } /// Parse an import statement. -#[cfg(not(feature = "no_module"))] fn parse_import<'a>( input: &mut Peekable>, stack: &mut Stack, @@ -1941,7 +1932,6 @@ fn parse_stmt<'a>( Token::LeftBrace => parse_block(input, stack, breakable, allow_stmt_expr), // fn ... - #[cfg(not(feature = "no_function"))] Token::Fn => Err(PERR::WrongFnDefinition.into_err(*pos)), Token::If => parse_if(input, stack, breakable, allow_stmt_expr), @@ -2115,14 +2105,11 @@ fn parse_global_level<'a>( while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions - #[cfg(not(feature = "no_function"))] - { - if let (Token::Fn, _) = input.peek().unwrap() { - let mut stack = Stack::new(); - let f = parse_fn(input, &mut stack, true)?; - functions.insert(calc_fn_def(&f.name, f.params.len()), f); - continue; - } + if let (Token::Fn, _) = input.peek().unwrap() { + let mut stack = Stack::new(); + let f = parse_fn(input, &mut stack, true)?; + functions.insert(calc_fn_def(&f.name, f.params.len()), f); + continue; } // Actual statement diff --git a/src/token.rs b/src/token.rs index 29e7ce87..f67a8e14 100644 --- a/src/token.rs +++ b/src/token.rs @@ -153,11 +153,9 @@ pub enum Token { RightShift, SemiColon, Colon, - #[cfg(not(feature = "no_module"))] DoubleColon, Comma, Period, - #[cfg(not(feature = "no_object"))] MapStart, Equals, True, @@ -182,7 +180,6 @@ pub enum Token { XOr, Ampersand, And, - #[cfg(not(feature = "no_function"))] Fn, Continue, Break, @@ -199,11 +196,8 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, - #[cfg(not(feature = "no_module"))] Import, - #[cfg(not(feature = "no_module"))] Export, - #[cfg(not(feature = "no_module"))] As, LexError(Box), EOF, @@ -238,11 +232,9 @@ impl Token { Divide => "/", SemiColon => ";", Colon => ":", - #[cfg(not(feature = "no_module"))] DoubleColon => "::", Comma => ",", Period => ".", - #[cfg(not(feature = "no_object"))] MapStart => "#{", Equals => "=", True => "true", @@ -266,7 +258,6 @@ impl Token { Or => "||", Ampersand => "&", And => "&&", - #[cfg(not(feature = "no_function"))] Fn => "fn", Continue => "continue", Break => "break", @@ -288,11 +279,8 @@ impl Token { ModuloAssign => "%=", PowerOf => "~", PowerOfAssign => "~=", - #[cfg(not(feature = "no_module"))] Import => "import", - #[cfg(not(feature = "no_module"))] Export => "export", - #[cfg(not(feature = "no_module"))] As => "as", EOF => "{EOF}", _ => panic!("operator should be match in outer scope"), diff --git a/tests/modules.rs b/tests/modules.rs index e1382764..e6028179 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -64,10 +64,10 @@ fn test_module_resolver() -> Result<(), Box> { let mut module = Module::new(); module.set_var("answer", 42 as INT); - resolver.add_module("hello", module); + resolver.insert("hello".to_string(), module); let mut engine = Engine::new(); - engine.set_module_resolver(resolver); + engine.set_module_resolver(Some(resolver)); assert_eq!( engine.eval::( From d75a8bc6cd3803803334f0ccb2daedc32f014041 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 May 2020 19:45:17 +0800 Subject: [PATCH 22/23] Make sure all features compile correctly. --- src/any.rs | 115 ++++++---- src/api.rs | 5 +- src/engine.rs | 187 +++++++++------- src/module.rs | 399 +++++++++++++++++++++++++++++++---- src/optimize.rs | 8 + src/packages/array_basic.rs | 2 + src/packages/map_basic.rs | 2 + src/packages/string_basic.rs | 9 +- src/packages/string_more.rs | 4 +- src/parser.rs | 82 +++++-- src/scope.rs | 6 +- 11 files changed, 640 insertions(+), 179 deletions(-) diff --git a/src/any.rs b/src/any.rs index 78fa76d9..16bb8ae7 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,12 +1,19 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::engine::{Array, Map}; +#[cfg(not(feature = "no_module"))] use crate::module::Module; + use crate::parser::INT; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{ any::{type_name, Any, TypeId}, boxed::Box, @@ -134,8 +141,11 @@ pub enum Union { Int(INT), #[cfg(not(feature = "no_float"))] Float(FLOAT), + #[cfg(not(feature = "no_index"))] Array(Box), + #[cfg(not(feature = "no_object"))] Map(Box), + #[cfg(not(feature = "no_module"))] Module(Box), Variant(Box>), } @@ -165,8 +175,11 @@ impl Dynamic { Union::Int(_) => TypeId::of::(), #[cfg(not(feature = "no_float"))] Union::Float(_) => TypeId::of::(), + #[cfg(not(feature = "no_index"))] Union::Array(_) => TypeId::of::(), + #[cfg(not(feature = "no_object"))] Union::Map(_) => TypeId::of::(), + #[cfg(not(feature = "no_module"))] Union::Module(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), } @@ -182,8 +195,11 @@ impl Dynamic { Union::Int(_) => type_name::(), #[cfg(not(feature = "no_float"))] Union::Float(_) => type_name::(), + #[cfg(not(feature = "no_index"))] Union::Array(_) => "array", + #[cfg(not(feature = "no_object"))] Union::Map(_) => "map", + #[cfg(not(feature = "no_module"))] Union::Module(_) => "sub-scope", #[cfg(not(feature = "no_std"))] @@ -203,8 +219,11 @@ impl fmt::Display for Dynamic { Union::Int(value) => write!(f, "{}", value), #[cfg(not(feature = "no_float"))] Union::Float(value) => write!(f, "{}", value), + #[cfg(not(feature = "no_index"))] Union::Array(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_object"))] Union::Map(value) => write!(f, "#{:?}", value), + #[cfg(not(feature = "no_module"))] Union::Module(value) => write!(f, "{:?}", value), #[cfg(not(feature = "no_std"))] @@ -224,8 +243,11 @@ impl fmt::Debug for Dynamic { Union::Int(value) => write!(f, "{:?}", value), #[cfg(not(feature = "no_float"))] Union::Float(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_index"))] Union::Array(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_object"))] Union::Map(value) => write!(f, "#{:?}", value), + #[cfg(not(feature = "no_module"))] Union::Module(value) => write!(f, "{:?}", value), #[cfg(not(feature = "no_std"))] @@ -245,8 +267,11 @@ impl Clone for Dynamic { Union::Int(value) => Self(Union::Int(value)), #[cfg(not(feature = "no_float"))] Union::Float(value) => Self(Union::Float(value)), + #[cfg(not(feature = "no_index"))] Union::Array(ref value) => Self(Union::Array(value.clone())), + #[cfg(not(feature = "no_object"))] Union::Map(ref value) => Self(Union::Map(value.clone())), + #[cfg(not(feature = "no_module"))] Union::Module(ref value) => Self(Union::Module(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), } @@ -260,13 +285,13 @@ impl Default for Dynamic { } /// Cast a Boxed type into another type. -fn cast_box(item: Box) -> Result> { +fn cast_box(item: Box) -> Result, Box> { // 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)) + Ok(Box::from_raw(raw as *mut T)) } } else { // Return the consumed item for chaining. @@ -320,31 +345,33 @@ impl Dynamic { } } - let var = Box::new(value); + let mut var = Box::new(value); - Self( - cast_box::<_, Dynamic>(var) - .map(|x| x.0) - .or_else(|var| { - cast_box::<_, String>(var) - .map(Box::new) - .map(Union::Str) - .or_else(|var| { - cast_box::<_, Array>(var) - .map(Box::new) - .map(Union::Array) - .or_else(|var| { - cast_box::<_, Map>(var) - .map(Box::new) - .map(Union::Map) - .or_else(|var| -> Result { - Ok(Union::Variant(Box::new(var))) - }) - }) - }) - }) - .unwrap(), - ) + var = match cast_box::<_, Dynamic>(var) { + Ok(d) => return *d, + Err(var) => var, + }; + var = match cast_box::<_, String>(var) { + Ok(s) => return Self(Union::Str(s)), + Err(var) => var, + }; + #[cfg(not(feature = "no_index"))] + { + var = match cast_box::<_, Array>(var) { + Ok(array) => return Self(Union::Array(array)), + Err(var) => var, + }; + } + + #[cfg(not(feature = "no_object"))] + { + var = match cast_box::<_, Map>(var) { + Ok(map) => return Self(Union::Map(map)), + Err(var) => var, + } + } + + Self(Union::Variant(Box::new(var))) } /// Get a copy of the `Dynamic` value as a specific type. @@ -363,20 +390,23 @@ impl Dynamic { /// ``` pub fn try_cast(self) -> Option { if TypeId::of::() == TypeId::of::() { - return cast_box::<_, T>(Box::new(self)).ok(); + return cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } match self.0 { Union::Unit(ref value) => (value as &dyn Any).downcast_ref::().cloned(), Union::Bool(ref value) => (value as &dyn Any).downcast_ref::().cloned(), - Union::Str(value) => cast_box::<_, T>(value).ok(), + Union::Str(value) => cast_box::<_, T>(value).ok().map(|v| *v), Union::Char(ref value) => (value as &dyn Any).downcast_ref::().cloned(), Union::Int(ref value) => (value as &dyn Any).downcast_ref::().cloned(), #[cfg(not(feature = "no_float"))] Union::Float(ref value) => (value as &dyn Any).downcast_ref::().cloned(), - Union::Array(value) => cast_box::<_, T>(value).ok(), - Union::Map(value) => cast_box::<_, T>(value).ok(), - Union::Module(value) => cast_box::<_, T>(value).ok(), + #[cfg(not(feature = "no_index"))] + Union::Array(value) => cast_box::<_, T>(value).ok().map(|v| *v), + #[cfg(not(feature = "no_object"))] + Union::Map(value) => cast_box::<_, T>(value).ok().map(|v| *v), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v), Union::Variant(value) => value.as_any().downcast_ref::().cloned(), } } @@ -401,20 +431,23 @@ impl Dynamic { //self.try_cast::().unwrap() if TypeId::of::() == TypeId::of::() { - return cast_box::<_, T>(Box::new(self)).unwrap(); + return *cast_box::<_, T>(Box::new(self)).unwrap(); } match self.0 { Union::Unit(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), Union::Bool(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), - Union::Str(value) => cast_box::<_, T>(value).unwrap(), + Union::Str(value) => *cast_box::<_, T>(value).unwrap(), Union::Char(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), Union::Int(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), #[cfg(not(feature = "no_float"))] Union::Float(ref value) => (value as &dyn Any).downcast_ref::().unwrap().clone(), - Union::Array(value) => cast_box::<_, T>(value).unwrap(), - Union::Map(value) => cast_box::<_, T>(value).unwrap(), - Union::Module(value) => cast_box::<_, T>(value).unwrap(), + #[cfg(not(feature = "no_index"))] + Union::Array(value) => *cast_box::<_, T>(value).unwrap(), + #[cfg(not(feature = "no_object"))] + Union::Map(value) => *cast_box::<_, T>(value).unwrap(), + #[cfg(not(feature = "no_module"))] + Union::Module(value) => *cast_box::<_, T>(value).unwrap(), Union::Variant(value) => value.as_any().downcast_ref::().unwrap().clone(), } } @@ -435,8 +468,11 @@ impl Dynamic { Union::Int(value) => (value as &dyn Any).downcast_ref::(), #[cfg(not(feature = "no_float"))] Union::Float(value) => (value as &dyn Any).downcast_ref::(), + #[cfg(not(feature = "no_index"))] Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + #[cfg(not(feature = "no_object"))] Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + #[cfg(not(feature = "no_module"))] Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::(), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), } @@ -458,8 +494,11 @@ impl Dynamic { Union::Int(value) => (value as &mut dyn Any).downcast_mut::(), #[cfg(not(feature = "no_float"))] Union::Float(value) => (value as &mut dyn Any).downcast_mut::(), + #[cfg(not(feature = "no_index"))] Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + #[cfg(not(feature = "no_object"))] Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + #[cfg(not(feature = "no_module"))] Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), } @@ -542,6 +581,7 @@ impl From for Dynamic { Self(Union::Str(Box::new(value))) } } +#[cfg(not(feature = "no_index"))] impl From> for Dynamic { fn from(value: Vec) -> Self { Self(Union::Array(Box::new( @@ -549,6 +589,7 @@ impl From> for Dynamic { ))) } } +#[cfg(not(feature = "no_object"))] impl From> for Dynamic { fn from(value: HashMap) -> Self { Self(Union::Map(Box::new( diff --git a/src/api.rs b/src/api.rs index abc4d37f..d0193549 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,7 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{make_getter, make_setter, Engine, Map, State, FUNC_INDEXER}; +use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; @@ -11,6 +11,9 @@ use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::{lex, Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, diff --git a/src/engine.rs b/src/engine.rs index e9e14e1a..b35419cb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,6 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::error::ParseErrorType; -use crate::module::{resolvers, Module, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST}; @@ -12,6 +11,9 @@ use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; use crate::utils::{calc_fn_def, StaticVec}; +#[cfg(not(feature = "no_module"))] +use crate::module::{resolvers, Module, ModuleResolver}; + use crate::stdlib::{ any::TypeId, boxed::Box, @@ -30,11 +32,13 @@ use crate::stdlib::{ /// An dynamic array of `Dynamic` values. /// /// Not available under the `no_index` feature. +#[cfg(not(feature = "no_index"))] pub type Array = Vec; /// An dynamic hash map of `Dynamic` values with `String` keys. /// /// Not available under the `no_object` feature. +#[cfg(not(feature = "no_object"))] pub type Map = HashMap; pub type FnCallArgs<'a> = [&'a mut Dynamic]; @@ -262,6 +266,7 @@ pub struct Engine { pub(crate) type_iterators: HashMap>, /// A module resolution service. + #[cfg(not(feature = "no_module"))] pub(crate) module_resolver: Option>, /// A hashmap mapping type names to pretty-print names. @@ -301,7 +306,8 @@ impl Default for Engine { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())), - #[cfg(any(feature = "no_std", feature = "no_module"))] + #[cfg(not(feature = "no_module"))] + #[cfg(feature = "no_std")] module_resolver: None, type_names: Default::default(), @@ -391,38 +397,41 @@ fn search_scope<'a>( index: Option, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { - if let Some(modules) = modules { - let (id, root_pos) = modules.get(0); // First module + #[cfg(not(feature = "no_module"))] + { + if let Some(modules) = modules { + let (id, root_pos) = modules.get(0); // First module - let module = if let Some(index) = index { - scope - .get_mut(scope.len() - index.get()) - .0 - .downcast_mut::() - .unwrap() - } else { - scope - .find_module(id) - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? - }; + let module = if let Some(index) = index { + scope + .get_mut(scope.len() - index.get()) + .0 + .downcast_mut::() + .unwrap() + } else { + scope.find_module(id).ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + })? + }; - Ok(( - module.get_qualified_var_mut(name, modules.as_ref(), pos)?, - // Module variables are constant - ScopeEntryType::Constant, - )) - } else { - let index = if let Some(index) = index { - scope.len() - index.get() - } else { - scope - .get_index(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))? - .0 - }; - - Ok(scope.get_mut(index)) + return Ok(( + module.get_qualified_var_mut(name, modules.as_ref(), pos)?, + // Module variables are constant + ScopeEntryType::Constant, + )); + } } + + let index = if let Some(index) = index { + scope.len() - index.get() + } else { + scope + .get_index(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))? + .0 + }; + + Ok(scope.get_mut(index)) } impl Engine { @@ -438,7 +447,10 @@ impl Engine { packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), type_iterators: Default::default(), + + #[cfg(not(feature = "no_module"))] module_resolver: None, + type_names: Default::default(), print: Box::new(|_| {}), debug: Box::new(|_| {}), @@ -721,7 +733,7 @@ impl Engine { } let statements = mem::take(ast.statements_mut()); - ast = AST::new(statements, fn_lib.clone()); + let ast = AST::new(statements, fn_lib.clone()); // Evaluate the AST self.eval_ast_with_scope_raw(scope, &ast) @@ -779,7 +791,7 @@ impl Engine { // xxx.fn_name(arg_expr_list) Expr::FnCall(fn_name, None,_, def_val, pos) => { let mut args: Vec<_> = once(obj) - .chain(idx_val.downcast_mut::().unwrap().iter_mut()) + .chain(idx_val.downcast_mut::>().unwrap().iter_mut()) .collect(); let def_val = def_val.as_deref(); // A function call is assumed to have side effects, so the value is changed @@ -789,6 +801,7 @@ impl Engine { // xxx.module::fn_name(...) - syntax error Expr::FnCall(_,_,_,_,_) => unreachable!(), // {xxx:map}.id = ??? + #[cfg(not(feature = "no_object"))] Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { let mut indexed_val = self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, true)?; @@ -796,6 +809,7 @@ impl Engine { Ok((Default::default(), true)) } // {xxx:map}.id + #[cfg(not(feature = "no_object"))] Expr::Property(id, pos) if obj.is::() => { let indexed_val = self.get_indexed_mut(fn_lib, obj, id.to_string().into(), *pos, op_pos, false)?; @@ -813,6 +827,7 @@ impl Engine { let mut args = [obj]; self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) } + #[cfg(not(feature = "no_object"))] // {xxx:map}.idx_lhs[idx_expr] Expr::Index(dot_lhs, dot_rhs, pos) | // {xxx:map}.dot_lhs.rhs @@ -959,7 +974,10 @@ impl Engine { .map(|arg_expr| self.eval_expr(scope, state, fn_lib, arg_expr, level)) .collect::, _>>()?; - idx_values.push(arg_values) + #[cfg(not(feature = "no_index"))] + idx_values.push(arg_values); + #[cfg(feature = "no_index")] + idx_values.push(Dynamic::from(arg_values)); } Expr::FnCall(_, _, _, _, _) => unreachable!(), Expr::Property(_, _) => idx_values.push(()), // Store a placeholder - no need to copy the property name @@ -994,6 +1012,7 @@ impl Engine { let type_name = self.map_type_name(val.type_name()); match val { + #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { // val_array[idx] let index = idx @@ -1015,6 +1034,7 @@ impl Engine { } } + #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map)) => { // val_map[idx] let index = idx @@ -1030,6 +1050,7 @@ impl Engine { }) } + #[cfg(not(feature = "no_index"))] Dynamic(Union::Str(s)) => { // val_string[idx] let index = idx @@ -1088,6 +1109,7 @@ impl Engine { let rhs_value = self.eval_expr(scope, state, fn_lib, rhs, level)?; match rhs_value { + #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value)) => { let def_value = false.into(); @@ -1108,6 +1130,7 @@ impl Engine { Ok(false.into()) } + #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(rhs_value)) => match lhs_value { // Only allows String or char Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_ref()).into()), @@ -1230,7 +1253,8 @@ impl Engine { .collect::, _>>()?, )))), - Expr::FnCall(fn_name, modules, arg_exprs, def_val, pos) => { + // Normal function call + Expr::FnCall(fn_name, None, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) @@ -1238,32 +1262,7 @@ impl Engine { let mut args: Vec<_> = arg_values.iter_mut().collect(); - if let Some(modules) = modules { - // Module-qualified function call - let modules = modules.as_ref(); - - let (id, root_pos) = modules.get(0); // First module - - let module = scope.find_module(id).ok_or_else(|| { - Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) - })?; - - // First search in script-defined functions (can override built-in) - if let Some(fn_def) = - module.get_qualified_fn_lib(fn_name, args.len(), modules)? - { - self.call_fn_from_lib(None, fn_lib, fn_def, &mut args, *pos, level) - } else { - // Then search in Rust functions - let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); - - match module.get_qualified_fn(fn_name, hash, modules, *pos) { - Ok(func) => func(&mut args, *pos), - Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), - Err(err) => Err(err), - } - } - } else if fn_name.as_ref() == KEYWORD_EVAL + if fn_name.as_ref() == KEYWORD_EVAL && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { @@ -1288,6 +1287,39 @@ impl Engine { } } + // Module-qualified function call + #[cfg(not(feature = "no_module"))] + Expr::FnCall(fn_name, Some(modules), arg_exprs, def_val, pos) => { + let modules = modules.as_ref(); + + let mut arg_values = arg_exprs + .iter() + .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level)) + .collect::, _>>()?; + + let mut args: Vec<_> = arg_values.iter_mut().collect(); + + let (id, root_pos) = modules.get(0); // First module + + let module = scope.find_module(id).ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + })?; + + // First search in script-defined functions (can override built-in) + if let Some(fn_def) = module.get_qualified_fn_lib(fn_name, args.len(), modules)? { + self.call_fn_from_lib(None, fn_lib, fn_def, &mut args, *pos, level) + } else { + // Then search in Rust functions + let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + + match module.get_qualified_fn(fn_name, hash, modules, *pos) { + Ok(func) => func(&mut args, *pos), + Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), + Err(err) => Err(err), + } + } + } + Expr::In(lhs, rhs, _) => { self.eval_in_expr(scope, state, fn_lib, lhs.as_ref(), rhs.as_ref(), level) } @@ -1519,25 +1551,28 @@ impl Engine { #[cfg(feature = "no_module")] unreachable!(); - if let Some(path) = self - .eval_expr(scope, state, fn_lib, expr, level)? - .try_cast::() + #[cfg(not(feature = "no_module"))] { - if let Some(resolver) = self.module_resolver.as_ref() { - let module = resolver.resolve(self, &path, expr.position())?; + if let Some(path) = self + .eval_expr(scope, state, fn_lib, expr, level)? + .try_cast::() + { + if let Some(resolver) = self.module_resolver.as_ref() { + let module = resolver.resolve(self, &path, expr.position())?; - // TODO - avoid copying module name in inner block? - let mod_name = name.as_ref().clone(); - scope.push_module(mod_name, module); - Ok(Default::default()) + // TODO - avoid copying module name in inner block? + let mod_name = name.as_ref().clone(); + scope.push_module(mod_name, module); + Ok(Default::default()) + } else { + Err(Box::new(EvalAltResult::ErrorModuleNotFound( + path, + expr.position(), + ))) + } } else { - Err(Box::new(EvalAltResult::ErrorModuleNotFound( - path, - expr.position(), - ))) + Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) } - } else { - Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) } } } diff --git a/src/module.rs b/src/module.rs index 0c329f27..bdb545b1 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,9 +1,10 @@ //! Module defining external-loaded modules for Rhai. +#![cfg(not(feature = "no_module"))] use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib}; -use crate::parser::FnDef; +use crate::parser::{FnDef, AST}; use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -12,11 +13,12 @@ use crate::utils::StaticVec; use crate::stdlib::{ any::TypeId, + boxed::Box, collections::HashMap, fmt, mem, ops::{Deref, DerefMut}, rc::Rc, - string::String, + string::{String, ToString}, sync::Arc, }; @@ -70,21 +72,61 @@ impl fmt::Debug for Module { impl Module { /// Create a new module. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// ``` pub fn new() -> Self { Default::default() } /// Does a variable exist in the module? + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert!(module.contains_var("answer")); + /// ``` pub fn contains_var(&self, name: &str) -> bool { self.variables.contains_key(name) } /// Get the value of a module variable. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// ``` pub fn get_var_value(&self, name: &str) -> Option { self.get_var(name).and_then(|v| v.try_cast::()) } - /// Get a module variable. + /// Get a module variable as a `Dynamic`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var("answer").unwrap().cast::(), 42); + /// ``` pub fn get_var(&self, name: &str) -> Option { self.variables.get(name).cloned() } @@ -97,6 +139,16 @@ impl Module { /// Set a variable into the module. /// /// If there is an existing variable of the same name, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_var("answer", 42_i64); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// ``` pub fn set_var, T: Into>(&mut self, name: K, value: T) { self.variables.insert(name.into(), value.into()); } @@ -115,16 +167,49 @@ impl Module { } /// Does a sub-module exist in the module? + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.contains_sub_module("question")); + /// ``` pub fn contains_sub_module(&self, name: &str) -> bool { self.modules.contains_key(name) } /// Get a sub-module. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.get_sub_module("question").is_some()); + /// ``` pub fn get_sub_module(&self, name: &str) -> Option<&Module> { self.modules.get(name) } /// Get a mutable reference to a sub-module. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.get_sub_module_mut("question").is_some()); + /// ``` pub fn get_sub_module_mut(&mut self, name: &str) -> Option<&mut Module> { self.modules.get_mut(name) } @@ -132,6 +217,17 @@ impl Module { /// Set a sub-module into the module. /// /// If there is an existing sub-module of the same name, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let sub_module = Module::new(); + /// module.set_sub_module("question", sub_module); + /// assert!(module.get_sub_module("question").is_some()); + /// ``` pub fn set_sub_module>(&mut self, name: K, sub_module: Module) { self.modules.insert(name.into(), sub_module.into()); } @@ -160,6 +256,16 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_0("calc", || Ok(42_i64)); + /// assert!(module.contains_fn(hash)); + /// ``` pub fn contains_fn(&self, hash: u64) -> bool { self.functions.contains_key(&hash) } @@ -181,6 +287,16 @@ impl Module { /// Set a Rust function taking no parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_0("calc", || Ok(42_i64)); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_0>( &mut self, fn_name: &str, @@ -199,6 +315,16 @@ impl Module { /// Set a Rust function taking one parameter into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_1>( &mut self, fn_name: &str, @@ -217,6 +343,16 @@ impl Module { /// Set a Rust function taking one mutable parameter into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_1_mut>( &mut self, fn_name: &str, @@ -235,6 +371,18 @@ impl Module { /// Set a Rust function taking two parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_2("calc", |x: i64, y: String| { + /// Ok(x + y.len() as i64) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_2>( &mut self, fn_name: &str, @@ -256,7 +404,17 @@ impl Module { /// Set a Rust function taking two parameters (the first one mutable) into the module, /// returning a hash key. /// - /// If there is a similar existing Rust function, it is replaced. + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: String| { + /// *x += y.len() as i64; Ok(*x) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_2_mut>( &mut self, fn_name: &str, @@ -278,6 +436,18 @@ impl Module { /// Set a Rust function taking three parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64| { + /// Ok(x + y.len() as i64 + z) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_3< A: Variant + Clone, B: Variant + Clone, @@ -306,6 +476,18 @@ impl Module { /// returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64| { + /// *x += y.len() as i64 + z; Ok(*x) + /// }); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn set_fn_3_mut< A: Variant + Clone, B: Variant + Clone, @@ -334,6 +516,16 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); + /// assert!(module.get_fn(hash).is_some()); + /// ``` pub fn get_fn(&self, hash: u64) -> Option<&Box> { self.functions.get(&hash).map(|v| v.as_ref()) } @@ -366,7 +558,16 @@ impl Module { })?) } - /// Get a script-defined function. + /// Get the script-defined functions. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// assert_eq!(module.get_fn_lib().len(), 0); + /// ``` pub fn get_fn_lib(&self) -> &FunctionsLib { &self.fn_lib } @@ -383,10 +584,61 @@ impl Module { .fn_lib .get_function(name, args)) } + + /// Create a new `Module` by evaluating an `AST`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Module}; + /// + /// let engine = Engine::new(); + /// let ast = engine.compile("let answer = 42;")?; + /// let module = Module::eval_ast_as_new(&ast, &engine)?; + /// assert!(module.contains_var("answer")); + /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); + /// # Ok(()) + /// # } + /// ``` + pub fn eval_ast_as_new(ast: &AST, engine: &Engine) -> FuncReturn { + // Use new scope + let mut scope = Scope::new(); + + // Run the script + engine.eval_ast_with_scope_raw(&mut scope, &ast)?; + + // Create new module + let mut module = Module::new(); + + scope.into_iter().for_each( + |ScopeEntry { + name, typ, value, .. + }| { + match typ { + // Variables left in the scope become module variables + ScopeEntryType::Normal | ScopeEntryType::Constant => { + module.variables.insert(name.into_owned(), value); + } + // Modules left in the scope become sub-modules + ScopeEntryType::Module => { + module + .modules + .insert(name.into_owned(), value.cast::()); + } + } + }, + ); + + module.fn_lib = module.fn_lib.merge(ast.fn_lib()); + + Ok(module) + } } /// Re-export module resolvers. pub mod resolvers { + #[cfg(not(feature = "no_std"))] pub use super::file::FileModuleResolver; pub use super::stat::StaticModuleResolver; } @@ -403,6 +655,20 @@ mod file { /// allow specification of a base directory with module path used as a relative path offset /// to the base directory. The script file is then forced to be in a specified extension /// (default `.rhai`). + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct FileModuleResolver { path: PathBuf, @@ -411,16 +677,66 @@ mod file { impl FileModuleResolver { /// Create a new `FileModuleResolver` with a specific base path. - pub fn new_with_path(path: PathBuf) -> Self { - Self::new_with_path_and_extension(path, "rhai".to_string()) + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::new_with_path("./scripts"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new_with_path>(path: P) -> Self { + Self::new_with_path_and_extension(path, "rhai") } + /// Create a new `FileModuleResolver` with a specific base path and file extension. /// /// The default extension is `.rhai`. - pub fn new_with_path_and_extension(path: PathBuf, extension: String) -> Self { - Self { path, extension } + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory + /// // with file extension '.x'. + /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` + pub fn new_with_path_and_extension, E: Into>( + path: P, + extension: E, + ) -> Self { + Self { + path: path.into(), + extension: extension.into(), + } } + /// Create a new `FileModuleResolver` with the current directory as base path. + /// + /// # Examples + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts from the current directory + /// // with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::new(); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` pub fn new() -> Self { Default::default() } @@ -443,39 +759,8 @@ mod file { .compile_file(file_path) .map_err(|err| EvalAltResult::set_position(err, pos))?; - // Use new scope - let mut scope = Scope::new(); - - // Run the script - engine - .eval_ast_with_scope_raw(&mut scope, &ast) - .map_err(|err| EvalAltResult::set_position(err, pos))?; - - // Create new module - let mut module = Module::new(); - - scope.into_iter().for_each( - |ScopeEntry { - name, typ, value, .. - }| { - match typ { - // Variables left in the scope become module variables - ScopeEntryType::Normal | ScopeEntryType::Constant => { - module.variables.insert(name.into_owned(), value); - } - // Modules left in the scope become sub-modules - ScopeEntryType::Module => { - module - .modules - .insert(name.into_owned(), value.cast::()); - } - } - }, - ); - - module.fn_lib = module.fn_lib.merge(ast.fn_lib()); - - Ok(module) + Module::eval_ast_as_new(&ast, engine) + .map_err(|err| EvalAltResult::set_position(err, pos)) } } } @@ -485,11 +770,41 @@ mod stat { use super::*; /// A module resolution service that serves modules added into it. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::StaticModuleResolver; + /// + /// let mut resolver = StaticModuleResolver::new(); + /// + /// let module = Module::new(); + /// resolver.insert("hello".to_string(), module); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` #[derive(Debug, Clone, Default)] pub struct StaticModuleResolver(HashMap); impl StaticModuleResolver { /// Create a new `StaticModuleResolver`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::StaticModuleResolver; + /// + /// let mut resolver = StaticModuleResolver::new(); + /// + /// let module = Module::new(); + /// resolver.insert("hello".to_string(), module); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(Some(resolver)); + /// ``` pub fn new() -> Self { Default::default() } diff --git a/src/optimize.rs b/src/optimize.rs index a2dc0048..33765a56 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -711,11 +711,16 @@ pub fn optimize_into_ast( #[cfg(feature = "no_optimize")] const level: OptimizationLevel = OptimizationLevel::None; + #[cfg(not(feature = "no_function"))] let fn_lib: Vec<_> = functions .iter() .map(|fn_def| (fn_def.name.as_str(), fn_def.params.len())) .collect(); + #[cfg(feature = "no_function")] + const fn_lib: &[(&str, usize)] = &[]; + + #[cfg(not(feature = "no_function"))] let lib = FunctionsLib::from_vec( functions .iter() @@ -745,6 +750,9 @@ pub fn optimize_into_ast( .collect(), ); + #[cfg(feature = "no_function")] + let lib: FunctionsLib = Default::default(); + AST::new( match level { OptimizationLevel::None => statements, diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index ca69f3fc..dfb80422 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_index"))] + use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut}; use crate::any::{Dynamic, Variant}; diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 40e2cec0..451897bf 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "no_object"))] + use super::{reg_binary, reg_binary_mut, reg_unary_mut}; use crate::any::Dynamic; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 87807d70..e58890d1 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,10 +1,16 @@ use super::{reg_binary, reg_binary_mut, reg_none, reg_unary, reg_unary_mut}; use crate::def_package; -use crate::engine::{Array, Map, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; +use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::map_dynamic as map; use crate::parser::INT; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{ fmt::{Debug, Display}, format, @@ -18,6 +24,7 @@ fn to_debug(x: &mut T) -> String { fn to_string(x: &mut T) -> String { format!("{}", x) } +#[cfg(not(feature = "no_object"))] fn format_map(x: &mut Map) -> String { format!("#{:?}", x) } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 0144792e..2badac23 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,10 +1,12 @@ use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut}; use crate::def_package; -use crate::engine::Array; use crate::fn_register::map_dynamic as map; use crate::parser::INT; +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; + use crate::stdlib::{ fmt::Display, format, diff --git a/src/parser.rs b/src/parser.rs index b1e91eb0..550feeb0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -39,6 +39,7 @@ pub type INT = i32; /// The system floating-point type. /// /// Not available under the `no_float` feature. +#[cfg(not(feature = "no_float"))] pub type FLOAT = f64; type PERR = ParseErrorType; @@ -143,13 +144,11 @@ impl AST { (true, true) => vec![], }; - #[cfg(feature = "sync")] - return Self(ast, Arc::new(functions.merge(other.1.as_ref()))); - #[cfg(not(feature = "sync"))] - return Self(ast, Rc::new(functions.merge(other.1.as_ref()))); + Self::new(ast, functions.merge(other.1.as_ref())) } /// Clear all function definitions in the `AST`. + #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { #[cfg(feature = "sync")] { @@ -162,6 +161,7 @@ impl AST { } /// Clear all statements in the `AST`, leaving only function definitions. + #[cfg(not(feature = "no_function"))] pub fn retain_functions(&mut self) { self.0 = vec![]; } @@ -351,6 +351,7 @@ pub enum Expr { /// Integer constant. IntegerConstant(INT, Position), /// Floating-point constant. + #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT, Position), /// Character constant. CharConstant(char, Position), @@ -413,6 +414,7 @@ impl Expr { Self::False(_) => false.into(), Self::Unit(_) => ().into(), + #[cfg(not(feature = "no_index"))] Self::Array(items, _) if items.iter().all(Self::is_constant) => { Dynamic(Union::Array(Box::new( items @@ -422,6 +424,7 @@ impl Expr { ))) } + #[cfg(not(feature = "no_object"))] Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => { Dynamic(Union::Map(Box::new( items @@ -442,8 +445,10 @@ impl Expr { /// Panics when the expression is not constant. pub fn get_constant_str(&self) -> String { match self { - Self::IntegerConstant(i, _) => i.to_string(), + #[cfg(not(feature = "no_float"))] Self::FloatConstant(f, _) => f.to_string(), + + Self::IntegerConstant(i, _) => i.to_string(), Self::CharConstant(c, _) => c.to_string(), Self::StringConstant(_, _) => "string".to_string(), Self::True(_) => "true".to_string(), @@ -459,8 +464,10 @@ impl Expr { /// Get the `Position` of the expression. pub fn position(&self) -> Position { match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos, + Self::IntegerConstant(_, pos) - | Self::FloatConstant(_, pos) | Self::CharConstant(_, pos) | Self::StringConstant(_, pos) | Self::Array(_, pos) @@ -485,8 +492,10 @@ impl Expr { /// Get the `Position` of the expression. pub(crate) fn set_position(mut self, new_pos: Position) -> Self { match &mut self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos = new_pos, + Self::IntegerConstant(_, pos) - | Self::FloatConstant(_, pos) | Self::CharConstant(_, pos) | Self::StringConstant(_, pos) | Self::Array(_, pos) @@ -531,8 +540,10 @@ impl Expr { /// Is the expression a constant? pub fn is_constant(&self) -> bool { match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => true, + Self::IntegerConstant(_, _) - | Self::FloatConstant(_, _) | Self::CharConstant(_, _) | Self::StringConstant(_, _) | Self::True(_) @@ -559,8 +570,10 @@ impl Expr { /// Is a particular token allowed as a postfix operator to this expression? pub fn is_valid_postfix(&self, token: &Token) -> bool { match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => false, + Self::IntegerConstant(_, _) - | Self::FloatConstant(_, _) | Self::CharConstant(_, _) | Self::In(_, _, _) | Self::And(_, _, _) @@ -764,8 +777,15 @@ fn parse_index_chain<'a>( .into_err(*pos)) } - Expr::FloatConstant(_, pos) - | Expr::CharConstant(_, pos) + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, pos) => { + return Err(PERR::MalformedIndexExpr( + "Only arrays, object maps and strings can be indexed".into(), + ) + .into_err(pos)) + } + + Expr::CharConstant(_, pos) | Expr::Assignment(_, _, pos) | Expr::And(_, _, pos) | Expr::Or(_, _, pos) @@ -792,8 +812,16 @@ fn parse_index_chain<'a>( ) .into_err(*pos)) } - Expr::FloatConstant(_, pos) - | Expr::CharConstant(_, pos) + + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, pos) => { + return Err(PERR::MalformedIndexExpr( + "Only arrays, object maps and strings can be indexed".into(), + ) + .into_err(pos)) + } + + Expr::CharConstant(_, pos) | Expr::Assignment(_, _, pos) | Expr::And(_, _, pos) | Expr::Or(_, _, pos) @@ -811,6 +839,7 @@ fn parse_index_chain<'a>( }, // lhs[float] + #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a float".into(), @@ -1095,6 +1124,7 @@ fn parse_primary<'a>( } } // Indexing + #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { parse_index_chain(input, stack, expr, token_pos, allow_stmt_expr)? } @@ -1280,7 +1310,6 @@ fn make_dot_expr( fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result> { match (&lhs, &rhs) { (_, Expr::IntegerConstant(_, pos)) - | (_, Expr::FloatConstant(_, pos)) | (_, Expr::And(_, _, pos)) | (_, Expr::Or(_, _, pos)) | (_, Expr::In(_, _, pos)) @@ -1294,11 +1323,20 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + return Err(PERR::MalformedInExpr( + "'in' expression expects a string, array or object map".into(), + ) + .into_err(*pos)) + } + // "xxx" in "xxxx", 'x' in "xxxx" - OK! (Expr::StringConstant(_, _), Expr::StringConstant(_, _)) | (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (), // 123.456 in "xxxx" + #[cfg(not(feature = "no_float"))] (Expr::FloatConstant(_, pos), Expr::StringConstant(_, _)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a float".into(), @@ -1352,6 +1390,7 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result (), // 123.456 in #{...} + #[cfg(not(feature = "no_float"))] (Expr::FloatConstant(_, pos), Expr::Map(_, _)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a float".into(), @@ -1975,6 +2014,7 @@ fn parse_stmt<'a>( Token::Let => parse_let(input, stack, ScopeEntryType::Normal, allow_stmt_expr), Token::Const => parse_let(input, stack, ScopeEntryType::Constant, allow_stmt_expr), + #[cfg(not(feature = "no_module"))] Token::Import => parse_import(input, stack, allow_stmt_expr), _ => parse_expr_stmt(input, stack, allow_stmt_expr), @@ -2105,13 +2145,15 @@ fn parse_global_level<'a>( while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions - if let (Token::Fn, _) = input.peek().unwrap() { - let mut stack = Stack::new(); - let f = parse_fn(input, &mut stack, true)?; - functions.insert(calc_fn_def(&f.name, f.params.len()), f); - continue; + #[cfg(not(feature = "no_function"))] + { + if let (Token::Fn, _) = input.peek().unwrap() { + let mut stack = Stack::new(); + let f = parse_fn(input, &mut stack, true)?; + functions.insert(calc_fn_def(&f.name, f.params.len()), f); + continue; + } } - // Actual statement let stmt = parse_stmt(input, &mut stack, false, true)?; diff --git a/src/scope.rs b/src/scope.rs index 94035dbb..2411dcc1 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,10 +1,12 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Dynamic, Union, Variant}; -use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr}; use crate::token::Position; +#[cfg(not(feature = "no_module"))] +use crate::module::Module; + use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec, vec::Vec}; /// Type of an entry in the Scope. @@ -172,6 +174,7 @@ impl<'a> Scope<'a> { /// Add (push) a new module to the Scope. /// /// Modules are used for accessing member variables, functions and plugins under a namespace. + #[cfg(not(feature = "no_module"))] pub fn push_module>>(&mut self, name: K, value: Module) { self.push_dynamic_value( name, @@ -340,6 +343,7 @@ impl<'a> Scope<'a> { } /// Find a module in the Scope, starting from the last entry. + #[cfg(not(feature = "no_module"))] pub fn find_module(&mut self, name: &str) -> Option<&mut Module> { let index = self.get_module_index(name)?; self.get_mut(index).0.downcast_mut::() From 0cb0393c24eab51095d49e3f446c11469cbd183c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 6 May 2020 19:59:45 +0800 Subject: [PATCH 23/23] Bump version. --- Cargo.toml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5cf15e1e..68837a2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.13.0" +version = "0.14.1" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/README.md b/README.md index e27bf079..cd95c92f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Rhai's current features set: to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.14.1, so the language and API's may change before they stabilize. Installation ------------ @@ -39,7 +39,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.13.0" +rhai = "0.14.1" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):