From e14cfbd9d7193bf5540adbbe47beb808b46eec2d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 2 Jul 2020 12:47:24 +0800 Subject: [PATCH 001/103] Restore plugins code. --- src/engine.rs | 1 + src/fn_native.rs | 56 +++++++++++++++++++++++++----- src/fn_register.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 +++- src/module.rs | 8 ++--- src/plugin.rs | 42 ++++++++++++++++++++++ 6 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 src/plugin.rs diff --git a/src/engine.rs b/src/engine.rs index 65c91735..0a46bc1b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2019,6 +2019,7 @@ impl Engine { ) .map_err(|err| err.new_position(*pos)) } + Ok(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), *pos), Ok(f) => { f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos)) } diff --git a/src/fn_native.rs b/src/fn_native.rs index e41ae7db..f3632d1e 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,7 @@ use crate::any::Dynamic; use crate::engine::Engine; use crate::parser::ScriptFnDef; +use crate::plugin::PluginFunction; use crate::result::EvalAltResult; use crate::utils::ImmutableString; @@ -96,6 +97,11 @@ pub type FnAny = /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; +#[cfg(feature = "sync")] +pub type SharedPluginFunction = Arc; +#[cfg(not(feature = "sync"))] +pub type SharedPluginFunction = Rc; + /// A standard callback function. #[cfg(not(feature = "sync"))] pub type Callback = Box R + 'static>; @@ -113,6 +119,8 @@ pub enum CallableFunction { Method(Shared), /// An iterator function. Iterator(IteratorFn), + /// A plugin-defined function, + Plugin(SharedPluginFunction), /// A script-defined function. Script(Shared), } @@ -123,6 +131,7 @@ impl fmt::Debug for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Plugin(_) => write!(f, "PluginFunction"), Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), } } @@ -134,6 +143,7 @@ impl fmt::Display for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Plugin(_) => write!(f, "PluginFunction"), CallableFunction::Script(s) => fmt::Display::fmt(s, f), } } @@ -144,28 +154,35 @@ impl CallableFunction { pub fn is_pure(&self) -> bool { match self { Self::Pure(_) => true, - Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Method(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false, } } /// Is this a native Rust method function? pub fn is_method(&self) -> bool { match self { Self::Method(_) => true, - Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Pure(_) | Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => false, } } /// Is this an iterator function? pub fn is_iter(&self) -> bool { match self { Self::Iterator(_) => true, - Self::Pure(_) | Self::Method(_) | Self::Script(_) => false, + Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => false, } } /// Is this a Rhai-scripted function? pub fn is_script(&self) -> bool { match self { Self::Script(_) => true, - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false, + } + } + /// Is this a plugin-defined function? + pub fn is_plugin_fn(&self) -> bool { + match self { + Self::Plugin(_) => true, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, } } /// Get a reference to a native Rust function. @@ -176,7 +193,7 @@ impl CallableFunction { pub fn get_native_fn(&self) -> &FnAny { match self { Self::Pure(f) | Self::Method(f) => f.as_ref(), - Self::Iterator(_) | Self::Script(_) => unreachable!(), + Self::Iterator(_) | Self::Script(_) | Self::Plugin(_) => unreachable!(), } } /// Get a shared reference to a script-defined function definition. @@ -186,7 +203,7 @@ impl CallableFunction { /// Panics if the `CallableFunction` is not `Script`. pub fn get_shared_fn_def(&self) -> Shared { match self { - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), Self::Script(f) => f.clone(), } } @@ -197,7 +214,7 @@ impl CallableFunction { /// Panics if the `CallableFunction` is not `Script`. pub fn get_fn_def(&self) -> &ScriptFnDef { match self { - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), Self::Script(f) => f, } } @@ -209,7 +226,18 @@ impl CallableFunction { pub fn get_iter_fn(&self) -> IteratorFn { match self { Self::Iterator(f) => *f, - Self::Pure(_) | Self::Method(_) | Self::Script(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Plugin(_) => unreachable!(), + } + } + /// Get a reference to a plugin function. + /// + /// # Panics + /// + /// Panics if the `CallableFunction` is not `Plugin`. + pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction { + match self { + Self::Plugin(f) => f.clone(), + Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Iterator(_) => unreachable!(), } } /// Create a new `CallableFunction::Pure`. @@ -220,6 +248,18 @@ impl CallableFunction { pub fn from_method(func: Box) -> Self { Self::Method(func.into()) } + + #[cfg(feature = "sync")] + /// Create a new `CallableFunction::Plugin`. + pub fn from_plugin(plugin: impl PluginFunction + 'static + Send + Sync) -> Self { + Self::Plugin(Arc::new(plugin)) + } + + #[cfg(not(feature = "sync"))] + /// Create a new `CallableFunction::Plugin`. + pub fn from_plugin(plugin: impl PluginFunction + 'static) -> Self { + Self::Plugin(Rc::new(plugin)) + } } impl From for CallableFunction { diff --git a/src/fn_register.rs b/src/fn_register.rs index 1b07cbe8..cf83ae14 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -5,11 +5,91 @@ use crate::any::{Dynamic, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::parser::FnAccess; +use crate::plugin::Plugin; use crate::result::EvalAltResult; use crate::utils::ImmutableString; use crate::stdlib::{any::TypeId, boxed::Box, mem}; +/// A trait to register custom plugins with the `Engine`. +/// +/// A plugin consists of a number of functions. All functions will be registered with the engine. +pub trait RegisterPlugin { + /// Allow extensions of the engine's behavior. + /// + /// This can include importing modules, registering functions to the global name space, and + /// more. + /// + /// # Example + /// + /// ``` + /// use rhai::{FLOAT, INT, Module, ModuleResolver, RegisterFn, RegisterPlugin}; + /// use rhai::plugin::*; + /// use rhai::module_resolvers::*; + /// + /// // A function we want to expose to Rhai. + /// #[derive(Copy, Clone)] + /// struct DistanceFunction(); + /// + /// impl PluginFunction for DistanceFunction { + /// fn is_method_call(&self) -> bool { false } + /// fn is_varadic(&self) -> bool { false } + /// + /// fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result> { + /// let x1: &FLOAT = args[0].downcast_ref::().unwrap(); + /// let y1: &FLOAT = args[1].downcast_ref::().unwrap(); + /// let x2: &FLOAT = args[2].downcast_ref::().unwrap(); + /// let y2: &FLOAT = args[3].downcast_ref::().unwrap(); + /// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0); + /// Ok(Dynamic::from(square_sum.sqrt())) + /// } + /// + /// fn clone_boxed(&self) -> Box { + /// Box::new(DistanceFunction()) + /// } + /// } + /// + /// // A simple custom plugin. This should not usually be done with hand-written code. + /// #[derive(Copy, Clone)] + /// pub struct AdvancedMathPlugin(); + /// + /// impl Plugin for AdvancedMathPlugin { + /// fn register_contents(self, engine: &mut Engine) { + /// // Plugins are allowed to have side-effects on the engine. + /// engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + /// + /// // Main purpose: create a module to expose the functions to Rhai. + /// // + /// // This is currently a hack. There needs to be a better API here for "plugin" + /// // modules. + /// let mut m = Module::new(); + /// m.set_fn("euclidean_distance".to_string(), FnAccess::Public, + /// &[std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()], + /// CallableFunction::from_plugin(DistanceFunction())); + /// let mut r = StaticModuleResolver::new(); + /// r.insert("Math::Advanced".to_string(), m); + /// engine.set_module_resolver(Some(r)); + /// } + /// } + /// + /// + /// # fn main() -> Result<(), Box> { + /// + /// let mut engine = Engine::new(); + /// engine.register_plugin(AdvancedMathPlugin()); + /// + /// assert_eq!(engine.eval::( + /// r#"import "Math::Advanced" as math; + /// let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 41.0); + /// # Ok(()) + /// # } + /// ``` + fn register_plugin(&mut self, plugin: PL); +} + /// Trait to register custom functions with the `Engine`. pub trait RegisterFn { /// Register a custom function with the `Engine`. @@ -111,6 +191,12 @@ pub fn by_value(data: &mut Dynamic) -> T { } } +impl RegisterPlugin for Engine { + fn register_plugin(&mut self, plugin: PL) { + plugin.register_contents(self); + } +} + /// This macro creates a closure wrapping a registered function. macro_rules! make_func { ($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { diff --git a/src/lib.rs b/src/lib.rs index aa205dec..6e441424 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,10 @@ mod module; mod optimize; pub mod packages; mod parser; +#[cfg(not(feature = "no_module"))] +pub mod plugin; +#[cfg(feature = "no_module")] +mod plugin; mod result; mod scope; mod stdlib; @@ -95,7 +99,7 @@ pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_native::IteratorFn; -pub use fn_register::{RegisterFn, RegisterResultFn}; +pub use fn_register::{RegisterFn, RegisterPlugin, RegisterResultFn}; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; pub use result::EvalAltResult; diff --git a/src/module.rs b/src/module.rs index 3cdeb711..c7198d83 100644 --- a/src/module.rs +++ b/src/module.rs @@ -876,12 +876,10 @@ impl Module { .functions .iter() .filter(|(_, (_, _, _, v))| match v { - CallableFunction::Pure(_) - | CallableFunction::Method(_) - | CallableFunction::Iterator(_) => true, CallableFunction::Script(ref f) => { filter(f.access, f.name.as_str(), f.params.len()) } + _ => true, }) .map(|(&k, v)| (k, v.clone())), ); @@ -897,10 +895,8 @@ impl Module { /// Filter out the functions, retaining only some based on a filter predicate. pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) { self.functions.retain(|_, (_, _, _, v)| match v { - CallableFunction::Pure(_) - | CallableFunction::Method(_) - | CallableFunction::Iterator(_) => true, CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), + _ => true, }); self.all_functions.clear(); diff --git a/src/plugin.rs b/src/plugin.rs new file mode 100644 index 00000000..e0d53507 --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,42 @@ +//! Module defining plugins in Rhai. Is exported for use by plugin authors. + +pub use crate::any::{Dynamic, Variant}; +pub use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn}; +pub use crate::parser::{ + FnAccess, + FnAccess::{Private, Public}, + AST, +}; +pub use crate::result::EvalAltResult; +pub use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; +pub use crate::token::{Position, Token}; +pub use crate::utils::StaticVec; +pub use crate::Engine; + +#[cfg(features = "sync")] +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait Plugin: Send { + fn register_contents(self, engine: &mut Engine); +} + +#[cfg(not(features = "sync"))] +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait Plugin: Send + Sync { + fn register_contents(self, engine: &mut Engine); +} + +/// Represents a function that is statically defined within a plugin. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait PluginFunction { + fn is_method_call(&self) -> bool; + fn is_varadic(&self) -> bool; + + fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result>; + + fn clone_boxed(&self) -> Box; +} From ba5c4fd0409c3baecf5625f764a9b5cf1c75cab8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 10:05:47 +0800 Subject: [PATCH 002/103] Improve writeup. --- README.md | 2 +- doc/src/SUMMARY.md | 21 ++++----- doc/src/about/index.md | 5 ++- doc/src/about/license.md | 3 +- doc/src/about/non-design.md | 23 ++++++++-- doc/src/advanced.md | 4 +- doc/src/engine/hello-world.md | 30 ++++++++----- doc/src/engine/raw.md | 9 ++-- doc/src/language/for.md | 3 +- doc/src/language/if.md | 3 ++ doc/src/language/logic.md | 39 ---------------- doc/src/language/method.md | 13 +++--- doc/src/language/other-op.md | 66 ++++++++++++++++++++++++++++ doc/src/rust/custom.md | 4 +- doc/src/rust/functions.md | 6 +-- doc/src/rust/modules/imp-resolver.md | 2 +- doc/src/rust/override.md | 4 +- doc/src/start/builds/performance.md | 4 +- doc/src/start/features.md | 4 +- 19 files changed, 156 insertions(+), 89 deletions(-) create mode 100644 doc/src/language/other-op.md diff --git a/README.md b/README.md index f5431f6e..1516abcc 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Licensed under either: * [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or * [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt) -at your option. +at your choice. Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in this crate, as defined in the Apache-2.0 license, shall diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index b1e5a561..3592dbc5 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -21,7 +21,7 @@ The Rhai Scripting Language 2. [Scripts](start/examples/scripts.md) 3. [Using the `Engine`](engine/index.md) 1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md) - 2. [Compile a Script to AST for Repeated Evaluations](engine/compile.md) + 2. [Compile to AST for Repeated Evaluations](engine/compile.md) 3. [Call a Rhai Function from Rust](engine/call-fn.md) 4. [Create a Rust Anonymous Function from a Rhai Function](engine/func.md) 5. [Evaluate Expressions Only](engine/expressions.md) @@ -65,21 +65,22 @@ The Rhai Scripting Language 5. [Variables](language/variables.md) 6. [Constants](language/constants.md) 7. [Logic Operators](language/logic.md) - 8. [If Statement](language/if.md) - 9. [While Loop](language/while.md) - 10. [Loop Statement](language/loop.md) - 11. [For Loop](language/for.md) - 12. [Return Values](language/return.md) - 13. [Throw Exception on Error](language/throw.md) - 14. [Functions](language/functions.md) + 8. [Other Operators](language/other-op.md) + 9. [If Statement](language/if.md) + 10. [While Loop](language/while.md) + 11. [Loop Statement](language/loop.md) + 12. [For Loop](language/for.md) + 13. [Return Values](language/return.md) + 14. [Throw Exception on Error](language/throw.md) + 15. [Functions](language/functions.md) 1. [Call Method as Function](language/method.md) 2. [Overloading](language/overload.md) 3. [Namespaces](language/fn-namespaces.md) 4. [Function Pointers](language/fn-ptr.md) 5. [Anonymous Functions](language/fn-anon.md) 6. [Currying](language/fn-curry.md) - 15. [Print and Debug](language/print-debug.md) - 16. [Modules](language/modules/index.md) + 16. [Print and Debug](language/print-debug.md) + 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) 3. [Create from Rust](rust/modules/index.md) diff --git a/doc/src/about/index.md b/doc/src/about/index.md index 76603bde..cf021da5 100644 --- a/doc/src/about/index.md +++ b/doc/src/about/index.md @@ -7,6 +7,9 @@ Rhai is an embedded scripting language and evaluation engine for Rust that gives to add scripting to any application. -This Book is for version {{version}} of Rhai. +Versions +-------- + +This Book is for version **{{version}}** of Rhai. For the latest development version, see [here]({{rootUrl}}/vnext/). diff --git a/doc/src/about/license.md b/doc/src/about/license.md index a60ac1eb..a1f24339 100644 --- a/doc/src/about/license.md +++ b/doc/src/about/license.md @@ -6,9 +6,10 @@ Licensing Rhai is licensed under either: * [Apache License, Version 2.0]({{repoHome}}/LICENSE-APACHE.txt), or + * [MIT license]({{repoHome}}/LICENSE-MIT.txt) -at your option. +at your choice. Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in this crate, as defined in the Apache-2.0 license, shall be dual-licensed as above, diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md index 52888164..7f40614e 100644 --- a/doc/src/about/non-design.md +++ b/doc/src/about/non-design.md @@ -18,21 +18,38 @@ It doesn't attempt to be a new language. For example: * No first-class functions - Code your functions in Rust instead, and register them with Rhai. - There is, however, support for simple [function pointers] allowing runtime dispatch by function name. + There is, however, support for simple [function pointers] to allow runtime dispatch by function name. * No garbage collection - this should be expected, so... * No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md). + But you can [curry][currying] a [function pointer] with arguments to simulate it somewhat. + * No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races. The purpose of Rhai is not to be extremely _fast_, but to make it as easy as possible to integrate with native Rust applications. + +Do Not Write The Next 4D VR Game in Rhai +--------------------------------------- + Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting advanced language features such as classes, inheritance, first-class functions, closures, concurrency, byte-codes, JIT etc. Avoid the temptation to write full-fledge application logic entirely in Rhai - that use case is best fulfilled by more complete languages such as JavaScript or Lua. -Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call. -All your core functionalities should be in Rust. + +Thin Dynamic Wrapper Layer Over Rust Code +---------------------------------------- + +In actual practice, it is usually best to expose a Rust API into Rhai for scripts to call. + +All the core functionalities should be written in Rust, with Rhai being the dynamic _control_ layer. + This is similar to some dynamic languages where most of the core functionalities reside in a C/C++ standard library. + +Another similar scenario is a web front-end driving back-end services written in a systems language. +In this case, JavaScript takes the role of Rhai while the back-end language, well... it can actually also be Rust. +Except that Rhai integrates with Rust _much_ more tightly, removing the need for interfaces such +as XHR calls and payload encoding such as JSON. diff --git a/doc/src/advanced.md b/doc/src/advanced.md index b7d69e39..cd958c73 100644 --- a/doc/src/advanced.md +++ b/doc/src/advanced.md @@ -5,7 +5,7 @@ Advanced Topics This section covers advanced features such as: -* Simulated [Object Oriented Programming][OOP]. +* Simulated [Object Oriented Programming (OOP)][OOP]. * [`serde`] integration. @@ -13,4 +13,6 @@ This section covers advanced features such as: * [Domain-Specific Languages][DSL]. +* Low-level [function registration API]({{rootUrl}}/rust/register-raw.md) + * The dreaded (or beloved for those with twisted tastes) [`eval`] statement. diff --git a/doc/src/engine/hello-world.md b/doc/src/engine/hello-world.md index c4e868af..908a8cf8 100644 --- a/doc/src/engine/hello-world.md +++ b/doc/src/engine/hello-world.md @@ -22,19 +22,31 @@ fn main() -> Result<(), Box> } ``` -`rhai::EvalAltResult` is a Rust `enum` containing all errors encountered during the parsing or evaluation process. +Evaluate a script file directly: + +```rust +// 'eval_file' takes a 'PathBuf' +let result = engine.eval_file::("hello_world.rhai".into())?; +``` -Evaluate a Script ----------------- +Error Type +---------- -The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. -Rhai is very strict here. +`rhai::EvalAltResult` is the standard Rhai error type, which is a Rust `enum` containing all errors encountered +during the parsing or evaluation process. -Use [`Dynamic`] for uncertain return types. + +Return Type +----------- + +The type parameter for `Engine::eval` is used to specify the type of the return value, +which _must_ match the actual type or an error is returned. Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. +Use [`Dynamic`] for uncertain return types. + ```rust let result = engine.eval::("40 + 2")?; // return type is i64, specified using 'turbofish' notation @@ -46,9 +58,3 @@ let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not s let result = engine.eval::("40 + 2")?; // returns an error because the actual return type is i64, not String ``` - -Evaluate a script file directly: - -```rust -let result = engine.eval_file::("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf' -``` diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md index ce61aa91..06e4ff33 100644 --- a/doc/src/engine/raw.md +++ b/doc/src/engine/raw.md @@ -10,7 +10,10 @@ In many controlled embedded environments, however, these may not be needed and u application code storage space. Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of -basic arithmetic and logical operators are supported. +basic arithmetic and logical operators are supported (see below). + +To add more functionalities to a _raw_ `Engine`, load [packages] into it. + Built-in Operators ------------------ @@ -20,7 +23,7 @@ Built-in Operators | `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `ImmutableString` | | `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) | | `<<`, `>>`, `^`, | `<<=`, `>>=`, `^=` | `INT` | -| `&`, `|`, | `&=`, `|=` | `INT`, `bool` | -| `&&`, `||` | | `bool` | +| `&`, \|, | `&=`, \|= | `INT`, `bool` | +| `&&`, \|\| | | `bool` | | `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` | | `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` | diff --git a/doc/src/language/for.md b/doc/src/language/for.md index 6780a1cd..15ea22e2 100644 --- a/doc/src/language/for.md +++ b/doc/src/language/for.md @@ -3,7 +3,8 @@ {{#include ../links.md}} -Iterating through a range or an [array] is provided by the `for` ... `in` loop. +Iterating through a range or an [array], or any type with a registered _iterator_, +is provided by the `for` ... `in` loop. Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; `break` can be used to break out of the loop unconditionally. diff --git a/doc/src/language/if.md b/doc/src/language/if.md index 3958a2e3..b9c63dab 100644 --- a/doc/src/language/if.md +++ b/doc/src/language/if.md @@ -19,6 +19,9 @@ if foo(x) { } ``` +Braces Are Mandatory +-------------------- + Unlike C, the condition expression does _not_ need to be enclosed in parentheses '`(`' .. '`)`', but all branches of the `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement inside the branch. diff --git a/doc/src/language/logic.md b/doc/src/language/logic.md index 113641f6..699b9827 100644 --- a/doc/src/language/logic.md +++ b/doc/src/language/logic.md @@ -65,42 +65,3 @@ a() | b(); // both a() and b() are evaluated a() & b(); // both a() and b() are evaluated ``` - -Compound Assignment Operators ----------------------------- - -```rust -let number = 9; - -number += 8; // number = number + 8 - -number -= 7; // number = number - 7 - -number *= 6; // number = number * 6 - -number /= 5; // number = number / 5 - -number %= 4; // number = number % 4 - -number ~= 3; // number = number ~ 3 - -number <<= 2; // number = number << 2 - -number >>= 1; // number = number >> 1 - -number &= 0x00ff; // number = number & 0x00ff; - -number |= 0x00ff; // number = number | 0x00ff; - -number ^= 0x00ff; // number = number ^ 0x00ff; -``` - -The `+=` operator can also be used to build [strings]: - -```rust -let my_str = "abc"; -my_str += "ABC"; -my_str += 12345; - -my_str == "abcABC12345" -``` diff --git a/doc/src/language/method.md b/doc/src/language/method.md index 6b6b9314..c2e9e99e 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -4,8 +4,8 @@ Call Method as Function {{#include ../links.md}} -First `&mut` Reference Parameter -------------------------------- +First `&mut` Parameter +---------------------- Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called just like a regular function. In fact, like Rust, property getters/setters and object methods @@ -37,19 +37,20 @@ array[0].update(); // <- call in method-call style will update 'a' ``` -Encouraged Usage ----------------- +`&mut` is Efficient +------------------ Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone, even when the intention is not to mutate that argument, because it avoids cloning that argument value. -For primary types that are cheap to clone, including `ImmutableString`, this is not necessary. +For primary types that are cheap to clone (e.g. those that implement `Copy`), +including `ImmutableString`, this is not necessary. Avoid `&mut ImmutableString` --------------------------- -`ImmutableString`, Rhai internal [string] type, is an exception. +`ImmutableString`, Rhai's internal [string] type, is an exception. `ImmutableString` is cheap to clone, but expensive to take a mutable reference (because the underlying string must be cloned to make a private copy). diff --git a/doc/src/language/other-op.md b/doc/src/language/other-op.md new file mode 100644 index 00000000..bf5017d3 --- /dev/null +++ b/doc/src/language/other-op.md @@ -0,0 +1,66 @@ +Other Operators +=============== + +{{#include ../links.md}} + + +Compound Assignment Operators +---------------------------- + +```rust +let number = 9; + +number += 8; // number = number + 8 + +number -= 7; // number = number - 7 + +number *= 6; // number = number * 6 + +number /= 5; // number = number / 5 + +number %= 4; // number = number % 4 + +number ~= 3; // number = number ~ 3 + +number <<= 2; // number = number << 2 + +number >>= 1; // number = number >> 1 + +number &= 0x00ff; // number = number & 0x00ff; + +number |= 0x00ff; // number = number | 0x00ff; + +number ^= 0x00ff; // number = number ^ 0x00ff; +``` + + +The Flexible `+=` +---------------- + +The `+=` operator can also be used to build [strings]: + +```rust +let my_str = "abc"; +my_str += "ABC"; +my_str += 12345; + +my_str == "abcABC12345" +``` + +It may also be used to concatenate [arrays]: + +```rust +let my_array = [1, 2, 3]; +my_array += [4, 5]; + +my_array == [1, 2, 3, 4, 5]; +``` + +or mix two [object maps] together: + +```rust +let my_obj = #{a:1, b:2}; +my_obj += #{c:3, d:4, e:5}; + +my_obj.len() == 5; +``` diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 7460f50e..3e6b5ca3 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -66,8 +66,8 @@ let mut engine = Engine::new(); engine.register_type::(); ``` -Methods on Custom Type ---------------------- +Methods on The Custom Type +------------------------- To use native custom types, methods and functions in Rhai scripts, simply register them using one of the `Engine::register_XXX` API. diff --git a/doc/src/rust/functions.md b/doc/src/rust/functions.md index a929484c..8d8666cf 100644 --- a/doc/src/rust/functions.md +++ b/doc/src/rust/functions.md @@ -11,8 +11,8 @@ see [fallible functions]({{rootUrl}}/rust/fallible.md)). ```rust use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString}; -use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' -use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' +use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' +use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' // Normal function that returns a standard type // Remember to use 'ImmutableString' and not 'String' @@ -26,7 +26,7 @@ fn add_len_str(x: i64, s: &str) -> i64 { // Function that returns a 'Dynamic' value - must return a 'Result' fn get_any_value() -> Result> { - Ok((42_i64).into()) // standard types can use 'into()' + Ok((42_i64).into()) // standard types can use 'into()' } let mut engine = Engine::new(); diff --git a/doc/src/rust/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md index 415abf72..55d68315 100644 --- a/doc/src/rust/modules/imp-resolver.md +++ b/doc/src/rust/modules/imp-resolver.md @@ -30,7 +30,7 @@ impl ModuleResolver for MyModuleResolver { &self, engine: &Engine, // reference to the current 'Engine' path: &str, // the module path - pos: Position, // location of the 'import' statement + pos: Position, // position of the 'import' statement ) -> Result> { // Check module path. if is_valid_module_path(path) { diff --git a/doc/src/rust/override.md b/doc/src/rust/override.md index 576b5ad8..f6d4d656 100644 --- a/doc/src/rust/override.md +++ b/doc/src/rust/override.md @@ -12,7 +12,9 @@ fn to_int(num) { print("Ha! Gotcha! " + num); } -print(to_int(123)); // what happens? +let x = (123).to_int(); + +print(x); // what happens? ``` A registered native Rust function, in turn, overrides any built-in function of the diff --git a/doc/src/start/builds/performance.md b/doc/src/start/builds/performance.md index dc47a4cc..27747208 100644 --- a/doc/src/start/builds/performance.md +++ b/doc/src/start/builds/performance.md @@ -37,8 +37,8 @@ Use `ImmutableString` Internally, Rhai uses _immutable_ [strings] instead of the Rust `String` type. This is mainly to avoid excessive cloning when passing function arguments. -The encapsulated immutable string type is `ImmutableString`. It is cheap to clone (just an `Rc` or `Arc` reference -count increment depending on the [`sync`] feature). +Rhai's internal string type is `ImmutableString` (basically `Rc` or `Arc` depending on the [`sync`] feature). +It is cheap to clone, but expensive to modify (a new copy of the string must be made in order to change it). Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`) for the best performance with Rhai. diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 023834fd..f2286a38 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -25,7 +25,7 @@ more control over what a script can (or cannot) do. | `no_module` | Disable loading external [modules]. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -| `internals` | Expose internal data structures (e.g. [`AST`] nodes) and enable defining [custom syntax]. Beware that Rhai internals are volatile and may change from version to version. | +| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | Example @@ -46,7 +46,7 @@ rhai = { version = "{{version}}", features = [ "sync", "unchecked", "only_i32", ``` The resulting scripting engine supports only the `i32` integer numeral type (and no others like `u32`, `i16` or `i64`), -no floating-point, is `Send + Sync` (so it can be safely used across threads), does not support defining [functions] +no floating-point, is `Send + Sync` (so it can be safely used across threads), and does not support defining [functions] nor loading external [modules]. This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware. From e7af008d744bc200515b66c4401a9613c2da7d98 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 13:51:09 +0800 Subject: [PATCH 003/103] Avoid warnings. --- src/any.rs | 4 +- src/api.rs | 9 +- src/engine.rs | 199 ++++++++++++++++++++++-------------- src/fn_args.rs | 7 +- src/fn_call.rs | 34 +++--- src/fn_native.rs | 19 +++- src/fn_register.rs | 5 +- src/lib.rs | 2 +- src/module.rs | 56 ++++++---- src/optimize.rs | 32 ++++-- src/packages/arithmetic.rs | 37 +++++-- src/packages/array_basic.rs | 19 ++-- src/packages/map_basic.rs | 8 +- src/packages/math_basic.rs | 12 ++- src/packages/mod.rs | 1 - src/packages/string_more.rs | 16 +-- src/packages/time_basic.rs | 7 +- src/parser.rs | 118 ++++++++++++++++++--- src/scope.rs | 2 + src/serde/de.rs | 19 ++-- src/serde/ser.rs | 33 +++--- src/settings.rs | 41 ++++---- src/token.rs | 5 +- tests/call_fn.rs | 4 +- 24 files changed, 464 insertions(+), 225 deletions(-) diff --git a/src/any.rs b/src/any.rs index 1d478e62..ea959db7 100644 --- a/src/any.rs +++ b/src/any.rs @@ -19,9 +19,11 @@ use crate::stdlib::{ collections::HashMap, fmt, string::String, - vec::Vec, }; +#[cfg(not(feature = "no_index"))] +use crate::stdlib::vec::Vec; + #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::time::Instant; diff --git a/src/api.rs b/src/api.rs index c4c91f38..0e8373e0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,9 +1,8 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET}; +use crate::engine::{make_getter, make_setter, Engine, Imports, State}; use crate::error::ParseError; -use crate::fn_args::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_register::RegisterFn; use crate::module::{FuncReturn, Module}; @@ -12,13 +11,15 @@ use crate::parser::AST; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::{lex, Position}; -use crate::utils::StaticVec; + +#[cfg(not(feature = "no_index"))] +use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::Map; #[cfg(not(feature = "no_function"))] -use crate::engine::get_script_function_by_signature; +use crate::{engine::get_script_function_by_signature, fn_args::FuncArgs, utils::StaticVec}; use crate::stdlib::{ any::{type_name, TypeId}, diff --git a/src/engine.rs b/src/engine.rs index b7b73f2a..78c05030 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,10 +4,10 @@ use crate::any::{map_std_type_name, Dynamic, Union, Variant}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{CallableFunction, Callback, FnPtr}; -use crate::module::{resolvers, Module, ModuleRef, ModuleResolver}; +use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt}; +use crate::parser::{Expr, ImmutableString, ReturnType, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -15,8 +15,13 @@ use crate::syntax::{CustomSyntax, EvalContext}; use crate::token::Position; use crate::utils::StaticVec; +#[cfg(not(feature = "no_function"))] +use crate::parser::{FnAccess, ScriptFnDef}; + +#[cfg(not(feature = "no_module"))] +use crate::module::{resolvers, ModuleResolver}; + use crate::stdlib::{ - any::TypeId, borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, @@ -26,6 +31,9 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_index"))] +use crate::stdlib::any::TypeId; + /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -66,13 +74,6 @@ pub const MAX_EXPR_DEPTH: usize = 128; #[cfg(not(debug_assertions))] pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; -#[cfg(feature = "unchecked")] -pub const MAX_CALL_STACK_DEPTH: usize = usize::MAX; -#[cfg(feature = "unchecked")] -pub const MAX_EXPR_DEPTH: usize = 0; -#[cfg(feature = "unchecked")] -pub const MAX_FUNCTION_EXPR_DEPTH: usize = 0; - pub const KEYWORD_PRINT: &str = "print"; pub const KEYWORD_DEBUG: &str = "debug"; pub const KEYWORD_TYPE_OF: &str = "type_of"; @@ -86,6 +87,7 @@ pub const FN_GET: &str = "get$"; pub const FN_SET: &str = "set$"; pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_SET: &str = "index$set$"; +#[cfg(not(feature = "no_function"))] pub const FN_ANONYMOUS: &str = "anon$"; pub const MARKER_EXPR: &str = "$expr$"; pub const MARKER_BLOCK: &str = "$block$"; @@ -108,6 +110,7 @@ pub enum Target<'a> { Value(Dynamic), /// The target is a character inside a String. /// This is necessary because directly pointing to a char inside a String is impossible. + #[cfg(not(feature = "no_index"))] StringChar(&'a mut Dynamic, usize, Dynamic), } @@ -116,7 +119,9 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, - Self::Value(_) | Self::StringChar(_, _, _) => false, + Self::Value(_) => false, + #[cfg(not(feature = "no_index"))] + Self::StringChar(_, _, _) => false, } } /// Is the `Target` an owned value? @@ -124,6 +129,7 @@ impl Target<'_> { match self { Self::Ref(_) => false, Self::Value(_) => true, + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, } } @@ -132,14 +138,16 @@ impl Target<'_> { match self { Target::Ref(r) => r.is::(), Target::Value(r) => r.is::(), + #[cfg(not(feature = "no_index"))] Target::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), } } /// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary. pub fn clone_into_dynamic(self) -> Dynamic { match self { - Self::Ref(r) => r.clone(), // Referenced value is cloned - Self::Value(v) => v, // Owned value is simply taken + Self::Ref(r) => r.clone(), // Referenced value is cloned + Self::Value(v) => v, // Owned value is simply taken + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } } @@ -148,6 +156,7 @@ impl Target<'_> { match self { Self::Ref(r) => *r, Self::Value(ref mut r) => r, + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ref mut r) => r, } } @@ -161,6 +170,7 @@ impl Target<'_> { Position::none(), ))) } + #[cfg(not(feature = "no_index"))] Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { // Replace the character at the specified index position let new_ch = new_val @@ -176,7 +186,8 @@ impl Target<'_> { *s = chars.iter().collect::().into(); } } - _ => unreachable!(), + #[cfg(not(feature = "no_index"))] + Self::StringChar(_, _, _) => unreachable!(), } Ok(()) @@ -249,6 +260,34 @@ pub fn get_script_function_by_signature<'a>( } } +/// [INTERNALS] A type containing all the limits imposed by the `Engine`. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. +#[cfg(not(feature = "unchecked"))] +pub struct Limits { + /// Maximum levels of call-stack to prevent infinite recursion. + /// + /// Defaults to 16 for debug builds and 128 for non-debug builds. + pub max_call_stack_depth: usize, + /// Maximum depth of statements/expressions at global level. + pub max_expr_depth: usize, + /// Maximum depth of statements/expressions in functions. + pub max_function_expr_depth: usize, + /// Maximum number of operations allowed to run. + pub max_operations: u64, + /// Maximum number of modules allowed to load. + pub max_modules: usize, + /// Maximum length of a string. + pub max_string_size: usize, + /// Maximum length of an array. + pub max_array_size: usize, + /// Maximum number of properties in a map. + pub max_map_size: usize, +} + /// Rhai main scripting engine. /// /// ``` @@ -275,6 +314,7 @@ pub struct Engine { pub(crate) packages: PackagesCollection, /// A module resolution service. + #[cfg(not(feature = "no_module"))] pub(crate) module_resolver: Option>, /// A hashmap mapping type names to pretty-print names. @@ -296,24 +336,10 @@ pub struct Engine { /// Optimize the AST after compilation. pub(crate) optimization_level: OptimizationLevel, - /// Maximum levels of call-stack to prevent infinite recursion. - /// - /// Defaults to 16 for debug builds and 128 for non-debug builds. - pub(crate) max_call_stack_depth: usize, - /// Maximum depth of statements/expressions at global level. - pub(crate) max_expr_depth: usize, - /// Maximum depth of statements/expressions in functions. - pub(crate) max_function_expr_depth: usize, - /// Maximum number of operations allowed to run. - pub(crate) max_operations: u64, - /// Maximum number of modules allowed to load. - pub(crate) max_modules: usize, - /// Maximum length of a string. - pub(crate) max_string_size: usize, - /// Maximum length of an array. - pub(crate) max_array_size: usize, - /// Maximum number of properties in a map. - pub(crate) max_map_size: usize, + + /// Max limits. + #[cfg(not(feature = "unchecked"))] + pub(crate) limits: Limits, } impl fmt::Debug for Engine { @@ -338,7 +364,8 @@ impl Default for Engine { #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())), - #[cfg(any(feature = "no_module", feature = "no_std", target_arch = "wasm32",))] + #[cfg(not(feature = "no_module"))] + #[cfg(any(feature = "no_std", target_arch = "wasm32",))] module_resolver: None, type_names: None, @@ -360,14 +387,17 @@ impl Default for Engine { #[cfg(not(feature = "no_optimize"))] optimization_level: OptimizationLevel::Simple, - max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: MAX_EXPR_DEPTH, - max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: 0, - max_modules: usize::MAX, - max_string_size: 0, - max_array_size: 0, - max_map_size: 0, + #[cfg(not(feature = "unchecked"))] + limits: Limits { + max_call_stack_depth: MAX_CALL_STACK_DEPTH, + max_expr_depth: MAX_EXPR_DEPTH, + max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, + max_operations: 0, + max_modules: usize::MAX, + max_string_size: 0, + max_array_size: 0, + max_map_size: 0, + }, }; engine.load_package(StandardPackage::new().get()); @@ -546,6 +576,8 @@ impl Engine { packages: Default::default(), global_module: Default::default(), + + #[cfg(not(feature = "no_module"))] module_resolver: None, type_names: None, @@ -563,14 +595,17 @@ impl Engine { #[cfg(not(feature = "no_optimize"))] optimization_level: OptimizationLevel::Simple, - max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: MAX_EXPR_DEPTH, - max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: 0, - max_modules: usize::MAX, - max_string_size: 0, - max_array_size: 0, - max_map_size: 0, + #[cfg(not(feature = "unchecked"))] + limits: Limits { + max_call_stack_depth: MAX_CALL_STACK_DEPTH, + max_expr_depth: MAX_EXPR_DEPTH, + max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, + max_operations: 0, + max_modules: usize::MAX, + max_string_size: 0, + max_array_size: 0, + max_map_size: 0, + }, } } @@ -982,23 +1017,25 @@ impl Engine { fn get_indexed_mut<'a>( &self, state: &mut State, - lib: &Module, + _lib: &Module, target: &'a mut Target, - mut idx: Dynamic, + mut _idx: Dynamic, idx_pos: Position, create: bool, - level: usize, + _level: usize, ) -> Result, Box> { self.inc_operations(state)?; + #[cfg(not(feature = "no_index"))] let is_ref = target.is_ref(); + let val = target.as_mut(); match val { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr)) => { // val_array[idx] - let index = idx + let index = _idx .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?; @@ -1021,13 +1058,13 @@ impl Engine { Dynamic(Union::Map(map)) => { // val_map[idx] Ok(if create { - let index = idx + let index = _idx .take_immutable_string() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; map.entry(index).or_insert(Default::default()).into() } else { - let index = idx + let index = _idx .downcast_ref::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; @@ -1041,7 +1078,7 @@ impl Engine { Dynamic(Union::Str(s)) => { // val_string[idx] let chars_len = s.chars().count(); - let index = idx + let index = _idx .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?; @@ -1062,9 +1099,9 @@ impl Engine { #[cfg(not(feature = "no_index"))] _ => { let type_name = val.type_name(); - let args = &mut [val, &mut idx]; + let args = &mut [val, &mut _idx]; self.exec_fn_call( - state, lib, FN_IDX_GET, true, 0, args, is_ref, true, None, level, + state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1639,19 +1676,20 @@ impl Engine { Stmt::Const(_) => unreachable!(), // Import statement + #[cfg(not(feature = "no_module"))] Stmt::Import(x) => { - let (expr, (name, pos)) = x.as_ref(); + let (expr, (name, _pos)) = x.as_ref(); // Guard against too many modules - if state.modules >= self.max_modules { - return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos))); + #[cfg(not(feature = "unchecked"))] + if state.modules >= self.limits.max_modules { + return Err(Box::new(EvalAltResult::ErrorTooManyModules(*_pos))); } if let Some(path) = self .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? .try_cast::() { - #[cfg(not(feature = "no_module"))] if let Some(resolver) = &self.module_resolver { let mut module = resolver.resolve(self, &path, expr.position())?; module.index_all_sub_modules(); @@ -1666,15 +1704,13 @@ impl Engine { expr.position(), ))) } - - #[cfg(feature = "no_module")] - Ok(Default::default()) } else { Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) } } // Export statement + #[cfg(not(feature = "no_module"))] Stmt::Export(list) => { for ((id, id_pos), rename) in list.iter() { // Mark scope variables as public @@ -1696,8 +1732,18 @@ impl Engine { .map_err(|err| err.new_position(stmt.position())) } + #[cfg(feature = "unchecked")] + #[inline(always)] + fn check_data_size( + &self, + result: Result>, + ) -> Result> { + return result; + } + /// Check a result to ensure that the data size is within allowable limit. /// Position in `EvalAltResult` may be None and should be set afterwards. + #[cfg(not(feature = "unchecked"))] fn check_data_size( &self, result: Result>, @@ -1706,7 +1752,8 @@ impl Engine { return result; // If no data size limits, just return - if self.max_string_size + self.max_array_size + self.max_map_size == 0 { + if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 + { return result; } @@ -1766,37 +1813,37 @@ impl Engine { // Simply return all errors Err(_) => return result, // String with limit - Ok(Dynamic(Union::Str(_))) if self.max_string_size > 0 => (), + Ok(Dynamic(Union::Str(_))) if self.limits.max_string_size > 0 => (), // Array with limit #[cfg(not(feature = "no_index"))] - Ok(Dynamic(Union::Array(_))) if self.max_array_size > 0 => (), + Ok(Dynamic(Union::Array(_))) if self.limits.max_array_size > 0 => (), // Map with limit #[cfg(not(feature = "no_object"))] - Ok(Dynamic(Union::Map(_))) if self.max_map_size > 0 => (), + Ok(Dynamic(Union::Map(_))) if self.limits.max_map_size > 0 => (), // Everything else is simply returned Ok(_) => return result, }; let (arr, map, s) = calc_size(result.as_ref().unwrap()); - if s > self.max_string_size { + if s > self.limits.max_string_size { Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), - self.max_string_size, + self.limits.max_string_size, s, Position::none(), ))) - } else if arr > self.max_array_size { + } else if arr > self.limits.max_array_size { Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Size of array".to_string(), - self.max_array_size, + self.limits.max_array_size, arr, Position::none(), ))) - } else if map > self.max_map_size { + } else if map > self.limits.max_map_size { Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Number of properties in object map".to_string(), - self.max_map_size, + self.limits.max_map_size, map, Position::none(), ))) @@ -1812,7 +1859,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] // Guard against too many operations - if self.max_operations > 0 && state.operations > self.max_operations { + if self.limits.max_operations > 0 && state.operations > self.limits.max_operations { return Err(Box::new(EvalAltResult::ErrorTooManyOperations( Position::none(), ))); diff --git a/src/fn_args.rs b/src/fn_args.rs index 833014bb..e5a22390 100644 --- a/src/fn_args.rs +++ b/src/fn_args.rs @@ -22,11 +22,10 @@ macro_rules! impl_args { fn into_vec(self) -> StaticVec { let ($($p,)*) = self; - #[allow(unused_mut)] - let mut v = StaticVec::new(); - $(v.push($p.into_dynamic());)* + let mut _v = StaticVec::new(); + $(_v.push($p.into_dynamic());)* - v + _v } } diff --git a/src/fn_call.rs b/src/fn_call.rs index 98228853..b2a06455 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -11,13 +11,18 @@ use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, ImmutableString, ScriptFnDef, AST, INT}; -use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; +use crate::parser::{Expr, ImmutableString, AST, INT}; use crate::result::EvalAltResult; -use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::scope::Scope; use crate::token::Position; use crate::utils::StaticVec; +#[cfg(not(feature = "no_function"))] +use crate::{ + parser::ScriptFnDef, r#unsafe::unsafe_cast_var_name_to_lifetime, + scope::EntryType as ScopeEntryType, +}; + #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -106,17 +111,17 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_fn_raw( &self, - scope: &mut Scope, - mods: &mut Imports, + _scope: &mut Scope, + _mods: &mut Imports, state: &mut State, lib: &Module, fn_name: &str, (hash_fn, hash_script): (u64, u64), args: &mut FnCallArgs, is_ref: bool, - is_method: bool, + _is_method: bool, def_val: Option, - level: usize, + _level: usize, ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; @@ -125,7 +130,7 @@ impl Engine { // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] - if level > self.max_call_stack_depth { + if _level > self.limits.max_call_stack_depth { return Err(Box::new( EvalAltResult::ErrorStackOverflow(Position::none()), )); @@ -151,7 +156,7 @@ impl Engine { if let Some(func) = func { #[cfg(not(feature = "no_function"))] - let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method)); + let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !_is_method)); #[cfg(feature = "no_function")] let need_normalize = is_ref && func.is_pure(); @@ -164,25 +169,25 @@ impl Engine { let fn_def = func.get_fn_def(); // Method call of script function - map first argument to `this` - return if is_method { + return if _is_method { let (first, rest) = args.split_at_mut(1); Ok(( self.call_script_fn( - scope, - mods, + _scope, + _mods, state, lib, &mut Some(first[0]), fn_name, fn_def, rest, - level, + _level, )?, false, )) } else { let result = self.call_script_fn( - scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, + _scope, _mods, state, lib, &mut None, fn_name, fn_def, args, _level, )?; // Restore the original reference @@ -305,6 +310,7 @@ impl Engine { /// Function call arguments may be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! + #[cfg(not(feature = "no_function"))] pub(crate) fn call_script_fn( &self, scope: &mut Scope, diff --git a/src/fn_native.rs b/src/fn_native.rs index 8fc60714..bfb8b956 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -2,14 +2,18 @@ use crate::any::Dynamic; use crate::engine::Engine; -use crate::module::{FuncReturn, Module}; -use crate::parser::ScriptFnDef; +use crate::module::Module; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; -use crate::utils::{ImmutableString, StaticVec}; -use crate::Scope; +use crate::utils::ImmutableString; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, mem, string::String, vec::Vec}; +#[cfg(not(feature = "no_function"))] +use crate::{module::FuncReturn, parser::ScriptFnDef, scope::Scope, utils::StaticVec}; + +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec}; + +#[cfg(not(feature = "no_function"))] +use crate::stdlib::mem; #[cfg(not(feature = "sync"))] use crate::stdlib::rc::Rc; @@ -85,12 +89,17 @@ impl FnPtr { /// Call the function pointer with curried arguments (if any). /// + /// The function must be a script-defined function. It cannot be a Rust function. + /// + /// To call a Rust function, just call it directly in Rust! + /// /// ## WARNING /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. + #[cfg(not(feature = "no_function"))] pub fn call_dynamic( &self, engine: &Engine, diff --git a/src/fn_register.rs b/src/fn_register.rs index 28ee084a..c2eb5534 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -133,12 +133,11 @@ macro_rules! make_func { Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - #[allow(unused_variables, unused_mut)] - let mut drain = args.iter_mut(); + let mut _drain = args.iter_mut(); $( // 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()); + let $par = ($convert)(_drain.next().unwrap()); )* // Call the function with each parameter value diff --git a/src/lib.rs b/src/lib.rs index 240d87d1..1194fd67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,7 +170,7 @@ pub use parser::{CustomExpr, Expr, FloatWrapper, ReturnType, ScriptFnDef, Stmt}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use engine::{Imports, State as EvalState}; +pub use engine::{Imports, Limits, State as EvalState}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/module.rs b/src/module.rs index 22632c7d..c5002544 100644 --- a/src/module.rs +++ b/src/module.rs @@ -2,22 +2,29 @@ use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; -use crate::engine::{make_getter, make_setter, Engine, Imports, FN_IDX_GET, FN_IDX_SET}; -use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync, Shared}; -use crate::parser::{ - FnAccess, - FnAccess::{Private, Public}, - ScriptFnDef, AST, -}; +use crate::engine::{make_getter, make_setter, Engine}; +use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; +use crate::parser::{FnAccess, FnAccess::Public}; use crate::result::EvalAltResult; -use crate::scope::{Entry as ScopeEntry, Scope}; use crate::token::{Position, Token}; use crate::utils::{StaticVec, StraightHasherBuilder}; +#[cfg(not(feature = "no_function"))] +use crate::{fn_native::Shared, parser::ScriptFnDef}; + +#[cfg(not(feature = "no_module"))] +use crate::{ + engine::Imports, + parser::AST, + scope::{Entry as ScopeEntry, Scope}, +}; + +#[cfg(not(feature = "no_index"))] +use crate::engine::{FN_IDX_GET, FN_IDX_SET}; + use crate::stdlib::{ any::TypeId, boxed::Box, - cell::RefCell, collections::HashMap, fmt, format, iter::empty, @@ -25,10 +32,14 @@ use crate::stdlib::{ num::NonZeroUsize, ops::{Deref, DerefMut}, string::{String, ToString}, - vec, vec::Vec, }; +#[cfg(not(feature = "no_module"))] +#[cfg(not(feature = "sync"))] +use crate::stdlib::cell::RefCell; + +#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] #[cfg(feature = "sync")] use crate::stdlib::sync::RwLock; @@ -738,6 +749,8 @@ impl Module { /// }); /// assert!(module.contains_fn(hash)); /// ``` + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_index"))] pub fn set_indexer_set_fn( &mut self, func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, @@ -781,6 +794,8 @@ impl Module { /// assert!(module.contains_fn(hash_get)); /// assert!(module.contains_fn(hash_set)); /// ``` + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_index"))] pub fn set_indexer_get_set_fn( &mut self, getter: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, @@ -909,11 +924,11 @@ impl Module { self.merge_filtered(other, |_, _, _| true) } - /// Merge another module into this module, with only selected functions based on a filter predicate. + /// Merge another module into this module, with only selected script-defined functions based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, - filter: impl Fn(FnAccess, &str, usize) -> bool, + _filter: impl Fn(FnAccess, &str, usize) -> bool, ) -> &mut Self { self.variables .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); @@ -924,7 +939,7 @@ impl Module { .iter() .filter(|(_, (_, _, _, v))| match v { #[cfg(not(feature = "no_function"))] - Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), + Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()), _ => true, }) .map(|(&k, v)| (k, v.clone())), @@ -975,6 +990,7 @@ impl Module { } /// Get an iterator to the functions in the module. + #[cfg(not(feature = "no_function"))] pub(crate) fn iter_fn( &self, ) -> impl Iterator, Func)> { @@ -1038,6 +1054,7 @@ impl Module { /// Scan through all the sub-modules in the module build an index of all /// variables and external Rust functions via hashing. + #[cfg(not(feature = "no_module"))] pub(crate) fn index_all_sub_modules(&mut self) { // Collect a particular module. fn index_module<'a>( @@ -1063,8 +1080,8 @@ impl Module { for (name, access, params, func) in module.functions.values() { match access { // Private functions are not exported - Private => continue, - Public => (), + FnAccess::Private => continue, + FnAccess::Public => (), } #[cfg(not(feature = "no_function"))] @@ -1100,10 +1117,13 @@ impl Module { return; } - let mut variables = Vec::new(); - let mut functions = Vec::new(); + let mut qualifiers: Vec<_> = Default::default(); + let mut variables: Vec<_> = Default::default(); + let mut functions: Vec<_> = Default::default(); - index_module(self, &mut vec!["root"], &mut variables, &mut functions); + qualifiers.push("root"); + + index_module(self, &mut qualifiers, &mut variables, &mut functions); self.all_variables = variables.into_iter().collect(); self.all_functions = functions.into_iter().collect(); diff --git a/src/optimize.rs b/src/optimize.rs index 738ea300..0948fbd5 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -6,11 +6,14 @@ use crate::engine::{ Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::module::Module; -use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::token::is_valid_identifier; use crate::utils::StaticVec; +#[cfg(not(feature = "no_function"))] +use crate::parser::ReturnType; + #[cfg(feature = "internals")] use crate::parser::CustomExpr; @@ -249,6 +252,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { // let id; stmt @ Stmt::Let(_) => stmt, // import expr as id; + #[cfg(not(feature = "no_module"))] Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))), // { block } Stmt::Block(x) => { @@ -290,6 +294,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { match expr { Stmt::Let(x) if x.1.is_none() => removed = true, Stmt::Let(x) if x.1.is_some() => removed = x.1.unwrap().is_pure(), + #[cfg(not(feature = "no_module"))] Stmt::Import(x) => removed = x.0.is_pure(), _ => { result.push(expr); @@ -345,8 +350,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { state.set_dirty(); Stmt::Noop(pos) } - // Only one let/import statement - leave it alone - [Stmt::Let(_)] | [Stmt::Import(_)] => Stmt::Block(Box::new((result.into(), pos))), + // Only one let statement - leave it alone + [Stmt::Let(_)] => Stmt::Block(Box::new((result.into(), pos))), + // Only one import statement - leave it alone + #[cfg(not(feature = "no_module"))] + [Stmt::Import(_)] => Stmt::Block(Box::new((result.into(), pos))), // Only one statement - promote [_] => { state.set_dirty(); @@ -557,16 +565,16 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { + let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); #[cfg(feature = "no_function")] - const has_script_fn: bool = false; + let _has_script_fn: bool = false; - if has_script_fn { + if _has_script_fn { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); @@ -686,7 +694,9 @@ fn optimize( // Keep all variable declarations at this level // and always keep the last return value let keep = match stmt { - Stmt::Let(_) | Stmt::Import(_) => true, + Stmt::Let(_) => true, + #[cfg(not(feature = "no_module"))] + Stmt::Import(_) => true, _ => i == num_statements - 1, }; optimize_stmt(stmt, &mut state, keep) @@ -721,7 +731,7 @@ pub fn optimize_into_ast( engine: &Engine, scope: &Scope, statements: Vec, - functions: Vec, + _functions: Vec, level: OptimizationLevel, ) -> AST { #[cfg(feature = "no_optimize")] @@ -735,7 +745,7 @@ pub fn optimize_into_ast( // We only need the script library's signatures for optimization purposes let mut lib2 = Module::new(); - functions + _functions .iter() .map(|fn_def| { ScriptFnDef { @@ -751,7 +761,7 @@ pub fn optimize_into_ast( lib2.set_script_fn(fn_def); }); - functions + _functions .into_iter() .map(|mut fn_def| { let pos = fn_def.body.position(); @@ -782,7 +792,7 @@ pub fn optimize_into_ast( module.set_script_fn(fn_def); }); } else { - functions.into_iter().for_each(|fn_def| { + _functions.into_iter().for_each(|fn_def| { module.set_script_fn(fn_def); }); } diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 3e3852d6..61350996 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -1,8 +1,9 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; -use crate::result::EvalAltResult; -use crate::token::Position; + +#[cfg(not(feature = "unchecked"))] +use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -11,22 +12,25 @@ use crate::parser::FLOAT; #[cfg(feature = "no_std")] use num_traits::*; +#[cfg(not(feature = "unchecked"))] use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; -use crate::stdlib::{ - boxed::Box, - fmt::Display, - format, - ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Sub}, -}; +use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; + +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] +use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; #[cfg(feature = "unchecked")] use crate::stdlib::ops::{Shl, Shr}; +#[cfg(not(feature = "unchecked"))] +use crate::stdlib::{boxed::Box, fmt::Display, format}; + // Checked add +#[cfg(not(feature = "unchecked"))] pub(crate) fn add(x: T, y: T) -> FuncReturn { x.checked_add(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( @@ -36,6 +40,7 @@ pub(crate) fn add(x: T, y: T) -> FuncReturn { }) } // Checked subtract +#[cfg(not(feature = "unchecked"))] pub(crate) fn sub(x: T, y: T) -> FuncReturn { x.checked_sub(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( @@ -45,6 +50,7 @@ pub(crate) fn sub(x: T, y: T) -> FuncReturn { }) } // Checked multiply +#[cfg(not(feature = "unchecked"))] pub(crate) fn mul(x: T, y: T) -> FuncReturn { x.checked_mul(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( @@ -54,6 +60,7 @@ pub(crate) fn mul(x: T, y: T) -> FuncReturn { }) } // Checked divide +#[cfg(not(feature = "unchecked"))] pub(crate) fn div(x: T, y: T) -> FuncReturn where T: Display + CheckedDiv + PartialEq + Zero, @@ -74,6 +81,7 @@ where }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX +#[cfg(not(feature = "unchecked"))] pub(crate) fn neg(x: T) -> FuncReturn { x.checked_neg().ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( @@ -83,6 +91,7 @@ pub(crate) fn neg(x: T) -> FuncReturn { }) } // Checked absolute +#[cfg(not(feature = "unchecked"))] pub(crate) fn abs(x: T) -> FuncReturn { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // when the number is ::MIN instead of returning ::MIN itself. @@ -98,26 +107,32 @@ pub(crate) fn abs(x: T) -> FuncRetu } } // Unchecked add - may panic on overflow +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x + y) } // Unchecked subtract - may panic on underflow +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x - y) } // Unchecked multiply - may panic on overflow +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x * y) } // Unchecked divide - may panic when dividing by zero +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x / y) } // Unchecked negative - may panic on overflow +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> FuncReturn<::Output> { Ok(-x) } // Unchecked absolute - may panic on overflow +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> FuncReturn<::Output> where T: Neg + PartialOrd + Default + Into<::Output>, @@ -140,6 +155,7 @@ fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } // Checked left-shift +#[cfg(not(feature = "unchecked"))] pub(crate) fn shl(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { @@ -157,6 +173,7 @@ pub(crate) fn shl(x: T, y: INT) -> FuncReturn { }) } // Checked right-shift +#[cfg(not(feature = "unchecked"))] pub(crate) fn shr(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { @@ -184,6 +201,7 @@ pub(crate) fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> Ok(x.shr(y)) } // Checked modulo +#[cfg(not(feature = "unchecked"))] pub(crate) fn modulo(x: T, y: T) -> FuncReturn { x.checked_rem(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( @@ -193,10 +211,12 @@ pub(crate) fn modulo(x: T, y: T) -> FuncReturn { }) } // Unchecked modulo - may panic if dividing by zero +#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x % y) } // Checked power +#[cfg(not(feature = "unchecked"))] pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn { #[cfg(not(feature = "only_i32"))] if y > (u32::MAX as INT) { @@ -245,6 +265,7 @@ pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { } // Checked power #[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "unchecked"))] pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 0a580933..c0bea7ee 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -5,10 +5,14 @@ use crate::def_package; use crate::engine::{Array, Engine}; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; -use crate::result::EvalAltResult; -use crate::token::Position; -use crate::stdlib::{any::TypeId, boxed::Box, string::ToString}; +#[cfg(not(feature = "unchecked"))] +use crate::{result::EvalAltResult, token::Position}; + +use crate::stdlib::{any::TypeId, boxed::Box}; + +#[cfg(not(feature = "unchecked"))] +use crate::stdlib::string::ToString; // Register array utility functions fn push(list: &mut Array, item: T) -> FuncReturn<()> { @@ -26,7 +30,7 @@ fn ins(list: &mut Array, position: INT, item: T) -> FuncRetu Ok(()) } fn pad( - engine: &Engine, + _engine: &Engine, _: &Module, args: &mut [&mut Dynamic], ) -> FuncReturn<()> { @@ -34,10 +38,13 @@ fn pad( // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] - if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size { + if _engine.limits.max_array_size > 0 + && len > 0 + && (len as usize) > _engine.limits.max_array_size + { return Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Size of array".to_string(), - engine.max_array_size, + _engine.limits.max_array_size, len as usize, Position::none(), ))); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index a86e4665..cc1aa000 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,16 +1,20 @@ #![cfg(not(feature = "no_object"))] -use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; -use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; +#[cfg(not(feature = "no_index"))] +use crate::{any::Dynamic, module::FuncReturn}; + +#[cfg(not(feature = "no_index"))] use crate::stdlib::vec::Vec; +#[cfg(not(feature = "no_index"))] fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) } +#[cfg(not(feature = "no_index"))] fn map_get_values(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(_, v)| v.clone()).collect()) } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index aed7e5b7..55bb68a3 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,20 +1,26 @@ use crate::def_package; use crate::parser::INT; -use crate::result::EvalAltResult; -use crate::token::Position; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "unchecked"))] +use crate::{result::EvalAltResult, token::Position}; + #[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] use num_traits::*; -use crate::stdlib::{boxed::Box, format, i32, i64}; +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "unchecked"))] +use crate::stdlib::{boxed::Box, format}; #[cfg(feature = "only_i32")] +#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i32::MAX; #[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 8207a966..ccd78add 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -24,7 +24,6 @@ pub use arithmetic::ArithmeticPackage; #[cfg(not(feature = "no_index"))] pub use array_basic::BasicArrayPackage; pub use eval::EvalPackage; -#[cfg(not(feature = "no_function"))] pub use fn_basic::BasicFnPackage; pub use iter_basic::BasicIteratorPackage; pub use logic::LogicPackage; diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 248b0e93..62a5fdb2 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -3,10 +3,11 @@ use crate::def_package; use crate::engine::Engine; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; -use crate::result::EvalAltResult; -use crate::token::Position; use crate::utils::StaticVec; +#[cfg(not(feature = "unchecked"))] +use crate::{result::EvalAltResult, token::Position}; + #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -226,15 +227,15 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str lib.set_raw_fn( "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - |engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { + |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { let len = *args[1].downcast_ref::< INT>().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] - if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size { + if _engine.limits.max_string_size > 0 && len > 0 && (len as usize) > _engine.limits.max_string_size { return Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), - engine.max_string_size, + _engine.limits.max_string_size, len as usize, Position::none(), ))); @@ -253,10 +254,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str p.push(ch); } - if engine.max_string_size > 0 && s.len() > engine.max_string_size { + #[cfg(not(feature = "unchecked"))] + if _engine.limits.max_string_size > 0 && s.len() > _engine.limits.max_string_size { return Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), - engine.max_string_size, + _engine.limits.max_string_size, s.len(), Position::none(), ))); diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 7c166ab1..d554b68e 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -2,6 +2,7 @@ use super::logic::{eq, gt, gte, lt, lte, ne}; #[cfg(feature = "no_float")] +#[cfg(not(feature = "unchecked"))] use super::math_basic::MAX_INT; use crate::def_package; @@ -11,7 +12,11 @@ use crate::result::EvalAltResult; use crate::parser::FLOAT; #[cfg(feature = "no_float")] -use crate::{module::FuncReturn, parser::INT, token::Position}; +use crate::parser::INT; + +#[cfg(feature = "no_float")] +#[cfg(not(feature = "unchecked"))] +use crate::token::Position; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::time::Instant; diff --git a/src/parser.rs b/src/parser.rs index da0409db..99722611 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,8 +3,7 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::engine::{ - make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, - MARKER_IDENT, + make_getter, make_setter, Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, }; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::Shared; @@ -15,6 +14,9 @@ use crate::syntax::FnCustomSyntaxEval; use crate::token::{is_valid_identifier, Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; +#[cfg(not(feature = "no_function"))] +use crate::engine::FN_ANONYMOUS; + use crate::stdlib::{ borrow::Cow, boxed::Box, @@ -32,6 +34,7 @@ use crate::stdlib::{ }; #[cfg(not(feature = "no_std"))] +#[cfg(not(feature = "no_function"))] use crate::stdlib::collections::hash_map::DefaultHasher; #[cfg(feature = "no_std")] @@ -84,7 +87,7 @@ impl AST { &self.0 } - /// Get the statements. + /// [INTERNALS] Get the statements. #[cfg(feature = "internals")] #[deprecated(note = "this method is volatile and may change")] pub fn statements(&self) -> &[Stmt] { @@ -102,7 +105,7 @@ impl AST { &self.1 } - /// Get the internal `Module` containing all script-defined functions. + /// [INTERNALS] Get the internal `Module` containing all script-defined functions. #[cfg(feature = "internals")] #[deprecated(note = "this method is volatile and may change")] pub fn lib(&self) -> &Module { @@ -404,20 +407,28 @@ struct ParseState<'e> { /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. + #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, /// Maximum levels of expression nesting in functions. + #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, } impl<'e> ParseState<'e> { /// Create a new `ParseState`. - pub fn new(engine: &'e Engine, max_expr_depth: usize, max_function_expr_depth: usize) -> Self { + pub fn new( + engine: &'e Engine, + #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, + #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, + ) -> Self { Self { engine, - max_expr_depth, - max_function_expr_depth, stack: Default::default(), modules: Default::default(), + #[cfg(not(feature = "unchecked"))] + max_expr_depth, + #[cfg(not(feature = "unchecked"))] + max_function_expr_depth, } } /// Find a variable by name in the `ParseState`, searching in reverse. @@ -476,6 +487,7 @@ impl ParseSettings { } } /// Make sure that the current level of expression nesting is within the maximum limit. + #[cfg(not(feature = "unchecked"))] pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { if limit == 0 { Ok(()) @@ -519,8 +531,10 @@ pub enum Stmt { /// return/throw ReturnWithVal(Box<((ReturnType, Position), Option)>), /// import expr as module + #[cfg(not(feature = "no_module"))] Import(Box<(Expr, (String, Position))>), /// expr id as name, ... + #[cfg(not(feature = "no_module"))] Export(Box)>>), } @@ -544,7 +558,10 @@ impl Stmt { Stmt::While(x) => x.1.position(), Stmt::Loop(x) => x.position(), Stmt::For(x) => x.2.position(), + + #[cfg(not(feature = "no_module"))] Stmt::Import(x) => (x.1).1, + #[cfg(not(feature = "no_module"))] Stmt::Export(x) => (x.get(0).0).1, } } @@ -563,12 +580,13 @@ impl Stmt { Stmt::Let(_) | Stmt::Const(_) - | Stmt::Import(_) - | Stmt::Export(_) | Stmt::Expr(_) | Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false, + + #[cfg(not(feature = "no_module"))] + Stmt::Import(_) | Stmt::Export(_) => false, } } @@ -587,7 +605,10 @@ impl Stmt { Stmt::Let(_) | Stmt::Const(_) => false, Stmt::Block(x) => x.0.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false, + + #[cfg(not(feature = "no_module"))] Stmt::Import(_) => false, + #[cfg(not(feature = "no_module"))] Stmt::Export(_) => false, } } @@ -996,6 +1017,7 @@ fn parse_paren_expr( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; if match_token(input, Token::RightParen)? { @@ -1028,6 +1050,8 @@ fn parse_call_expr( settings: ParseSettings, ) -> Result { let (token, token_pos) = input.peek().unwrap(); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut args = StaticVec::new(); @@ -1141,6 +1165,7 @@ fn parse_call_expr( /// Parse an indexing chain. /// Indexing binds to the right, so this call parses all possible levels of indexing following in the input. +#[cfg(not(feature = "no_index"))] fn parse_index_chain( input: &mut TokenStream, state: &mut ParseState, @@ -1148,6 +1173,7 @@ fn parse_index_chain( lhs: Expr, mut settings: ParseSettings, ) -> Result { + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let idx_expr = parse_expr(input, state, lib, settings.level_up())?; @@ -1321,21 +1347,25 @@ fn parse_index_chain( } /// Parse an array literal. +#[cfg(not(feature = "no_index"))] fn parse_array_literal( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut arr = StaticVec::new(); while !input.peek().unwrap().0.is_eof() { - if state.engine.max_array_size > 0 && arr.len() >= state.engine.max_array_size { + #[cfg(not(feature = "unchecked"))] + if state.engine.limits.max_array_size > 0 && arr.len() >= state.engine.limits.max_array_size + { return Err(PERR::LiteralTooLarge( "Size of array literal".to_string(), - state.engine.max_array_size, + state.engine.limits.max_array_size, ) .into_err(input.peek().unwrap().1)); } @@ -1384,6 +1414,7 @@ fn parse_map_literal( lib: &mut FunctionsLib, settings: ParseSettings, ) -> Result { + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut map = StaticVec::new(); @@ -1436,10 +1467,11 @@ fn parse_map_literal( } }; - if state.engine.max_map_size > 0 && map.len() >= state.engine.max_map_size { + #[cfg(not(feature = "unchecked"))] + if state.engine.limits.max_map_size > 0 && map.len() >= state.engine.limits.max_map_size { return Err(PERR::LiteralTooLarge( "Number of properties in object map literal".to_string(), - state.engine.max_map_size, + state.engine.limits.max_map_size, ) .into_err(input.peek().unwrap().1)); } @@ -1492,6 +1524,8 @@ fn parse_primary( ) -> Result { let (token, token_pos) = input.peek().unwrap(); settings.pos = *token_pos; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let (token, _) = match token { @@ -1626,6 +1660,8 @@ fn parse_unary( ) -> Result { let (token, token_pos) = input.peek().unwrap(); settings.pos = *token_pos; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { @@ -1708,7 +1744,9 @@ fn parse_unary( Token::Pipe | Token::Or => { let mut state = ParseState::new( state.engine, + #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, + #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -1809,6 +1847,8 @@ fn parse_op_assignment_stmt( ) -> Result { let (token, token_pos) = input.peek().unwrap(); settings.pos = *token_pos; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let op = match token { @@ -2048,6 +2088,8 @@ fn parse_binary_op( mut settings: ParseSettings, ) -> Result { settings.pos = lhs.position(); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut root = lhs; @@ -2081,6 +2123,8 @@ fn parse_binary_op( settings = settings.level_up(); settings.pos = pos; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let cmp_def = Some(false); @@ -2164,6 +2208,8 @@ fn parse_expr( mut settings: ParseSettings, ) -> Result { settings.pos = input.peek().unwrap().1; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // Check if it is a custom syntax. @@ -2291,6 +2337,8 @@ fn parse_if( ) -> Result { // if ... settings.pos = eat_token(input, Token::If); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // if guard { if_body } @@ -2324,6 +2372,8 @@ fn parse_while( ) -> Result { // while ... settings.pos = eat_token(input, Token::While); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // while guard { body } @@ -2346,6 +2396,8 @@ fn parse_loop( ) -> Result { // loop ... settings.pos = eat_token(input, Token::Loop); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // loop { body } @@ -2364,6 +2416,8 @@ fn parse_for( ) -> Result { // for ... settings.pos = eat_token(input, Token::For); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // for name ... @@ -2417,6 +2471,8 @@ fn parse_let( ) -> Result { // let/const... (specified in `var_type`) settings.pos = input.next().unwrap().1; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // let name ... @@ -2475,6 +2531,8 @@ fn parse_import( ) -> Result { // import ... settings.pos = eat_token(input, Token::Import); + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; // import expr ... @@ -2509,12 +2567,14 @@ fn parse_import( #[cfg(not(feature = "no_module"))] fn parse_export( input: &mut TokenStream, - state: &mut ParseState, + _state: &mut ParseState, _lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { settings.pos = eat_token(input, Token::Export); - settings.ensure_level_within_max_limit(state.max_expr_depth)?; + + #[cfg(not(feature = "unchecked"))] + settings.ensure_level_within_max_limit(_state.max_expr_depth)?; let mut exports = StaticVec::new(); @@ -2594,6 +2654,7 @@ fn parse_block( } }; + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut statements = StaticVec::new(); @@ -2657,6 +2718,8 @@ fn parse_expr_stmt( mut settings: ParseSettings, ) -> Result { settings.pos = input.peek().unwrap().1; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let expr = parse_expr(input, state, lib, settings.level_up())?; @@ -2678,6 +2741,8 @@ fn parse_stmt( x => x, }; settings.pos = *token_pos; + + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { @@ -2703,7 +2768,9 @@ fn parse_stmt( (Token::Fn, pos) => { let mut state = ParseState::new( state.engine, + #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, + #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, ); @@ -2807,6 +2874,7 @@ fn parse_fn( access: FnAccess, mut settings: ParseSettings, ) -> Result { + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let name = match input.next().unwrap() { @@ -2899,6 +2967,7 @@ fn parse_anon_fn( lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result<(Expr, ScriptFnDef), ParseError> { + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut params = Vec::new(); @@ -2994,7 +3063,15 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let mut functions = Default::default(); - let mut state = ParseState::new(self, self.max_expr_depth, self.max_function_expr_depth); + + let mut state = ParseState::new( + self, + #[cfg(not(feature = "unchecked"))] + self.limits.max_expr_depth, + #[cfg(not(feature = "unchecked"))] + self.limits.max_function_expr_depth, + ); + let settings = ParseSettings { allow_if_expr: false, allow_stmt_expr: false, @@ -3032,7 +3109,14 @@ impl Engine { ) -> Result<(Vec, Vec), ParseError> { let mut statements: Vec = Default::default(); let mut functions = Default::default(); - let mut state = ParseState::new(self, self.max_expr_depth, self.max_function_expr_depth); + + let mut state = ParseState::new( + self, + #[cfg(not(feature = "unchecked"))] + self.limits.max_expr_depth, + #[cfg(not(feature = "unchecked"))] + self.limits.max_function_expr_depth, + ); while !input.peek().unwrap().0.is_eof() { let settings = ParseSettings { diff --git a/src/scope.rs b/src/scope.rs index 8342c792..5693554c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -377,6 +377,7 @@ impl<'a> Scope<'a> { } /// Update the access type of an entry in the Scope. + #[cfg(not(feature = "no_module"))] pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) -> &mut Self { let entry = self.0.get_mut(index).expect("invalid index in Scope"); entry.alias = Some(Box::new(alias)); @@ -384,6 +385,7 @@ impl<'a> Scope<'a> { } /// Get an iterator to entries in the Scope. + #[cfg(not(feature = "no_module"))] pub(crate) fn into_iter(self) -> impl Iterator> { self.0.into_iter() } diff --git a/src/serde/de.rs b/src/serde/de.rs index 4ab9919a..6a7cbf6d 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -46,8 +46,12 @@ impl<'de> DynamicDeserializer<'de> { } /// Shortcut for a type conversion error. fn type_error(&self) -> Result> { + self.type_error_str(type_name::()) + } + /// Shortcut for a type conversion error. + fn type_error_str(&self, error: &str) -> Result> { Err(Box::new(EvalAltResult::ErrorMismatchOutputType( - type_name::().into(), + error.into(), self.value.type_name().into(), Position::none(), ))) @@ -283,23 +287,23 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_f32>(self, visitor: V) -> Result> { + fn deserialize_f32>(self, _visitor: V) -> Result> { #[cfg(not(feature = "no_float"))] return self .value .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_f32(x)); + .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); #[cfg(feature = "no_float")] return self.type_error_str("f32"); } - fn deserialize_f64>(self, visitor: V) -> Result> { + fn deserialize_f64>(self, _visitor: V) -> Result> { #[cfg(not(feature = "no_float"))] return self .value .downcast_ref::() - .map_or_else(|| self.type_error(), |&x| visitor.visit_f64(x)); + .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); #[cfg(feature = "no_float")] return self.type_error_str("f64"); @@ -359,11 +363,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { visitor.visit_newtype_struct(self) } - fn deserialize_seq>(self, visitor: V) -> Result> { + fn deserialize_seq>(self, _visitor: V) -> Result> { #[cfg(not(feature = "no_index"))] return self.value.downcast_ref::().map_or_else( || self.type_error(), - |arr| visitor.visit_seq(IterateArray::new(arr.iter())), + |arr| _visitor.visit_seq(IterateArray::new(arr.iter())), ); #[cfg(feature = "no_index")] @@ -461,6 +465,7 @@ where iter: ITER, } +#[cfg(not(feature = "no_index"))] impl<'a, ITER> IterateArray<'a, ITER> where ITER: Iterator, diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 7c3f9321..50ee077d 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -11,11 +11,14 @@ use crate::engine::Map; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, - SerializeTupleStruct, SerializeTupleVariant, Serializer, + SerializeTupleStruct, Serializer, }; use serde::Serialize; -use crate::stdlib::{any::type_name, fmt, mem}; +#[cfg(not(any(feature = "no_object", feature = "no_index")))] +use serde::ser::SerializeTupleVariant; + +use crate::stdlib::{fmt, mem}; /// Serializer for `Dynamic` which is kept as a reference. pub struct DynamicSerializer { @@ -323,13 +326,13 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, _variant_index: u32, - variant: &'static str, - len: usize, + _variant: &'static str, + _len: usize, ) -> Result> { #[cfg(not(any(feature = "no_object", feature = "no_index")))] return Ok(TupleVariantSerializer { - variant, - array: Array::with_capacity(len), + variant: _variant, + array: Array::with_capacity(_len), }); #[cfg(any(feature = "no_object", feature = "no_index"))] { @@ -391,13 +394,13 @@ impl SerializeSeq for DynamicSerializer { fn serialize_element( &mut self, - value: &T, + _value: &T, ) -> Result<(), Box> { #[cfg(not(feature = "no_index"))] { - let value = value.serialize(&mut *self)?; + let _value = _value.serialize(&mut *self)?; let arr = self.value.downcast_mut::().unwrap(); - arr.push(value); + arr.push(_value); Ok(()) } #[cfg(feature = "no_index")] @@ -419,13 +422,13 @@ impl SerializeTuple for DynamicSerializer { fn serialize_element( &mut self, - value: &T, + _value: &T, ) -> Result<(), Box> { #[cfg(not(feature = "no_index"))] { - let value = value.serialize(&mut *self)?; + let _value = _value.serialize(&mut *self)?; let arr = self.value.downcast_mut::().unwrap(); - arr.push(value); + arr.push(_value); Ok(()) } #[cfg(feature = "no_index")] @@ -446,13 +449,13 @@ impl SerializeTupleStruct for DynamicSerializer { fn serialize_field( &mut self, - value: &T, + _value: &T, ) -> Result<(), Box> { #[cfg(not(feature = "no_index"))] { - let value = value.serialize(&mut *self)?; + let _value = _value.serialize(&mut *self)?; let arr = self.value.downcast_mut::().unwrap(); - arr.push(value); + arr.push(_value); Ok(()) } #[cfg(feature = "no_index")] diff --git a/src/settings.rs b/src/settings.rs index 60c1a22a..d638499c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,12 +1,17 @@ //! Configuration settings for `Engine`. use crate::engine::Engine; -use crate::module::ModuleResolver; use crate::optimize::OptimizationLevel; use crate::packages::PackageLibrary; use crate::token::{is_valid_identifier, Token}; -use crate::stdlib::{boxed::Box, format, string::String}; +#[cfg(not(feature = "no_module"))] +use crate::module::ModuleResolver; + +use crate::stdlib::{format, string::String}; + +#[cfg(not(feature = "no_module"))] +use crate::stdlib::boxed::Box; impl Engine { /// Load a new package into the `Engine`. @@ -41,21 +46,21 @@ impl Engine { /// infinite recursion and stack overflows. #[cfg(not(feature = "unchecked"))] pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { - self.max_call_stack_depth = levels; + self.limits.max_call_stack_depth = levels; self } /// The maximum levels of function calls allowed for a script. #[cfg(not(feature = "unchecked"))] pub fn max_call_levels(&self) -> usize { - self.max_call_stack_depth + self.limits.max_call_stack_depth } /// Set the maximum number of operations allowed for a script to run to avoid /// consuming too much resources (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { - self.max_operations = if operations == u64::MAX { + self.limits.max_operations = if operations == u64::MAX { 0 } else { operations @@ -66,20 +71,20 @@ impl Engine { /// The maximum number of operations allowed for a script to run (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn max_operations(&self) -> u64 { - self.max_operations + self.limits.max_operations } /// Set the maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { - self.max_modules = modules; + self.limits.max_modules = modules; self } /// The maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] pub fn max_modules(&self) -> usize { - self.max_modules + self.limits.max_modules } /// Set the depth limits for expressions (0 for unlimited). @@ -89,12 +94,12 @@ impl Engine { max_expr_depth: usize, max_function_expr_depth: usize, ) -> &mut Self { - self.max_expr_depth = if max_expr_depth == usize::MAX { + self.limits.max_expr_depth = if max_expr_depth == usize::MAX { 0 } else { max_expr_depth }; - self.max_function_expr_depth = if max_function_expr_depth == usize::MAX { + self.limits.max_function_expr_depth = if max_function_expr_depth == usize::MAX { 0 } else { max_function_expr_depth @@ -105,33 +110,33 @@ impl Engine { /// The depth limit for expressions (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn max_expr_depth(&self) -> usize { - self.max_expr_depth + self.limits.max_expr_depth } /// The depth limit for expressions in functions (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn max_function_expr_depth(&self) -> usize { - self.max_function_expr_depth + self.limits.max_function_expr_depth } /// Set the maximum length of strings (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { - self.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; self } /// The maximum length of strings (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn max_string_size(&self) -> usize { - self.max_string_size + self.limits.max_string_size } /// Set the maximum length of arrays (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { - self.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; self } @@ -139,14 +144,14 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] pub fn max_array_size(&self) -> usize { - self.max_array_size + self.limits.max_array_size } /// Set the maximum length of object maps (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { - self.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; self } @@ -154,7 +159,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] pub fn max_map_size(&self) -> usize { - self.max_map_size + self.limits.max_map_size } /// Set the module resolution service used by the `Engine`. diff --git a/src/token.rs b/src/token.rs index 72e733d3..775e78db 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1549,7 +1549,10 @@ pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a TokenIterator { engine, state: TokenizeState { - max_string_size: engine.max_string_size, + #[cfg(not(feature = "unchecked"))] + max_string_size: engine.limits.max_string_size, + #[cfg(feature = "unchecked")] + max_string_size: 0, non_unary: false, comment_level: 0, end_with_none: false, diff --git a/tests/call_fn.rs b/tests/call_fn.rs index e96ee63d..9808ff70 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -166,9 +166,9 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { module.set_raw_fn( "call_with_arg", &[TypeId::of::(), TypeId::of::()], - |engine: &Engine, module: &Module, args: &mut [&mut Dynamic]| { + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { let fn_ptr = std::mem::take(args[0]).cast::(); - fn_ptr.call_dynamic(engine, module, None, [std::mem::take(args[1])]) + fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])]) }, ); From 3d36a25c908fc2dff296fb065c2fee654dd3a180 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Jul 2020 16:10:38 +0800 Subject: [PATCH 004/103] Add back PluginFunction. --- src/fn_native.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fn_native.rs b/src/fn_native.rs index 2de9cb52..af7fe62c 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -3,6 +3,7 @@ use crate::any::Dynamic; use crate::engine::Engine; use crate::module::Module; +use crate::plugin::PluginFunction; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; From 6e663eecb4a488d11b39e4b2663fc138cf9f007c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 29 Jul 2020 23:39:32 +0800 Subject: [PATCH 005/103] Fix fn_call. --- src/fn_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 96e81580..ce78d7f6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -893,8 +893,8 @@ impl Engine { &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, ) } - Some(f) => f.get_native_fn()(self, lib, args.as_mut()), Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()), + Some(f) => f.get_native_fn()(self, lib, args.as_mut()), None if def_val.is_some() => Ok(def_val.unwrap().into()), None => Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!( From 5785fa6b741cc915a4a1a458a71853a8cee56559 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 29 Jul 2020 16:56:29 -0500 Subject: [PATCH 006/103] Fix Plugins API for no_std --- src/plugin.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugin.rs b/src/plugin.rs index e0d53507..56e6af5d 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,5 +1,7 @@ //! Module defining plugins in Rhai. Is exported for use by plugin authors. +use crate::stdlib::boxed::Box; + pub use crate::any::{Dynamic, Variant}; pub use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn}; pub use crate::parser::{ From d01203cb5df7acd6066067e5483fa1dfda009be2 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 1 Aug 2020 11:52:26 -0500 Subject: [PATCH 007/103] New Procedural Macros Crate v0.1 --- .github/workflows/build.yml | 23 +- Cargo.toml | 5 +- README.md | 1 + codegen/Cargo.toml | 21 + codegen/src/function.rs | 746 ++++++++++++++++++++ codegen/src/lib.rs | 143 ++++ codegen/src/module.rs | 661 +++++++++++++++++ codegen/src/rhai_module.rs | 90 +++ codegen/tests/test_functions.rs | 141 ++++ codegen/tests/test_modules.rs | 165 +++++ codegen/ui_tests/first_shared_ref.rs | 22 + codegen/ui_tests/first_shared_ref.stderr | 11 + codegen/ui_tests/non_clonable.rs | 22 + codegen/ui_tests/non_clonable.stderr | 5 + codegen/ui_tests/non_clonable_second.rs | 22 + codegen/ui_tests/non_clonable_second.stderr | 5 + codegen/ui_tests/return_mut_ref.rs | 23 + codegen/ui_tests/return_mut_ref.stderr | 11 + codegen/ui_tests/return_pointer.rs | 19 + codegen/ui_tests/return_pointer.stderr | 11 + codegen/ui_tests/return_shared_ref.rs | 19 + codegen/ui_tests/return_shared_ref.stderr | 11 + codegen/ui_tests/second_shared_ref.rs | 23 + codegen/ui_tests/second_shared_ref.stderr | 11 + src/any.rs | 7 + src/fn_register.rs | 9 +- src/lib.rs | 2 + src/plugin.rs | 6 +- 28 files changed, 2230 insertions(+), 5 deletions(-) create mode 100644 codegen/Cargo.toml create mode 100644 codegen/src/function.rs create mode 100644 codegen/src/lib.rs create mode 100644 codegen/src/module.rs create mode 100644 codegen/src/rhai_module.rs create mode 100644 codegen/tests/test_functions.rs create mode 100644 codegen/tests/test_modules.rs create mode 100644 codegen/ui_tests/first_shared_ref.rs create mode 100644 codegen/ui_tests/first_shared_ref.stderr create mode 100644 codegen/ui_tests/non_clonable.rs create mode 100644 codegen/ui_tests/non_clonable.stderr create mode 100644 codegen/ui_tests/non_clonable_second.rs create mode 100644 codegen/ui_tests/non_clonable_second.stderr create mode 100644 codegen/ui_tests/return_mut_ref.rs create mode 100644 codegen/ui_tests/return_mut_ref.stderr create mode 100644 codegen/ui_tests/return_pointer.rs create mode 100644 codegen/ui_tests/return_pointer.stderr create mode 100644 codegen/ui_tests/return_shared_ref.rs create mode 100644 codegen/ui_tests/return_shared_ref.stderr create mode 100644 codegen/ui_tests/second_shared_ref.rs create mode 100644 codegen/ui_tests/second_shared_ref.stderr diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1370e03e..259d8c51 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,6 @@ jobs: flags: - "" - "--features serde" - - "--features plugins" - "--features unchecked" - "--features sync" - "--features no_optimize" @@ -75,3 +74,25 @@ jobs: with: command: build args: --manifest-path=no_std/no_std_test/Cargo.toml ${{matrix.flags}} + codegen_build: + name: Codegen Build + runs-on: ${{matrix.os}} + continue-on-error: ${{matrix.experimental}} + strategy: + matrix: + include: + - {toolchain: nightly, os: ubuntu-latest, experimental: false, flags: ""} + - {toolchain: nightly, os: windows-latest, experimental: false, flags: ""} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{matrix.toolchain}} + override: true + - name: Build Project + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path=codegen/Cargo.toml ${{matrix.flags}} diff --git a/Cargo.toml b/Cargo.toml index ec00a10c..9a282a45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] default = [] -plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_optimize = [] # no script optimizer @@ -81,6 +80,10 @@ version = "0.2.1" default_features = false optional = true +[dependencies.rhai_codegen] +version = "0.1" +path = "codegen" + [target.'cfg(target_arch = "wasm32")'.dependencies] instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant diff --git a/README.md b/README.md index 7aef538c..c755d4b2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Supported targets and builds * All common CPU targets for Windows, Linux and MacOS. * WebAssembly (WASM) * `no-std` +* Minimum Rust version 1.45 Standard features ----------------- diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 00000000..53aa7d31 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rhai_codegen" +version = "0.1.0" +edition = "2018" +authors = ["Stephen Chung", "jhwgh1968"] +description = "Proceducral macro support package for Rhai, a scripting language for Rust" +homepage = "https://github.com/jonathandturner/rhai" +repository = "https://github.com/jonathandturner/rhai" +license = "MIT OR Apache-2.0" + +[lib] +proc-macro = true + +[dev-dependencies] +rhai = { version = "*", path = ".." } +trybuild = "1" + +[dependencies] +proc-macro2 = "1" +syn = { version = "1", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] } +quote = "1" diff --git a/codegen/src/function.rs b/codegen/src/function.rs new file mode 100644 index 00000000..46015a7c --- /dev/null +++ b/codegen/src/function.rs @@ -0,0 +1,746 @@ +#![allow(unused)] +use quote::{quote, quote_spanned}; +use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; + +#[derive(Debug)] +pub(crate) struct ExportedFn { + entire_span: proc_macro2::Span, + signature: syn::Signature, + is_public: bool, + mut_receiver: bool, +} + +impl Parse for ExportedFn { + fn parse(input: ParseStream) -> syn::Result { + let fn_all: syn::ItemFn = input.parse()?; + let entire_span = fn_all.span(); + let str_type_path = syn::parse2::(quote! { str }).unwrap(); + + // Determine if the function is public. + let is_public = match fn_all.vis { + syn::Visibility::Public(_) => true, + _ => false + }; + // Determine whether function generates a special calling convention for a mutable + // reciever. + let mut_receiver = { + if let Some(first_arg) = fn_all.sig.inputs.first() { + match first_arg { + syn::FnArg::Receiver(syn::Receiver { reference: Some(_), ..}) => true, + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + match ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), .. }) => true, + &syn::Type::Reference(syn::TypeReference { mutability: None, + ref elem, .. }) => { + match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => false, + _ => return Err(syn::Error::new(ty.span(), + "references from Rhai in this position \ + must be mutable")), + } + }, + _ => false, + } + }, + _ => false, + } + } else { + false + } + }; + + // All arguments after the first must be moved except for &str. + for arg in fn_all.sig.inputs.iter().skip(1) { + let ty = match arg { + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => ty, + _ => panic!("internal error: receiver argument outside of first position!?"), + }; + let is_ok = match ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. }) => false, + &syn::Type::Reference(syn::TypeReference { mutability: None, + ref elem, .. }) => { + match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => true, + _ => false, + } + }, + &syn::Type::Verbatim(_) => false, + _ => true, + }; + if !is_ok { + return Err(syn::Error::new(ty.span(), "this type in this position passes from \ + Rhai by value")); + } + } + + // No returning references or pointers. + if let syn::ReturnType::Type(_, ref rtype) = fn_all.sig.output { + match rtype.as_ref() { + &syn::Type::Ptr(_) => return Err(syn::Error::new(fn_all.sig.output.span(), + "cannot return a pointer to Rhai")), + &syn::Type::Reference(_) => return Err(syn::Error::new(fn_all.sig.output.span(), + "cannot return a reference to Rhai")), + _ => {}, + } + } + Ok(ExportedFn { + entire_span, + signature: fn_all.sig, + is_public, + mut_receiver, + }) + } +} + +impl ExportedFn { + pub(crate) fn mutable_receiver(&self) -> bool { + self.mut_receiver + } + + pub(crate) fn is_public(&self) -> bool { + self.is_public + } + + pub(crate) fn span(&self) -> &proc_macro2::Span { + &self.entire_span + } + + pub(crate) fn name(&self) -> &syn::Ident { + &self.signature.ident + } + + pub(crate) fn arg_list(&self) -> impl Iterator { + self.signature.inputs.iter() + } + + pub(crate) fn arg_count(&self) -> usize { + self.signature.inputs.len() + } + + pub(crate) fn return_type(&self) -> Option<&syn::Type> { + if let syn::ReturnType::Type(_, ref rtype) = self.signature.output { + Some(rtype) + } else { + None + } + } + + pub fn generate(self) -> proc_macro2::TokenStream { + let name: syn::Ident = syn::Ident::new(&format!("rhai_fn__{}", self.name().to_string()), + self.name().span()); + let impl_block = self.generate_impl("Token"); + quote! { + #[allow(unused)] + pub mod #name { + use super::*; + pub struct Token(); + #impl_block + } + } + } + + pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { + let name: syn::Ident = self.name().clone(); + let arg_count = self.arg_count(); + let is_method_call = self.mutable_receiver(); + + let mut unpack_stmts: Vec = Vec::new(); + let mut unpack_exprs: Vec = Vec::new(); + let mut input_type_exprs: Vec = Vec::new(); + let skip_first_arg; + + // Handle the first argument separately if the function has a "method like" receiver + if is_method_call { + skip_first_arg = true; + let first_arg = self.arg_list().next().unwrap(); + let var = syn::Ident::new("arg0", proc_macro2::Span::call_site()); + match first_arg { + syn::FnArg::Typed(pattern) => { + let arg_type: &syn::Type = { + match pattern.ty.as_ref() { + &syn::Type::Reference( + syn::TypeReference { ref elem, .. }) => elem.as_ref(), + ref p => p, + } + }; + let downcast_span = quote_spanned!( + arg_type.span()=> args[0usize].downcast_mut::<#arg_type>().unwrap()); + unpack_stmts.push(syn::parse2::(quote! { + let #var: &mut _ = #downcast_span; + }).unwrap()); + input_type_exprs.push(syn::parse2::(quote_spanned!( + arg_type.span()=> std::any::TypeId::of::<#arg_type>() + )).unwrap()); + }, + syn::FnArg::Receiver(_) => todo!("true self parameters not implemented yet"), + } + unpack_exprs.push(syn::parse2::(quote! { #var }).unwrap()); + } else { + skip_first_arg = false; + } + + // Handle the rest of the arguments, which all are passed by value. + // + // The only exception is strings, which need to be downcast to ImmutableString to enable a + // zero-copy conversion to &str by reference. + let str_type_path = syn::parse2::(quote! { str }).unwrap(); + for (i, arg) in self.arg_list().enumerate().skip(skip_first_arg as usize) { + let var = syn::Ident::new(&format!("arg{}", i), proc_macro2::Span::call_site()); + let is_str_ref; + match arg { + syn::FnArg::Typed(pattern) => { + let arg_type: &syn::Type = pattern.ty.as_ref(); + let downcast_span = match pattern.ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { mutability: None, + ref elem, .. }) => { + match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => { + is_str_ref = true; + quote_spanned!(arg_type.span()=> + args[#i] + .downcast_clone::() + .unwrap()) + }, + _ => panic!("internal error: why wasn't this found earlier!?"), + } + }, + _ => { + is_str_ref = false; + quote_spanned!(arg_type.span()=> + args[#i].downcast_clone::<#arg_type>().unwrap()) + }, + }; + + unpack_stmts.push(syn::parse2::(quote! { + let #var = #downcast_span; + }).unwrap()); + if !is_str_ref { + input_type_exprs.push(syn::parse2::(quote_spanned!( + arg_type.span()=> std::any::TypeId::of::<#arg_type>() + )).unwrap()); + } else { + input_type_exprs.push(syn::parse2::(quote_spanned!( + arg_type.span()=> std::any::TypeId::of::() + )).unwrap()); + } + }, + syn::FnArg::Receiver(_) => panic!("internal error: how did this happen!?"), + } + if !is_str_ref { + unpack_exprs.push(syn::parse2::(quote! { #var }).unwrap()); + } else { + unpack_exprs.push(syn::parse2::(quote! { &#var }).unwrap()); + } + } + + // In method calls, the first argument will need to be mutably borrowed. Because Rust marks + // that as needing to borrow the entire array, all of the previous argument unpacking via + // clone needs to happen first. + if is_method_call { + let arg0 = unpack_stmts.remove(0); + unpack_stmts.push(arg0); + } + + let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site()); + quote! { + impl rhai::plugin::PluginFunction for #type_name { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != #arg_count { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), #arg_count), rhai::Position::none()))); + } + #(#unpack_stmts)* + Ok(rhai::Dynamic::from(#name(#(#unpack_exprs),*))) + } + + fn is_method_call(&self) -> bool { #is_method_call } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(#type_name()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![#(#input_type_exprs),*].into_boxed_slice() + } + } + } + } +} + +#[cfg(test)] +mod function_tests { + use super::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn one_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!(item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap()); + } + + #[test] + fn two_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize, y: f32) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!(item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap()); + assert_eq!(item_fn.arg_list().skip(1).next().unwrap(), + &syn::parse2::(quote! { y: f32 }).unwrap()); + } + + #[test] + fn usize_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_number() -> usize { 42 } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "get_magic_number"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert_eq!(item_fn.arg_list().count(), 0); + assert_eq!(item_fn.return_type().unwrap(), + &syn::Type::Path(syn::TypePath { + qself: None, + path: syn::parse2::(quote! { usize }).unwrap() + }) + ); + } + + #[test] + fn ref_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> &'static str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), + "cannot return a reference to Rhai"); + } + + #[test] + fn ptr_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> *const str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), + "cannot return a pointer to Rhai"); + } + + #[test] + fn ref_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), + "references from Rhai in this position must be mutable"); + } + + #[test] + fn ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(count: usize, who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), + "this type in this position passes from Rhai by value"); + } + + #[test] + fn mut_ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn give(item_name: &str, who: &mut Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), + "this type in this position passes from Rhai by value"); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!(item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap()); + } + + #[test] + fn str_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(level: usize, message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!(item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { level: usize }).unwrap()); + assert_eq!(item_fn.arg_list().skip(1).next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap()); + } + + #[test] + fn private_fn() { + let input_tokens: TokenStream = quote! { + fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(!item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&mut self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } + + #[test] + fn immutable_receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } +} + +#[cfg(test)] +mod generate_tests { + use super::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + let expected = expected.to_string(); + if &actual != &expected { + let mut counter = 0; + let iter = actual.chars().zip(expected.chars()) + .inspect(|_| counter += 1) + .skip_while(|(a, e)| *a == *e); + let (actual_diff, expected_diff) = { + let mut actual_diff = String::new(); + let mut expected_diff = String::new(); + for (a, e) in iter.take(50) { + actual_diff.push(a); + expected_diff.push(e); + } + (actual_diff, expected_diff) + }; + eprintln!("actual != expected, diverge at char {}", counter); + } + assert_eq!(actual, expected); + } + + #[test] + fn minimal_fn () { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn__do_nothing { + use super::*; + pub struct Token(); + impl rhai::plugin::PluginFunction for Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 0usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 0usize), rhai::Position::none()))); + } + Ok(rhai::Dynamic::from(do_nothing())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![].into_boxed_slice() + } + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn__do_something { + use super::*; + pub struct Token(); + impl rhai::plugin::PluginFunction for Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 1usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 1usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn_impl() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + impl rhai::plugin::PluginFunction for MyType { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 1usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 1usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(MyType()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::()].into_boxed_slice() + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); + } + + #[test] + fn two_arg_returning_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn add_together(x: usize, y: usize) -> usize { x + y } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn__add_together { + use super::*; + pub struct Token(); + impl rhai::plugin::PluginFunction for Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 2usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 2usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg1 = args[1usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::(), + std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn mut_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn increment(x: &mut usize, y: usize) { *x += y; } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn__increment { + use super::*; + pub struct Token(); + impl rhai::plugin::PluginFunction for Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 2usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 2usize), rhai::Position::none()))); + } + let arg1 = args[1usize].downcast_clone::().unwrap(); + let arg0: &mut _ = args[0usize].downcast_mut::().unwrap(); + Ok(rhai::Dynamic::from(increment(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::(), + std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn special_print(message: &str) { eprintln!("----{}----", message); } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn__special_print { + use super::*; + pub struct Token(); + impl rhai::plugin::PluginFunction for Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 1usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 1usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(special_print(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(!item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } +} + +#[cfg(test)] +mod ui_tests { + #[test] + fn all() { + let t = trybuild::TestCases::new(); + t.compile_fail("ui_tests/*.rs"); + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 00000000..f22b9da9 --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,143 @@ +//! +//! This crate contains procedural macros to make creating Rhai modules much easier. +//! +//! # Exporting a Macro to Rhai +//! +//! ``` +//! use rhai::{EvalAltResult, FLOAT, RegisterFn}; +//! use rhai::plugin::*; +//! use rhai::module_resolvers::*; +//! +//! #[rhai::export_module] +//! pub mod advanced_math { +//! use rhai::FLOAT; +//! +//! pub const MYSTIC_NUMBER: FLOAT = 42.0 as FLOAT; +//! +//! pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { +//! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() +//! } +//! } +//! +//! fn main() -> Result<(), Box> { +//! let mut engine = Engine::new(); +//! let m = rhai::exported_module!(advanced_math); +//! let mut r = StaticModuleResolver::new(); +//! r.insert("Math::Advanced".to_string(), m); +//! engine.set_module_resolver(Some(r)); +//! +//! assert_eq!(engine.eval::( +//! r#"import "Math::Advanced" as math; +//! let m = math::MYSTIC_NUMBER; +//! let x = math::euclidean_distance(0.0, 1.0, 0.0, m); +//! x"#)?, 41.0); +//! Ok(()) +//! } +//! ``` +//! +//! # Exporting a Function to Rhai +//! +//! ``` +//! use rhai::{EvalAltResult, FLOAT, Module, RegisterFn}; +//! use rhai::plugin::*; +//! use rhai::module_resolvers::*; +//! +//! #[rhai::export_fn] +//! pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { +//! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() +//! } +//! +//! fn main() -> Result<(), Box> { +//! +//! let mut engine = Engine::new(); +//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); +//! let mut m = Module::new(); +//! rhai::register_exported_fn!(m, "euclidean_distance", distance_function); +//! let mut r = StaticModuleResolver::new(); +//! r.insert("Math::Advanced".to_string(), m); +//! engine.set_module_resolver(Some(r)); +//! +//! assert_eq!(engine.eval::( +//! r#"import "Math::Advanced" as math; +//! let m = get_mystic_number(); +//! let x = math::euclidean_distance(0.0, 1.0, 0.0, m); +//! x"#)?, 41.0); +//! Ok(()) +//! } +//! ``` +//! + +use quote::{quote, quote_spanned}; +use syn::{parse::Parser, parse_macro_input, spanned::Spanned}; + +mod function; +mod module; +mod rhai_module; + +#[proc_macro_attribute] +pub fn export_fn(_args: proc_macro::TokenStream, + input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let mut output = proc_macro2::TokenStream::from(input.clone()); + let function_def = parse_macro_input!(input as function::ExportedFn); + output.extend(function_def.generate()); + proc_macro::TokenStream::from(output) +} + +#[proc_macro_attribute] +pub fn export_module(_args: proc_macro::TokenStream, + input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let module_def = parse_macro_input!(input as module::Module); + let tokens = module_def.generate(); + proc_macro::TokenStream::from(tokens) +} + +#[proc_macro] +pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::TokenStream { + let module_path = parse_macro_input!(module_path as syn::Path); + let tokens = quote::quote! { + #module_path::rhai_module__generate() + }; + proc_macro::TokenStream::from(tokens) +} + +#[proc_macro] +pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { + let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; + let args = parser.parse(args).unwrap(); + let arg_span = args.span(); + let items: Vec = args.into_iter().collect(); + if items.len() != 3 { + return proc_macro::TokenStream::from( + syn::Error::new(arg_span, "this macro requires three arguments") + .to_compile_error()); + } + let rhai_module = &items[0]; + let export_name = match &items[1] { + syn::Expr::Lit(litstr) => quote_spanned!(items[1].span()=> + #litstr.to_string()), + expr => quote! { #expr }, + }; + let rust_modpath = if let syn::Expr::Path(ref path) = &items[2] { + &path.path + } else { + return proc_macro::TokenStream::from( + syn::Error::new(items[2].span(), "third argument must be a function name") + .to_compile_error()); + }; + let gen_mod_path: syn::punctuated::Punctuated = { + let mut g = rust_modpath.clone().segments; + g.pop(); + let ident = syn::Ident::new(&format!("rhai_fn__{}", + rust_modpath.segments.last().unwrap().ident), + items[2].span()); + g.push_value(syn::PathSegment { ident, arguments: syn::PathArguments::None }); + g + }; + let tokens = quote! { + #rhai_module.set_fn(#export_name, rhai::FnAccess::Public, + #gen_mod_path::Token().input_types().as_ref(), + CallableFunction::from_plugin(#gen_mod_path::Token())); + + }; + proc_macro::TokenStream::from(tokens) +} diff --git a/codegen/src/module.rs b/codegen/src/module.rs new file mode 100644 index 00000000..e22b6ec8 --- /dev/null +++ b/codegen/src/module.rs @@ -0,0 +1,661 @@ +use quote::{quote, ToTokens}; +use syn::{parse::Parse, parse::ParseStream}; + +use crate::function::ExportedFn; +use crate::rhai_module::ExportedConst; + +#[derive(Debug)] +pub(crate) struct Module { + mod_all: Option, + fns: Vec, + consts: Vec, +} + +impl Parse for Module { + fn parse(input: ParseStream) -> syn::Result { + let mod_all: syn::ItemMod = input.parse()?; + let fns: Vec<_>; + let consts: Vec<_>; + if let Some((_, ref content)) = mod_all.content { + fns = content.iter() + .filter_map(|item| { + match item { + syn::Item::Fn(f) => { + if let syn::Visibility::Public(_) = f.vis { + Some(f) + } else { + None + } + }, + _ => None, + } + }) + .try_fold(Vec::new(), |mut vec, itemfn| { + syn::parse2::(itemfn.to_token_stream()) + .map(|f| vec.push(f)) + .map(|_| vec) + })?; + consts = content.iter() + .filter_map(|item| { + match item { + syn::Item::Const(syn::ItemConst {vis, ref expr, ident, ..}) => { + if let syn::Visibility::Public(_) = vis { + Some((ident.to_string(), expr.as_ref().clone())) + } else { + None + } + }, + _ => None, + } + }) + .collect(); + } else { + consts = vec![]; + fns = vec![]; + } + Ok(Module { + mod_all: Some(mod_all), + fns, + consts, + }) + } +} + +impl Module { + pub fn generate(self) -> proc_macro2::TokenStream { + let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts); + let Module { mod_all, .. } = self; + let mut mod_all = mod_all.unwrap(); + let mod_name = mod_all.ident.clone(); + let (_, orig_content) = mod_all.content.take().unwrap(); + + quote! { + pub mod #mod_name { + #(#orig_content)* + #mod_gen + } + } + } +} + +#[cfg(test)] +mod module_tests { + use super::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn empty_module() { + + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + } + + #[test] + fn one_factory_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert_eq!(item_mod.fns[0].name().to_string(), "get_mystic_number"); + assert_eq!(item_mod.fns[0].arg_count(), 0); + assert_eq!(item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap()); + } + + #[test] + fn one_single_arg_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert_eq!(item_mod.fns[0].name().to_string(), "add_one_to"); + assert_eq!(item_mod.fns[0].arg_count(), 1); + assert_eq!(item_mod.fns[0].arg_list().next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap()); + assert_eq!(item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap()); + } + + #[test] + fn one_double_arg_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + let mut args = item_mod.fns[0].arg_list(); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert_eq!(item_mod.fns[0].name().to_string(), "add_together"); + assert_eq!(item_mod.fns[0].arg_count(), 2); + assert_eq!(args.next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap()); + assert_eq!(args.next().unwrap(), + &syn::parse2::(quote! { y: INT }).unwrap()); + assert!(args.next().is_none()); + assert_eq!(item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap()); + } + + #[test] + fn one_constant_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert_eq!(item_mod.consts.len(), 1); + assert_eq!(&item_mod.consts[0].0, "MYSTIC_NUMBER"); + assert_eq!(item_mod.consts[0].1, syn::parse2::(quote! { 42 }).unwrap()); + } + + #[test] + fn one_private_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + } + + #[test] + fn one_private_constant_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + } +} + +#[cfg(test)] +mod generate_tests { + use super::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + let expected = expected.to_string(); + if &actual != &expected { + let mut counter = 0; + let iter = actual.chars().zip(expected.chars()) + .inspect(|_| counter += 1) + .skip_while(|(a, e)| *a == *e); + let (actual_diff, expected_diff) = { + let mut actual_diff = String::new(); + let mut expected_diff = String::new(); + for (a, e) in iter.take(50) { + actual_diff.push(a); + expected_diff.push(e); + } + (actual_diff, expected_diff) + }; + eprintln!("actual != expected, diverge at char {}", counter); + } + assert_eq!(actual, expected); + } + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let expected_tokens = quote! { + pub mod empty { + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_factory_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + rhai::plugin::CallableFunction::from_plugin(get_mystic_number__Token())); + m + } + #[allow(non_camel_case_types)] + pub struct get_mystic_number__Token(); + impl rhai::plugin::PluginFunction for get_mystic_number__Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 0usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 0usize), rhai::Position::none()))); + } + Ok(rhai::Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number__Token()) + } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![].into_boxed_slice() + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_single_arg_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], + rhai::plugin::CallableFunction::from_plugin(add_one_to__Token())); + m + } + #[allow(non_camel_case_types)] + pub struct add_one_to__Token(); + impl rhai::plugin::PluginFunction for add_one_to__Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 1usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 1usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to__Token()) + } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_double_arg_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + rhai::plugin::CallableFunction::from_plugin(add_together__Token())); + m + } + #[allow(non_camel_case_types)] + pub struct add_together__Token(); + impl rhai::plugin::PluginFunction for add_together__Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 2usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 2usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg1 = args[1usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_together__Token()) + } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::(), + std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module_imports_preserved() { + + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_constant_module() { + + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_str_arg_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_fn("print_out_to", FnAccess::Public, + &[core::any::TypeId::of::()], + rhai::plugin::CallableFunction::from_plugin(print_out_to__Token())); + m + } + #[allow(non_camel_case_types)] + pub struct print_out_to__Token(); + impl rhai::plugin::PluginFunction for print_out_to__Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 1usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 1usize), rhai::Position::none()))); + } + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(rhai::Dynamic::from(print_out_to(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(print_out_to__Token()) + } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_mut_ref_fn_module() { + + let input_tokens: TokenStream = quote! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + }; + + let expected_tokens = quote! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + rhai::plugin::CallableFunction::from_plugin(increment__Token())); + m + } + #[allow(non_camel_case_types)] + pub struct increment__Token(); + impl rhai::plugin::PluginFunction for increment__Token { + fn call(&self, + args: &mut [&mut rhai::Dynamic], pos: rhai::Position + ) -> Result> { + if args.len() != 1usize { + return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + format!("wrong arg count: {} != {}", + args.len(), 1usize), rhai::Position::none()))); + } + let arg0: &mut _ = args[0usize].downcast_mut::().unwrap(); + Ok(rhai::Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment__Token()) + } + fn input_types(&self) -> Box<[std::any::TypeId]> { + vec![std::any::TypeId::of::()].into_boxed_slice() + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } +} diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs new file mode 100644 index 00000000..ba04b50c --- /dev/null +++ b/codegen/src/rhai_module.rs @@ -0,0 +1,90 @@ +use quote::quote; + +use crate::function::ExportedFn; + +pub(crate) type ExportedConst = (String, syn::Expr); + +pub(crate) fn generate_body( + fns: &Vec, + consts: &Vec +) -> proc_macro2::TokenStream { + let mut set_fn_stmts: Vec = Vec::new(); + let mut set_const_stmts: Vec = Vec::new(); + let str_type_path = syn::parse2::(quote! { str }).unwrap(); + + for (const_name, const_expr) in consts { + let const_literal = syn::LitStr::new(&const_name, proc_macro2::Span::call_site()); + set_const_stmts.push(syn::parse2::(quote! { + m.set_var(#const_literal, #const_expr); + }).unwrap()); + } + + // NB: these are token streams, because reparsing messes up "> >" vs ">>" + let mut gen_fn_tokens: Vec = Vec::new(); + for function in fns { + let fn_token_name = syn::Ident::new(&format!("{}__Token", function.name().to_string()), + function.name().span()); + let fn_literal = syn::LitStr::new(&function.name().to_string(), + proc_macro2::Span::call_site()); + let fn_input_types: Vec = function.arg_list() + .map(|fnarg| match fnarg { + syn::FnArg::Receiver(_) => panic!("internal error: receiver fn outside impl!?"), + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + let arg_type = match ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { mutability: None, + ref elem, .. }) => { + match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => + syn::parse2::(quote! { + rhai::ImmutableString }).unwrap(), + _ => panic!("internal error: non-string shared reference!?"), + } + }, + &syn::Type::Reference(syn::TypeReference { mutability: Some(_), + ref elem, .. }) => { + match elem.as_ref() { + &syn::Type::Path(ref p) => syn::parse2::(quote! { + #p }).unwrap(), + _ => panic!("internal error: non-string shared reference!?"), + } + }, + t => t.clone(), + }; + syn::parse2::(quote! { + core::any::TypeId::of::<#arg_type>()}).unwrap() + }, + }).collect(); + + set_fn_stmts.push(syn::parse2::(quote! { + m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], + rhai::plugin::CallableFunction::from_plugin(#fn_token_name())); + }).unwrap()); + + gen_fn_tokens.push(quote! { + #[allow(non_camel_case_types)] + pub struct #fn_token_name(); + }); + gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string())); + } + + let mut generate_fncall = syn::parse2::(quote! { + pub mod generate_info { + #[allow(unused_imports)] + use rhai::{Module, FnAccess}; + #[allow(unused_mut)] + pub fn rhai_module__generate() -> Module { + let mut m = Module::new(); + #(#set_fn_stmts)* + #(#set_const_stmts)* + m + } + } + }).unwrap(); + + let (_, generate_call_content) = generate_fncall.content.take().unwrap(); + + quote! { + #(#generate_call_content)* + #(#gen_fn_tokens)* + } +} diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs new file mode 100644 index 00000000..a0ebb6c0 --- /dev/null +++ b/codegen/tests/test_functions.rs @@ -0,0 +1,141 @@ +use rhai::{EvalAltResult, FLOAT, INT, Module, RegisterFn}; +use rhai::plugin::*; +use rhai::module_resolvers::*; + +pub mod raw_fn { + use rhai::FLOAT; + use rhai::export_fn; + + #[export_fn] + pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { + ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() + } +} + +#[test] +fn raw_fn_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + let mut m = Module::new(); + rhai::register_exported_fn!(m, "euclidean_distance".to_string(), raw_fn::distance_function); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Math::Advanced" as math; + let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 41.0); + Ok(()) +} + +mod raw_fn_mut { + use rhai::FLOAT; + use rhai::export_fn; + + #[export_fn] + pub fn add_in_place(f1: &mut FLOAT, f2: FLOAT) { + *f1 += f2; + } +} + +#[test] +fn raw_fn_mut_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + let mut m = Module::new(); + rhai::register_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Math::Advanced" as math; + let x = get_mystic_number(); + math::add_in_place(x, 1.0); + x"#)?, 43.0); + Ok(()) +} + +mod raw_fn_str { + use rhai::export_fn; + + #[export_fn] + pub fn write_out_str(message: &str) -> bool { + eprintln!("{}", message); + true + } +} + +#[test] +fn raw_fn_str_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + let mut m = Module::new(); + rhai::register_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); + let mut r = StaticModuleResolver::new(); + r.insert("Host::IO".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Host::IO" as io; + let x = io::write_out_str("hello world!"); + x"#)?, true); + Ok(()) +} + +mod mut_opaque_ref { + use rhai::INT; + use rhai::export_fn; + + #[derive(Clone)] + pub struct StatusMessage { + os_code: Option, + message: String, + is_ok: bool + } + + #[export_fn] + pub fn new_message(is_ok: bool, message: &str) -> StatusMessage { + StatusMessage { + is_ok, + os_code: None, + message: message.to_string(), + } + } + + #[export_fn] + pub fn new_os_message(is_ok: bool, os_code: INT) -> StatusMessage { + StatusMessage { + is_ok, + os_code: Some(os_code), + message: format!("OS Code {}", os_code), + } + } + + #[export_fn] + pub fn write_out_message(message: &mut StatusMessage) -> bool { + eprintln!("{}", message.message); + true + } +} + +#[test] +fn mut_opaque_ref_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let mut m = Module::new(); + rhai::register_exported_fn!(m, "new_message", mut_opaque_ref::new_message); + rhai::register_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); + rhai::register_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); + let mut r = StaticModuleResolver::new(); + r.insert("Host::Msg".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Host::Msg" as msg; + let message1 = msg::new_message(true, "it worked"); + let ok1 = msg::write_out_message(message1); + let message2 = msg::new_os_message(true, 0); + let ok2 = msg::write_out_message(message2); + ok1 && ok2"#)?, true); + Ok(()) +} diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs new file mode 100644 index 00000000..ebc661a9 --- /dev/null +++ b/codegen/tests/test_modules.rs @@ -0,0 +1,165 @@ +use rhai::{EvalAltResult, FLOAT, INT, RegisterFn}; +use rhai::plugin::*; +use rhai::module_resolvers::*; + +pub mod empty_module { + use rhai::export_module; + + #[export_module] + pub mod EmptyModule { } +} + +#[test] +fn empty_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::empty_module::EmptyModule); + let mut r = StaticModuleResolver::new(); + r.insert("Module::Empty".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Module::Empty" as m; 42"#)?, 42); + Ok(()) +} + +pub mod one_fn_module { + use rhai::export_module; + + #[export_module] + pub mod advanced_math { + use rhai::FLOAT; + pub fn get_mystic_number() -> FLOAT { + 42.0 as FLOAT + } + } +} + +#[test] +fn one_fn_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_module::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::get_mystic_number(); + m"#)?, 42.0); + Ok(()) +} + +pub mod one_fn_and_const_module { + use rhai::export_module; + + #[export_module] + pub mod advanced_math { + use rhai::FLOAT; + + pub const MYSTIC_NUMBER: FLOAT = 42.0 as FLOAT; + + pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { + ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() + } + } +} + +#[test] +fn one_fn_and_const_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::MYSTIC_NUMBER; + let x = math::euclidean_distance(0.0, 1.0, 0.0, m); + x"#)?, 41.0); + Ok(()) +} + +pub mod raw_fn_str_module { + use rhai::export_module; + + #[export_module] + pub mod host_io { + pub fn write_out_str(message: &str) -> bool { + eprintln!("{}", message); + true + } + } +} + +#[test] +fn raw_fn_str_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::raw_fn_str_module::host_io); + let mut r = StaticModuleResolver::new(); + r.insert("Host::IO".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Host::IO" as io; + let x = io::write_out_str("hello world!"); + x"#)?, true); + Ok(()) +} + +pub mod mut_opaque_ref_module { + use rhai::INT; + use rhai::export_module; + + #[derive(Clone)] + pub struct StatusMessage { + os_code: Option, + message: String, + is_ok: bool + } + + #[export_module] + pub mod host_msg { + use super::{INT, StatusMessage}; + + pub fn new_message(is_ok: bool, message: &str) -> StatusMessage { + StatusMessage { + is_ok, + os_code: None, + message: message.to_string(), + } + } + + pub fn new_os_message(is_ok: bool, os_code: INT) -> StatusMessage { + StatusMessage { + is_ok, + os_code: Some(os_code), + message: format!("OS Code {}", os_code), + } + } + + pub fn write_out_message(message: &mut StatusMessage) -> bool { + eprintln!("{}", message.message); + true + } + } +} + +#[test] +fn mut_opaque_ref_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg); + let mut r = StaticModuleResolver::new(); + r.insert("Host::Msg".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!(engine.eval::( + r#"import "Host::Msg" as msg; + let success = "it worked"; + let message1 = msg::new_message(true, success); + let ok1 = msg::write_out_message(message1); + let message2 = msg::new_os_message(true, 0); + let ok2 = msg::write_out_message(message2); + ok1 && ok2"#)?, true); + Ok(()) +} diff --git a/codegen/ui_tests/first_shared_ref.rs b/codegen/ui_tests/first_shared_ref.rs new file mode 100644 index 00000000..c8d6c9f4 --- /dev/null +++ b/codegen/ui_tests/first_shared_ref.rs @@ -0,0 +1,22 @@ +use rhai::export_fn; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: &NonClonable) -> bool { + input.d +} + +fn main() { + let n = NonClonable { a: 0.0, b: 10, c: 'a', d: true }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/first_shared_ref.stderr b/codegen/ui_tests/first_shared_ref.stderr new file mode 100644 index 00000000..a92ef117 --- /dev/null +++ b/codegen/ui_tests/first_shared_ref.stderr @@ -0,0 +1,11 @@ +error: references from Rhai in this position must be mutable + --> $DIR/first_shared_ref.rs:11:23 + | +11 | pub fn test_fn(input: &NonClonable) -> bool { + | ^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/first_shared_ref.rs:17:8 + | +17 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/non_clonable.rs b/codegen/ui_tests/non_clonable.rs new file mode 100644 index 00000000..c5c6d5a2 --- /dev/null +++ b/codegen/ui_tests/non_clonable.rs @@ -0,0 +1,22 @@ +use rhai::export_fn; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: NonClonable) -> bool { + input.d +} + +fn main() { + let n = NonClonable { a: 0.0, b: 10, c: 'a', d: true }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/non_clonable.stderr b/codegen/ui_tests/non_clonable.stderr new file mode 100644 index 00000000..dbae1d47 --- /dev/null +++ b/codegen/ui_tests/non_clonable.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied + --> $DIR/non_clonable.rs:11:23 + | +11 | pub fn test_fn(input: NonClonable) -> bool { + | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` diff --git a/codegen/ui_tests/non_clonable_second.rs b/codegen/ui_tests/non_clonable_second.rs new file mode 100644 index 00000000..d31e3436 --- /dev/null +++ b/codegen/ui_tests/non_clonable_second.rs @@ -0,0 +1,22 @@ +use rhai::export_fn; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(a: u32, b: NonClonable) -> bool { + a == 0 && b.d +} + +fn main() { + let n = NonClonable { a: 0.0, b: 10, c: 'a', d: true }; + if test_fn(10, n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/non_clonable_second.stderr b/codegen/ui_tests/non_clonable_second.stderr new file mode 100644 index 00000000..26202d83 --- /dev/null +++ b/codegen/ui_tests/non_clonable_second.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied + --> $DIR/non_clonable_second.rs:11:27 + | +11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { + | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` diff --git a/codegen/ui_tests/return_mut_ref.rs b/codegen/ui_tests/return_mut_ref.rs new file mode 100644 index 00000000..5568beee --- /dev/null +++ b/codegen/ui_tests/return_mut_ref.rs @@ -0,0 +1,23 @@ +use rhai::export_fn; + +#[derive(Clone)] +struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: &mut Clonable) -> &mut bool { + &mut input.d +} + +fn main() { + let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/return_mut_ref.stderr b/codegen/ui_tests/return_mut_ref.stderr new file mode 100644 index 00000000..04853c8b --- /dev/null +++ b/codegen/ui_tests/return_mut_ref.stderr @@ -0,0 +1,11 @@ +error: cannot return a reference to Rhai + --> $DIR/return_mut_ref.rs:12:38 + | +12 | pub fn test_fn(input: &mut Clonable) -> &mut bool { + | ^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/return_mut_ref.rs:18:8 + | +18 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/return_pointer.rs b/codegen/ui_tests/return_pointer.rs new file mode 100644 index 00000000..fa20d73c --- /dev/null +++ b/codegen/ui_tests/return_pointer.rs @@ -0,0 +1,19 @@ +use rhai::export_fn; + +#[derive(Clone)] +struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: Clonable) -> *const str { + "yes" +} + +fn main() { + let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + println!("{}", unsafe { let ptr = test_fn(n); *ptr }); +} diff --git a/codegen/ui_tests/return_pointer.stderr b/codegen/ui_tests/return_pointer.stderr new file mode 100644 index 00000000..bee7f1d3 --- /dev/null +++ b/codegen/ui_tests/return_pointer.stderr @@ -0,0 +1,11 @@ +error: cannot return a pointer to Rhai + --> $DIR/return_pointer.rs:12:33 + | +12 | pub fn test_fn(input: Clonable) -> *const str { + | ^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/return_pointer.rs:18:39 + | +18 | println!("{}", unsafe { let ptr = test_fn(n); *ptr }); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/return_shared_ref.rs b/codegen/ui_tests/return_shared_ref.rs new file mode 100644 index 00000000..384c3882 --- /dev/null +++ b/codegen/ui_tests/return_shared_ref.rs @@ -0,0 +1,19 @@ +use rhai::export_fn; + +#[derive(Clone)] +struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: Clonable) -> & 'static str { + "yes" +} + +fn main() { + let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + println!("{}", test_fn(n)); +} diff --git a/codegen/ui_tests/return_shared_ref.stderr b/codegen/ui_tests/return_shared_ref.stderr new file mode 100644 index 00000000..6be110ae --- /dev/null +++ b/codegen/ui_tests/return_shared_ref.stderr @@ -0,0 +1,11 @@ +error: cannot return a reference to Rhai + --> $DIR/return_shared_ref.rs:12:33 + | +12 | pub fn test_fn(input: Clonable) -> & 'static str { + | ^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/return_shared_ref.rs:18:20 + | +18 | println!("{}", test_fn(n)); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/second_shared_ref.rs b/codegen/ui_tests/second_shared_ref.rs new file mode 100644 index 00000000..cf09608d --- /dev/null +++ b/codegen/ui_tests/second_shared_ref.rs @@ -0,0 +1,23 @@ +use rhai::export_fn; + +#[derive(Clone)] +pub struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: Clonable, factor: &bool) -> bool { + input.d & factor +} + +fn main() { + let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + if test_fn(n, &true) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/second_shared_ref.stderr b/codegen/ui_tests/second_shared_ref.stderr new file mode 100644 index 00000000..51908e82 --- /dev/null +++ b/codegen/ui_tests/second_shared_ref.stderr @@ -0,0 +1,11 @@ +error: this type in this position passes from Rhai by value + --> $DIR/second_shared_ref.rs:12:41 + | +12 | pub fn test_fn(input: Clonable, factor: &bool) -> bool { + | ^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/second_shared_ref.rs:18:8 + | +18 | if test_fn(n, &true) { + | ^^^^^^^ not found in this scope diff --git a/src/any.rs b/src/any.rs index 9d28c768..e310787a 100644 --- a/src/any.rs +++ b/src/any.rs @@ -681,6 +681,13 @@ impl Dynamic { } } + /// Copy and return a `Dynamic` if it contains a type that can be trivially copied. + /// Returns `None` if the cast fails. + #[inline(always)] + pub fn downcast_clone(&self) -> Option { + self.downcast_ref::().map(|t| t.clone()) + } + /// Cast the `Dynamic` as the system integer type `INT` and return it. /// Returns the name of the actual type if the cast fails. pub fn as_int(&self) -> Result { diff --git a/src/fn_register.rs b/src/fn_register.rs index afc76295..55fb2218 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -43,7 +43,7 @@ pub trait RegisterPlugin { /// fn is_method_call(&self) -> bool { false } /// fn is_varadic(&self) -> bool { false } /// - /// fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result> { + /// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result> { /// let x1: &FLOAT = args[0].downcast_ref::().unwrap(); /// let y1: &FLOAT = args[1].downcast_ref::().unwrap(); /// let x2: &FLOAT = args[2].downcast_ref::().unwrap(); @@ -55,6 +55,13 @@ pub trait RegisterPlugin { /// fn clone_boxed(&self) -> Box { /// Box::new(DistanceFunction()) /// } + /// + /// fn input_types(&self) -> Box<[std::any::TypeId]> { + /// vec![std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()].into_boxed_slice() + /// } /// } /// /// // A simple custom plugin. This should not usually be done with hand-written code. diff --git a/src/lib.rs b/src/lib.rs index 1ba4cda1..b6177699 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,8 @@ pub use syntax::{EvalContext, Expression}; pub use token::Position; pub use utils::calc_fn_spec as calc_fn_hash; +pub use rhai_codegen::*; + #[cfg(not(feature = "no_function"))] pub use parser::FnAccess; diff --git a/src/plugin.rs b/src/plugin.rs index 56e6af5d..01a652e3 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,6 +1,6 @@ //! Module defining plugins in Rhai. Is exported for use by plugin authors. -use crate::stdlib::boxed::Box; +use crate::stdlib::{any::TypeId, boxed::Box}; pub use crate::any::{Dynamic, Variant}; pub use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn}; @@ -38,7 +38,9 @@ pub trait PluginFunction { fn is_method_call(&self) -> bool; fn is_varadic(&self) -> bool; - fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result>; + fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result>; fn clone_boxed(&self) -> Box; + + fn input_types(&self) -> Box<[TypeId]>; } From 73cda2d4f9d81e5803657a839afb5b843de98906 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 15:39:08 +0800 Subject: [PATCH 008/103] rustfmt. --- Cargo.toml | 5 +- codegen/Cargo.toml | 4 +- codegen/src/function.rs | 246 +++++++++++++++--------- codegen/src/lib.rs | 31 +-- codegen/src/module.rs | 108 ++++++----- codegen/src/rhai_module.rs | 79 +++++--- codegen/tests/test_functions.rs | 66 ++++--- codegen/tests/test_modules.rs | 60 ++++-- codegen/ui_tests/first_shared_ref.rs | 7 +- codegen/ui_tests/non_clonable.rs | 7 +- codegen/ui_tests/non_clonable_second.rs | 7 +- codegen/ui_tests/return_mut_ref.rs | 7 +- codegen/ui_tests/return_pointer.rs | 12 +- codegen/ui_tests/return_shared_ref.rs | 9 +- codegen/ui_tests/second_shared_ref.rs | 7 +- 15 files changed, 409 insertions(+), 246 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a282a45..297a5a88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] num-traits = { version = "0.2.11", default-features = false } smallvec = { version = "1.4.1", default-features = false } +rhai_codegen = { version = "0.1", path = "codegen" } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] @@ -80,10 +81,6 @@ version = "0.2.1" default_features = false optional = true -[dependencies.rhai_codegen] -version = "0.1" -path = "codegen" - [target.'cfg(target_arch = "wasm32")'.dependencies] instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 53aa7d31..89a6f66f 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -2,7 +2,7 @@ name = "rhai_codegen" version = "0.1.0" edition = "2018" -authors = ["Stephen Chung", "jhwgh1968"] +authors = ["jhwgh1968", "Stephen Chung"] description = "Proceducral macro support package for Rhai, a scripting language for Rust" homepage = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai" @@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0" proc-macro = true [dev-dependencies] -rhai = { version = "*", path = ".." } +rhai = { path = ".." } trybuild = "1" [dependencies] diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 46015a7c..eab15386 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -19,29 +19,36 @@ impl Parse for ExportedFn { // Determine if the function is public. let is_public = match fn_all.vis { syn::Visibility::Public(_) => true, - _ => false + _ => false, }; // Determine whether function generates a special calling convention for a mutable // reciever. let mut_receiver = { if let Some(first_arg) = fn_all.sig.inputs.first() { match first_arg { - syn::FnArg::Receiver(syn::Receiver { reference: Some(_), ..}) => true, - syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { - match ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { - mutability: Some(_), .. }) => true, - &syn::Type::Reference(syn::TypeReference { mutability: None, - ref elem, .. }) => { - match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => false, - _ => return Err(syn::Error::new(ty.span(), - "references from Rhai in this position \ - must be mutable")), - } - }, - _ => false, - } + syn::FnArg::Receiver(syn::Receiver { + reference: Some(_), .. + }) => true, + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => match ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + .. + }) => true, + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => false, + _ => { + return Err(syn::Error::new( + ty.span(), + "references from Rhai in this position \ + must be mutable", + )) + } + }, + _ => false, }, _ => false, } @@ -57,31 +64,46 @@ impl Parse for ExportedFn { _ => panic!("internal error: receiver argument outside of first position!?"), }; let is_ok = match ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. }) => false, - &syn::Type::Reference(syn::TypeReference { mutability: None, - ref elem, .. }) => { - match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => true, - _ => false, - } + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + .. + }) => false, + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => true, + _ => false, }, &syn::Type::Verbatim(_) => false, _ => true, }; if !is_ok { - return Err(syn::Error::new(ty.span(), "this type in this position passes from \ - Rhai by value")); + return Err(syn::Error::new( + ty.span(), + "this type in this position passes from \ + Rhai by value", + )); } } // No returning references or pointers. if let syn::ReturnType::Type(_, ref rtype) = fn_all.sig.output { match rtype.as_ref() { - &syn::Type::Ptr(_) => return Err(syn::Error::new(fn_all.sig.output.span(), - "cannot return a pointer to Rhai")), - &syn::Type::Reference(_) => return Err(syn::Error::new(fn_all.sig.output.span(), - "cannot return a reference to Rhai")), - _ => {}, + &syn::Type::Ptr(_) => { + return Err(syn::Error::new( + fn_all.sig.output.span(), + "cannot return a pointer to Rhai", + )) + } + &syn::Type::Reference(_) => { + return Err(syn::Error::new( + fn_all.sig.output.span(), + "cannot return a reference to Rhai", + )) + } + _ => {} } } Ok(ExportedFn { @@ -110,7 +132,7 @@ impl ExportedFn { &self.signature.ident } - pub(crate) fn arg_list(&self) -> impl Iterator { + pub(crate) fn arg_list(&self) -> impl Iterator { self.signature.inputs.iter() } @@ -127,8 +149,10 @@ impl ExportedFn { } pub fn generate(self) -> proc_macro2::TokenStream { - let name: syn::Ident = syn::Ident::new(&format!("rhai_fn__{}", self.name().to_string()), - self.name().span()); + let name: syn::Ident = syn::Ident::new( + &format!("rhai_fn__{}", self.name().to_string()), + self.name().span(), + ); let impl_block = self.generate_impl("Token"); quote! { #[allow(unused)] @@ -156,23 +180,30 @@ impl ExportedFn { let first_arg = self.arg_list().next().unwrap(); let var = syn::Ident::new("arg0", proc_macro2::Span::call_site()); match first_arg { - syn::FnArg::Typed(pattern) => { + syn::FnArg::Typed(pattern) => { let arg_type: &syn::Type = { match pattern.ty.as_ref() { - &syn::Type::Reference( - syn::TypeReference { ref elem, .. }) => elem.as_ref(), + &syn::Type::Reference(syn::TypeReference { ref elem, .. }) => { + elem.as_ref() + } ref p => p, } }; let downcast_span = quote_spanned!( arg_type.span()=> args[0usize].downcast_mut::<#arg_type>().unwrap()); - unpack_stmts.push(syn::parse2::(quote! { - let #var: &mut _ = #downcast_span; - }).unwrap()); - input_type_exprs.push(syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::<#arg_type>() - )).unwrap()); - }, + unpack_stmts.push( + syn::parse2::(quote! { + let #var: &mut _ = #downcast_span; + }) + .unwrap(), + ); + input_type_exprs.push( + syn::parse2::(quote_spanned!( + arg_type.span()=> std::any::TypeId::of::<#arg_type>() + )) + .unwrap(), + ); + } syn::FnArg::Receiver(_) => todo!("true self parameters not implemented yet"), } unpack_exprs.push(syn::parse2::(quote! { #var }).unwrap()); @@ -189,42 +220,52 @@ impl ExportedFn { let var = syn::Ident::new(&format!("arg{}", i), proc_macro2::Span::call_site()); let is_str_ref; match arg { - syn::FnArg::Typed(pattern) => { + syn::FnArg::Typed(pattern) => { let arg_type: &syn::Type = pattern.ty.as_ref(); let downcast_span = match pattern.ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { mutability: None, - ref elem, .. }) => { - match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => { - is_str_ref = true; - quote_spanned!(arg_type.span()=> + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => { + is_str_ref = true; + quote_spanned!(arg_type.span()=> args[#i] .downcast_clone::() .unwrap()) - }, - _ => panic!("internal error: why wasn't this found earlier!?"), } + _ => panic!("internal error: why wasn't this found earlier!?"), }, _ => { is_str_ref = false; quote_spanned!(arg_type.span()=> args[#i].downcast_clone::<#arg_type>().unwrap()) - }, + } }; - unpack_stmts.push(syn::parse2::(quote! { - let #var = #downcast_span; - }).unwrap()); + unpack_stmts.push( + syn::parse2::(quote! { + let #var = #downcast_span; + }) + .unwrap(), + ); if !is_str_ref { - input_type_exprs.push(syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::<#arg_type>() - )).unwrap()); + input_type_exprs.push( + syn::parse2::(quote_spanned!( + arg_type.span()=> std::any::TypeId::of::<#arg_type>() + )) + .unwrap(), + ); } else { - input_type_exprs.push(syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::() - )).unwrap()); + input_type_exprs.push( + syn::parse2::(quote_spanned!( + arg_type.span()=> std::any::TypeId::of::() + )) + .unwrap(), + ); } - }, + } syn::FnArg::Receiver(_) => panic!("internal error: how did this happen!?"), } if !is_str_ref { @@ -302,8 +343,10 @@ mod function_tests { assert!(item_fn.is_public()); assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { x: usize }).unwrap()); + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); } #[test] @@ -319,10 +362,14 @@ mod function_tests { assert!(item_fn.is_public()); assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { x: usize }).unwrap()); - assert_eq!(item_fn.arg_list().skip(1).next().unwrap(), - &syn::parse2::(quote! { y: f32 }).unwrap()); + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().skip(1).next().unwrap(), + &syn::parse2::(quote! { y: f32 }).unwrap() + ); } #[test] @@ -336,11 +383,12 @@ mod function_tests { assert!(!item_fn.mutable_receiver()); assert!(item_fn.is_public()); assert_eq!(item_fn.arg_list().count(), 0); - assert_eq!(item_fn.return_type().unwrap(), - &syn::Type::Path(syn::TypePath { - qself: None, - path: syn::parse2::(quote! { usize }).unwrap() - }) + assert_eq!( + item_fn.return_type().unwrap(), + &syn::Type::Path(syn::TypePath { + qself: None, + path: syn::parse2::(quote! { usize }).unwrap() + }) ); } @@ -351,8 +399,7 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), - "cannot return a reference to Rhai"); + assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); } #[test] @@ -362,8 +409,7 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), - "cannot return a pointer to Rhai"); + assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); } #[test] @@ -373,8 +419,10 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), - "references from Rhai in this position must be mutable"); + assert_eq!( + format!("{}", err), + "references from Rhai in this position must be mutable" + ); } #[test] @@ -384,8 +432,10 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), - "this type in this position passes from Rhai by value"); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); } #[test] @@ -395,8 +445,10 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), - "this type in this position passes from Rhai by value"); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); } #[test] @@ -412,8 +464,10 @@ mod function_tests { assert!(item_fn.is_public()); assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { message: &str }).unwrap()); + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); } #[test] @@ -429,10 +483,14 @@ mod function_tests { assert!(item_fn.is_public()); assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { level: usize }).unwrap()); - assert_eq!(item_fn.arg_list().skip(1).next().unwrap(), - &syn::parse2::(quote! { message: &str }).unwrap()); + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { level: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().skip(1).next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); } #[test] @@ -490,7 +548,9 @@ mod generate_tests { let expected = expected.to_string(); if &actual != &expected { let mut counter = 0; - let iter = actual.chars().zip(expected.chars()) + let iter = actual + .chars() + .zip(expected.chars()) .inspect(|_| counter += 1) .skip_while(|(a, e)| *a == *e); let (actual_diff, expected_diff) = { @@ -508,7 +568,7 @@ mod generate_tests { } #[test] - fn minimal_fn () { + fn minimal_fn() { let input_tokens: TokenStream = quote! { pub fn do_nothing() { } }; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index f22b9da9..fb214c5d 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -75,8 +75,10 @@ mod module; mod rhai_module; #[proc_macro_attribute] -pub fn export_fn(_args: proc_macro::TokenStream, - input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn export_fn( + _args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { let mut output = proc_macro2::TokenStream::from(input.clone()); let function_def = parse_macro_input!(input as function::ExportedFn); output.extend(function_def.generate()); @@ -84,8 +86,10 @@ pub fn export_fn(_args: proc_macro::TokenStream, } #[proc_macro_attribute] -pub fn export_module(_args: proc_macro::TokenStream, - input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn export_module( + _args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { let module_def = parse_macro_input!(input as module::Module); let tokens = module_def.generate(); proc_macro::TokenStream::from(tokens) @@ -108,8 +112,8 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS let items: Vec = args.into_iter().collect(); if items.len() != 3 { return proc_macro::TokenStream::from( - syn::Error::new(arg_span, "this macro requires three arguments") - .to_compile_error()); + syn::Error::new(arg_span, "this macro requires three arguments").to_compile_error(), + ); } let rhai_module = &items[0]; let export_name = match &items[1] { @@ -122,15 +126,20 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS } else { return proc_macro::TokenStream::from( syn::Error::new(items[2].span(), "third argument must be a function name") - .to_compile_error()); + .to_compile_error(), + ); }; let gen_mod_path: syn::punctuated::Punctuated = { let mut g = rust_modpath.clone().segments; g.pop(); - let ident = syn::Ident::new(&format!("rhai_fn__{}", - rust_modpath.segments.last().unwrap().ident), - items[2].span()); - g.push_value(syn::PathSegment { ident, arguments: syn::PathArguments::None }); + let ident = syn::Ident::new( + &format!("rhai_fn__{}", rust_modpath.segments.last().unwrap().ident), + items[2].span(), + ); + g.push_value(syn::PathSegment { + ident, + arguments: syn::PathArguments::None, + }); g }; let tokens = quote! { diff --git a/codegen/src/module.rs b/codegen/src/module.rs index e22b6ec8..e2c236e4 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -17,36 +17,39 @@ impl Parse for Module { let fns: Vec<_>; let consts: Vec<_>; if let Some((_, ref content)) = mod_all.content { - fns = content.iter() - .filter_map(|item| { - match item { - syn::Item::Fn(f) => { - if let syn::Visibility::Public(_) = f.vis { - Some(f) - } else { - None - } - }, - _ => None, + fns = content + .iter() + .filter_map(|item| match item { + syn::Item::Fn(f) => { + if let syn::Visibility::Public(_) = f.vis { + Some(f) + } else { + None + } } + _ => None, }) .try_fold(Vec::new(), |mut vec, itemfn| { syn::parse2::(itemfn.to_token_stream()) .map(|f| vec.push(f)) .map(|_| vec) })?; - consts = content.iter() - .filter_map(|item| { - match item { - syn::Item::Const(syn::ItemConst {vis, ref expr, ident, ..}) => { - if let syn::Visibility::Public(_) = vis { - Some((ident.to_string(), expr.as_ref().clone())) - } else { - None - } - }, - _ => None, + consts = content + .iter() + .filter_map(|item| match item { + syn::Item::Const(syn::ItemConst { + vis, + ref expr, + ident, + .. + }) => { + if let syn::Visibility::Public(_) = vis { + Some((ident.to_string(), expr.as_ref().clone())) + } else { + None + } } + _ => None, }) .collect(); } else { @@ -87,7 +90,6 @@ mod module_tests { #[test] fn empty_module() { - let input_tokens: TokenStream = quote! { pub mod empty { } }; @@ -99,7 +101,6 @@ mod module_tests { #[test] fn one_factory_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { pub fn get_mystic_number() -> INT { @@ -113,13 +114,14 @@ mod module_tests { assert_eq!(item_mod.fns.len(), 1); assert_eq!(item_mod.fns[0].name().to_string(), "get_mystic_number"); assert_eq!(item_mod.fns[0].arg_count(), 0); - assert_eq!(item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap()); + assert_eq!( + item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); } #[test] fn one_single_arg_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { pub fn add_one_to(x: INT) -> INT { @@ -133,15 +135,18 @@ mod module_tests { assert_eq!(item_mod.fns.len(), 1); assert_eq!(item_mod.fns[0].name().to_string(), "add_one_to"); assert_eq!(item_mod.fns[0].arg_count(), 1); - assert_eq!(item_mod.fns[0].arg_list().next().unwrap(), - &syn::parse2::(quote! { x: INT }).unwrap()); - assert_eq!(item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap()); + assert_eq!( + item_mod.fns[0].arg_list().next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); } #[test] fn one_double_arg_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { pub fn add_together(x: INT, y: INT) -> INT { @@ -156,18 +161,23 @@ mod module_tests { assert_eq!(item_mod.fns.len(), 1); assert_eq!(item_mod.fns[0].name().to_string(), "add_together"); assert_eq!(item_mod.fns[0].arg_count(), 2); - assert_eq!(args.next().unwrap(), - &syn::parse2::(quote! { x: INT }).unwrap()); - assert_eq!(args.next().unwrap(), - &syn::parse2::(quote! { y: INT }).unwrap()); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { y: INT }).unwrap() + ); assert!(args.next().is_none()); - assert_eq!(item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap()); + assert_eq!( + item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); } #[test] fn one_constant_module() { - let input_tokens: TokenStream = quote! { pub mod one_constant { pub const MYSTIC_NUMBER: INT = 42; @@ -178,12 +188,14 @@ mod module_tests { assert!(item_mod.fns.is_empty()); assert_eq!(item_mod.consts.len(), 1); assert_eq!(&item_mod.consts[0].0, "MYSTIC_NUMBER"); - assert_eq!(item_mod.consts[0].1, syn::parse2::(quote! { 42 }).unwrap()); + assert_eq!( + item_mod.consts[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); } #[test] fn one_private_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { fn get_mystic_number() -> INT { @@ -199,7 +211,6 @@ mod module_tests { #[test] fn one_private_constant_module() { - let input_tokens: TokenStream = quote! { pub mod one_constant { const MYSTIC_NUMBER: INT = 42; @@ -224,7 +235,9 @@ mod generate_tests { let expected = expected.to_string(); if &actual != &expected { let mut counter = 0; - let iter = actual.chars().zip(expected.chars()) + let iter = actual + .chars() + .zip(expected.chars()) .inspect(|_| counter += 1) .skip_while(|(a, e)| *a == *e); let (actual_diff, expected_diff) = { @@ -265,7 +278,6 @@ mod generate_tests { #[test] fn one_factory_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { pub fn get_mystic_number() -> INT { @@ -320,7 +332,6 @@ mod generate_tests { #[test] fn one_single_arg_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { pub fn add_one_to(x: INT) -> INT { @@ -376,7 +387,6 @@ mod generate_tests { #[test] fn one_double_arg_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { pub fn add_together(x: INT, y: INT) -> INT { @@ -435,7 +445,6 @@ mod generate_tests { #[test] fn one_constant_module() { - let input_tokens: TokenStream = quote! { pub mod one_constant { pub const MYSTIC_NUMBER: INT = 42; @@ -462,7 +471,6 @@ mod generate_tests { #[test] fn one_constant_module_imports_preserved() { - let input_tokens: TokenStream = quote! { pub mod one_constant { pub use rhai::INT; @@ -491,7 +499,6 @@ mod generate_tests { #[test] fn one_private_fn_module() { - let input_tokens: TokenStream = quote! { pub mod one_fn { fn get_mystic_number() -> INT { @@ -521,7 +528,6 @@ mod generate_tests { #[test] fn one_private_constant_module() { - let input_tokens: TokenStream = quote! { pub mod one_constant { const MYSTIC_NUMBER: INT = 42; @@ -547,7 +553,6 @@ mod generate_tests { #[test] fn one_str_arg_fn_module() { - let input_tokens: TokenStream = quote! { pub mod str_fn { pub fn print_out_to(x: &str) { @@ -604,7 +609,6 @@ mod generate_tests { #[test] fn one_mut_ref_fn_module() { - let input_tokens: TokenStream = quote! { pub mod ref_fn { pub fn increment(x: &mut FLOAT) { diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index ba04b50c..f817e213 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -6,7 +6,7 @@ pub(crate) type ExportedConst = (String, syn::Expr); pub(crate) fn generate_body( fns: &Vec, - consts: &Vec + consts: &Vec, ) -> proc_macro2::TokenStream { let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); @@ -14,51 +14,67 @@ pub(crate) fn generate_body( for (const_name, const_expr) in consts { let const_literal = syn::LitStr::new(&const_name, proc_macro2::Span::call_site()); - set_const_stmts.push(syn::parse2::(quote! { - m.set_var(#const_literal, #const_expr); - }).unwrap()); + set_const_stmts.push( + syn::parse2::(quote! { + m.set_var(#const_literal, #const_expr); + }) + .unwrap(), + ); } // NB: these are token streams, because reparsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { - let fn_token_name = syn::Ident::new(&format!("{}__Token", function.name().to_string()), - function.name().span()); - let fn_literal = syn::LitStr::new(&function.name().to_string(), - proc_macro2::Span::call_site()); - let fn_input_types: Vec = function.arg_list() + let fn_token_name = syn::Ident::new( + &format!("{}__Token", function.name().to_string()), + function.name().span(), + ); + let fn_literal = + syn::LitStr::new(&function.name().to_string(), proc_macro2::Span::call_site()); + let fn_input_types: Vec = function + .arg_list() .map(|fnarg| match fnarg { syn::FnArg::Receiver(_) => panic!("internal error: receiver fn outside impl!?"), syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { let arg_type = match ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { mutability: None, - ref elem, .. }) => { - match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => - syn::parse2::(quote! { - rhai::ImmutableString }).unwrap(), - _ => panic!("internal error: non-string shared reference!?"), + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => { + syn::parse2::(quote! { + rhai::ImmutableString }) + .unwrap() } + _ => panic!("internal error: non-string shared reference!?"), }, - &syn::Type::Reference(syn::TypeReference { mutability: Some(_), - ref elem, .. }) => { - match elem.as_ref() { - &syn::Type::Path(ref p) => syn::parse2::(quote! { - #p }).unwrap(), - _ => panic!("internal error: non-string shared reference!?"), - } + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) => syn::parse2::(quote! { + #p }) + .unwrap(), + _ => panic!("internal error: non-string shared reference!?"), }, t => t.clone(), }; syn::parse2::(quote! { - core::any::TypeId::of::<#arg_type>()}).unwrap() - }, - }).collect(); + core::any::TypeId::of::<#arg_type>()}) + .unwrap() + } + }) + .collect(); - set_fn_stmts.push(syn::parse2::(quote! { - m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], - rhai::plugin::CallableFunction::from_plugin(#fn_token_name())); - }).unwrap()); + set_fn_stmts.push( + syn::parse2::(quote! { + m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], + rhai::plugin::CallableFunction::from_plugin(#fn_token_name())); + }) + .unwrap(), + ); gen_fn_tokens.push(quote! { #[allow(non_camel_case_types)] @@ -79,7 +95,8 @@ pub(crate) fn generate_body( m } } - }).unwrap(); + }) + .unwrap(); let (_, generate_call_content) = generate_fncall.content.take().unwrap(); diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index a0ebb6c0..bc779b74 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -1,36 +1,44 @@ -use rhai::{EvalAltResult, FLOAT, INT, Module, RegisterFn}; -use rhai::plugin::*; use rhai::module_resolvers::*; +use rhai::plugin::*; +use rhai::{EvalAltResult, Module, RegisterFn, FLOAT, INT}; pub mod raw_fn { - use rhai::FLOAT; use rhai::export_fn; + use rhai::FLOAT; #[export_fn] pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { - ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt() } } #[test] fn raw_fn_test() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!(m, "euclidean_distance".to_string(), raw_fn::distance_function); + rhai::register_exported_fn!( + m, + "euclidean_distance".to_string(), + raw_fn::distance_function + ); let mut r = StaticModuleResolver::new(); r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Math::Advanced" as math; - let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 41.0); + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + )?, + 41.0 + ); Ok(()) } mod raw_fn_mut { - use rhai::FLOAT; use rhai::export_fn; + use rhai::FLOAT; #[export_fn] pub fn add_in_place(f1: &mut FLOAT, f2: FLOAT) { @@ -41,18 +49,22 @@ mod raw_fn_mut { #[test] fn raw_fn_mut_test() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::register_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); let mut r = StaticModuleResolver::new(); r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Math::Advanced" as math; + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; let x = get_mystic_number(); math::add_in_place(x, 1.0); - x"#)?, 43.0); + x"# + )?, + 43.0 + ); Ok(()) } @@ -69,29 +81,33 @@ mod raw_fn_str { #[test] fn raw_fn_str_test() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::register_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); let mut r = StaticModuleResolver::new(); r.insert("Host::IO".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Host::IO" as io; + assert_eq!( + engine.eval::( + r#"import "Host::IO" as io; let x = io::write_out_str("hello world!"); - x"#)?, true); + x"# + )?, + true + ); Ok(()) } mod mut_opaque_ref { - use rhai::INT; use rhai::export_fn; + use rhai::INT; #[derive(Clone)] pub struct StatusMessage { os_code: Option, message: String, - is_ok: bool + is_ok: bool, } #[export_fn] @@ -130,12 +146,16 @@ fn mut_opaque_ref_test() -> Result<(), Box> { r.insert("Host::Msg".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Host::Msg" as msg; + assert_eq!( + engine.eval::( + r#"import "Host::Msg" as msg; let message1 = msg::new_message(true, "it worked"); let ok1 = msg::write_out_message(message1); let message2 = msg::new_os_message(true, 0); let ok2 = msg::write_out_message(message2); - ok1 && ok2"#)?, true); + ok1 && ok2"# + )?, + true + ); Ok(()) } diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index ebc661a9..0794991b 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -1,12 +1,12 @@ -use rhai::{EvalAltResult, FLOAT, INT, RegisterFn}; -use rhai::plugin::*; use rhai::module_resolvers::*; +use rhai::plugin::*; +use rhai::{EvalAltResult, RegisterFn, FLOAT, INT}; pub mod empty_module { use rhai::export_module; #[export_module] - pub mod EmptyModule { } + pub mod EmptyModule {} } #[test] @@ -17,8 +17,10 @@ fn empty_module_test() -> Result<(), Box> { r.insert("Module::Empty".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Module::Empty" as m; 42"#)?, 42); + assert_eq!( + engine.eval::(r#"import "Module::Empty" as m; 42"#)?, + 42 + ); Ok(()) } @@ -42,10 +44,14 @@ fn one_fn_module_test() -> Result<(), Box> { r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Math::Advanced" as math; + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; let m = math::get_mystic_number(); - m"#)?, 42.0); + m"# + )?, + 42.0 + ); Ok(()) } @@ -59,7 +65,7 @@ pub mod one_fn_and_const_module { pub const MYSTIC_NUMBER: FLOAT = 42.0 as FLOAT; pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { - ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt() } } } @@ -72,11 +78,15 @@ fn one_fn_and_const_module_test() -> Result<(), Box> { r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Math::Advanced" as math; + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; let m = math::MYSTIC_NUMBER; let x = math::euclidean_distance(0.0, 1.0, 0.0, m); - x"#)?, 41.0); + x"# + )?, + 41.0 + ); Ok(()) } @@ -100,27 +110,31 @@ fn raw_fn_str_module_test() -> Result<(), Box> { r.insert("Host::IO".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Host::IO" as io; + assert_eq!( + engine.eval::( + r#"import "Host::IO" as io; let x = io::write_out_str("hello world!"); - x"#)?, true); + x"# + )?, + true + ); Ok(()) } pub mod mut_opaque_ref_module { - use rhai::INT; use rhai::export_module; + use rhai::INT; #[derive(Clone)] pub struct StatusMessage { os_code: Option, message: String, - is_ok: bool + is_ok: bool, } #[export_module] pub mod host_msg { - use super::{INT, StatusMessage}; + use super::{StatusMessage, INT}; pub fn new_message(is_ok: bool, message: &str) -> StatusMessage { StatusMessage { @@ -153,13 +167,17 @@ fn mut_opaque_ref_test() -> Result<(), Box> { r.insert("Host::Msg".to_string(), m); engine.set_module_resolver(Some(r)); - assert_eq!(engine.eval::( - r#"import "Host::Msg" as msg; + assert_eq!( + engine.eval::( + r#"import "Host::Msg" as msg; let success = "it worked"; let message1 = msg::new_message(true, success); let ok1 = msg::write_out_message(message1); let message2 = msg::new_os_message(true, 0); let ok2 = msg::write_out_message(message2); - ok1 && ok2"#)?, true); + ok1 && ok2"# + )?, + true + ); Ok(()) } diff --git a/codegen/ui_tests/first_shared_ref.rs b/codegen/ui_tests/first_shared_ref.rs index c8d6c9f4..d8c88abf 100644 --- a/codegen/ui_tests/first_shared_ref.rs +++ b/codegen/ui_tests/first_shared_ref.rs @@ -13,7 +13,12 @@ pub fn test_fn(input: &NonClonable) -> bool { } fn main() { - let n = NonClonable { a: 0.0, b: 10, c: 'a', d: true }; + let n = NonClonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; if test_fn(n) { println!("yes"); } else { diff --git a/codegen/ui_tests/non_clonable.rs b/codegen/ui_tests/non_clonable.rs index c5c6d5a2..8283d3f8 100644 --- a/codegen/ui_tests/non_clonable.rs +++ b/codegen/ui_tests/non_clonable.rs @@ -13,7 +13,12 @@ pub fn test_fn(input: NonClonable) -> bool { } fn main() { - let n = NonClonable { a: 0.0, b: 10, c: 'a', d: true }; + let n = NonClonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; if test_fn(n) { println!("yes"); } else { diff --git a/codegen/ui_tests/non_clonable_second.rs b/codegen/ui_tests/non_clonable_second.rs index d31e3436..dd2642ba 100644 --- a/codegen/ui_tests/non_clonable_second.rs +++ b/codegen/ui_tests/non_clonable_second.rs @@ -13,7 +13,12 @@ pub fn test_fn(a: u32, b: NonClonable) -> bool { } fn main() { - let n = NonClonable { a: 0.0, b: 10, c: 'a', d: true }; + let n = NonClonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; if test_fn(10, n) { println!("yes"); } else { diff --git a/codegen/ui_tests/return_mut_ref.rs b/codegen/ui_tests/return_mut_ref.rs index 5568beee..aeeadbe5 100644 --- a/codegen/ui_tests/return_mut_ref.rs +++ b/codegen/ui_tests/return_mut_ref.rs @@ -14,7 +14,12 @@ pub fn test_fn(input: &mut Clonable) -> &mut bool { } fn main() { - let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; if test_fn(n) { println!("yes"); } else { diff --git a/codegen/ui_tests/return_pointer.rs b/codegen/ui_tests/return_pointer.rs index fa20d73c..4a7f8182 100644 --- a/codegen/ui_tests/return_pointer.rs +++ b/codegen/ui_tests/return_pointer.rs @@ -14,6 +14,14 @@ pub fn test_fn(input: Clonable) -> *const str { } fn main() { - let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; - println!("{}", unsafe { let ptr = test_fn(n); *ptr }); + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + println!("{}", unsafe { + let ptr = test_fn(n); + *ptr + }); } diff --git a/codegen/ui_tests/return_shared_ref.rs b/codegen/ui_tests/return_shared_ref.rs index 384c3882..2091eada 100644 --- a/codegen/ui_tests/return_shared_ref.rs +++ b/codegen/ui_tests/return_shared_ref.rs @@ -9,11 +9,16 @@ struct Clonable { } #[export_fn] -pub fn test_fn(input: Clonable) -> & 'static str { +pub fn test_fn(input: Clonable) -> &'static str { "yes" } fn main() { - let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; println!("{}", test_fn(n)); } diff --git a/codegen/ui_tests/second_shared_ref.rs b/codegen/ui_tests/second_shared_ref.rs index cf09608d..06058814 100644 --- a/codegen/ui_tests/second_shared_ref.rs +++ b/codegen/ui_tests/second_shared_ref.rs @@ -14,7 +14,12 @@ pub fn test_fn(input: Clonable, factor: &bool) -> bool { } fn main() { - let n = Clonable { a: 0.0, b: 10, c: 'a', d: true }; + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; if test_fn(n, &true) { println!("yes"); } else { From 675c4eb606017fdc1ebb789b9db99083d55ea2ed Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 15:39:16 +0800 Subject: [PATCH 009/103] Add plugins test. --- tests/plugins.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/plugins.rs diff --git a/tests/plugins.rs b/tests/plugins.rs new file mode 100644 index 00000000..b6a36cf3 --- /dev/null +++ b/tests/plugins.rs @@ -0,0 +1,24 @@ +use rhai::{export_module, exported_module}; +use rhai::{Engine, EvalAltResult, INT}; + +#[export_module] +pub mod array_package { + use rhai::{Array, INT}; + + pub fn len(array: &mut Array, mul: INT) -> INT { + (array.len() as INT) * mul + } +} + +#[test] +fn test_plugins() -> Result<(), Box> { + let mut engine = Engine::new(); + + let m = exported_module!(array_package); + + engine.load_package(m.into()); + + assert_eq!(engine.eval::("let a = [1, 2, 3]; a.len(2)")?, 6); + + Ok(()) +} From 5eed5fe6a3d9997f92ce097740fbf0ffa9c8cb1a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 2 Aug 2020 18:53:25 +0800 Subject: [PATCH 010/103] Add plugins test. --- src/fn_call.rs | 6 +++++- src/plugin.rs | 20 +++++++------------- src/settings.rs | 4 ++-- tests/closures.rs | 2 +- tests/plugins.rs | 26 ++++++++++++++++++++------ 5 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index ce78d7f6..f4c9cb83 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -204,7 +204,11 @@ impl Engine { } // Run external function - let result = func.get_native_fn()(self, lib, args)?; + let result = if func.is_plugin_fn() { + func.get_plugin_fn().call(args, Position::none())? + } else { + func.get_native_fn()(self, lib, args)? + }; // Restore the original reference restore_first_arg(old_this_ptr, args); diff --git a/src/plugin.rs b/src/plugin.rs index 01a652e3..0cff4886 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,18 +2,11 @@ use crate::stdlib::{any::TypeId, boxed::Box}; -pub use crate::any::{Dynamic, Variant}; -pub use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn}; -pub use crate::parser::{ - FnAccess, - FnAccess::{Private, Public}, - AST, -}; -pub use crate::result::EvalAltResult; -pub use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -pub use crate::token::{Position, Token}; -pub use crate::utils::StaticVec; -pub use crate::Engine; +use crate::any::Dynamic; +use crate::engine::Engine; +pub use crate::fn_native::CallableFunction; +use crate::result::EvalAltResult; +use crate::token::Position; #[cfg(features = "sync")] /// Represents an externally-written plugin for the Rhai interpreter. @@ -38,7 +31,8 @@ pub trait PluginFunction { fn is_method_call(&self) -> bool; fn is_varadic(&self) -> bool; - fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result>; + fn call(&self, args: &mut [&mut Dynamic], pos: Position) + -> Result>; fn clone_boxed(&self) -> Box; diff --git a/src/settings.rs b/src/settings.rs index d638499c..d4949a41 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,9 +18,9 @@ impl Engine { /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. - pub fn load_package(&mut self, package: PackageLibrary) -> &mut Self { + pub fn load_package(&mut self, package: impl Into) -> &mut Self { // Push the package to the top - packages are searched in reverse order - self.packages.push(package); + self.packages.push(package.into()); self } diff --git a/tests/closures.rs b/tests/closures.rs index 3aec4865..27ecc5e6 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -16,7 +16,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { ); let mut engine = Engine::new(); - engine.load_package(module.into()); + engine.load_package(module); #[cfg(not(feature = "no_object"))] assert_eq!( diff --git a/tests/plugins.rs b/tests/plugins.rs index b6a36cf3..95edbf47 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -1,8 +1,12 @@ -use rhai::{export_module, exported_module}; +use rhai::{ + export_fn, export_module, exported_module, + plugin::{CallableFunction, PluginFunction}, + register_exported_fn, +}; use rhai::{Engine, EvalAltResult, INT}; #[export_module] -pub mod array_package { +mod special_array_package { use rhai::{Array, INT}; pub fn len(array: &mut Array, mul: INT) -> INT { @@ -10,15 +14,25 @@ pub mod array_package { } } +#[export_fn] +fn make_greeting(n: INT) -> String { + format!("{} {}", n, if n > 1 { "kitties" } else { "kitty" }).into() +} + #[test] -fn test_plugins() -> Result<(), Box> { +fn test_plugins_package() -> Result<(), Box> { let mut engine = Engine::new(); - let m = exported_module!(array_package); + let mut m = exported_module!(special_array_package); + register_exported_fn!(m, "greet", make_greeting); - engine.load_package(m.into()); + engine.load_package(m); - assert_eq!(engine.eval::("let a = [1, 2, 3]; a.len(2)")?, 6); + assert_eq!(engine.eval::("let a = [1, 2, 3]; len(a, 2)")?, 6); + assert_eq!( + engine.eval::("let a = [1, 2, 3]; greet(len(a, 2))")?, + "6 kitties" + ); Ok(()) } From 31361888013086b5aeb7fb814c1b21839c7e29e6 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 2 Aug 2020 19:27:19 -0500 Subject: [PATCH 011/103] Improve proc macro hygiene --- codegen/src/function.rs | 176 ++++++++++++++-------- codegen/src/lib.rs | 6 +- codegen/src/module.rs | 148 ++++++++++-------- codegen/src/rhai_module.rs | 10 +- codegen/tests/test_functions.rs | 11 +- codegen/tests/test_modules.rs | 13 +- codegen/ui_tests/first_shared_ref.rs | 2 +- codegen/ui_tests/first_shared_ref.stderr | 4 +- codegen/ui_tests/non_clonable.rs | 2 +- codegen/ui_tests/non_clonable_second.rs | 2 +- codegen/ui_tests/return_mut_ref.rs | 2 +- codegen/ui_tests/return_mut_ref.stderr | 4 +- codegen/ui_tests/return_pointer.rs | 2 +- codegen/ui_tests/return_pointer.stderr | 6 +- codegen/ui_tests/return_shared_ref.rs | 2 +- codegen/ui_tests/return_shared_ref.stderr | 8 +- codegen/ui_tests/second_shared_ref.rs | 2 +- codegen/ui_tests/second_shared_ref.stderr | 4 +- src/plugin.rs | 17 ++- 19 files changed, 258 insertions(+), 163 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index eab15386..dfdce6c5 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -154,12 +154,40 @@ impl ExportedFn { self.name().span(), ); let impl_block = self.generate_impl("Token"); + let callable_block = self.generate_callable("Token"); + let input_types_block = self.generate_input_types("Token"); quote! { #[allow(unused)] pub mod #name { use super::*; - pub struct Token(); + struct Token(); #impl_block + #callable_block + #input_types_block + } + } + } + + pub fn generate_callable(&self, on_type_name: &str) -> proc_macro2::TokenStream { + let token_name: syn::Ident = syn::Ident::new(on_type_name, self.name().span()); + let callable_fn_name: syn::Ident = syn::Ident::new( + format!("{}__callable", on_type_name).as_str(), + self.name().span()); + quote! { + pub fn #callable_fn_name() -> CallableFunction { + CallableFunction::from_plugin(#token_name()) + } + } + } + + pub fn generate_input_types(&self, on_type_name: &str) -> proc_macro2::TokenStream { + let token_name: syn::Ident = syn::Ident::new(on_type_name, self.name().span()); + let input_types_fn_name: syn::Ident = syn::Ident::new( + format!("{}__input_types", on_type_name).as_str(), + self.name().span()); + quote! { + pub fn #input_types_fn_name() -> Box<[std::any::TypeId]> { + #token_name().input_types() } } } @@ -232,7 +260,7 @@ impl ExportedFn { is_str_ref = true; quote_spanned!(arg_type.span()=> args[#i] - .downcast_clone::() + .downcast_clone::() .unwrap()) } _ => panic!("internal error: why wasn't this found earlier!?"), @@ -260,7 +288,7 @@ impl ExportedFn { } else { input_type_exprs.push( syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::() + arg_type.span()=> std::any::TypeId::of::() )) .unwrap(), ); @@ -285,22 +313,22 @@ impl ExportedFn { let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site()); quote! { - impl rhai::plugin::PluginFunction for #type_name { + impl PluginFunction for #type_name { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != #arg_count { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), #arg_count), rhai::Position::none()))); + args.len(), #arg_count), Position::none()))); } #(#unpack_stmts)* - Ok(rhai::Dynamic::from(#name(#(#unpack_exprs),*))) + Ok(Dynamic::from(#name(#(#unpack_exprs),*))) } fn is_method_call(&self) -> bool { #is_method_call } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(#type_name()) } + fn clone_boxed(&self) -> Box { Box::new(#type_name()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![#(#input_type_exprs),*].into_boxed_slice() } @@ -577,26 +605,32 @@ mod generate_tests { #[allow(unused)] pub mod rhai_fn__do_nothing { use super::*; - pub struct Token(); - impl rhai::plugin::PluginFunction for Token { + struct Token(); + impl PluginFunction for Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 0usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 0usize), rhai::Position::none()))); + args.len(), 0usize), Position::none()))); } - Ok(rhai::Dynamic::from(do_nothing())) + Ok(Dynamic::from(do_nothing())) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn clone_boxed(&self) -> Box { Box::new(Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![].into_boxed_slice() } } + pub fn Token__callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn Token__input_types() -> Box<[std::any::TypeId]> { + Token().input_types() + } } }; @@ -614,27 +648,33 @@ mod generate_tests { #[allow(unused)] pub mod rhai_fn__do_something { use super::*; - pub struct Token(); - impl rhai::plugin::PluginFunction for Token { + struct Token(); + impl PluginFunction for Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 1usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 1usize), rhai::Position::none()))); + args.len(), 1usize), Position::none()))); } let arg0 = args[0usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(do_something(arg0))) + Ok(Dynamic::from(do_something(arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn clone_boxed(&self) -> Box { Box::new(Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } } + pub fn Token__callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn Token__input_types() -> Box<[std::any::TypeId]> { + Token().input_types() + } } }; @@ -649,22 +689,22 @@ mod generate_tests { }; let expected_tokens = quote! { - impl rhai::plugin::PluginFunction for MyType { + impl PluginFunction for MyType { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 1usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 1usize), rhai::Position::none()))); + args.len(), 1usize), Position::none()))); } let arg0 = args[0usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(do_something(arg0))) + Ok(Dynamic::from(do_something(arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(MyType()) } + fn clone_boxed(&self) -> Box { Box::new(MyType()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } @@ -685,29 +725,35 @@ mod generate_tests { #[allow(unused)] pub mod rhai_fn__add_together { use super::*; - pub struct Token(); - impl rhai::plugin::PluginFunction for Token { + struct Token(); + impl PluginFunction for Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 2usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 2usize), rhai::Position::none()))); + args.len(), 2usize), Position::none()))); } let arg0 = args[0usize].downcast_clone::().unwrap(); let arg1 = args[1usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(add_together(arg0, arg1))) + Ok(Dynamic::from(add_together(arg0, arg1))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn clone_boxed(&self) -> Box { Box::new(Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::(), std::any::TypeId::of::()].into_boxed_slice() } } + pub fn Token__callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn Token__input_types() -> Box<[std::any::TypeId]> { + Token().input_types() + } } }; @@ -725,29 +771,35 @@ mod generate_tests { #[allow(unused)] pub mod rhai_fn__increment { use super::*; - pub struct Token(); - impl rhai::plugin::PluginFunction for Token { + struct Token(); + impl PluginFunction for Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 2usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 2usize), rhai::Position::none()))); + args.len(), 2usize), Position::none()))); } let arg1 = args[1usize].downcast_clone::().unwrap(); let arg0: &mut _ = args[0usize].downcast_mut::().unwrap(); - Ok(rhai::Dynamic::from(increment(arg0, arg1))) + Ok(Dynamic::from(increment(arg0, arg1))) } fn is_method_call(&self) -> bool { true } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn clone_boxed(&self) -> Box { Box::new(Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::(), std::any::TypeId::of::()].into_boxed_slice() } } + pub fn Token__callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn Token__input_types() -> Box<[std::any::TypeId]> { + Token().input_types() + } } }; @@ -766,27 +818,33 @@ mod generate_tests { #[allow(unused)] pub mod rhai_fn__special_print { use super::*; - pub struct Token(); - impl rhai::plugin::PluginFunction for Token { + struct Token(); + impl PluginFunction for Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 1usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 1usize), rhai::Position::none()))); + args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(special_print(&arg0))) + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(Dynamic::from(special_print(&arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn clone_boxed(&self) -> Box { Box::new(Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + vec![std::any::TypeId::of::()].into_boxed_slice() } } + pub fn Token__callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn Token__input_types() -> Box<[std::any::TypeId]> { + Token().input_types() + } } }; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index fb214c5d..925c0501 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -4,7 +4,7 @@ //! # Exporting a Macro to Rhai //! //! ``` -//! use rhai::{EvalAltResult, FLOAT, RegisterFn}; +//! use rhai::{EvalAltResult, FLOAT}; //! use rhai::plugin::*; //! use rhai::module_resolvers::*; //! @@ -144,8 +144,8 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS }; let tokens = quote! { #rhai_module.set_fn(#export_name, rhai::FnAccess::Public, - #gen_mod_path::Token().input_types().as_ref(), - CallableFunction::from_plugin(#gen_mod_path::Token())); + #gen_mod_path::Token__input_types().as_ref(), + #gen_mod_path::Token__callable()); }; proc_macro::TokenStream::from(tokens) diff --git a/codegen/src/module.rs b/codegen/src/module.rs index e2c236e4..8e00a7a4 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -263,7 +263,7 @@ mod generate_tests { let expected_tokens = quote! { pub mod empty { #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); @@ -292,37 +292,43 @@ mod generate_tests { 42 } #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); m.set_fn("get_mystic_number", FnAccess::Public, &[], - rhai::plugin::CallableFunction::from_plugin(get_mystic_number__Token())); + CallableFunction::from_plugin(get_mystic_number__Token())); m } #[allow(non_camel_case_types)] - pub struct get_mystic_number__Token(); - impl rhai::plugin::PluginFunction for get_mystic_number__Token { + struct get_mystic_number__Token(); + impl PluginFunction for get_mystic_number__Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 0usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 0usize), rhai::Position::none()))); + args.len(), 0usize), Position::none()))); } - Ok(rhai::Dynamic::from(get_mystic_number())) + Ok(Dynamic::from(get_mystic_number())) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(get_mystic_number__Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![].into_boxed_slice() } } + pub fn get_mystic_number__Token__callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number__Token()) + } + pub fn get_mystic_number__Token__input_types() -> Box<[std::any::TypeId]> { + get_mystic_number__Token().input_types() + } } }; @@ -346,38 +352,44 @@ mod generate_tests { x + 1 } #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], - rhai::plugin::CallableFunction::from_plugin(add_one_to__Token())); + CallableFunction::from_plugin(add_one_to__Token())); m } #[allow(non_camel_case_types)] - pub struct add_one_to__Token(); - impl rhai::plugin::PluginFunction for add_one_to__Token { + struct add_one_to__Token(); + impl PluginFunction for add_one_to__Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 1usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 1usize), rhai::Position::none()))); + args.len(), 1usize), Position::none()))); } let arg0 = args[0usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(add_one_to(arg0))) + Ok(Dynamic::from(add_one_to(arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(add_one_to__Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } } + pub fn add_one_to__Token__callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to__Token()) + } + pub fn add_one_to__Token__input_types() -> Box<[std::any::TypeId]> { + add_one_to__Token().input_types() + } } }; @@ -401,34 +413,34 @@ mod generate_tests { x + y } #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], - rhai::plugin::CallableFunction::from_plugin(add_together__Token())); + CallableFunction::from_plugin(add_together__Token())); m } #[allow(non_camel_case_types)] - pub struct add_together__Token(); - impl rhai::plugin::PluginFunction for add_together__Token { + struct add_together__Token(); + impl PluginFunction for add_together__Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 2usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 2usize), rhai::Position::none()))); + args.len(), 2usize), Position::none()))); } let arg0 = args[0usize].downcast_clone::().unwrap(); let arg1 = args[1usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(add_together(arg0, arg1))) + Ok(Dynamic::from(add_together(arg0, arg1))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(add_together__Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { @@ -436,6 +448,12 @@ mod generate_tests { std::any::TypeId::of::()].into_boxed_slice() } } + pub fn add_together__Token__callable() -> CallableFunction { + CallableFunction::from_plugin(add_together__Token()) + } + pub fn add_together__Token__input_types() -> Box<[std::any::TypeId]> { + add_together__Token().input_types() + } } }; @@ -455,7 +473,7 @@ mod generate_tests { pub mod one_constant { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); @@ -483,7 +501,7 @@ mod generate_tests { pub use rhai::INT; pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); @@ -513,7 +531,7 @@ mod generate_tests { 42 } #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); @@ -538,7 +556,7 @@ mod generate_tests { pub mod one_constant { const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); @@ -567,39 +585,45 @@ mod generate_tests { x + 1 } #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); m.set_fn("print_out_to", FnAccess::Public, - &[core::any::TypeId::of::()], - rhai::plugin::CallableFunction::from_plugin(print_out_to__Token())); + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(print_out_to__Token())); m } #[allow(non_camel_case_types)] - pub struct print_out_to__Token(); - impl rhai::plugin::PluginFunction for print_out_to__Token { + struct print_out_to__Token(); + impl PluginFunction for print_out_to__Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 1usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 1usize), rhai::Position::none()))); + args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); - Ok(rhai::Dynamic::from(print_out_to(&arg0))) + let arg0 = args[0usize].downcast_clone::().unwrap(); + Ok(Dynamic::from(print_out_to(&arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(print_out_to__Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + vec![std::any::TypeId::of::()].into_boxed_slice() } } + pub fn print_out_to__Token__callable() -> CallableFunction { + CallableFunction::from_plugin(print_out_to__Token()) + } + pub fn print_out_to__Token__input_types() -> Box<[std::any::TypeId]> { + print_out_to__Token().input_types() + } } }; @@ -623,40 +647,46 @@ mod generate_tests { *x += 1.0 as FLOAT; } #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); m.set_fn("increment", FnAccess::Public, &[core::any::TypeId::of::()], - rhai::plugin::CallableFunction::from_plugin(increment__Token())); + CallableFunction::from_plugin(increment__Token())); m } #[allow(non_camel_case_types)] - pub struct increment__Token(); - impl rhai::plugin::PluginFunction for increment__Token { + struct increment__Token(); + impl PluginFunction for increment__Token { fn call(&self, - args: &mut [&mut rhai::Dynamic], pos: rhai::Position - ) -> Result> { + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { if args.len() != 1usize { - return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( + return Err(Box::new(EvalAltResult::ErrorRuntime( format!("wrong arg count: {} != {}", - args.len(), 1usize), rhai::Position::none()))); + args.len(), 1usize), Position::none()))); } let arg0: &mut _ = args[0usize].downcast_mut::().unwrap(); - Ok(rhai::Dynamic::from(increment(arg0))) + Ok(Dynamic::from(increment(arg0))) } fn is_method_call(&self) -> bool { true } fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(increment__Token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } } - } + pub fn increment__Token__callable() -> CallableFunction { + CallableFunction::from_plugin(increment__Token()) + } + pub fn increment__Token__input_types() -> Box<[std::any::TypeId]> { + increment__Token().input_types() + } + } }; let item_mod = syn::parse2::(input_tokens).unwrap(); diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index f817e213..e9ad66d5 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -44,7 +44,7 @@ pub(crate) fn generate_body( }) => match elem.as_ref() { &syn::Type::Path(ref p) if p.path == str_type_path => { syn::parse2::(quote! { - rhai::ImmutableString }) + ImmutableString }) .unwrap() } _ => panic!("internal error: non-string shared reference!?"), @@ -71,22 +71,24 @@ pub(crate) fn generate_body( set_fn_stmts.push( syn::parse2::(quote! { m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], - rhai::plugin::CallableFunction::from_plugin(#fn_token_name())); + CallableFunction::from_plugin(#fn_token_name())); }) .unwrap(), ); gen_fn_tokens.push(quote! { #[allow(non_camel_case_types)] - pub struct #fn_token_name(); + struct #fn_token_name(); }); gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string())); + gen_fn_tokens.push(function.generate_callable(&fn_token_name.to_string())); + gen_fn_tokens.push(function.generate_input_types(&fn_token_name.to_string())); } let mut generate_fncall = syn::parse2::(quote! { pub mod generate_info { #[allow(unused_imports)] - use rhai::{Module, FnAccess}; + use super::*; #[allow(unused_mut)] pub fn rhai_module__generate() -> Module { let mut m = Module::new(); diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index bc779b74..ee88be4a 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -1,9 +1,8 @@ use rhai::module_resolvers::*; -use rhai::plugin::*; -use rhai::{EvalAltResult, Module, RegisterFn, FLOAT, INT}; +use rhai::{Engine, EvalAltResult, Module, RegisterFn, FLOAT, INT}; pub mod raw_fn { - use rhai::export_fn; + use rhai::plugin::*; use rhai::FLOAT; #[export_fn] @@ -37,7 +36,7 @@ fn raw_fn_test() -> Result<(), Box> { } mod raw_fn_mut { - use rhai::export_fn; + use rhai::plugin::*; use rhai::FLOAT; #[export_fn] @@ -69,7 +68,7 @@ fn raw_fn_mut_test() -> Result<(), Box> { } mod raw_fn_str { - use rhai::export_fn; + use rhai::plugin::*; #[export_fn] pub fn write_out_str(message: &str) -> bool { @@ -100,7 +99,7 @@ fn raw_fn_str_test() -> Result<(), Box> { } mod mut_opaque_ref { - use rhai::export_fn; + use rhai::plugin::*; use rhai::INT; #[derive(Clone)] diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index 0794991b..56ffb4a5 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -1,9 +1,8 @@ use rhai::module_resolvers::*; -use rhai::plugin::*; -use rhai::{EvalAltResult, RegisterFn, FLOAT, INT}; +use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT, INT}; pub mod empty_module { - use rhai::export_module; + use rhai::plugin::*; #[export_module] pub mod EmptyModule {} @@ -25,7 +24,7 @@ fn empty_module_test() -> Result<(), Box> { } pub mod one_fn_module { - use rhai::export_module; + use rhai::plugin::*; #[export_module] pub mod advanced_math { @@ -56,7 +55,7 @@ fn one_fn_module_test() -> Result<(), Box> { } pub mod one_fn_and_const_module { - use rhai::export_module; + use rhai::plugin::*; #[export_module] pub mod advanced_math { @@ -91,7 +90,7 @@ fn one_fn_and_const_module_test() -> Result<(), Box> { } pub mod raw_fn_str_module { - use rhai::export_module; + use rhai::plugin::*; #[export_module] pub mod host_io { @@ -122,7 +121,7 @@ fn raw_fn_str_module_test() -> Result<(), Box> { } pub mod mut_opaque_ref_module { - use rhai::export_module; + use rhai::plugin::*; use rhai::INT; #[derive(Clone)] diff --git a/codegen/ui_tests/first_shared_ref.rs b/codegen/ui_tests/first_shared_ref.rs index d8c88abf..f26fc40f 100644 --- a/codegen/ui_tests/first_shared_ref.rs +++ b/codegen/ui_tests/first_shared_ref.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; struct NonClonable { a: f32, diff --git a/codegen/ui_tests/first_shared_ref.stderr b/codegen/ui_tests/first_shared_ref.stderr index a92ef117..41b0085e 100644 --- a/codegen/ui_tests/first_shared_ref.stderr +++ b/codegen/ui_tests/first_shared_ref.stderr @@ -5,7 +5,7 @@ error: references from Rhai in this position must be mutable | ^^^^^^^^^^^^ error[E0425]: cannot find function `test_fn` in this scope - --> $DIR/first_shared_ref.rs:17:8 + --> $DIR/first_shared_ref.rs:22:8 | -17 | if test_fn(n) { +22 | if test_fn(n) { | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/non_clonable.rs b/codegen/ui_tests/non_clonable.rs index 8283d3f8..69aa85ef 100644 --- a/codegen/ui_tests/non_clonable.rs +++ b/codegen/ui_tests/non_clonable.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; struct NonClonable { a: f32, diff --git a/codegen/ui_tests/non_clonable_second.rs b/codegen/ui_tests/non_clonable_second.rs index dd2642ba..ef33d481 100644 --- a/codegen/ui_tests/non_clonable_second.rs +++ b/codegen/ui_tests/non_clonable_second.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; struct NonClonable { a: f32, diff --git a/codegen/ui_tests/return_mut_ref.rs b/codegen/ui_tests/return_mut_ref.rs index aeeadbe5..26b56794 100644 --- a/codegen/ui_tests/return_mut_ref.rs +++ b/codegen/ui_tests/return_mut_ref.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; #[derive(Clone)] struct Clonable { diff --git a/codegen/ui_tests/return_mut_ref.stderr b/codegen/ui_tests/return_mut_ref.stderr index 04853c8b..c849a2c7 100644 --- a/codegen/ui_tests/return_mut_ref.stderr +++ b/codegen/ui_tests/return_mut_ref.stderr @@ -5,7 +5,7 @@ error: cannot return a reference to Rhai | ^^^^^^^^^^^^ error[E0425]: cannot find function `test_fn` in this scope - --> $DIR/return_mut_ref.rs:18:8 + --> $DIR/return_mut_ref.rs:23:8 | -18 | if test_fn(n) { +23 | if test_fn(n) { | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/return_pointer.rs b/codegen/ui_tests/return_pointer.rs index 4a7f8182..302799bd 100644 --- a/codegen/ui_tests/return_pointer.rs +++ b/codegen/ui_tests/return_pointer.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; #[derive(Clone)] struct Clonable { diff --git a/codegen/ui_tests/return_pointer.stderr b/codegen/ui_tests/return_pointer.stderr index bee7f1d3..1b736db6 100644 --- a/codegen/ui_tests/return_pointer.stderr +++ b/codegen/ui_tests/return_pointer.stderr @@ -5,7 +5,7 @@ error: cannot return a pointer to Rhai | ^^^^^^^^^^^^^ error[E0425]: cannot find function `test_fn` in this scope - --> $DIR/return_pointer.rs:18:39 + --> $DIR/return_pointer.rs:24:19 | -18 | println!("{}", unsafe { let ptr = test_fn(n); *ptr }); - | ^^^^^^^ not found in this scope +24 | let ptr = test_fn(n); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/return_shared_ref.rs b/codegen/ui_tests/return_shared_ref.rs index 2091eada..2fde9c25 100644 --- a/codegen/ui_tests/return_shared_ref.rs +++ b/codegen/ui_tests/return_shared_ref.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; #[derive(Clone)] struct Clonable { diff --git a/codegen/ui_tests/return_shared_ref.stderr b/codegen/ui_tests/return_shared_ref.stderr index 6be110ae..13577531 100644 --- a/codegen/ui_tests/return_shared_ref.stderr +++ b/codegen/ui_tests/return_shared_ref.stderr @@ -1,11 +1,11 @@ error: cannot return a reference to Rhai --> $DIR/return_shared_ref.rs:12:33 | -12 | pub fn test_fn(input: Clonable) -> & 'static str { - | ^^^^^^^^^^^^^^^^ +12 | pub fn test_fn(input: Clonable) -> &'static str { + | ^^^^^^^^^^^^^^^ error[E0425]: cannot find function `test_fn` in this scope - --> $DIR/return_shared_ref.rs:18:20 + --> $DIR/return_shared_ref.rs:23:20 | -18 | println!("{}", test_fn(n)); +23 | println!("{}", test_fn(n)); | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/second_shared_ref.rs b/codegen/ui_tests/second_shared_ref.rs index 06058814..4768c065 100644 --- a/codegen/ui_tests/second_shared_ref.rs +++ b/codegen/ui_tests/second_shared_ref.rs @@ -1,4 +1,4 @@ -use rhai::export_fn; +use rhai::plugin::*; #[derive(Clone)] pub struct Clonable { diff --git a/codegen/ui_tests/second_shared_ref.stderr b/codegen/ui_tests/second_shared_ref.stderr index 51908e82..91b493ed 100644 --- a/codegen/ui_tests/second_shared_ref.stderr +++ b/codegen/ui_tests/second_shared_ref.stderr @@ -5,7 +5,7 @@ error: this type in this position passes from Rhai by value | ^^^^^ error[E0425]: cannot find function `test_fn` in this scope - --> $DIR/second_shared_ref.rs:18:8 + --> $DIR/second_shared_ref.rs:23:8 | -18 | if test_fn(n, &true) { +23 | if test_fn(n, &true) { | ^^^^^^^ not found in this scope diff --git a/src/plugin.rs b/src/plugin.rs index 0cff4886..0de6e63f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,11 +2,18 @@ use crate::stdlib::{any::TypeId, boxed::Box}; -use crate::any::Dynamic; -use crate::engine::Engine; -pub use crate::fn_native::CallableFunction; -use crate::result::EvalAltResult; -use crate::token::Position; +pub use crate::{ + fn_native::CallableFunction, + Dynamic, + Engine, + EvalAltResult, + FnAccess, + ImmutableString, + Module, + Position, +}; + +pub use rhai_codegen::*; #[cfg(features = "sync")] /// Represents an externally-written plugin for the Rhai interpreter. From 7219fd6b790cf7cd8a3a0f5d43378d39afe18abf Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 2 Aug 2020 19:49:24 -0500 Subject: [PATCH 012/103] Fix tests/plugins.rs to match new hygiene --- tests/plugins.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/plugins.rs b/tests/plugins.rs index 95edbf47..adf1840e 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -1,8 +1,4 @@ -use rhai::{ - export_fn, export_module, exported_module, - plugin::{CallableFunction, PluginFunction}, - register_exported_fn, -}; +use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT}; #[export_module] From 316918c741864ffc2d2a4cf98c72631b10f741a1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 6 Aug 2020 10:17:40 +0800 Subject: [PATCH 013/103] no_closure when no_function. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 73cc5b2e..7dee574f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types no_index = [] # no arrays and indexing no_object = [] # no custom objects -no_function = [] # no script-defined functions +no_function = [ "no_closure "] # no script-defined functions (meaning no closures) no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules internals = [] # expose internal data structures From 4465a44673a6cabf53d2ac8ff497834a3150a384 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 6 Aug 2020 10:31:32 +0800 Subject: [PATCH 014/103] Fix typos. --- Cargo.toml | 2 +- src/fn_call.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9131e94d..9ed6f6c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types no_index = [] # no arrays and indexing no_object = [] # no custom objects -no_function = [ "no_closure "] # no script-defined functions (meaning no closures) +no_function = [ "no_closure" ] # no script-defined functions (meaning no closures) no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules internals = [] # expose internal data structures diff --git a/src/fn_call.rs b/src/fn_call.rs index 2c94a47e..08088443 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -228,9 +228,9 @@ impl Engine { // Run external function let result = if func.is_plugin_fn() { - func.get_plugin_fn().call(args, Position::none())? + func.get_plugin_fn().call(args, Position::none()) } else { - func.get_native_fn()(self, lib, args)? + func.get_native_fn()(self, lib, args) }; // Restore the original reference From dd6b6cd49fb25ec3510aeaf808313c7f8b704a8a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 6 Aug 2020 14:10:27 +0800 Subject: [PATCH 015/103] Fix codegen and tests to new API changes. --- codegen/src/function.rs | 46 ++++++++-------- codegen/src/lib.rs | 8 +-- codegen/src/module.rs | 102 +++++++++++++++++------------------ codegen/src/rhai_module.rs | 4 +- doc/src/rust/register-raw.md | 2 +- 5 files changed, 82 insertions(+), 80 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index dfdce6c5..5d869a2b 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -150,7 +150,7 @@ impl ExportedFn { pub fn generate(self) -> proc_macro2::TokenStream { let name: syn::Ident = syn::Ident::new( - &format!("rhai_fn__{}", self.name().to_string()), + &format!("rhai_fn_{}", self.name().to_string()), self.name().span(), ); let impl_block = self.generate_impl("Token"); @@ -171,8 +171,9 @@ impl ExportedFn { pub fn generate_callable(&self, on_type_name: &str) -> proc_macro2::TokenStream { let token_name: syn::Ident = syn::Ident::new(on_type_name, self.name().span()); let callable_fn_name: syn::Ident = syn::Ident::new( - format!("{}__callable", on_type_name).as_str(), - self.name().span()); + format!("{}_callable", on_type_name.to_lowercase()).as_str(), + self.name().span(), + ); quote! { pub fn #callable_fn_name() -> CallableFunction { CallableFunction::from_plugin(#token_name()) @@ -183,8 +184,9 @@ impl ExportedFn { pub fn generate_input_types(&self, on_type_name: &str) -> proc_macro2::TokenStream { let token_name: syn::Ident = syn::Ident::new(on_type_name, self.name().span()); let input_types_fn_name: syn::Ident = syn::Ident::new( - format!("{}__input_types", on_type_name).as_str(), - self.name().span()); + format!("{}_input_types", on_type_name.to_lowercase()).as_str(), + self.name().span(), + ); quote! { pub fn #input_types_fn_name() -> Box<[std::any::TypeId]> { #token_name().input_types() @@ -218,7 +220,7 @@ impl ExportedFn { } }; let downcast_span = quote_spanned!( - arg_type.span()=> args[0usize].downcast_mut::<#arg_type>().unwrap()); + arg_type.span()=> &mut *args[0usize].write_lock::<#arg_type>().unwrap()); unpack_stmts.push( syn::parse2::(quote! { let #var: &mut _ = #downcast_span; @@ -603,7 +605,7 @@ mod generate_tests { let expected_tokens = quote! { #[allow(unused)] - pub mod rhai_fn__do_nothing { + pub mod rhai_fn_do_nothing { use super::*; struct Token(); impl PluginFunction for Token { @@ -625,10 +627,10 @@ mod generate_tests { vec![].into_boxed_slice() } } - pub fn Token__callable() -> CallableFunction { + pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn Token__input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } } @@ -646,7 +648,7 @@ mod generate_tests { let expected_tokens = quote! { #[allow(unused)] - pub mod rhai_fn__do_something { + pub mod rhai_fn_do_something { use super::*; struct Token(); impl PluginFunction for Token { @@ -669,10 +671,10 @@ mod generate_tests { vec![std::any::TypeId::of::()].into_boxed_slice() } } - pub fn Token__callable() -> CallableFunction { + pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn Token__input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } } @@ -723,7 +725,7 @@ mod generate_tests { let expected_tokens = quote! { #[allow(unused)] - pub mod rhai_fn__add_together { + pub mod rhai_fn_add_together { use super::*; struct Token(); impl PluginFunction for Token { @@ -748,10 +750,10 @@ mod generate_tests { std::any::TypeId::of::()].into_boxed_slice() } } - pub fn Token__callable() -> CallableFunction { + pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn Token__input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } } @@ -769,7 +771,7 @@ mod generate_tests { let expected_tokens = quote! { #[allow(unused)] - pub mod rhai_fn__increment { + pub mod rhai_fn_increment { use super::*; struct Token(); impl PluginFunction for Token { @@ -782,7 +784,7 @@ mod generate_tests { args.len(), 2usize), Position::none()))); } let arg1 = args[1usize].downcast_clone::().unwrap(); - let arg0: &mut _ = args[0usize].downcast_mut::().unwrap(); + let arg0: &mut _ = args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } @@ -794,10 +796,10 @@ mod generate_tests { std::any::TypeId::of::()].into_boxed_slice() } } - pub fn Token__callable() -> CallableFunction { + pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn Token__input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } } @@ -816,7 +818,7 @@ mod generate_tests { let expected_tokens = quote! { #[allow(unused)] - pub mod rhai_fn__special_print { + pub mod rhai_fn_special_print { use super::*; struct Token(); impl PluginFunction for Token { @@ -839,10 +841,10 @@ mod generate_tests { vec![std::any::TypeId::of::()].into_boxed_slice() } } - pub fn Token__callable() -> CallableFunction { + pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn Token__input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 925c0501..af641be8 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -99,7 +99,7 @@ pub fn export_module( pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::TokenStream { let module_path = parse_macro_input!(module_path as syn::Path); let tokens = quote::quote! { - #module_path::rhai_module__generate() + #module_path::rhai_module_generate() }; proc_macro::TokenStream::from(tokens) } @@ -133,7 +133,7 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS let mut g = rust_modpath.clone().segments; g.pop(); let ident = syn::Ident::new( - &format!("rhai_fn__{}", rust_modpath.segments.last().unwrap().ident), + &format!("rhai_fn_{}", rust_modpath.segments.last().unwrap().ident), items[2].span(), ); g.push_value(syn::PathSegment { @@ -144,8 +144,8 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS }; let tokens = quote! { #rhai_module.set_fn(#export_name, rhai::FnAccess::Public, - #gen_mod_path::Token__input_types().as_ref(), - #gen_mod_path::Token__callable()); + #gen_mod_path::token_input_types().as_ref(), + #gen_mod_path::token_callable()); }; proc_macro::TokenStream::from(tokens) diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 8e00a7a4..48d9758c 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -265,7 +265,7 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m } @@ -294,15 +294,15 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_fn("get_mystic_number", FnAccess::Public, &[], - CallableFunction::from_plugin(get_mystic_number__Token())); + CallableFunction::from_plugin(get_mystic_number_token())); m } #[allow(non_camel_case_types)] - struct get_mystic_number__Token(); - impl PluginFunction for get_mystic_number__Token { + struct get_mystic_number_token(); + impl PluginFunction for get_mystic_number_token { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { @@ -317,17 +317,17 @@ mod generate_tests { fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { - Box::new(get_mystic_number__Token()) + Box::new(get_mystic_number_token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![].into_boxed_slice() } } - pub fn get_mystic_number__Token__callable() -> CallableFunction { - CallableFunction::from_plugin(get_mystic_number__Token()) + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) } - pub fn get_mystic_number__Token__input_types() -> Box<[std::any::TypeId]> { - get_mystic_number__Token().input_types() + pub fn get_mystic_number_token_input_types() -> Box<[std::any::TypeId]> { + get_mystic_number_token().input_types() } } }; @@ -354,15 +354,15 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(add_one_to__Token())); + CallableFunction::from_plugin(add_one_to_token())); m } #[allow(non_camel_case_types)] - struct add_one_to__Token(); - impl PluginFunction for add_one_to__Token { + struct add_one_to_token(); + impl PluginFunction for add_one_to_token { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { @@ -378,17 +378,17 @@ mod generate_tests { fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { - Box::new(add_one_to__Token()) + Box::new(add_one_to_token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } } - pub fn add_one_to__Token__callable() -> CallableFunction { - CallableFunction::from_plugin(add_one_to__Token()) + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) } - pub fn add_one_to__Token__input_types() -> Box<[std::any::TypeId]> { - add_one_to__Token().input_types() + pub fn add_one_to_token_input_types() -> Box<[std::any::TypeId]> { + add_one_to_token().input_types() } } }; @@ -415,16 +415,16 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], - CallableFunction::from_plugin(add_together__Token())); + CallableFunction::from_plugin(add_together_token())); m } #[allow(non_camel_case_types)] - struct add_together__Token(); - impl PluginFunction for add_together__Token { + struct add_together_token(); + impl PluginFunction for add_together_token { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { @@ -441,18 +441,18 @@ mod generate_tests { fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { - Box::new(add_together__Token()) + Box::new(add_together_token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::(), std::any::TypeId::of::()].into_boxed_slice() } } - pub fn add_together__Token__callable() -> CallableFunction { - CallableFunction::from_plugin(add_together__Token()) + pub fn add_together_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_together_token()) } - pub fn add_together__Token__input_types() -> Box<[std::any::TypeId]> { - add_together__Token().input_types() + pub fn add_together_token_input_types() -> Box<[std::any::TypeId]> { + add_together_token().input_types() } } }; @@ -475,7 +475,7 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_var("MYSTIC_NUMBER", 42); m @@ -503,7 +503,7 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_var("MYSTIC_NUMBER", 42); m @@ -533,7 +533,7 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m } @@ -558,7 +558,7 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m } @@ -587,16 +587,16 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_fn("print_out_to", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(print_out_to__Token())); + CallableFunction::from_plugin(print_out_to_token())); m } #[allow(non_camel_case_types)] - struct print_out_to__Token(); - impl PluginFunction for print_out_to__Token { + struct print_out_to_token(); + impl PluginFunction for print_out_to_token { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { @@ -612,17 +612,17 @@ mod generate_tests { fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { - Box::new(print_out_to__Token()) + Box::new(print_out_to_token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } } - pub fn print_out_to__Token__callable() -> CallableFunction { - CallableFunction::from_plugin(print_out_to__Token()) + pub fn print_out_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(print_out_to_token()) } - pub fn print_out_to__Token__input_types() -> Box<[std::any::TypeId]> { - print_out_to__Token().input_types() + pub fn print_out_to_token_input_types() -> Box<[std::any::TypeId]> { + print_out_to_token().input_types() } } }; @@ -649,16 +649,16 @@ mod generate_tests { #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_fn("increment", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment__Token())); + CallableFunction::from_plugin(increment_token())); m } #[allow(non_camel_case_types)] - struct increment__Token(); - impl PluginFunction for increment__Token { + struct increment_token(); + impl PluginFunction for increment_token { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { @@ -667,24 +667,24 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0: &mut _ = args[0usize].downcast_mut::().unwrap(); + let arg0: &mut _ = args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } fn is_method_call(&self) -> bool { true } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { - Box::new(increment__Token()) + Box::new(increment_token()) } fn input_types(&self) -> Box<[std::any::TypeId]> { vec![std::any::TypeId::of::()].into_boxed_slice() } } - pub fn increment__Token__callable() -> CallableFunction { - CallableFunction::from_plugin(increment__Token()) + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) } - pub fn increment__Token__input_types() -> Box<[std::any::TypeId]> { - increment__Token().input_types() + pub fn increment_token_input_types() -> Box<[std::any::TypeId]> { + increment_token().input_types() } } }; diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index e9ad66d5..1a2b1021 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -26,7 +26,7 @@ pub(crate) fn generate_body( let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { let fn_token_name = syn::Ident::new( - &format!("{}__Token", function.name().to_string()), + &format!("{}_Token", function.name().to_string()), function.name().span(), ); let fn_literal = @@ -90,7 +90,7 @@ pub(crate) fn generate_body( #[allow(unused_imports)] use super::*; #[allow(unused_mut)] - pub fn rhai_module__generate() -> Module { + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); #(#set_fn_stmts)* #(#set_const_stmts)* diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index b666d588..5216a56d 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -152,7 +152,7 @@ to partition the slice: let (first, rest) = args.split_at_mut(1); // Mutable reference to the first parameter -let this_ptr = first[0].downcast_mut::().unwrap(); +let this_ptr = first[0].write_lock::().unwrap(); // Immutable reference to the second value parameter // This can be mutable but there is no point because the parameter is passed by value From be315aebaf58355c86a9f226937a23cbbca74e46 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 6 Aug 2020 18:36:15 -0500 Subject: [PATCH 016/103] Fix breakage due to write_lock() --- codegen/src/function.rs | 4 ++-- codegen/src/module.rs | 2 +- codegen/src/rhai_module.rs | 2 +- src/fn_register.rs | 8 ++++---- src/lib.rs | 2 ++ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 5d869a2b..6ee13ed8 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -220,7 +220,7 @@ impl ExportedFn { } }; let downcast_span = quote_spanned!( - arg_type.span()=> &mut *args[0usize].write_lock::<#arg_type>().unwrap()); + arg_type.span()=> &mut args[0usize].write_lock::<#arg_type>().unwrap()); unpack_stmts.push( syn::parse2::(quote! { let #var: &mut _ = #downcast_span; @@ -784,7 +784,7 @@ mod generate_tests { args.len(), 2usize), Position::none()))); } let arg1 = args[1usize].downcast_clone::().unwrap(); - let arg0: &mut _ = args[0usize].write_lock::().unwrap(); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 48d9758c..9db6a3cd 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -667,7 +667,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0: &mut _ = args[0usize].write_lock::().unwrap(); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 1a2b1021..08c7f11f 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -26,7 +26,7 @@ pub(crate) fn generate_body( let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { let fn_token_name = syn::Ident::new( - &format!("{}_Token", function.name().to_string()), + &format!("{}_token", function.name().to_string()), function.name().span(), ); let fn_literal = diff --git a/src/fn_register.rs b/src/fn_register.rs index 91974c2e..104b305f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -39,10 +39,10 @@ pub trait RegisterPlugin { /// fn is_varadic(&self) -> bool { false } /// /// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result> { - /// let x1: &FLOAT = args[0].downcast_ref::().unwrap(); - /// let y1: &FLOAT = args[1].downcast_ref::().unwrap(); - /// let x2: &FLOAT = args[2].downcast_ref::().unwrap(); - /// let y2: &FLOAT = args[3].downcast_ref::().unwrap(); + /// let x1: FLOAT = args[0].downcast_clone::().unwrap(); + /// let y1: FLOAT = args[1].downcast_clone::().unwrap(); + /// let x2: FLOAT = args[2].downcast_clone::().unwrap(); + /// let y2: FLOAT = args[3].downcast_clone::().unwrap(); /// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0); /// Ok(Dynamic::from(square_sum.sqrt())) /// } diff --git a/src/lib.rs b/src/lib.rs index b6177699..a8a783d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,8 @@ pub use rhai_codegen::*; #[cfg(not(feature = "no_function"))] pub use parser::FnAccess; +#[cfg(feature = "no_function")] +pub use parser::FnAccess; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; From 893a084b7ac9dce9bf033b9af39579caf69dd342 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 6 Aug 2020 19:09:13 -0500 Subject: [PATCH 017/103] Avoid failing fast on features GHA workflow --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39610a97..0384c01c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,7 @@ jobs: - {toolchain: stable, os: macos-latest, experimental: false, flags: ""} - {toolchain: beta, os: ubuntu-latest, experimental: false, flags: ""} - {toolchain: nightly, os: ubuntu-latest, experimental: true, flags: ""} + fail-fast: false steps: - name: Checkout uses: actions/checkout@v2 From 75bcbb74eb540109584865a774884716ae87d174 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 6 Aug 2020 19:41:06 -0500 Subject: [PATCH 018/103] Fix unrelated CI failure for features --- src/fn_register.rs | 53 +++++++++++++++++++++++++++++++--------------- tests/plugins.rs | 2 ++ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/fn_register.rs b/src/fn_register.rs index 104b305f..9645fba9 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -26,25 +26,36 @@ pub trait RegisterPlugin { /// # Example /// /// ``` - /// use rhai::{FLOAT, INT, Module, ModuleResolver, RegisterFn, RegisterPlugin}; + /// # #[cfg(not(feature = "no_float"))] + /// use rhai::FLOAT as NUMBER; + /// # #[cfg(feature = "no_float")] + /// use rhai::INT as NUMBER; + /// # #[cfg(not(feature = "no_module"))] + /// use rhai::{Module, ModuleResolver, RegisterFn, RegisterPlugin}; + /// # #[cfg(not(feature = "no_module"))] /// use rhai::plugin::*; + /// # #[cfg(not(feature = "no_module"))] /// use rhai::module_resolvers::*; /// /// // A function we want to expose to Rhai. /// #[derive(Copy, Clone)] /// struct DistanceFunction(); /// + /// # #[cfg(not(feature = "no_module"))] /// impl PluginFunction for DistanceFunction { /// fn is_method_call(&self) -> bool { false } /// fn is_varadic(&self) -> bool { false } /// /// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result> { - /// let x1: FLOAT = args[0].downcast_clone::().unwrap(); - /// let y1: FLOAT = args[1].downcast_clone::().unwrap(); - /// let x2: FLOAT = args[2].downcast_clone::().unwrap(); - /// let y2: FLOAT = args[3].downcast_clone::().unwrap(); + /// let x1: NUMBER = args[0].downcast_clone::().unwrap(); + /// let y1: NUMBER = args[1].downcast_clone::().unwrap(); + /// let x2: NUMBER = args[2].downcast_clone::().unwrap(); + /// let y2: NUMBER = args[3].downcast_clone::().unwrap(); + /// # #[cfg(not(feature = "no_float"))] /// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0); - /// Ok(Dynamic::from(square_sum.sqrt())) + /// # #[cfg(feature = "no_float")] + /// let square_sum = (y2 - y1).abs().pow(2) + (x2 -x1).abs().pow(2); + /// Ok(Dynamic::from(square_sum)) /// } /// /// fn clone_boxed(&self) -> Box { @@ -52,10 +63,10 @@ pub trait RegisterPlugin { /// } /// /// fn input_types(&self) -> Box<[std::any::TypeId]> { - /// vec![std::any::TypeId::of::(), - /// std::any::TypeId::of::(), - /// std::any::TypeId::of::(), - /// std::any::TypeId::of::()].into_boxed_slice() + /// vec![std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()].into_boxed_slice() /// } /// } /// @@ -63,10 +74,11 @@ pub trait RegisterPlugin { /// #[derive(Copy, Clone)] /// pub struct AdvancedMathPlugin(); /// + /// # #[cfg(not(feature = "no_module"))] /// impl Plugin for AdvancedMathPlugin { /// fn register_contents(self, engine: &mut Engine) { /// // Plugins are allowed to have side-effects on the engine. - /// engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + /// engine.register_fn("get_mystic_number", || { 42 as NUMBER }); /// /// // Main purpose: create a module to expose the functions to Rhai. /// // @@ -74,10 +86,10 @@ pub trait RegisterPlugin { /// // modules. /// let mut m = Module::new(); /// m.set_fn("euclidean_distance".to_string(), FnAccess::Public, - /// &[std::any::TypeId::of::(), - /// std::any::TypeId::of::(), - /// std::any::TypeId::of::(), - /// std::any::TypeId::of::()], + /// &[std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()], /// CallableFunction::from_plugin(DistanceFunction())); /// let mut r = StaticModuleResolver::new(); /// r.insert("Math::Advanced".to_string(), m); @@ -88,12 +100,19 @@ pub trait RegisterPlugin { /// /// # fn main() -> Result<(), Box> { /// + /// # #[cfg(not(feature = "no_module"))] { /// let mut engine = Engine::new(); /// engine.register_plugin(AdvancedMathPlugin()); /// - /// assert_eq!(engine.eval::( + /// # #[cfg(feature = "no_float")] + /// assert_eq!(engine.eval::( /// r#"import "Math::Advanced" as math; - /// let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 41.0); + /// let x = math::euclidean_distance(0, 1, 0, get_mystic_number()); x"#)?, 1681); + /// # #[cfg(not(feature = "no_float"))] + /// assert_eq!(engine.eval::( + /// r#"import "Math::Advanced" as math; + /// let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 1681.0); + /// # } // end cfg /// # Ok(()) /// # } /// ``` diff --git a/tests/plugins.rs b/tests/plugins.rs index adf1840e..1b701ddd 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -1,3 +1,5 @@ +#![cfg(not(any(feature = "no_index", feature = "no_module")))] + use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT}; From 7410f40c92588100ff954661dd33644aba54ce65 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 7 Aug 2020 21:19:17 -0500 Subject: [PATCH 019/103] export_fn: add "name" parameter --- codegen/src/function.rs | 51 +++++++++++++++++++-- codegen/src/lib.rs | 5 +- codegen/tests/test_functions.rs | 32 +++++++++++++ codegen/ui_tests/export_fn_bad_attr.rs | 24 ++++++++++ codegen/ui_tests/export_fn_bad_attr.stderr | 11 +++++ codegen/ui_tests/export_fn_bad_value.rs | 24 ++++++++++ codegen/ui_tests/export_fn_bad_value.stderr | 11 +++++ codegen/ui_tests/export_fn_junk_arg.rs | 24 ++++++++++ codegen/ui_tests/export_fn_junk_arg.stderr | 11 +++++ 9 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 codegen/ui_tests/export_fn_bad_attr.rs create mode 100644 codegen/ui_tests/export_fn_bad_attr.stderr create mode 100644 codegen/ui_tests/export_fn_bad_value.rs create mode 100644 codegen/ui_tests/export_fn_bad_value.stderr create mode 100644 codegen/ui_tests/export_fn_junk_arg.rs create mode 100644 codegen/ui_tests/export_fn_junk_arg.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 6ee13ed8..88ff685d 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -2,12 +2,45 @@ use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; +#[derive(Debug, Default)] +pub(crate) struct ExportedFnParams { + name: Option, +} + +impl Parse for ExportedFnParams { + fn parse(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportedFnParams::default()); + } + let assignment: syn::ExprAssign = args.parse()?; + + let attr_name: syn::Ident = match assignment.left.as_ref() { + syn::Expr::Path(syn::ExprPath { path: attr_path, .. }) => attr_path.get_ident().cloned() + .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, + x => return Err(syn::Error::new(x.span(), "expecting attribute name")), + }; + if &attr_name != "name" { + return Err(syn::Error::new(attr_name.span(), format!("unknown attribute '{}'", &attr_name))); + } + + let attr_value: String = match assignment.right.as_ref() { + syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(string), .. }) => string.value(), + x => return Err(syn::Error::new(x.span(), "expecting string literal value")), + }; + + Ok(ExportedFnParams { + name: Some(attr_value), + }) + } +} + #[derive(Debug)] pub(crate) struct ExportedFn { entire_span: proc_macro2::Span, signature: syn::Signature, is_public: bool, mut_receiver: bool, + params: ExportedFnParams, } impl Parse for ExportedFn { @@ -111,6 +144,7 @@ impl Parse for ExportedFn { signature: fn_all.sig, is_public, mut_receiver, + params: ExportedFnParams::default(), }) } } @@ -148,11 +182,20 @@ impl ExportedFn { } } + pub fn generate_with_params(mut self, + mut params: ExportedFnParams) -> proc_macro2::TokenStream { + self.params = params; + self.generate() + } + pub fn generate(self) -> proc_macro2::TokenStream { - let name: syn::Ident = syn::Ident::new( - &format!("rhai_fn_{}", self.name().to_string()), - self.name().span(), - ); + let name_str = if let Some(ref name) = self.params.name { + name.clone() + } else { + self.name().to_string() + }; + let name: syn::Ident = syn::Ident::new(&format!("rhai_fn_{}", name_str), + self.name().span()); let impl_block = self.generate_impl("Token"); let callable_block = self.generate_callable("Token"); let input_types_block = self.generate_input_types("Token"); diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index af641be8..167bd0fb 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -76,12 +76,13 @@ mod rhai_module; #[proc_macro_attribute] pub fn export_fn( - _args: proc_macro::TokenStream, + args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let mut output = proc_macro2::TokenStream::from(input.clone()); + let parsed_params = parse_macro_input!(args as function::ExportedFnParams); let function_def = parse_macro_input!(input as function::ExportedFn); - output.extend(function_def.generate()); + output.extend(function_def.generate_with_params(parsed_params)); proc_macro::TokenStream::from(output) } diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index ee88be4a..24dba324 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -158,3 +158,35 @@ fn mut_opaque_ref_test() -> Result<(), Box> { ); Ok(()) } + +mod rename_fn { + use rhai::plugin::*; + use rhai::FLOAT; + + #[export_fn(name = "add_float")] + pub fn add(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } +} + +#[test] +fn rename_fn_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::register_exported_fn!(m, "add_two_floats", rename_fn::add_float); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = get_mystic_number(); + let y = math::add_two_floats(x, 1.0); + y"# + )?, + 43.0 + ); + Ok(()) +} diff --git a/codegen/ui_tests/export_fn_bad_attr.rs b/codegen/ui_tests/export_fn_bad_attr.rs new file mode 100644 index 00000000..7a268f01 --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_attr.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(unknown = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_bad_attr.stderr b/codegen/ui_tests/export_fn_bad_attr.stderr new file mode 100644 index 00000000..f08dd188 --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/export_fn_bad_attr.rs:9:13 + | +9 | #[export_fn(unknown = true)] + | ^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_bad_attr.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_bad_value.rs b/codegen/ui_tests/export_fn_bad_value.rs new file mode 100644 index 00000000..f8044f9c --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(name = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_bad_value.stderr b/codegen/ui_tests/export_fn_bad_value.stderr new file mode 100644 index 00000000..0db1969f --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal value + --> $DIR/export_fn_bad_value.rs:9:20 + | +9 | #[export_fn(name = true)] + | ^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_bad_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_junk_arg.rs b/codegen/ui_tests/export_fn_junk_arg.rs new file mode 100644 index 00000000..3abb9399 --- /dev/null +++ b/codegen/ui_tests/export_fn_junk_arg.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn("wheeeee")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_junk_arg.stderr b/codegen/ui_tests/export_fn_junk_arg.stderr new file mode 100644 index 00000000..d6003354 --- /dev/null +++ b/codegen/ui_tests/export_fn_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expected assignment expression + --> $DIR/export_fn_junk_arg.rs:9:13 + | +9 | #[export_fn("wheeeee")] + | ^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_junk_arg.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope From 1df5d052392da6ef9cd8bf993d59d1a20806ddad Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 8 Aug 2020 09:31:15 -0500 Subject: [PATCH 020/103] export_fn: allow duplicate Rust names --- codegen/src/function.rs | 9 +++++-- codegen/src/lib.rs | 15 ++++++++++-- codegen/tests/test_functions.rs | 42 ++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 88ff685d..f2e0ab11 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -4,7 +4,7 @@ use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { - name: Option, + pub name: Option, } impl Parse for ExportedFnParams { @@ -238,7 +238,12 @@ impl ExportedFn { } pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { - let name: syn::Ident = self.name().clone(); + let name: syn::Ident = if let Some(ref name) = self.params.name { + syn::Ident::new(name, self.name().span()) + } else { + self.name().clone() + }; + let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 167bd0fb..8cc5bbd4 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -67,7 +67,7 @@ //! ``` //! -use quote::{quote, quote_spanned}; +use quote::{quote, quote_spanned, ToTokens}; use syn::{parse::Parser, parse_macro_input, spanned::Spanned}; mod function; @@ -79,9 +79,20 @@ pub fn export_fn( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let mut output = proc_macro2::TokenStream::from(input.clone()); + let output = proc_macro2::TokenStream::from(input.clone()); + let parsed_params = parse_macro_input!(args as function::ExportedFnParams); let function_def = parse_macro_input!(input as function::ExportedFn); + + let mut output = if let Some(ref rename) = parsed_params.name { + // If it wasn't a function, it wouldn't have parsed earlier, so unwrap() is fine. + let mut output_fn: syn::ItemFn = syn::parse2(output.clone()).unwrap(); + let new_name = syn::Ident::new(rename, output_fn.sig.ident.span()); + output_fn.sig.ident = new_name; + output_fn.into_token_stream() + } else { + output + }; output.extend(function_def.generate_with_params(parsed_params)); proc_macro::TokenStream::from(output) } diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 24dba324..85fcede7 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -1,5 +1,5 @@ use rhai::module_resolvers::*; -use rhai::{Engine, EvalAltResult, Module, RegisterFn, FLOAT, INT}; +use rhai::{Array, Engine, EvalAltResult, Module, RegisterFn, FLOAT}; pub mod raw_fn { use rhai::plugin::*; @@ -190,3 +190,43 @@ fn rename_fn_test() -> Result<(), Box> { ); Ok(()) } + +mod duplicate_fn_rename { + use rhai::plugin::*; + use rhai::{FLOAT, INT}; + + #[export_fn(name = "add_float")] + pub fn add(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + #[export_fn(name = "add_int")] + pub fn add(i1: INT, i2: INT) -> INT { + i1 + i2 + } +} + +#[test] +fn duplicate_fn_rename_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::register_exported_fn!(m, "add_two_floats", duplicate_fn_rename::add_float); + rhai::register_exported_fn!(m, "add_two_ints", duplicate_fn_rename::add_int); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + let output_array = engine.eval::( + r#"import "Math::Advanced" as math; + let fx = get_mystic_number(); + let fy = math::add_two_floats(fx, 1.0); + let ix = 42; + let iy = math::add_two_ints(ix, 1); + [fy, iy] + "# + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &43.0); + assert_eq!(&output_array[1].as_int().unwrap(), &43); + Ok(()) +} From a24d11324c405e26a4d49301d2567a797dfcd01d Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 11:21:26 -0500 Subject: [PATCH 021/103] Create functional test for generated bulk operations --- tests/macro_unroll.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/macro_unroll.rs diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs new file mode 100644 index 00000000..17f2dfb9 --- /dev/null +++ b/tests/macro_unroll.rs @@ -0,0 +1,48 @@ +#![cfg(not(any(feature = "no_index", feature = "no_module")))] + +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, INT, Module}; + +macro_rules! generate_add { + ($type_name:ident) => { + pub mod $type_name { + use rhai::plugin::*; + #[export_fn] + pub fn add(x: $type_name, y: $type_name) -> $type_name { + x + y + } + } + } +} + +macro_rules! register_adds_in_bulk { + ($mod_name:expr, $($type_names:ident),+) => { + $( + { + let type_str = stringify!($type_names); + register_exported_fn!($mod_name, + format!("add_{}", type_str), + crate::$type_names::add); + } + )* + } +} + +generate_add!(i8); +generate_add!(i16); +generate_add!(i32); +generate_add!(i64); + +#[test] +fn test_generated_adds() -> Result<(), Box> { + let mut engine = Engine::new(); + + let mut m = Module::new(); + register_adds_in_bulk!(m, i8, i16, i32, i64); + + engine.load_package(m); + + assert_eq!(engine.eval::("let a = 0; add_i64(a, 1)")?, 1); + + Ok(()) +} From dc7b36fd11ebf2ca8ee9b18ff5e0628777993740 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 11:42:37 -0500 Subject: [PATCH 022/103] Fix for stable --- tests/macro_unroll.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs index 17f2dfb9..9b4a7b75 100644 --- a/tests/macro_unroll.rs +++ b/tests/macro_unroll.rs @@ -16,7 +16,7 @@ macro_rules! generate_add { } macro_rules! register_adds_in_bulk { - ($mod_name:expr, $($type_names:ident),+) => { + ($mod_name:ident, $($type_names:ident),+) => { $( { let type_str = stringify!($type_names); From a45b1d406f89fa285c1eb52f9ec032c39da928ad Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 11:47:11 -0500 Subject: [PATCH 023/103] Add only_i32 feature support --- tests/macro_unroll.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs index 9b4a7b75..c253666f 100644 --- a/tests/macro_unroll.rs +++ b/tests/macro_unroll.rs @@ -42,6 +42,9 @@ fn test_generated_adds() -> Result<(), Box> { engine.load_package(m); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let a = 0; add_i32(a, 1)")?, 1); + #[cfg(not(feature = "only_i32"))] assert_eq!(engine.eval::("let a = 0; add_i64(a, 1)")?, 1); Ok(()) From 3fa252e73254741c24a1d5ded280585faad78d17 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 11:57:09 -0500 Subject: [PATCH 024/103] Fold generate_add once more --- tests/macro_unroll.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs index c253666f..f24e2d3b 100644 --- a/tests/macro_unroll.rs +++ b/tests/macro_unroll.rs @@ -3,15 +3,17 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT, Module}; -macro_rules! generate_add { - ($type_name:ident) => { - pub mod $type_name { +macro_rules! generate_adds { + ($($type_names:ident),+) => { + $( + pub mod $type_names { use rhai::plugin::*; #[export_fn] - pub fn add(x: $type_name, y: $type_name) -> $type_name { + pub fn add(x: $type_names, y: $type_names) -> $type_names { x + y } } + )* } } @@ -28,10 +30,7 @@ macro_rules! register_adds_in_bulk { } } -generate_add!(i8); -generate_add!(i16); -generate_add!(i32); -generate_add!(i64); +generate_adds!(i8, i16, i32, i64); #[test] fn test_generated_adds() -> Result<(), Box> { From ce8fbe40c4edccc705cca9edbffff1d8599e98fb Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 12:13:02 -0500 Subject: [PATCH 025/103] Fold into fully generic form --- tests/macro_unroll.rs | 52 +++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs index f24e2d3b..fd0920ed 100644 --- a/tests/macro_unroll.rs +++ b/tests/macro_unroll.rs @@ -3,41 +3,54 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT, Module}; -macro_rules! generate_adds { - ($($type_names:ident),+) => { - $( - pub mod $type_names { - use rhai::plugin::*; - #[export_fn] - pub fn add(x: $type_names, y: $type_names) -> $type_names { - x + y - } +pub fn add_generic>(x: T, y: T) -> T { + x + y +} + +pub fn mul_generic>(x: T, y: T) -> T { + x * y +} + +macro_rules! generate_ops { + ($op_name:ident, $op_fn:ident, $($type_names:ident),+) => { + pub mod $op_name { + $( + pub mod $type_names { + use rhai::plugin::*; + use super::super::$op_fn; + #[export_fn] + pub fn op(x: $type_names, y: $type_names) -> $type_names { + $op_fn(x, y) + } + } + )* } - )* } } -macro_rules! register_adds_in_bulk { - ($mod_name:ident, $($type_names:ident),+) => { +macro_rules! register_in_bulk { + ($mod_name:ident, $op_name:ident, $($type_names:ident),+) => { $( { let type_str = stringify!($type_names); register_exported_fn!($mod_name, - format!("add_{}", type_str), - crate::$type_names::add); + format!(concat!(stringify!($op_name), "_{}"), type_str), + crate::$op_name::$type_names::op); } )* } } -generate_adds!(i8, i16, i32, i64); +generate_ops!(add, add_generic, i8, i16, i32, i64); +generate_ops!(mul, mul_generic, i8, i16, i32, i64); #[test] -fn test_generated_adds() -> Result<(), Box> { +fn test_generated_ops() -> Result<(), Box> { let mut engine = Engine::new(); let mut m = Module::new(); - register_adds_in_bulk!(m, i8, i16, i32, i64); + register_in_bulk!(m, add, i8, i16, i32, i64); + register_in_bulk!(m, mul, i8, i16, i32, i64); engine.load_package(m); @@ -46,5 +59,10 @@ fn test_generated_adds() -> Result<(), Box> { #[cfg(not(feature = "only_i32"))] assert_eq!(engine.eval::("let a = 0; add_i64(a, 1)")?, 1); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let a = 1; mul_i32(a, 2)")?, 2); + #[cfg(not(feature = "only_i32"))] + assert_eq!(engine.eval::("let a = 1; mul_i64(a, 2)")?, 2); + Ok(()) } From 07a454194942b75bd7170df2e0b19609e54306ad Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 14:19:39 -0500 Subject: [PATCH 026/103] export_fn: add return_raw attribute --- codegen/src/function.rs | 94 +++++++++++++++---- codegen/tests/test_functions.rs | 41 ++++++++ codegen/ui_tests/export_fn_bad_attr.rs | 2 +- codegen/ui_tests/export_fn_bad_attr.stderr | 2 +- codegen/ui_tests/export_fn_bad_value.stderr | 2 +- codegen/ui_tests/export_fn_extra_value.rs | 24 +++++ codegen/ui_tests/export_fn_extra_value.stderr | 11 +++ codegen/ui_tests/export_fn_junk_arg.stderr | 2 +- codegen/ui_tests/export_fn_missing_value.rs | 24 +++++ .../ui_tests/export_fn_missing_value.stderr | 11 +++ codegen/ui_tests/export_fn_path_attr.rs | 24 +++++ codegen/ui_tests/export_fn_path_attr.stderr | 11 +++ 12 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 codegen/ui_tests/export_fn_extra_value.rs create mode 100644 codegen/ui_tests/export_fn_extra_value.stderr create mode 100644 codegen/ui_tests/export_fn_missing_value.rs create mode 100644 codegen/ui_tests/export_fn_missing_value.stderr create mode 100644 codegen/ui_tests/export_fn_path_attr.rs create mode 100644 codegen/ui_tests/export_fn_path_attr.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index f2e0ab11..7b28eeeb 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -1,10 +1,14 @@ #![allow(unused)] + +use std::collections::HashMap; + use quote::{quote, quote_spanned}; -use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; +use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option, + pub return_raw: bool, } impl Parse for ExportedFnParams { @@ -12,25 +16,68 @@ impl Parse for ExportedFnParams { if args.is_empty() { return Ok(ExportedFnParams::default()); } - let assignment: syn::ExprAssign = args.parse()?; - let attr_name: syn::Ident = match assignment.left.as_ref() { - syn::Expr::Path(syn::ExprPath { path: attr_path, .. }) => attr_path.get_ident().cloned() - .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, - x => return Err(syn::Error::new(x.span(), "expecting attribute name")), - }; - if &attr_name != "name" { - return Err(syn::Error::new(attr_name.span(), format!("unknown attribute '{}'", &attr_name))); + let arg_list = args.call( + syn::punctuated::Punctuated::::parse_separated_nonempty, + )?; + + let mut attrs: HashMap> = HashMap::new(); + for arg in arg_list { + let (left, right) = match arg { + syn::Expr::Assign(syn::ExprAssign { + ref left, + ref right, + .. + }) => { + let attr_name: syn::Ident = match left.as_ref() { + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path.get_ident().cloned().ok_or_else(|| { + syn::Error::new(attr_path.span(), "expecting attribute name") + })?, + x => return Err(syn::Error::new(x.span(), "expecting attribute name")), + }; + let attr_value = match right.as_ref() { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(string), + .. + }) => string.clone(), + x => return Err(syn::Error::new(x.span(), "expecting string literal")), + }; + (attr_name, Some(attr_value)) + } + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path + .get_ident() + .cloned() + .map(|a| (a, None)) + .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, + x => return Err(syn::Error::new(x.span(), "expecting identifier")), + }; + attrs.insert(left, right); } - let attr_value: String = match assignment.right.as_ref() { - syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(string), .. }) => string.value(), - x => return Err(syn::Error::new(x.span(), "expecting string literal value")), - }; + let mut name = None; + let mut return_raw = false; + for (ident, value) in attrs.drain() { + match (ident.to_string().as_ref(), value) { + ("name", Some(s)) => name = Some(s.value()), + ("name", None) => return Err(syn::Error::new(ident.span(), "requires value")), + ("return_raw", None) => return_raw = true, + ("return_raw", Some(s)) => { + return Err(syn::Error::new(s.span(), "extraneous value")) + } + (attr, _) => { + return Err(syn::Error::new( + ident.span(), + format!("unknown attribute '{}'", attr), + )) + } + } + } - Ok(ExportedFnParams { - name: Some(attr_value), - }) + Ok(ExportedFnParams { name, return_raw }) } } @@ -361,6 +408,19 @@ impl ExportedFn { unpack_stmts.push(arg0); } + // Handle "raw returns", aka cases where the result is a dynamic or an error. + // + // This allows skipping the Dynamic::from wrap. + let return_expr = if !self.params.return_raw { + quote! { + Ok(Dynamic::from(#name(#(#unpack_exprs),*))) + } + } else { + quote! { + #name(#(#unpack_exprs),*) + } + }; + let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site()); quote! { impl PluginFunction for #type_name { @@ -373,7 +433,7 @@ impl ExportedFn { args.len(), #arg_count), Position::none()))); } #(#unpack_stmts)* - Ok(Dynamic::from(#name(#(#unpack_exprs),*))) + #return_expr } fn is_method_call(&self) -> bool { #is_method_call } diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 85fcede7..876fccf5 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -230,3 +230,44 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { assert_eq!(&output_array[1].as_int().unwrap(), &43); Ok(()) } + +pub mod raw_returning_fn { + use rhai::plugin::*; + use rhai::FLOAT; + + #[export_fn(return_raw)] + pub fn distance_function( + x1: FLOAT, + y1: FLOAT, + x2: FLOAT, + y2: FLOAT, + ) -> Result> { + Ok(Dynamic::from( + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt(), + )) + } +} + +#[test] +fn raw_returning_fn_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::register_exported_fn!( + m, + "euclidean_distance".to_string(), + raw_returning_fn::distance_function + ); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + )?, + 41.0 + ); + Ok(()) +} diff --git a/codegen/ui_tests/export_fn_bad_attr.rs b/codegen/ui_tests/export_fn_bad_attr.rs index 7a268f01..685b8e88 100644 --- a/codegen/ui_tests/export_fn_bad_attr.rs +++ b/codegen/ui_tests/export_fn_bad_attr.rs @@ -6,7 +6,7 @@ struct Point { y: f32, } -#[export_fn(unknown = true)] +#[export_fn(unknown = "thing")] pub fn test_fn(input: Point) -> bool { input.x > input.y } diff --git a/codegen/ui_tests/export_fn_bad_attr.stderr b/codegen/ui_tests/export_fn_bad_attr.stderr index f08dd188..12dd2a82 100644 --- a/codegen/ui_tests/export_fn_bad_attr.stderr +++ b/codegen/ui_tests/export_fn_bad_attr.stderr @@ -1,7 +1,7 @@ error: unknown attribute 'unknown' --> $DIR/export_fn_bad_attr.rs:9:13 | -9 | #[export_fn(unknown = true)] +9 | #[export_fn(unknown = "thing")] | ^^^^^^^ error[E0425]: cannot find function `test_fn` in this scope diff --git a/codegen/ui_tests/export_fn_bad_value.stderr b/codegen/ui_tests/export_fn_bad_value.stderr index 0db1969f..4695abb0 100644 --- a/codegen/ui_tests/export_fn_bad_value.stderr +++ b/codegen/ui_tests/export_fn_bad_value.stderr @@ -1,4 +1,4 @@ -error: expecting string literal value +error: expecting string literal --> $DIR/export_fn_bad_value.rs:9:20 | 9 | #[export_fn(name = true)] diff --git a/codegen/ui_tests/export_fn_extra_value.rs b/codegen/ui_tests/export_fn_extra_value.rs new file mode 100644 index 00000000..d8cb9623 --- /dev/null +++ b/codegen/ui_tests/export_fn_extra_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_extra_value.stderr b/codegen/ui_tests/export_fn_extra_value.stderr new file mode 100644 index 00000000..6ebc2763 --- /dev/null +++ b/codegen/ui_tests/export_fn_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/export_fn_extra_value.rs:9:26 + | +9 | #[export_fn(return_raw = "yes")] + | ^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_extra_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_junk_arg.stderr b/codegen/ui_tests/export_fn_junk_arg.stderr index d6003354..04cf996b 100644 --- a/codegen/ui_tests/export_fn_junk_arg.stderr +++ b/codegen/ui_tests/export_fn_junk_arg.stderr @@ -1,4 +1,4 @@ -error: expected assignment expression +error: expecting identifier --> $DIR/export_fn_junk_arg.rs:9:13 | 9 | #[export_fn("wheeeee")] diff --git a/codegen/ui_tests/export_fn_missing_value.rs b/codegen/ui_tests/export_fn_missing_value.rs new file mode 100644 index 00000000..7497a518 --- /dev/null +++ b/codegen/ui_tests/export_fn_missing_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_missing_value.stderr b/codegen/ui_tests/export_fn_missing_value.stderr new file mode 100644 index 00000000..978798e5 --- /dev/null +++ b/codegen/ui_tests/export_fn_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/export_fn_missing_value.rs:9:13 + | +9 | #[export_fn(name)] + | ^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_missing_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_path_attr.rs b/codegen/ui_tests/export_fn_path_attr.rs new file mode 100644 index 00000000..a9fed9e9 --- /dev/null +++ b/codegen/ui_tests/export_fn_path_attr.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_path_attr.stderr b/codegen/ui_tests/export_fn_path_attr.stderr new file mode 100644 index 00000000..baf499cb --- /dev/null +++ b/codegen/ui_tests/export_fn_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/export_fn_path_attr.rs:9:13 + | +9 | #[export_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_path_attr.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope From 6d11fdcd18546cdd9bafd1f81b61ecc328654dd6 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 11 Aug 2020 19:05:52 -0500 Subject: [PATCH 027/103] codegen: replace downcast_clone with more efficient mem::take --- codegen/src/function.rs | 19 +++++++++---------- codegen/src/module.rs | 8 ++++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 7b28eeeb..ae50e45a 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -356,16 +356,15 @@ impl ExportedFn { &syn::Type::Path(ref p) if p.path == str_type_path => { is_str_ref = true; quote_spanned!(arg_type.span()=> - args[#i] - .downcast_clone::() - .unwrap()) + std::mem::take(args[#i]) + .clone().cast::()) } _ => panic!("internal error: why wasn't this found earlier!?"), }, _ => { is_str_ref = false; quote_spanned!(arg_type.span()=> - args[#i].downcast_clone::<#arg_type>().unwrap()) + std::mem::take(args[#i]).clone().cast::<#arg_type>()) } }; @@ -768,7 +767,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(do_something(arg0))) } @@ -808,7 +807,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(do_something(arg0))) } @@ -845,8 +844,8 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 2usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); - let arg1 = args[1usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg1 = std::mem::take(args[1usize]).clone().cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } @@ -891,7 +890,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 2usize), Position::none()))); } - let arg1 = args[1usize].downcast_clone::().unwrap(); + let arg1 = std::mem::take(args[1usize]).clone().cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } @@ -938,7 +937,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(special_print(&arg0))) } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 9db6a3cd..256ca062 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -371,7 +371,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(add_one_to(arg0))) } @@ -433,8 +433,8 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 2usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); - let arg1 = args[1usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg1 = std::mem::take(args[1usize]).clone().cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } @@ -605,7 +605,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = args[0usize].downcast_clone::().unwrap(); + let arg0 = std::mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(print_out_to(&arg0))) } From 59e3ca0e79225463e9314f96e846a076ac3494d4 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 11 Aug 2020 19:09:49 -0500 Subject: [PATCH 028/103] Remove Dynamic::downcast_clone --- src/any.rs | 7 ------- src/fn_register.rs | 8 ++++---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/any.rs b/src/any.rs index afbfc956..c154a682 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1082,13 +1082,6 @@ impl Dynamic { } } - /// Copy and return a `Dynamic` if it contains a type that can be trivially copied. - /// Returns `None` if the cast fails. - #[inline(always)] - pub fn downcast_clone(&self) -> Option { - self.downcast_ref::().map(|t| t.clone()) - } - /// Cast the `Dynamic` as the system integer type `INT` and return it. /// Returns the name of the actual type if the cast fails. #[inline(always)] diff --git a/src/fn_register.rs b/src/fn_register.rs index 9645fba9..28d33198 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -47,10 +47,10 @@ pub trait RegisterPlugin { /// fn is_varadic(&self) -> bool { false } /// /// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result> { - /// let x1: NUMBER = args[0].downcast_clone::().unwrap(); - /// let y1: NUMBER = args[1].downcast_clone::().unwrap(); - /// let x2: NUMBER = args[2].downcast_clone::().unwrap(); - /// let y2: NUMBER = args[3].downcast_clone::().unwrap(); + /// let x1: NUMBER = std::mem::take(args[0]).clone().cast::(); + /// let y1: NUMBER = std::mem::take(args[1]).clone().cast::(); + /// let x2: NUMBER = std::mem::take(args[2]).clone().cast::(); + /// let y2: NUMBER = std::mem::take(args[3]).clone().cast::(); /// # #[cfg(not(feature = "no_float"))] /// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0); /// # #[cfg(feature = "no_float")] From 28572544d81a70bab5937415827355e294cab49f Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 12 Aug 2020 23:34:53 -0500 Subject: [PATCH 029/103] Split register export macro, add Engine support --- codegen/src/function.rs | 79 +++++++++++++++++++++++++-- codegen/src/lib.rs | 96 ++++++++++++++++++--------------- codegen/src/register.rs | 49 +++++++++++++++++ codegen/tests/test_functions.rs | 22 ++++---- src/plugin.rs | 1 + 5 files changed, 189 insertions(+), 58 deletions(-) create mode 100644 codegen/src/register.rs diff --git a/codegen/src/function.rs b/codegen/src/function.rs index ae50e45a..0c8391bb 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -229,8 +229,10 @@ impl ExportedFn { } } - pub fn generate_with_params(mut self, - mut params: ExportedFnParams) -> proc_macro2::TokenStream { + pub fn generate_with_params( + mut self, + mut params: ExportedFnParams, + ) -> proc_macro2::TokenStream { self.params = params; self.generate() } @@ -241,11 +243,12 @@ impl ExportedFn { } else { self.name().to_string() }; - let name: syn::Ident = syn::Ident::new(&format!("rhai_fn_{}", name_str), - self.name().span()); + let name: syn::Ident = + syn::Ident::new(&format!("rhai_fn_{}", name_str), self.name().span()); let impl_block = self.generate_impl("Token"); let callable_block = self.generate_callable("Token"); let input_types_block = self.generate_input_types("Token"); + let dyn_result_fn_block = self.generate_dynamic_fn(); quote! { #[allow(unused)] pub mod #name { @@ -254,6 +257,54 @@ impl ExportedFn { #impl_block #callable_block #input_types_block + #dyn_result_fn_block + } + } + } + + pub fn generate_dynamic_fn(&self) -> proc_macro2::TokenStream { + let name: syn::Ident = if let Some(ref name) = self.params.name { + syn::Ident::new(name, self.name().span()) + } else { + self.name().clone() + }; + + let mut dynamic_signature = self.signature.clone(); + dynamic_signature.ident = + syn::Ident::new("dynamic_result_fn", proc_macro2::Span::call_site()); + dynamic_signature.output = syn::parse2::(quote! { + -> Result + }) + .unwrap(); + let arguments: Vec = dynamic_signature + .inputs + .iter() + .filter_map(|fnarg| { + if let syn::FnArg::Typed(syn::PatType { ref pat, .. }) = fnarg { + if let syn::Pat::Ident(ref ident) = pat.as_ref() { + Some(ident.ident.clone()) + } else { + None + } + } else { + None + } + }) + .collect(); + + if !self.params.return_raw { + quote! { + type EvalBox = Box; + pub #dynamic_signature { + Ok(Dynamic::from(super::#name(#(#arguments),*))) + } + } + } else { + quote! { + type EvalBox = Box; + pub #dynamic_signature { + super::#name(#(#arguments),*) + } } } } @@ -740,6 +791,10 @@ mod generate_tests { pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } + type EvalBox = Box; + pub fn dynamic_result_fn() -> Result { + Ok(Dynamic::from(super::do_nothing())) + } } }; @@ -784,6 +839,10 @@ mod generate_tests { pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize) -> Result { + Ok(Dynamic::from(super::do_something(x))) + } } }; @@ -863,6 +922,10 @@ mod generate_tests { pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize, y: usize) -> Result { + Ok(Dynamic::from(super::add_together(x, y))) + } } }; @@ -909,6 +972,10 @@ mod generate_tests { pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } + type EvalBox = Box; + pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { + Ok(Dynamic::from(super::increment(x, y))) + } } }; @@ -954,6 +1021,10 @@ mod generate_tests { pub fn token_input_types() -> Box<[std::any::TypeId]> { Token().input_types() } + type EvalBox = Box; + pub fn dynamic_result_fn(message: &str) -> Result { + Ok(Dynamic::from(super::special_print(message))) + } } }; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 8cc5bbd4..9a526883 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -35,7 +35,7 @@ //! } //! ``` //! -//! # Exporting a Function to Rhai +//! # Exporting a Function to a Rhai Module //! //! ``` //! use rhai::{EvalAltResult, FLOAT, Module, RegisterFn}; @@ -52,7 +52,7 @@ //! let mut engine = Engine::new(); //! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); //! let mut m = Module::new(); -//! rhai::register_exported_fn!(m, "euclidean_distance", distance_function); +//! rhai::set_exported_fn!(m, "euclidean_distance", distance_function); //! let mut r = StaticModuleResolver::new(); //! r.insert("Math::Advanced".to_string(), m); //! engine.set_module_resolver(Some(r)); @@ -66,12 +66,39 @@ //! } //! ``` //! +//! # Exporting a Function to an Engine +//! +//! ``` +//! use rhai::{EvalAltResult, FLOAT, Module, RegisterFn}; +//! use rhai::plugin::*; +//! use rhai::module_resolvers::*; +//! +//! #[rhai::export_fn] +//! pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { +//! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() +//! } +//! +//! fn main() -> Result<(), Box> { +//! +//! let mut engine = Engine::new(); +//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); +//! rhai::register_exported_fn!(engine, "euclidean_distance", distance_function); +//! +//! assert_eq!(engine.eval::( +//! r#"let m = get_mystic_number(); +//! let x = euclidean_distance(0.0, 1.0, 0.0, m); +//! x"#)?, 41.0); +//! Ok(()) +//! } +//! ``` +//! -use quote::{quote, quote_spanned, ToTokens}; -use syn::{parse::Parser, parse_macro_input, spanned::Spanned}; +use quote::{quote, ToTokens}; +use syn::parse_macro_input; mod function; mod module; +mod register; mod rhai_module; #[proc_macro_attribute] @@ -118,47 +145,30 @@ pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::Toke #[proc_macro] pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { - let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; - let args = parser.parse(args).unwrap(); - let arg_span = args.span(); - let items: Vec = args.into_iter().collect(); - if items.len() != 3 { - return proc_macro::TokenStream::from( - syn::Error::new(arg_span, "this macro requires three arguments").to_compile_error(), - ); - } - let rhai_module = &items[0]; - let export_name = match &items[1] { - syn::Expr::Lit(litstr) => quote_spanned!(items[1].span()=> - #litstr.to_string()), - expr => quote! { #expr }, - }; - let rust_modpath = if let syn::Expr::Path(ref path) = &items[2] { - &path.path - } else { - return proc_macro::TokenStream::from( - syn::Error::new(items[2].span(), "third argument must be a function name") - .to_compile_error(), - ); - }; - let gen_mod_path: syn::punctuated::Punctuated = { - let mut g = rust_modpath.clone().segments; - g.pop(); - let ident = syn::Ident::new( - &format!("rhai_fn_{}", rust_modpath.segments.last().unwrap().ident), - items[2].span(), - ); - g.push_value(syn::PathSegment { - ident, - arguments: syn::PathArguments::None, - }); - g + let (engine_expr, export_name, rust_modpath) = match crate::register::parse_register_macro(args) + { + Ok(triple) => triple, + Err(e) => return e.to_compile_error().into(), }; + let gen_mod_path = crate::register::generated_module_path(&rust_modpath); let tokens = quote! { - #rhai_module.set_fn(#export_name, rhai::FnAccess::Public, - #gen_mod_path::token_input_types().as_ref(), - #gen_mod_path::token_callable()); - + #engine_expr.register_result_fn(&(#export_name), #gen_mod_path::dynamic_result_fn); + }; + proc_macro::TokenStream::from(tokens) +} + +#[proc_macro] +pub fn set_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { + let (module_expr, export_name, rust_modpath) = match crate::register::parse_register_macro(args) + { + Ok(triple) => triple, + Err(e) => return e.to_compile_error().into(), + }; + let gen_mod_path = crate::register::generated_module_path(&rust_modpath); + let tokens = quote! { + #module_expr.set_fn(#export_name, rhai::FnAccess::Public, + #gen_mod_path::token_input_types().as_ref(), + #gen_mod_path::token_callable()); }; proc_macro::TokenStream::from(tokens) } diff --git a/codegen/src/register.rs b/codegen/src/register.rs new file mode 100644 index 00000000..321769b7 --- /dev/null +++ b/codegen/src/register.rs @@ -0,0 +1,49 @@ +use quote::{quote, quote_spanned}; +use syn::{parse::Parser, spanned::Spanned}; + +pub(crate) fn generated_module_path( + fn_path: &syn::Path, +) -> syn::punctuated::Punctuated { + let mut g = fn_path.clone().segments; + g.pop(); + let ident = syn::Ident::new( + &format!("rhai_fn_{}", fn_path.segments.last().unwrap().ident), + fn_path.span(), + ); + g.push_value(syn::PathSegment { + ident, + arguments: syn::PathArguments::None, + }); + g +} + +type RegisterMacroInput = (syn::Expr, proc_macro2::TokenStream, syn::Path); +pub fn parse_register_macro( + args: proc_macro::TokenStream, +) -> Result { + let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; + let args = parser.parse(args).unwrap(); + let arg_span = args.span(); + let mut items: Vec = args.into_iter().collect(); + if items.len() != 3 { + return Err(syn::Error::new( + arg_span, + "this macro requires three arguments", + )); + } + let export_name = match &items[1] { + syn::Expr::Lit(litstr) => quote_spanned!(items[1].span()=> + #litstr.to_string()), + expr => quote! { #expr }, + }; + let rust_modpath = if let syn::Expr::Path(ref path) = &items[2] { + path.path.clone() + } else { + return Err(syn::Error::new( + items[2].span(), + "third argument must be a function name", + )); + }; + let module = items.remove(0); + Ok((module, export_name, rust_modpath)) +} diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 876fccf5..b61ae340 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -16,7 +16,7 @@ fn raw_fn_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!( + rhai::set_exported_fn!( m, "euclidean_distance".to_string(), raw_fn::distance_function @@ -50,7 +50,7 @@ fn raw_fn_mut_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); + rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); let mut r = StaticModuleResolver::new(); r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); @@ -82,7 +82,7 @@ fn raw_fn_str_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); + rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); let mut r = StaticModuleResolver::new(); r.insert("Host::IO".to_string(), m); engine.set_module_resolver(Some(r)); @@ -138,9 +138,9 @@ mod mut_opaque_ref { fn mut_opaque_ref_test() -> Result<(), Box> { let mut engine = Engine::new(); let mut m = Module::new(); - rhai::register_exported_fn!(m, "new_message", mut_opaque_ref::new_message); - rhai::register_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); - rhai::register_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); + rhai::set_exported_fn!(m, "new_message", mut_opaque_ref::new_message); + rhai::set_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); + rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); let mut r = StaticModuleResolver::new(); r.insert("Host::Msg".to_string(), m); engine.set_module_resolver(Some(r)); @@ -174,7 +174,7 @@ fn rename_fn_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!(m, "add_two_floats", rename_fn::add_float); + rhai::set_exported_fn!(m, "add_two_floats", rename_fn::add_float); let mut r = StaticModuleResolver::new(); r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); @@ -211,8 +211,8 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!(m, "add_two_floats", duplicate_fn_rename::add_float); - rhai::register_exported_fn!(m, "add_two_ints", duplicate_fn_rename::add_int); + rhai::set_exported_fn!(m, "add_two_floats", duplicate_fn_rename::add_float); + rhai::set_exported_fn!(m, "add_two_ints", duplicate_fn_rename::add_int); let mut r = StaticModuleResolver::new(); r.insert("Math::Advanced".to_string(), m); engine.set_module_resolver(Some(r)); @@ -224,7 +224,7 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { let ix = 42; let iy = math::add_two_ints(ix, 1); [fy, iy] - "# + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &43.0); assert_eq!(&output_array[1].as_int().unwrap(), &43); @@ -253,7 +253,7 @@ fn raw_returning_fn_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::register_exported_fn!( + rhai::set_exported_fn!( m, "euclidean_distance".to_string(), raw_returning_fn::distance_function diff --git a/src/plugin.rs b/src/plugin.rs index 0de6e63f..c9716ea3 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -11,6 +11,7 @@ pub use crate::{ ImmutableString, Module, Position, + RegisterResultFn, }; pub use rhai_codegen::*; From 1a2ef7b531c40568c91434c0a18f2082fd426079 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 12 Aug 2020 23:40:50 -0500 Subject: [PATCH 030/103] Fix macro_unroll test --- tests/macro_unroll.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs index fd0920ed..e994fa0e 100644 --- a/tests/macro_unroll.rs +++ b/tests/macro_unroll.rs @@ -33,9 +33,9 @@ macro_rules! register_in_bulk { $( { let type_str = stringify!($type_names); - register_exported_fn!($mod_name, - format!(concat!(stringify!($op_name), "_{}"), type_str), - crate::$op_name::$type_names::op); + set_exported_fn!($mod_name, + format!(concat!(stringify!($op_name), "_{}"), type_str), + crate::$op_name::$type_names::op); } )* } From a25f6b35a16de5a339b545c2eae4711dcdb01dfd Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 12 Aug 2020 23:46:12 -0500 Subject: [PATCH 031/103] Add new macro_register test which runs in no_std --- tests/macro_register.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/macro_register.rs diff --git a/tests/macro_register.rs b/tests/macro_register.rs new file mode 100644 index 00000000..99e7fe22 --- /dev/null +++ b/tests/macro_register.rs @@ -0,0 +1,17 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, INT}; + + +#[export_fn] +pub fn add_together(x: INT, y: INT) -> INT { + x + y +} + +#[test] +fn test_exported_fn_register() -> Result<(), Box> { + let mut engine = Engine::new(); + register_exported_fn!(engine, "add_two", add_together); + assert_eq!(engine.eval::("let a = 1; add_two(a, 41)")?, 42); + + Ok(()) +} From 0fb61eab00e0ff2fb626d0bf5295dc050284c40c Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 12 Aug 2020 23:47:57 -0500 Subject: [PATCH 032/103] Fix plugin test --- tests/plugins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins.rs b/tests/plugins.rs index 1b701ddd..02fdf7c0 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -22,7 +22,7 @@ fn test_plugins_package() -> Result<(), Box> { let mut engine = Engine::new(); let mut m = exported_module!(special_array_package); - register_exported_fn!(m, "greet", make_greeting); + set_exported_fn!(m, "greet", make_greeting); engine.load_package(m); From fdcaa90cd90a5426446751dade28229cd865e408 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 12 Aug 2020 23:57:51 -0500 Subject: [PATCH 033/103] Clean up plugin module for no_module macro support --- src/lib.rs | 3 --- src/plugin.rs | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a8a783d1..4939e119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,10 +70,7 @@ mod module; mod optimize; pub mod packages; mod parser; -#[cfg(not(feature = "no_module"))] pub mod plugin; -#[cfg(feature = "no_module")] -mod plugin; mod result; mod scope; #[cfg(feature = "serde")] diff --git a/src/plugin.rs b/src/plugin.rs index c9716ea3..23e7f03e 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -14,6 +14,9 @@ pub use crate::{ RegisterResultFn, }; +#[cfg(features = "no_module")] +pub use rhai_codegen::{export_fn, register_exported_fn}; +#[cfg(not(features = "no_module"))] pub use rhai_codegen::*; #[cfg(features = "sync")] From 631c4be064acd2a4248dd4a498d024063662cf2d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Aug 2020 14:57:23 +0800 Subject: [PATCH 034/103] rhai::FnAccess -> FnAccess to enable use in internal code. --- codegen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 9a526883..2e5d0860 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -166,7 +166,7 @@ pub fn set_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream }; let gen_mod_path = crate::register::generated_module_path(&rust_modpath); let tokens = quote! { - #module_expr.set_fn(#export_name, rhai::FnAccess::Public, + #module_expr.set_fn(#export_name, FnAccess::Public, #gen_mod_path::token_input_types().as_ref(), #gen_mod_path::token_callable()); }; From b956639c2a74c3c9967648c0e0918e1fc74f8ecf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 13 Aug 2020 14:57:46 +0800 Subject: [PATCH 035/103] Move some math functions into plugins. --- src/packages/math_basic.rs | 173 ++++++++++++++++++++++++++----------- tests/math.rs | 9 ++ 2 files changed, 131 insertions(+), 51 deletions(-) diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 0818e8fe..389a7bec 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,5 +1,7 @@ use crate::def_package; +use crate::module::Module; use crate::parser::INT; +use crate::plugin::*; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -22,35 +24,105 @@ pub const MAX_INT: INT = i32::MAX; pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { + init_module(lib); +}); + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod trig { + use crate::parser::FLOAT; + + pub fn sin(x: FLOAT) -> FLOAT { + x.to_radians().sin() + } + pub fn cos(x: FLOAT) -> FLOAT { + x.to_radians().cos() + } + pub fn tan(x: FLOAT) -> FLOAT { + x.to_radians().tan() + } + pub fn sinh(x: FLOAT) -> FLOAT { + x.to_radians().sinh() + } + pub fn cosh(x: FLOAT) -> FLOAT { + x.to_radians().cosh() + } + pub fn tanh(x: FLOAT) -> FLOAT { + x.to_radians().tanh() + } + pub fn asin(x: FLOAT) -> FLOAT { + x.asin().to_degrees() + } + pub fn acos(x: FLOAT) -> FLOAT { + x.acos().to_degrees() + } + pub fn atan(x: FLOAT) -> FLOAT { + x.atan().to_degrees() + } + pub fn asinh(x: FLOAT) -> FLOAT { + x.asinh().to_degrees() + } + pub fn acosh(x: FLOAT) -> FLOAT { + x.acosh().to_degrees() + } + pub fn atanh(x: FLOAT) -> FLOAT { + x.atanh().to_degrees() + } +} + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod float { + use crate::parser::FLOAT; + + pub fn sqrt(x: FLOAT) -> FLOAT { + x.sqrt() + } + pub fn exp(x: FLOAT) -> FLOAT { + x.exp() + } + pub fn ln(x: FLOAT) -> FLOAT { + x.ln() + } + pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { + x.log(base) + } + pub fn log10(x: FLOAT) -> FLOAT { + x.log10() + } + pub fn floor(x: FLOAT) -> FLOAT { + x.floor() + } + pub fn ceiling(x: FLOAT) -> FLOAT { + x.ceil() + } + pub fn round(x: FLOAT) -> FLOAT { + x.ceil() + } + pub fn int(x: FLOAT) -> FLOAT { + x.trunc() + } + pub fn fraction(x: FLOAT) -> FLOAT { + x.fract() + } + pub fn is_nan(x: FLOAT) -> bool { + x.is_nan() + } + pub fn is_finite(x: FLOAT) -> bool { + x.is_finite() + } + pub fn is_infinite(x: FLOAT) -> bool { + x.is_infinite() + } +} + +fn init_module(lib: &mut Module) { #[cfg(not(feature = "no_float"))] { - // Advanced math functions - lib.set_fn_1("sin", |x: FLOAT| Ok(x.to_radians().sin())); - lib.set_fn_1("cos", |x: FLOAT| Ok(x.to_radians().cos())); - lib.set_fn_1("tan", |x: FLOAT| Ok(x.to_radians().tan())); - lib.set_fn_1("sinh", |x: FLOAT| Ok(x.to_radians().sinh())); - lib.set_fn_1("cosh", |x: FLOAT| Ok(x.to_radians().cosh())); - lib.set_fn_1("tanh", |x: FLOAT| Ok(x.to_radians().tanh())); - lib.set_fn_1("asin", |x: FLOAT| Ok(x.asin().to_degrees())); - lib.set_fn_1("acos", |x: FLOAT| Ok(x.acos().to_degrees())); - lib.set_fn_1("atan", |x: FLOAT| Ok(x.atan().to_degrees())); - lib.set_fn_1("asinh", |x: FLOAT| Ok(x.asinh().to_degrees())); - lib.set_fn_1("acosh", |x: FLOAT| Ok(x.acosh().to_degrees())); - lib.set_fn_1("atanh", |x: FLOAT| Ok(x.atanh().to_degrees())); - lib.set_fn_1("sqrt", |x: FLOAT| Ok(x.sqrt())); - lib.set_fn_1("exp", |x: FLOAT| Ok(x.exp())); - lib.set_fn_1("ln", |x: FLOAT| Ok(x.ln())); - lib.set_fn_2("log", |x: FLOAT, base: FLOAT| Ok(x.log(base))); - lib.set_fn_1("log10", |x: FLOAT| Ok(x.log10())); - lib.set_fn_1("floor", |x: FLOAT| Ok(x.floor())); - lib.set_fn_1("ceiling", |x: FLOAT| Ok(x.ceil())); - lib.set_fn_1("round", |x: FLOAT| Ok(x.ceil())); - lib.set_fn_1("int", |x: FLOAT| Ok(x.trunc())); - lib.set_fn_1("fraction", |x: FLOAT| Ok(x.fract())); - lib.set_fn_1("is_nan", |x: FLOAT| Ok(x.is_nan())); - lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite())); - lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite())); + // Floating point functions + lib.merge(&exported_module!(float)); + // Floating point properties #[cfg(not(feature = "no_object"))] { lib.set_getter_fn("floor", |x: &mut FLOAT| Ok(x.floor())); @@ -63,6 +135,9 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_getter_fn("is_infinite", |x: &mut FLOAT| Ok(x.is_infinite())); } + // Trig functions + lib.merge(&exported_module!(trig)); + // Register conversion functions lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); @@ -105,32 +180,28 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] { if cfg!(not(feature = "unchecked")) { - lib.set_fn_1( - "to_int", - |x: f32| { - if x > (MAX_INT as f32) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - ).into(); - } + lib.set_fn_1("to_int", |x: f32| { + if x > (MAX_INT as f32) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + ) + .into(); + } - Ok(x.trunc() as INT) - }, - ); - lib.set_fn_1( - "to_int", - |x: FLOAT| { - if x > (MAX_INT as FLOAT) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - ).into(); - } + Ok(x.trunc() as INT) + }); + lib.set_fn_1("to_int", |x: FLOAT| { + if x > (MAX_INT as FLOAT) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + ) + .into(); + } - Ok(x.trunc() as INT) - }, - ); + Ok(x.trunc() as INT) + }); } if cfg!(feature = "unchecked") { @@ -138,4 +209,4 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } } -}); +} diff --git a/tests/math.rs b/tests/math.rs index 4e4a5d4b..c12e3e34 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,5 +1,8 @@ use rhai::{Engine, EvalAltResult, INT}; +#[cfg(not(feature = "no_float"))] +use rhai::FLOAT; + #[test] fn test_math() -> Result<(), Box> { let engine = Engine::new(); @@ -10,6 +13,12 @@ fn test_math() -> Result<(), Box> { assert_eq!(engine.eval::("1 / 2")?, 0); assert_eq!(engine.eval::("3 % 2")?, 1); + #[cfg(not(feature = "no_float"))] + assert!((engine.eval::("sin(30.0)")? - 0.5).abs() < 0.001); + + #[cfg(not(feature = "no_float"))] + assert!(engine.eval::("cos(90.0)")?.abs() < 0.001); + #[cfg(not(feature = "only_i32"))] assert_eq!( engine.eval::("abs(-9223372036854775807)")?, From e77dc4cc3423e8b5233858ffa8040766b2d6d32d Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 13 Aug 2020 22:00:01 -0500 Subject: [PATCH 036/103] Fix missing import in test_functions --- codegen/tests/test_functions.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index b61ae340..cfc73275 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -1,4 +1,5 @@ use rhai::module_resolvers::*; +use rhai::plugin::*; use rhai::{Array, Engine, EvalAltResult, Module, RegisterFn, FLOAT}; pub mod raw_fn { From b27ebb318d43fe4559ce22d4a8ed95d32a079e3f Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 13 Aug 2020 23:04:24 -0500 Subject: [PATCH 037/103] Rearrange imports to support no_std --- codegen/src/function.rs | 69 +++++++++++++++++++++-------------------- codegen/src/module.rs | 52 ++++++++++++++++++------------- src/plugin.rs | 7 +++-- 3 files changed, 71 insertions(+), 57 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0c8391bb..f3bfd344 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -1,5 +1,8 @@ #![allow(unused)] +#[cfg(no_std)] +use core::mem; + use std::collections::HashMap; use quote::{quote, quote_spanned}; @@ -329,7 +332,7 @@ impl ExportedFn { self.name().span(), ); quote! { - pub fn #input_types_fn_name() -> Box<[std::any::TypeId]> { + pub fn #input_types_fn_name() -> Box<[TypeId]> { #token_name().input_types() } } @@ -375,7 +378,7 @@ impl ExportedFn { ); input_type_exprs.push( syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::<#arg_type>() + arg_type.span()=> TypeId::of::<#arg_type>() )) .unwrap(), ); @@ -407,7 +410,7 @@ impl ExportedFn { &syn::Type::Path(ref p) if p.path == str_type_path => { is_str_ref = true; quote_spanned!(arg_type.span()=> - std::mem::take(args[#i]) + mem::take(args[#i]) .clone().cast::()) } _ => panic!("internal error: why wasn't this found earlier!?"), @@ -415,7 +418,7 @@ impl ExportedFn { _ => { is_str_ref = false; quote_spanned!(arg_type.span()=> - std::mem::take(args[#i]).clone().cast::<#arg_type>()) + mem::take(args[#i]).clone().cast::<#arg_type>()) } }; @@ -428,14 +431,14 @@ impl ExportedFn { if !is_str_ref { input_type_exprs.push( syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::<#arg_type>() + arg_type.span()=> TypeId::of::<#arg_type>() )) .unwrap(), ); } else { input_type_exprs.push( syn::parse2::(quote_spanned!( - arg_type.span()=> std::any::TypeId::of::() + arg_type.span()=> TypeId::of::() )) .unwrap(), ); @@ -489,8 +492,8 @@ impl ExportedFn { fn is_method_call(&self) -> bool { #is_method_call } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(#type_name()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![#(#input_type_exprs),*].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![#(#input_type_exprs),*].into_boxed_slice() } } } @@ -781,14 +784,14 @@ mod generate_tests { fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() } } pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn token_input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } type EvalBox = Box; @@ -822,21 +825,21 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(do_something(arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() } } pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn token_input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } type EvalBox = Box; @@ -866,15 +869,15 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(do_something(arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(MyType()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() } } }; @@ -903,23 +906,23 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 2usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); - let arg1 = std::mem::take(args[1usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::(), - std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() } } pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn token_input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } type EvalBox = Box; @@ -953,7 +956,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 2usize), Position::none()))); } - let arg1 = std::mem::take(args[1usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } @@ -961,15 +964,15 @@ mod generate_tests { fn is_method_call(&self) -> bool { true } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::(), - std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() } } pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn token_input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } type EvalBox = Box; @@ -1004,21 +1007,21 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(special_print(&arg0))) } fn is_method_call(&self) -> bool { false } fn is_varadic(&self) -> bool { false } fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() } } pub fn token_callable() -> CallableFunction { CallableFunction::from_plugin(Token()) } - pub fn token_input_types() -> Box<[std::any::TypeId]> { + pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } type EvalBox = Box; diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 256ca062..9e042bb9 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -4,6 +4,14 @@ use syn::{parse::Parse, parse::ParseStream}; use crate::function::ExportedFn; use crate::rhai_module::ExportedConst; +#[cfg(no_std)] +use alloc::vec as new_vec; +#[cfg(not(no_std))] +use std::vec as new_vec; + +#[cfg(no_std)] +use core::mem; + #[derive(Debug)] pub(crate) struct Module { mod_all: Option, @@ -53,8 +61,8 @@ impl Parse for Module { }) .collect(); } else { - consts = vec![]; - fns = vec![]; + consts = new_vec![]; + fns = new_vec![]; } Ok(Module { mod_all: Some(mod_all), @@ -319,14 +327,14 @@ mod generate_tests { fn clone_boxed(&self) -> Box { Box::new(get_mystic_number_token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() } } pub fn get_mystic_number_token_callable() -> CallableFunction { CallableFunction::from_plugin(get_mystic_number_token()) } - pub fn get_mystic_number_token_input_types() -> Box<[std::any::TypeId]> { + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { get_mystic_number_token().input_types() } } @@ -371,7 +379,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(add_one_to(arg0))) } @@ -380,14 +388,14 @@ mod generate_tests { fn clone_boxed(&self) -> Box { Box::new(add_one_to_token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() } } pub fn add_one_to_token_callable() -> CallableFunction { CallableFunction::from_plugin(add_one_to_token()) } - pub fn add_one_to_token_input_types() -> Box<[std::any::TypeId]> { + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { add_one_to_token().input_types() } } @@ -433,8 +441,8 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 2usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); - let arg1 = std::mem::take(args[1usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } @@ -443,15 +451,15 @@ mod generate_tests { fn clone_boxed(&self) -> Box { Box::new(add_together_token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::(), - std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() } } pub fn add_together_token_callable() -> CallableFunction { CallableFunction::from_plugin(add_together_token()) } - pub fn add_together_token_input_types() -> Box<[std::any::TypeId]> { + pub fn add_together_token_input_types() -> Box<[TypeId]> { add_together_token().input_types() } } @@ -605,7 +613,7 @@ mod generate_tests { format!("wrong arg count: {} != {}", args.len(), 1usize), Position::none()))); } - let arg0 = std::mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(print_out_to(&arg0))) } @@ -614,14 +622,14 @@ mod generate_tests { fn clone_boxed(&self) -> Box { Box::new(print_out_to_token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() } } pub fn print_out_to_token_callable() -> CallableFunction { CallableFunction::from_plugin(print_out_to_token()) } - pub fn print_out_to_token_input_types() -> Box<[std::any::TypeId]> { + pub fn print_out_to_token_input_types() -> Box<[TypeId]> { print_out_to_token().input_types() } } @@ -676,14 +684,14 @@ mod generate_tests { fn clone_boxed(&self) -> Box { Box::new(increment_token()) } - fn input_types(&self) -> Box<[std::any::TypeId]> { - vec![std::any::TypeId::of::()].into_boxed_slice() + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() } } pub fn increment_token_callable() -> CallableFunction { CallableFunction::from_plugin(increment_token()) } - pub fn increment_token_input_types() -> Box<[std::any::TypeId]> { + pub fn increment_token_input_types() -> Box<[TypeId]> { increment_token().input_types() } } diff --git a/src/plugin.rs b/src/plugin.rs index 23e7f03e..19e9939a 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,8 +1,11 @@ //! Module defining plugins in Rhai. Is exported for use by plugin authors. -use crate::stdlib::{any::TypeId, boxed::Box}; - pub use crate::{ + stdlib::any::TypeId, + stdlib::boxed::Box, + stdlib::vec::Vec, + stdlib::vec as new_vec, + stdlib::mem, fn_native::CallableFunction, Dynamic, Engine, From c0dc47c9db135fe678fce6dccf1e0e230a341d04 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 14 Aug 2020 13:43:26 +0800 Subject: [PATCH 038/103] Convert some packages into plugins. --- src/any.rs | 93 +++++++++---------- src/packages/logic.rs | 159 ++++++++++++++++++++++---------- src/packages/math_basic.rs | 183 ++++++++++++++++++------------------- src/packages/time_basic.rs | 153 ++++++++++++++++++------------- tests/plugins.rs | 34 +++++-- 5 files changed, 361 insertions(+), 261 deletions(-) diff --git a/src/any.rs b/src/any.rs index c154a682..7b1fa184 100644 --- a/src/any.rs +++ b/src/any.rs @@ -535,45 +535,47 @@ impl Dynamic { /// ``` #[inline(always)] pub fn from(value: T) -> Self { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return ().into(); } let mut boxed = Box::new(value); + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { + Ok(d) => return *d, + Err(val) => val, + }; boxed = match unsafe_cast_box::<_, String>(boxed) { Ok(s) => return (*s).into(), Err(val) => val, @@ -599,11 +601,6 @@ impl Dynamic { Err(val) => val, }; - boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { - Ok(d) => return *d, - Err(val) => val, - }; - Self(Union::Variant(Box::new(boxed))) } @@ -660,8 +657,6 @@ impl Dynamic { /// ``` #[inline(always)] pub fn try_cast(self) -> Option { - let type_id = TypeId::of::(); - match self.0 { #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] @@ -673,11 +668,11 @@ impl Dynamic { _ => (), } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), _ => None, @@ -685,35 +680,35 @@ impl Dynamic { } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Float(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Bool(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value.into_owned()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Char(value) => unsafe_try_cast(value), _ => None, @@ -721,7 +716,7 @@ impl Dynamic { } #[cfg(not(feature = "no_index"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, @@ -729,21 +724,21 @@ impl Dynamic { } #[cfg(not(feature = "no_object"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return match self.0 { Union::Unit(value) => unsafe_try_cast(value), _ => None, @@ -928,72 +923,70 @@ impl Dynamic { /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] pub(crate) fn downcast_ref(&self) -> Option<&T> { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Int(value) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Float(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Bool(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Str(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Str(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Char(value) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Array(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(not(feature = "no_object"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Map(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::FnPtr(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return match &self.0 { Union::Unit(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(self); } @@ -1011,66 +1004,64 @@ impl Dynamic { /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Int(value) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Float(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Bool(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Str(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Char(value) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Array(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(not(feature = "no_object"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Map(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::FnPtr(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return match &mut self.0 { Union::Unit(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_mut::(self); } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 6c70a38c..22f60d93 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,65 +1,126 @@ use crate::def_package; -use crate::module::FuncReturn; +use crate::plugin::*; -// Comparison operators -pub fn lt(x: T, y: T) -> FuncReturn { - Ok(x < y) -} -pub fn lte(x: T, y: T) -> FuncReturn { - Ok(x <= y) -} -pub fn gt(x: T, y: T) -> FuncReturn { - Ok(x > y) -} -pub fn gte(x: T, y: T) -> FuncReturn { - Ok(x >= y) -} -pub fn eq(x: T, y: T) -> FuncReturn { - Ok(x == y) -} -pub fn ne(x: T, y: T) -> FuncReturn { - Ok(x != y) +macro_rules! gen_cmp_functions { + ($op_name:tt = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { + pub mod $op_fn { $( + pub mod $arg_type { + use crate::plugin::*; + + pub const OP_NAME: &'static str = $op_name; + + #[export_fn] + pub fn cmp_func(x: $arg_type, y: $arg_type) -> $return_type { + super::super::super::$op_fn(x, y) + } + } + )* } + } } -// Logic operators -fn not(x: bool) -> FuncReturn { - Ok(!x) -} - -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2($op, $func::<$par>); )* - }; +macro_rules! reg_functions { + ($mod_name:ident += $root:ident :: $op_name:ident ( $($arg_type:ident),+ )) => { + $(set_exported_fn!($mod_name, $root::$op_name::$arg_type::OP_NAME, $root::$op_name::$arg_type::cmp_func);)* + } } def_package!(crate:LogicPackage:"Logical operators.", lib, { - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += cmp::lt(i8, u8, i16, u16, i32, u32, u64)); + reg_functions!(lib += cmp::lte(i8, u8, i16, u16, i32, u32, u64)); + reg_functions!(lib += cmp::gt(i8, u8, i16, u16, i32, u32, u64)); + reg_functions!(lib += cmp::gte(i8, u8, i16, u16, i32, u32, u64)); + reg_functions!(lib += cmp::eq(i8, u8, i16, u16, i32, u32, u64)); + reg_functions!(lib += cmp::ne(i8, u8, i16, u16, i32, u32, u64)); - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "<", lt, i128, u128); - reg_op!(lib, "<=", lte, i128, u128); - reg_op!(lib, ">", gt, i128, u128); - reg_op!(lib, ">=", gte, i128, u128); - reg_op!(lib, "==", eq, i128, u128); - reg_op!(lib, "!=", ne, i128, u128); + #[cfg(not(target_arch = "wasm32"))] + { + reg_functions!(lib += cmp_128::lt(i128, u128)); + reg_functions!(lib += cmp_128::lte(i128, u128)); + reg_functions!(lib += cmp_128::gt(i128, u128)); + reg_functions!(lib += cmp_128::gte(i128, u128)); + reg_functions!(lib += cmp_128::eq(i128, u128)); + reg_functions!(lib += cmp_128::ne(i128, u128)); } } #[cfg(not(feature = "no_float"))] { - reg_op!(lib, "<", lt, f32); - reg_op!(lib, "<=", lte, f32); - reg_op!(lib, ">", gt, f32); - reg_op!(lib, ">=", gte, f32); - reg_op!(lib, "==", eq, f32); - reg_op!(lib, "!=", ne, f32); + reg_functions!(lib += cmp_float::lt(f32)); + reg_functions!(lib += cmp_float::lte(f32)); + reg_functions!(lib += cmp_float::gt(f32)); + reg_functions!(lib += cmp_float::gte(f32)); + reg_functions!(lib += cmp_float::eq(f32)); + reg_functions!(lib += cmp_float::ne(f32)); } - lib.set_fn_1("!", not); + set_exported_fn!(lib, "!", not); }); + +// Comparison operators +#[inline(always)] +pub fn lt(x: T, y: T) -> bool { + x < y +} +#[inline(always)] +pub fn lte(x: T, y: T) -> bool { + x <= y +} +#[inline(always)] +pub fn gt(x: T, y: T) -> bool { + x > y +} +#[inline(always)] +pub fn gte(x: T, y: T) -> bool { + x >= y +} +#[inline(always)] +pub fn eq(x: T, y: T) -> bool { + x == y +} +#[inline(always)] +pub fn ne(x: T, y: T) -> bool { + x != y +} + +// Logic operators +#[export_fn] +#[inline(always)] +fn not(x: bool) -> bool { + !x +} + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +mod cmp { + gen_cmp_functions!("<" = lt(i8, u8, i16, u16, i32, u32, u64) -> bool); + gen_cmp_functions!("<=" = lte(i8, u8, i16, u16, i32, u32, u64) -> bool); + gen_cmp_functions!(">" = gt(i8, u8, i16, u16, i32, u32, u64) -> bool); + gen_cmp_functions!(">=" = gte(i8, u8, i16, u16, i32, u32, u64) -> bool); + gen_cmp_functions!("==" = eq(i8, u8, i16, u16, i32, u32, u64) -> bool); + gen_cmp_functions!("!=" = ne(i8, u8, i16, u16, i32, u32, u64) -> bool); +} + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +mod cmp_128 { + gen_cmp_functions!("<" = lt(i128, u128) -> bool); + gen_cmp_functions!("<=" = lte(i128, u128) -> bool); + gen_cmp_functions!(">" = gt(i128, u128) -> bool); + gen_cmp_functions!(">=" = gte(i128, u128) -> bool); + gen_cmp_functions!("==" = eq(i128, u128) -> bool); + gen_cmp_functions!("!=" = ne(i128, u128) -> bool); +} + +#[cfg(not(feature = "no_float"))] +mod cmp_float { + gen_cmp_functions!("<" = lt(f32) -> bool); + gen_cmp_functions!("<=" = lte(f32) -> bool); + gen_cmp_functions!(">" = gt(f32) -> bool); + gen_cmp_functions!(">=" = gte(f32) -> bool); + gen_cmp_functions!("==" = eq(f32) -> bool); + gen_cmp_functions!("!=" = ne(f32) -> bool); +} diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 389a7bec..322626e7 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,5 +1,4 @@ use crate::def_package; -use crate::module::Module; use crate::parser::INT; use crate::plugin::*; @@ -24,99 +23,6 @@ pub const MAX_INT: INT = i32::MAX; pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { - init_module(lib); -}); - -#[cfg(not(feature = "no_float"))] -#[export_module] -mod trig { - use crate::parser::FLOAT; - - pub fn sin(x: FLOAT) -> FLOAT { - x.to_radians().sin() - } - pub fn cos(x: FLOAT) -> FLOAT { - x.to_radians().cos() - } - pub fn tan(x: FLOAT) -> FLOAT { - x.to_radians().tan() - } - pub fn sinh(x: FLOAT) -> FLOAT { - x.to_radians().sinh() - } - pub fn cosh(x: FLOAT) -> FLOAT { - x.to_radians().cosh() - } - pub fn tanh(x: FLOAT) -> FLOAT { - x.to_radians().tanh() - } - pub fn asin(x: FLOAT) -> FLOAT { - x.asin().to_degrees() - } - pub fn acos(x: FLOAT) -> FLOAT { - x.acos().to_degrees() - } - pub fn atan(x: FLOAT) -> FLOAT { - x.atan().to_degrees() - } - pub fn asinh(x: FLOAT) -> FLOAT { - x.asinh().to_degrees() - } - pub fn acosh(x: FLOAT) -> FLOAT { - x.acosh().to_degrees() - } - pub fn atanh(x: FLOAT) -> FLOAT { - x.atanh().to_degrees() - } -} - -#[cfg(not(feature = "no_float"))] -#[export_module] -mod float { - use crate::parser::FLOAT; - - pub fn sqrt(x: FLOAT) -> FLOAT { - x.sqrt() - } - pub fn exp(x: FLOAT) -> FLOAT { - x.exp() - } - pub fn ln(x: FLOAT) -> FLOAT { - x.ln() - } - pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { - x.log(base) - } - pub fn log10(x: FLOAT) -> FLOAT { - x.log10() - } - pub fn floor(x: FLOAT) -> FLOAT { - x.floor() - } - pub fn ceiling(x: FLOAT) -> FLOAT { - x.ceil() - } - pub fn round(x: FLOAT) -> FLOAT { - x.ceil() - } - pub fn int(x: FLOAT) -> FLOAT { - x.trunc() - } - pub fn fraction(x: FLOAT) -> FLOAT { - x.fract() - } - pub fn is_nan(x: FLOAT) -> bool { - x.is_nan() - } - pub fn is_finite(x: FLOAT) -> bool { - x.is_finite() - } - pub fn is_infinite(x: FLOAT) -> bool { - x.is_infinite() - } -} - -fn init_module(lib: &mut Module) { #[cfg(not(feature = "no_float"))] { // Floating point functions @@ -209,4 +115,93 @@ fn init_module(lib: &mut Module) { lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } } +}); + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod trig { + use crate::parser::FLOAT; + + pub fn sin(x: FLOAT) -> FLOAT { + x.to_radians().sin() + } + pub fn cos(x: FLOAT) -> FLOAT { + x.to_radians().cos() + } + pub fn tan(x: FLOAT) -> FLOAT { + x.to_radians().tan() + } + pub fn sinh(x: FLOAT) -> FLOAT { + x.to_radians().sinh() + } + pub fn cosh(x: FLOAT) -> FLOAT { + x.to_radians().cosh() + } + pub fn tanh(x: FLOAT) -> FLOAT { + x.to_radians().tanh() + } + pub fn asin(x: FLOAT) -> FLOAT { + x.asin().to_degrees() + } + pub fn acos(x: FLOAT) -> FLOAT { + x.acos().to_degrees() + } + pub fn atan(x: FLOAT) -> FLOAT { + x.atan().to_degrees() + } + pub fn asinh(x: FLOAT) -> FLOAT { + x.asinh().to_degrees() + } + pub fn acosh(x: FLOAT) -> FLOAT { + x.acosh().to_degrees() + } + pub fn atanh(x: FLOAT) -> FLOAT { + x.atanh().to_degrees() + } +} + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod float { + use crate::parser::FLOAT; + + pub fn sqrt(x: FLOAT) -> FLOAT { + x.sqrt() + } + pub fn exp(x: FLOAT) -> FLOAT { + x.exp() + } + pub fn ln(x: FLOAT) -> FLOAT { + x.ln() + } + pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { + x.log(base) + } + pub fn log10(x: FLOAT) -> FLOAT { + x.log10() + } + pub fn floor(x: FLOAT) -> FLOAT { + x.floor() + } + pub fn ceiling(x: FLOAT) -> FLOAT { + x.ceil() + } + pub fn round(x: FLOAT) -> FLOAT { + x.ceil() + } + pub fn int(x: FLOAT) -> FLOAT { + x.trunc() + } + pub fn fraction(x: FLOAT) -> FLOAT { + x.fract() + } + pub fn is_nan(x: FLOAT) -> bool { + x.is_nan() + } + pub fn is_finite(x: FLOAT) -> bool { + x.is_finite() + } + pub fn is_infinite(x: FLOAT) -> bool { + x.is_infinite() + } } diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 34f6821d..61843b14 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -6,6 +6,8 @@ use super::logic::{eq, gt, gte, lt, lte, ne}; use super::math_basic::MAX_INT; use crate::def_package; +use crate::engine::make_getter; +use crate::plugin::*; use crate::result::EvalAltResult; #[cfg(not(feature = "no_float"))] @@ -26,83 +28,112 @@ use instant::Instant; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions - lib.set_fn_0("timestamp", || Ok(Instant::now())); + set_exported_fn!(lib, "timestamp", create_timestamp); + set_exported_fn!(lib, "elapsed", elapsed); - lib.set_fn_2( - "-", - |ts1: Instant, ts2: Instant| { - if ts2 > ts1 { - #[cfg(not(feature = "no_float"))] - return Ok(-(ts2 - ts1).as_secs_f64()); + #[cfg(not(feature = "no_object"))] + set_exported_fn!(lib, make_getter("elapsed"), elapsed); - #[cfg(feature = "no_float")] - { - let seconds = (ts2 - ts1).as_secs(); + set_exported_fn!(lib, "-", time_diff); - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!( - "Integer overflow for timestamp duration: {}", - -(seconds as i64) - ), - Position::none(), - ).into(); - } + //lib.merge(&exported_module!(time_compare)); - return Ok(-(seconds as INT)); - } - } else { - #[cfg(not(feature = "no_float"))] - return Ok((ts1 - ts2).as_secs_f64()); + lib.set_fn_2("<", |x:Instant, y:Instant| Ok(lt(x, y))); + lib.set_fn_2("<=", |x:Instant, y:Instant| Ok(lte(x, y))); + lib.set_fn_2(">", |x:Instant, y:Instant| Ok(gt(x, y))); + lib.set_fn_2(">=", |x:Instant, y:Instant| Ok(gte(x, y))); + lib.set_fn_2("==", |x:Instant, y:Instant| Ok(eq(x, y))); + lib.set_fn_2("!=", |x:Instant, y:Instant| Ok(ne(x, y))); +}); - #[cfg(feature = "no_float")] - { - let seconds = (ts1 - ts2).as_secs(); +#[export_fn] +fn create_timestamp() -> Instant { + Instant::now() +} - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: {}", seconds), - Position::none(), - ).into(); - } +#[cfg(not(feature = "no_float"))] +#[export_fn] +fn elapsed(timestamp: &mut Instant) -> FLOAT { + timestamp.elapsed().as_secs_f64() as FLOAT +} - return Ok(seconds as INT); - } - } - }, - ); +#[cfg(feature = "no_float")] +#[export_fn(return_raw)] +fn elapsed(timestamp: &mut Instant) -> Result> { + let seconds = timestamp.elapsed().as_secs(); - lib.set_fn_2("<", lt::); - lib.set_fn_2("<=", lte::); - lib.set_fn_2(">", gt::); - lib.set_fn_2(">=", gte::); - lib.set_fn_2("==", eq::); - lib.set_fn_2("!=", ne::); - - #[cfg(not(feature = "no_float"))] - fn elapsed (timestamp: &mut Instant) -> Result> { - Ok(timestamp.elapsed().as_secs_f64()) + #[cfg(not(feature = "unchecked"))] + if seconds > (MAX_INT as u64) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp.elapsed: {}", seconds), + Position::none(), + ) + .into(); } - #[cfg(feature = "no_float")] - fn elapsed (timestamp: &mut Instant) -> Result> { - let seconds = timestamp.elapsed().as_secs(); + Ok((seconds as INT).into()) +} + +#[cfg(not(feature = "no_float"))] +#[export_fn] +fn time_diff(ts1: Instant, ts2: Instant) -> FLOAT { + if ts2 > ts1 { + -(ts2 - ts1).as_secs_f64() as FLOAT + } else { + (ts1 - ts2).as_secs_f64() as FLOAT + } +} + +#[cfg(feature = "no_float")] +#[export_fn(return_raw)] +fn time_diff(ts1: Instant, ts2: Instant) -> Result> { + if ts2 > ts1 { + let seconds = (ts2 - ts1).as_secs(); #[cfg(not(feature = "unchecked"))] if seconds > (MAX_INT as u64) { return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp.elapsed: {}", seconds), + format!("Integer overflow for timestamp duration: -{}", seconds), Position::none(), - ).into(); + ) + .into(); } - Ok(seconds as INT) + Ok(-(seconds as INT).into()) + } else { + let seconds = (ts1 - ts2).as_secs(); + + #[cfg(not(feature = "unchecked"))] + if seconds > (MAX_INT as u64) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp duration: {}", seconds), + Position::none(), + ) + .into(); + } + + Ok((seconds as INT).into()) } +} - lib.set_fn_1_mut("elapsed", elapsed); - - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("elapsed", elapsed); -}); +#[export_module] +mod time_compare { + pub fn eq(x: Instant, y: Instant) -> bool { + x == y + } + pub fn ne(x: Instant, y: Instant) -> bool { + x != y + } + pub fn lt(x: Instant, y: Instant) -> bool { + x < y + } + pub fn lte(x: Instant, y: Instant) -> bool { + x <= y + } + pub fn gt(x: Instant, y: Instant) -> bool { + x > y + } + pub fn gte(x: Instant, y: Instant) -> bool { + x >= y + } +} diff --git a/tests/plugins.rs b/tests/plugins.rs index 02fdf7c0..b6399ced 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -12,20 +12,42 @@ mod special_array_package { } } -#[export_fn] -fn make_greeting(n: INT) -> String { - format!("{} {}", n, if n > 1 { "kitties" } else { "kitty" }).into() +macro_rules! gen_unary_functions { + ($op_name:ident = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { + mod $op_name { $( + pub mod $arg_type { + use super::super::*; + + #[export_fn] + pub fn single(x: $arg_type) -> $return_type { + super::super::$op_fn(x) + } + } + )* } + } } +macro_rules! reg_functions { + ($mod_name:ident += $op_name:ident :: $func:ident ( $($arg_type:ident),+ )) => { + $(register_exported_fn!($mod_name, stringify!($op_name), $op_name::$arg_type::$func);)* + } +} + +fn make_greeting(n: T) -> String { + format!("{} kitties", n) +} + +gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String); + #[test] fn test_plugins_package() -> Result<(), Box> { let mut engine = Engine::new(); - let mut m = exported_module!(special_array_package); - set_exported_fn!(m, "greet", make_greeting); - + let m = exported_module!(special_array_package); engine.load_package(m); + reg_functions!(engine += greet::single(INT, bool, char)); + assert_eq!(engine.eval::("let a = [1, 2, 3]; len(a, 2)")?, 6); assert_eq!( engine.eval::("let a = [1, 2, 3]; greet(len(a, 2))")?, From ceb1a267334f413277938185f48959afbc298d7c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 14 Aug 2020 18:58:34 +0800 Subject: [PATCH 039/103] Move more packages to plugins. --- src/packages/array_basic.rs | 260 ++++++++++++++++++++---------------- src/packages/eval.rs | 16 ++- src/packages/fn_basic.rs | 11 +- src/packages/logic.rs | 2 +- src/packages/time_basic.rs | 13 +- 5 files changed, 167 insertions(+), 135 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index fc6ca5ec..d173bcf2 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,11 +1,12 @@ #![cfg(not(feature = "no_index"))] +#![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; use crate::fn_native::FnPtr; -use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; @@ -15,26 +16,148 @@ use crate::stdlib::{any::TypeId, boxed::Box}; #[cfg(not(feature = "unchecked"))] use crate::stdlib::string::ToString; -// Register array utility functions -fn push(list: &mut Array, item: T) -> FuncReturn<()> { - list.push(Dynamic::from(item)); - Ok(()) +pub type Unit = (); + +macro_rules! gen_array_functions { + ($root:ident => $($arg_type:ident),+ ) => { + pub mod $root { $( + pub mod $arg_type { + use super::super::*; + + #[export_fn] + pub fn push_func(list: &mut Array, item: $arg_type) { + super::super::push(list, item); + } + + #[export_fn] + pub fn insert_func(list: &mut Array, len: INT, item: $arg_type) { + super::super::insert(list, len, item); + } + } + )* } + } } -fn ins(list: &mut Array, position: INT, item: T) -> FuncReturn<()> { + +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { + $(set_exported_fn!($mod_name, "push", $root::$arg_type::push_func);)* + $(set_exported_fn!($mod_name, "insert", $root::$arg_type::insert_func);)* + } +} + +macro_rules! reg_pad { + ($lib:expr, $($par:ty),*) => { + $({ + $lib.set_raw_fn("pad", + &[TypeId::of::(), TypeId::of::(), TypeId::of::<$par>()], + pad::<$par> + ); + })* + }; +} + +def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { + lib.merge(&exported_module!(array_functions)); + set_exported_fn!(lib, "+", append); + set_exported_fn!(lib, "+", concat); + + reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); + reg_pad!(lib, INT, bool, char, ImmutableString, FnPtr, Array, Unit); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, i64, u32, u64); + reg_pad!(lib, u8, i16, u16, i32, u32, i64, u64); + + #[cfg(not(target_arch = "wasm32"))] + { + reg_functions!(lib += num_128; i128, u128); + reg_pad!(lib, i128, u128); + } + } + + #[cfg(not(feature = "no_float"))] + { + reg_functions!(lib += float; f32, f64); + reg_pad!(lib, f32, f64); + } + + #[cfg(not(feature = "no_object"))] + lib.set_getter_fn("len", |list: &mut Array| Ok(list.len() as INT)); + + // Register array iterator + lib.set_iter( + TypeId::of::(), + |arr| Box::new(arr.cast::().into_iter()) as Box>, + ); +}); + +#[export_fn] +fn append(x: &mut Array, y: Array) { + x.extend(y); +} +#[export_fn] +fn concat(mut x: Array, y: Array) -> Array { + x.extend(y); + x +} + +#[export_module] +mod array_functions { + pub fn len(list: &mut Array) -> INT { + list.len() as INT + } + pub fn append(x: &mut Array, y: Array) { + x.extend(y); + } + pub fn pop(list: &mut Array) -> Dynamic { + list.pop().unwrap_or_else(|| ().into()) + } + pub fn shift(list: &mut Array) -> Dynamic { + if list.is_empty() { + ().into() + } else { + list.remove(0) + } + } + pub fn remove(list: &mut Array, len: INT) -> Dynamic { + if len < 0 || (len as usize) >= list.len() { + ().into() + } else { + list.remove(len as usize) + } + } + pub fn clear(list: &mut Array) { + list.clear(); + } + pub fn truncate(list: &mut Array, len: INT) { + if len >= 0 { + list.truncate(len as usize); + } else { + list.clear(); + } + } +} + +// Register array utility functions +fn push(list: &mut Array, item: T) { + list.push(Dynamic::from(item)); +} +fn insert(list: &mut Array, position: INT, item: T) { if position <= 0 { list.insert(0, Dynamic::from(item)); } else if (position as usize) >= list.len() - 1 { - push(list, item)?; + push(list, item); } else { list.insert(position as usize, Dynamic::from(item)); } - Ok(()) } fn pad( _engine: &Engine, _: &Module, args: &mut [&mut Dynamic], -) -> FuncReturn<()> { +) -> Result<(), Box> { let len = *args[1].read_lock::().unwrap(); // Check if array will be over max size limit @@ -63,115 +186,16 @@ fn pad( Ok(()) } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2_mut($op, $func::<$par>); )* - }; -} -macro_rules! reg_tri { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_3_mut($op, $func::<$par>); )* - }; -} -macro_rules! reg_pad { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $({ - $lib.set_raw_fn($op, - &[TypeId::of::(), TypeId::of::(), TypeId::of::<$par>()], - $func::<$par> - ); - })* - }; -} +gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit); -def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_op!(lib, "push", push, INT, bool, char, ImmutableString, FnPtr, Array, ()); - reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, FnPtr, Array, ()); - reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, FnPtr, Array, ()); +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_array_functions!(numbers => i8, u8, i16, u16, i32, i64, u32, u64); - lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { - x.extend(y); - Ok(()) - }); - lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| { - x.extend(y); - Ok(()) - }); - lib.set_fn_2( - "+", - |mut x: Array, y: Array| { - x.extend(y); - Ok(x) - }, - ); +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_array_functions!(num_128 => i128, u128); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); - reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); - reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "push", push, i128, u128); - reg_pad!(lib, "pad", pad, i128, u128); - reg_tri!(lib, "insert", ins, i128, u128); - } - } - - #[cfg(not(feature = "no_float"))] - { - reg_op!(lib, "push", push, f32, f64); - reg_pad!(lib, "pad", pad, f32, f64); - reg_tri!(lib, "insert", ins, f32, f64); - } - - lib.set_fn_1_mut( - "pop", - |list: &mut Array| Ok(list.pop().unwrap_or_else(|| ().into())), - ); - lib.set_fn_1_mut( - "shift", - |list: &mut Array| { - Ok(if list.is_empty() { - ().into() - } else { - list.remove(0) - }) - }, - ); - lib.set_fn_2_mut( - "remove", - |list: &mut Array, len: INT| { - Ok(if len < 0 || (len as usize) >= list.len() { - ().into() - } else { - list.remove(len as usize) - }) - }, - ); - lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT)); - - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("len", |list: &mut Array| Ok(list.len() as INT)); - - lib.set_fn_1_mut("clear", |list: &mut Array| { - list.clear(); - Ok(()) - }); - lib.set_fn_2_mut( - "truncate", - |list: &mut Array, len: INT| { - if len >= 0 { - list.truncate(len as usize); - } else { - list.clear(); - } - Ok(()) - }, - ); - - // Register array iterator - lib.set_iter( - TypeId::of::(), - |arr| Box::new(arr.cast::().into_iter()) as Box>, - ); -}); +#[cfg(not(feature = "no_float"))] +gen_array_functions!(float => f32, f64); diff --git a/src/packages/eval.rs b/src/packages/eval.rs index 6f97901d..6b2b9aa9 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -1,12 +1,14 @@ +use crate::any::Dynamic; use crate::def_package; -use crate::module::FuncReturn; use crate::parser::ImmutableString; +use crate::plugin::*; +use crate::result::EvalAltResult; + +#[export_fn(return_raw)] +fn eval_override(_script: ImmutableString) -> Result> { + Err("eval is evil!".into()) +} def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { - lib.set_fn_1( - "eval", - |_: ImmutableString| -> FuncReturn<()> { - Err("eval is evil!".into()) - }, - ); + set_exported_fn!(lib, "eval", eval_override); }); diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index dde08fd5..8fe886dc 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,9 +1,16 @@ use crate::def_package; +use crate::engine::make_getter; use crate::fn_native::FnPtr; +use crate::plugin::*; + +#[export_fn] +fn get_fn_name(f: &mut FnPtr) -> ImmutableString { + f.get_fn_name().clone() +} def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { - lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone())); + set_exported_fn!(lib, "name", get_fn_name); #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone())); + set_exported_fn!(lib, make_getter("name"), get_fn_name); }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 22f60d93..91ae1abf 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -2,7 +2,7 @@ use crate::def_package; use crate::plugin::*; macro_rules! gen_cmp_functions { - ($op_name:tt = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { + ($op_name:literal = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { pub mod $op_fn { $( pub mod $arg_type { use crate::plugin::*; diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 61843b14..8a216b2d 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,5 +1,4 @@ #![cfg(not(feature = "no_std"))] -use super::logic::{eq, gt, gte, lt, lte, ne}; #[cfg(feature = "no_float")] #[cfg(not(feature = "unchecked"))] @@ -38,12 +37,12 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { //lib.merge(&exported_module!(time_compare)); - lib.set_fn_2("<", |x:Instant, y:Instant| Ok(lt(x, y))); - lib.set_fn_2("<=", |x:Instant, y:Instant| Ok(lte(x, y))); - lib.set_fn_2(">", |x:Instant, y:Instant| Ok(gt(x, y))); - lib.set_fn_2(">=", |x:Instant, y:Instant| Ok(gte(x, y))); - lib.set_fn_2("==", |x:Instant, y:Instant| Ok(eq(x, y))); - lib.set_fn_2("!=", |x:Instant, y:Instant| Ok(ne(x, y))); + lib.set_fn_2("<", |x:Instant, y:Instant| Ok(x < y)); + lib.set_fn_2("<=", |x:Instant, y:Instant| Ok(x <= y)); + lib.set_fn_2(">", |x:Instant, y:Instant| Ok(x > y)); + lib.set_fn_2(">=", |x:Instant, y:Instant| Ok(x >= y)); + lib.set_fn_2("==", |x:Instant, y:Instant| Ok(x == y)); + lib.set_fn_2("!=", |x:Instant, y:Instant| Ok(x != y)); }); #[export_fn] From 209d1a174ccd1db366e01f91381ab83956c4ac75 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Aug 2020 00:04:10 +0800 Subject: [PATCH 040/103] Yet more packages into plugins. --- src/module.rs | 12 + src/packages/array_basic.rs | 19 +- src/packages/map_basic.rs | 136 +++++----- src/packages/math_basic.rs | 64 ++++- src/packages/string_more.rs | 505 ++++++++++++++++++++---------------- src/packages/time_basic.rs | 2 +- tests/macro_register.rs | 1 - tests/macro_unroll.rs | 2 +- 8 files changed, 425 insertions(+), 316 deletions(-) diff --git a/src/module.rs b/src/module.rs index dbb416a2..28c64ff0 100644 --- a/src/module.rs +++ b/src/module.rs @@ -928,6 +928,18 @@ impl Module { self.all_functions.get(&hash_qualified_fn) } + /// Combine another module into this module. + /// The other module is consumed to merge into this module. + pub fn combine(&mut self, other: Self) -> &mut Self { + self.variables.extend(other.variables.into_iter()); + self.functions.extend(other.functions.into_iter()); + self.type_iterators.extend(other.type_iterators.into_iter()); + self.all_functions.clear(); + self.all_variables.clear(); + self.indexed = false; + self + } + /// Merge another module into this module. pub fn merge(&mut self, other: &Self) -> &mut Self { self.merge_filtered(other, |_, _, _| true) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index d173bcf2..a75fe369 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; -use crate::engine::{Array, Engine}; +use crate::engine::{make_getter, Array, Engine}; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; @@ -57,7 +57,7 @@ macro_rules! reg_pad { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - lib.merge(&exported_module!(array_functions)); + lib.combine(exported_module!(array_functions)); set_exported_fn!(lib, "+", append); set_exported_fn!(lib, "+", concat); @@ -84,7 +84,7 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { } #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("len", |list: &mut Array| Ok(list.len() as INT)); + set_exported_fn!(lib, make_getter("len"), array_funcs::len); // Register array iterator lib.set_iter( @@ -106,7 +106,7 @@ fn concat(mut x: Array, y: Array) -> Array { #[export_module] mod array_functions { pub fn len(list: &mut Array) -> INT { - list.len() as INT + array_funcs::len(list) } pub fn append(x: &mut Array, y: Array) { x.extend(y); @@ -140,6 +140,17 @@ mod array_functions { } } +mod array_funcs { + use crate::engine::Array; + use crate::parser::INT; + use crate::plugin::*; + + #[export_fn] + pub fn len(list: &mut Array) -> INT { + list.len() as INT + } +} + // Register array utility functions fn push(list: &mut Array, item: T) { list.push(Dynamic::from(item)); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 4e2e58ad..bb2f4f09 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -2,78 +2,80 @@ use crate::any::Dynamic; use crate::def_package; -use crate::engine::Map; -use crate::module::FuncReturn; +use crate::engine::{make_getter, Map}; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; use crate::stdlib::vec::Vec; -fn map_get_keys(map: &mut Map) -> FuncReturn> { - Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) -} -fn map_get_values(map: &mut Map) -> FuncReturn> { - Ok(map.iter().map(|(_, v)| v.clone()).collect()) -} - def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { - lib.set_fn_2_mut( - "has", - |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(&prop)), - ); - lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); - lib.set_fn_1_mut("clear", |map: &mut Map| { - map.clear(); - Ok(()) - }); - lib.set_fn_2_mut( - "remove", - |x: &mut Map, name: ImmutableString| Ok(x.remove(&name).unwrap_or_else(|| ().into())), - ); - lib.set_fn_2_mut( - "mixin", - |map1: &mut Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - Ok(()) - }, - ); - lib.set_fn_2_mut( - "fill_with", - |map1: &mut Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - if !map1.contains_key(&key) { - map1.insert(key, value); - } - }); - Ok(()) - }, - ); - lib.set_fn_2_mut( - "+=", - |map1: &mut Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - Ok(()) - }, - ); - lib.set_fn_2( - "+", - |mut map1: Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - Ok(map1) - }, - ); + lib.combine(exported_module!(map_functions)); + set_exported_fn!(lib, make_getter("len"), map_funcs::len); + set_exported_fn!(lib, "+=", map_funcs::mixin); + set_exported_fn!(lib, "+", map_funcs::merge); // Register map access functions - if cfg!(not(feature = "no_index")) { - lib.set_fn_1_mut("keys", map_get_keys); - } - - if cfg!(not(feature = "no_index")) { - lib.set_fn_1_mut("values", map_get_values); - } + #[cfg(not(feature = "no_index"))] + lib.combine(exported_module!(index_functions)); }); + +#[export_module] +mod map_functions { + pub fn has(map: &mut Map, prop: ImmutableString) -> bool { + map.contains_key(&prop) + } + pub fn len(map: &mut Map) -> INT { + map_funcs::len(map) + } + pub fn clear(map: &mut Map) { + map.clear(); + } + pub fn remove(x: &mut Map, name: ImmutableString) -> Dynamic { + x.remove(&name).unwrap_or_else(|| ().into()) + } + pub fn mixin(map1: &mut Map, map2: Map) { + map_funcs::mixin(map1, map2); + } + pub fn fill_with(map1: &mut Map, map2: Map) { + map2.into_iter().for_each(|(key, value)| { + if !map1.contains_key(&key) { + map1.insert(key, value); + } + }); + } +} + +mod map_funcs { + use crate::engine::Map; + use crate::parser::INT; + use crate::plugin::*; + + #[export_fn] + pub fn len(map: &mut Map) -> INT { + map.len() as INT + } + #[export_fn] + pub fn mixin(map1: &mut Map, map2: Map) { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + } + #[export_fn] + pub fn merge(mut map1: Map, map2: Map) -> Map { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + map1 + } +} + +#[cfg(not(feature = "no_index"))] +#[export_module] +mod index_functions { + pub fn keys(map: &mut Map) -> Vec { + map.iter().map(|(k, _)| k.clone().into()).collect() + } + pub fn values(map: &mut Map) -> Vec { + map.iter().map(|(_, v)| v.clone()).collect() + } +} diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 322626e7..e8b267fb 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,4 +1,5 @@ use crate::def_package; +use crate::engine::make_getter; use crate::parser::INT; use crate::plugin::*; @@ -26,23 +27,23 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] { // Floating point functions - lib.merge(&exported_module!(float)); + lib.combine(exported_module!(float_functions)); // Floating point properties #[cfg(not(feature = "no_object"))] { - lib.set_getter_fn("floor", |x: &mut FLOAT| Ok(x.floor())); - lib.set_getter_fn("ceiling", |x: &mut FLOAT| Ok(x.ceil())); - lib.set_getter_fn("round", |x: &mut FLOAT| Ok(x.ceil())); - lib.set_getter_fn("int", |x: &mut FLOAT| Ok(x.trunc())); - lib.set_getter_fn("fraction", |x: &mut FLOAT| Ok(x.fract())); - lib.set_getter_fn("is_nan", |x: &mut FLOAT| Ok(x.is_nan())); - lib.set_getter_fn("is_finite", |x: &mut FLOAT| Ok(x.is_finite())); - lib.set_getter_fn("is_infinite", |x: &mut FLOAT| Ok(x.is_infinite())); + set_exported_fn!(lib, make_getter("floor"), float_funcs::floor); + set_exported_fn!(lib, make_getter("ceiling"), float_funcs::ceiling); + set_exported_fn!(lib, make_getter("round"), float_funcs::round); + set_exported_fn!(lib, make_getter("int"), float_funcs::int); + set_exported_fn!(lib, make_getter("fraction"), float_funcs::fraction); + set_exported_fn!(lib, make_getter("is_nan"), float_funcs::is_nan); + set_exported_fn!(lib, make_getter("is_finite"), float_funcs::is_finite); + set_exported_fn!(lib, make_getter("is_infinite"), float_funcs::is_infinite); } // Trig functions - lib.merge(&exported_module!(trig)); + lib.combine(exported_module!(trig_functions)); // Register conversion functions lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); @@ -119,7 +120,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] #[export_module] -mod trig { +mod trig_functions { use crate::parser::FLOAT; pub fn sin(x: FLOAT) -> FLOAT { @@ -162,7 +163,7 @@ mod trig { #[cfg(not(feature = "no_float"))] #[export_module] -mod float { +mod float_functions { use crate::parser::FLOAT; pub fn sqrt(x: FLOAT) -> FLOAT { @@ -180,27 +181,66 @@ mod float { pub fn log10(x: FLOAT) -> FLOAT { x.log10() } + pub fn floor(x: FLOAT) -> FLOAT { + float_funcs::floor(x) + } + pub fn ceiling(x: FLOAT) -> FLOAT { + float_funcs::ceiling(x) + } + pub fn round(x: FLOAT) -> FLOAT { + float_funcs::round(x) + } + pub fn int(x: FLOAT) -> FLOAT { + float_funcs::int(x) + } + pub fn fraction(x: FLOAT) -> FLOAT { + float_funcs::fraction(x) + } + pub fn is_nan(x: FLOAT) -> bool { + float_funcs::is_nan(x) + } + pub fn is_finite(x: FLOAT) -> bool { + float_funcs::is_finite(x) + } + pub fn is_infinite(x: FLOAT) -> bool { + float_funcs::is_infinite(x) + } +} + +#[cfg(not(feature = "no_float"))] +mod float_funcs { + use crate::parser::FLOAT; + use crate::plugin::*; + + #[export_fn] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } + #[export_fn] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } + #[export_fn] pub fn round(x: FLOAT) -> FLOAT { x.ceil() } + #[export_fn] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } + #[export_fn] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } + #[export_fn] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } + #[export_fn] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } + #[export_fn] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 0b58c19d..56f3a1d7 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,9 +1,11 @@ +#![allow(non_snake_case)] + use crate::any::Dynamic; use crate::def_package; -use crate::engine::Engine; +use crate::engine::{make_getter, Engine}; use crate::fn_native::FnPtr; -use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; use crate::utils::StaticVec; #[cfg(not(feature = "unchecked"))] @@ -13,220 +15,82 @@ use crate::{result::EvalAltResult, token::Position}; use crate::engine::Array; use crate::stdlib::{ - any::TypeId, - boxed::Box, - fmt::Display, - format, mem, - string::{String, ToString}, - vec::Vec, + any::TypeId, boxed::Box, fmt::Display, format, mem, string::ToString, vec::Vec, }; -fn prepend(x: T, y: ImmutableString) -> FuncReturn { - Ok(format!("{}{}", x, y).into()) -} -fn append(x: ImmutableString, y: T) -> FuncReturn { - Ok(format!("{}{}", x, y).into()) -} -fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn { - let offset = if s.is_empty() || len <= 0 { - return Ok("".to_string().into()); - } else if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - return Ok("".to_string().into()); - } else { - start as usize - }; +macro_rules! gen_concat_functions { + ($root:ident => $($arg_type:ident),+ ) => { + pub mod $root { $( + pub mod $arg_type { + use super::super::*; - let chars: StaticVec<_> = s.chars().collect(); + #[export_fn] + pub fn append_func(x: ImmutableString, y: $arg_type) -> String { + super::super::append(x, y) + } - let len = if offset + (len as usize) > chars.len() { - chars.len() - offset - } else { - len as usize - }; - - Ok(chars - .iter() - .skip(offset) - .take(len) - .cloned() - .collect::() - .into()) -} -fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> { - let offset = if s.is_empty() || len <= 0 { - s.make_mut().clear(); - return Ok(()); - } else if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - s.make_mut().clear(); - return Ok(()); - } else { - start as usize - }; - - let chars: StaticVec<_> = s.chars().collect(); - - let len = if offset + (len as usize) > chars.len() { - chars.len() - offset - } else { - len as usize - }; - - let copy = s.make_mut(); - copy.clear(); - copy.extend(chars.iter().skip(offset).take(len)); - - Ok(()) + #[export_fn] + pub fn prepend_func(x: $arg_type, y: ImmutableString) -> String { + super::super::prepend(x, y) + } + } + )* } + } } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2($op, $func::<$par>); )* - }; +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { + $(set_exported_fn!($mod_name, "+", $root::$arg_type::append_func);)* + $(set_exported_fn!($mod_name, "+", $root::$arg_type::prepend_func);)* + } } def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { - reg_op!(lib, "+", append, INT, bool, char, FnPtr); - lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x)); + reg_functions!(lib += basic; INT, bool, char, FnPtr); + set_exported_fn!(lib, "+", string_funcs::append_unit); + set_exported_fn!(lib, "+", string_funcs::prepend_unit); - reg_op!(lib, "+", prepend, INT, bool, char, FnPtr); - lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, i64, u32, u64); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "+", append, i128, u128); - reg_op!(lib, "+", prepend, i128, u128); - } + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128; i128, u128); } #[cfg(not(feature = "no_float"))] - { - reg_op!(lib, "+", append, f32, f64); - reg_op!(lib, "+", prepend, f32, f64); - } + reg_functions!(lib += float; f32, f64); #[cfg(not(feature = "no_index"))] { - lib.set_fn_2_mut("+", |x: &mut ImmutableString, y: Array| Ok(format!("{}{:?}", x, y))); - lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y))); + set_exported_fn!(lib, "+", string_funcs::append_array); + set_exported_fn!(lib, "+", string_funcs::prepend_array); } - lib.set_fn_1_mut("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT)); + lib.combine(exported_module!(string_functions)); + set_exported_fn!(lib, "contains", string_funcs::contains_char); + set_exported_fn!(lib, "contains", string_funcs::contains_string); + set_exported_fn!(lib, "index_of", string_funcs::index_of_char); + set_exported_fn!(lib, "index_of", string_funcs::index_of_char_starting_from); + set_exported_fn!(lib, "index_of", string_funcs::index_of_string); + set_exported_fn!(lib, "index_of", string_funcs::index_of_string_starting_from); + set_exported_fn!(lib, "append", string_funcs::append_char); + set_exported_fn!(lib, "append", string_funcs::append_string); + set_exported_fn!(lib, "sub_string", string_funcs::sub_string); + set_exported_fn!(lib, "sub_string", string_funcs::sub_string_starting_from); + set_exported_fn!(lib, "crop", string_funcs::crop_string); + set_exported_fn!(lib, "crop", string_funcs::crop_string_starting_from); + set_exported_fn!(lib, "replace", string_funcs::replace_string); + set_exported_fn!(lib, "replace", string_funcs::replace_char); + set_exported_fn!(lib, "replace", string_funcs::replace_string_with_char); + set_exported_fn!(lib, "replace", string_funcs::replace_char_with_string); - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT)); - - lib.set_fn_2_mut( - "contains", - |s: &mut ImmutableString, ch: char| Ok(s.contains(ch)), - ); - lib.set_fn_2_mut( - "contains", - |s: &mut ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())), - ); - lib.set_fn_3_mut( - "index_of", - |s: &mut ImmutableString, ch: char, start: INT| { - let start = if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - return Ok(-1 as INT); - } else { - s.chars().take(start as usize).collect::().len() - }; - - Ok(s[start..] - .find(ch) - .map(|index| s[0..start + index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_2_mut( - "index_of", - |s: &mut ImmutableString, ch: char| { - Ok(s.find(ch) - .map(|index| s[0..index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_3_mut( - "index_of", - |s: &mut ImmutableString, find: ImmutableString, start: INT| { - let start = if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - return Ok(-1 as INT); - } else { - s.chars().take(start as usize).collect::().len() - }; - - Ok(s[start..] - .find(find.as_str()) - .map(|index| s[0..start + index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_2_mut( - "index_of", - |s: &mut ImmutableString, find: ImmutableString| { - Ok(s.find(find.as_str()) - .map(|index| s[0..index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_1_mut("clear", |s: &mut ImmutableString| { - s.make_mut().clear(); - Ok(()) - }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { - s.make_mut().push(ch); - Ok(()) - }); - lib.set_fn_2_mut( - "append", - |s: &mut ImmutableString, add: ImmutableString| { - s.make_mut().push_str(add.as_str()); - Ok(()) - } - ); - lib.set_fn_3("sub_string", sub_string); - lib.set_fn_2( - "sub_string", - |s: ImmutableString, start: INT| { - let len = s.len() as INT; - sub_string(s, start, len) - }, - ); - lib.set_fn_3_mut("crop", crop_string); - lib.set_fn_2_mut( - "crop", - |s: &mut ImmutableString, start: INT| crop_string(s, start, s.len() as INT), - ); - lib.set_fn_2_mut( - "truncate", - |s: &mut ImmutableString, len: INT| { - if len > 0 { - let chars: StaticVec<_> = s.chars().collect(); - let copy = s.make_mut(); - copy.clear(); - copy.extend(chars.into_iter().take(len as usize)); - } else { - s.make_mut().clear(); - } - Ok(()) - }, - ); lib.set_raw_fn( "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].read_lock::< INT>().unwrap(); + let len = *args[1].read_lock::().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -267,45 +131,9 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str Ok(()) }, ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString| { - *s = s.replace(find.as_str(), sub.as_str()).into(); - Ok(()) - }, - ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: ImmutableString, sub: char| { - *s = s.replace(find.as_str(), &sub.to_string()).into(); - Ok(()) - }, - ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: char, sub: ImmutableString| { - *s = s.replace(&find.to_string(), sub.as_str()).into(); - Ok(()) - }, - ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: char, sub: char| { - *s = s.replace(&find.to_string(), &sub.to_string()).into(); - Ok(()) - }, - ); - lib.set_fn_1_mut( - "trim", - |s: &mut ImmutableString| { - let trimmed = s.trim(); - if trimmed.len() < s.len() { - *s = trimmed.to_string().into(); - } - Ok(()) - }, - ); + #[cfg(not(feature = "no_object"))] + set_exported_fn!(lib, make_getter("len"), string_funcs::len); // Register string iterator lib.set_iter( @@ -315,3 +143,220 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ) as Box>, ); }); + +fn prepend(x: T, y: ImmutableString) -> String { + format!("{}{}", x, y) +} +fn append(x: ImmutableString, y: T) -> String { + format!("{}{}", x, y) +} + +gen_concat_functions!(basic => INT, bool, char, FnPtr); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_concat_functions!(numbers => i8, u8, i16, u16, i32, i64, u32, u64); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_concat_functions!(num_128 => i128, u128); + +#[cfg(not(feature = "no_float"))] +gen_concat_functions!(float => f32, f64); + +#[export_module] +mod string_functions { + pub fn len(s: &mut ImmutableString) -> INT { + string_funcs::len(s) + } + pub fn clear(s: &mut ImmutableString) { + s.make_mut().clear(); + } + pub fn truncate(s: &mut ImmutableString, len: INT) { + if len > 0 { + let chars: StaticVec<_> = s.chars().collect(); + let copy = s.make_mut(); + copy.clear(); + copy.extend(chars.into_iter().take(len as usize)); + } else { + s.make_mut().clear(); + } + } + pub fn trim(s: &mut ImmutableString) { + let trimmed = s.trim(); + + if trimmed.len() < s.len() { + *s = trimmed.to_string().into(); + } + } +} + +mod string_funcs { + use crate::engine::Array; + use crate::parser::INT; + use crate::plugin::*; + use crate::utils::{ImmutableString, StaticVec}; + + #[export_fn] + pub fn append_unit(s: ImmutableString, _x: ()) -> ImmutableString { + s + } + #[export_fn] + pub fn prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { + s + } + #[export_fn] + pub fn append_array(x: &mut ImmutableString, y: Array) -> String { + format!("{}{:?}", x, y) + } + #[export_fn] + pub fn prepend_array(x: &mut Array, y: ImmutableString) -> String { + format!("{:?}{}", x, y) + } + #[export_fn] + pub fn len(s: &mut ImmutableString) -> INT { + s.chars().count() as INT + } + #[export_fn] + pub fn contains_char(s: &mut ImmutableString, ch: char) -> bool { + s.contains(ch) + } + #[export_fn] + pub fn contains_string(s: &mut ImmutableString, find: ImmutableString) -> bool { + s.contains(find.as_str()) + } + #[export_fn] + pub fn index_of_char_starting_from(s: &mut ImmutableString, ch: char, start: INT) -> INT { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(ch) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[export_fn] + pub fn index_of_char(s: &mut ImmutableString, ch: char) -> INT { + s.find(ch) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[export_fn] + pub fn index_of_string_starting_from( + s: &mut ImmutableString, + find: ImmutableString, + start: INT, + ) -> INT { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(find.as_str()) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[export_fn] + pub fn index_of_string(s: &mut ImmutableString, find: ImmutableString) -> INT { + s.find(find.as_str()) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[export_fn] + pub fn append_char(s: &mut ImmutableString, ch: char) { + s.make_mut().push(ch); + } + #[export_fn] + pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { + s.make_mut().push_str(add.as_str()); + } + #[export_fn] + pub fn sub_string(s: ImmutableString, start: INT, len: INT) -> ImmutableString { + let offset = if s.is_empty() || len <= 0 { + return "".to_string().into(); + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return "".to_string().into(); + } else { + start as usize + }; + + let chars: StaticVec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + chars + .iter() + .skip(offset) + .take(len) + .cloned() + .collect::() + .into() + } + #[export_fn] + pub fn sub_string_starting_from(s: ImmutableString, start: INT) -> ImmutableString { + let len = s.len() as INT; + sub_string(s, start, len) + } + #[export_fn] + fn crop_string(s: &mut ImmutableString, start: INT, len: INT) { + let offset = if s.is_empty() || len <= 0 { + s.make_mut().clear(); + return; + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + s.make_mut().clear(); + return; + } else { + start as usize + }; + + let chars: StaticVec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + let copy = s.make_mut(); + copy.clear(); + copy.extend(chars.iter().skip(offset).take(len)); + } + #[export_fn] + pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) { + crop_string(s, start, s.len() as INT); + } + #[export_fn] + pub fn replace_string(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) { + *s = s.replace(find.as_str(), sub.as_str()).into(); + } + #[export_fn] + pub fn replace_string_with_char(s: &mut ImmutableString, find: ImmutableString, sub: char) { + *s = s.replace(find.as_str(), &sub.to_string()).into(); + } + #[export_fn] + pub fn replace_char_with_string(s: &mut ImmutableString, find: char, sub: ImmutableString) { + *s = s.replace(&find.to_string(), sub.as_str()).into(); + } + #[export_fn] + pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { + *s = s.replace(&find.to_string(), &sub.to_string()).into(); + } +} diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 8a216b2d..ce0f9abf 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -35,7 +35,7 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { set_exported_fn!(lib, "-", time_diff); - //lib.merge(&exported_module!(time_compare)); + //lib.combine(exported_module!(time_compare)); lib.set_fn_2("<", |x:Instant, y:Instant| Ok(x < y)); lib.set_fn_2("<=", |x:Instant, y:Instant| Ok(x <= y)); diff --git a/tests/macro_register.rs b/tests/macro_register.rs index 99e7fe22..525704c2 100644 --- a/tests/macro_register.rs +++ b/tests/macro_register.rs @@ -1,7 +1,6 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT}; - #[export_fn] pub fn add_together(x: INT, y: INT) -> INT { x + y diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs index e994fa0e..c872381a 100644 --- a/tests/macro_unroll.rs +++ b/tests/macro_unroll.rs @@ -1,7 +1,7 @@ #![cfg(not(any(feature = "no_index", feature = "no_module")))] use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, INT, Module}; +use rhai::{Engine, EvalAltResult, Module, INT}; pub fn add_generic>(x: T, y: T) -> T { x + y From 431c80b46b86090da278538eb34d3e4754489102 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 16:27:47 -0500 Subject: [PATCH 041/103] Run CI on every push to plugins branch --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0384c01c..b5465c05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - plugins pull_request: {} jobs: From 4de02c2bd7f791cdce6296abf132c9214d3e3075 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 16:30:00 -0500 Subject: [PATCH 042/103] Remove extra import --- src/packages/string_more.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 56f3a1d7..e19bdbc6 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -11,9 +11,6 @@ use crate::utils::StaticVec; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_index"))] -use crate::engine::Array; - use crate::stdlib::{ any::TypeId, boxed::Box, fmt::Display, format, mem, string::ToString, vec::Vec, }; From e6a1b88b4ca4580c72adeaf472b8c088b5d2f3ba Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 16:49:24 -0500 Subject: [PATCH 043/103] Fully feature gate array string functions --- src/packages/string_more.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e19bdbc6..9b185ec6 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -44,8 +44,6 @@ macro_rules! reg_functions { def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { reg_functions!(lib += basic; INT, bool, char, FnPtr); - set_exported_fn!(lib, "+", string_funcs::append_unit); - set_exported_fn!(lib, "+", string_funcs::prepend_unit); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] @@ -61,8 +59,8 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str #[cfg(not(feature = "no_index"))] { - set_exported_fn!(lib, "+", string_funcs::append_array); - set_exported_fn!(lib, "+", string_funcs::prepend_array); + set_exported_fn!(lib, "+", string_funcs_array::append_array); + set_exported_fn!(lib, "+", string_funcs_array::prepend_array); } lib.combine(exported_module!(string_functions)); @@ -189,8 +187,23 @@ mod string_functions { } } -mod string_funcs { +#[cfg(not(feature = "no_index"))] +mod string_funcs_array { use crate::engine::Array; + use crate::plugin::*; + use crate::utils::ImmutableString; + + #[export_fn] + pub fn append_array(x: &mut ImmutableString, y: Array) -> String { + format!("{}{:?}", x, y) + } + #[export_fn] + pub fn prepend_array(x: &mut Array, y: ImmutableString) -> String { + format!("{:?}{}", x, y) + } +} + +mod string_funcs { use crate::parser::INT; use crate::plugin::*; use crate::utils::{ImmutableString, StaticVec}; @@ -204,14 +217,6 @@ mod string_funcs { s } #[export_fn] - pub fn append_array(x: &mut ImmutableString, y: Array) -> String { - format!("{}{:?}", x, y) - } - #[export_fn] - pub fn prepend_array(x: &mut Array, y: ImmutableString) -> String { - format!("{:?}{}", x, y) - } - #[export_fn] pub fn len(s: &mut ImmutableString) -> INT { s.chars().count() as INT } From 1051a3f108ab1d8a94361d60552d99475a939692 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 19:24:53 -0500 Subject: [PATCH 044/103] Add missing import of String and ToString --- src/packages/string_more.rs | 4 +++- src/plugin.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 9b185ec6..f33fd080 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -12,7 +12,7 @@ use crate::utils::StaticVec; use crate::{result::EvalAltResult, token::Position}; use crate::stdlib::{ - any::TypeId, boxed::Box, fmt::Display, format, mem, string::ToString, vec::Vec, + any::TypeId, boxed::Box, fmt::Display, format, mem, string::String, string::ToString, vec::Vec, }; macro_rules! gen_concat_functions { @@ -192,6 +192,7 @@ mod string_funcs_array { use crate::engine::Array; use crate::plugin::*; use crate::utils::ImmutableString; + use crate::stdlib::string::String; #[export_fn] pub fn append_array(x: &mut ImmutableString, y: Array) -> String { @@ -207,6 +208,7 @@ mod string_funcs { use crate::parser::INT; use crate::plugin::*; use crate::utils::{ImmutableString, StaticVec}; + use crate::stdlib::string::{String, ToString}; #[export_fn] pub fn append_unit(s: ImmutableString, _x: ()) -> ImmutableString { diff --git a/src/plugin.rs b/src/plugin.rs index 19e9939a..70697015 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -3,6 +3,7 @@ pub use crate::{ stdlib::any::TypeId, stdlib::boxed::Box, + stdlib::string::ToString, stdlib::vec::Vec, stdlib::vec as new_vec, stdlib::mem, From 7fcbfcc7fdc2cd243f6487663506c220fa3870dc Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 19:26:01 -0500 Subject: [PATCH 045/103] codegen: explicit import of format! macro --- codegen/src/function.rs | 5 +++++ src/plugin.rs | 1 + 2 files changed, 6 insertions(+) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index f3bfd344..0e4f1260 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -3,6 +3,11 @@ #[cfg(no_std)] use core::mem; +#[cfg(no_std)] +use alloc::format; +#[cfg(not(no_std))] +use std::format; + use std::collections::HashMap; use quote::{quote, quote_spanned}; diff --git a/src/plugin.rs b/src/plugin.rs index 70697015..b03df99c 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -3,6 +3,7 @@ pub use crate::{ stdlib::any::TypeId, stdlib::boxed::Box, + stdlib::format, stdlib::string::ToString, stdlib::vec::Vec, stdlib::vec as new_vec, From eabf9db2634ac611e829feafea12634f820f8bf2 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 19:30:27 -0500 Subject: [PATCH 046/103] Add missing num_traits import --- src/packages/math_basic.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index e8b267fb..f31e64dd 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -211,6 +211,8 @@ mod float_functions { mod float_funcs { use crate::parser::FLOAT; use crate::plugin::*; + #[cfg(feature = "no_std")] + use num_traits::float::Float; #[export_fn] pub fn floor(x: FLOAT) -> FLOAT { From 89de2fe6b2886465a4e0775990ef41aad0efc32a Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 19:37:16 -0500 Subject: [PATCH 047/103] Feature gate make_getter imports --- src/packages/array_basic.rs | 4 +++- src/packages/fn_basic.rs | 1 + src/packages/math_basic.rs | 1 + src/packages/string_more.rs | 4 +++- src/packages/time_basic.rs | 1 + 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index a75fe369..c044eddc 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,7 +3,9 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; -use crate::engine::{make_getter, Array, Engine}; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; +use crate::engine::{Array, Engine}; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 8fe886dc..fd3b5052 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,4 +1,5 @@ use crate::def_package; +#[cfg(not(feature = "no_object"))] use crate::engine::make_getter; use crate::fn_native::FnPtr; use crate::plugin::*; diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index f31e64dd..f8b905b2 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,4 +1,5 @@ use crate::def_package; +#[cfg(not(feature = "no_object"))] use crate::engine::make_getter; use crate::parser::INT; use crate::plugin::*; diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index f33fd080..d3b913fa 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -2,7 +2,9 @@ use crate::any::Dynamic; use crate::def_package; -use crate::engine::{make_getter, Engine}; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; +use crate::engine::Engine; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index ce0f9abf..202e7115 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -5,6 +5,7 @@ use super::math_basic::MAX_INT; use crate::def_package; +#[cfg(not(feature = "no_object"))] use crate::engine::make_getter; use crate::plugin::*; use crate::result::EvalAltResult; From e4045256fe7a015902b81a1caa05c06518837bdb Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 14 Aug 2020 19:39:28 -0500 Subject: [PATCH 048/103] Fix type resolution issue with no_float enabled --- src/packages/time_basic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 202e7115..0b3cad13 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -99,7 +99,7 @@ fn time_diff(ts1: Instant, ts2: Instant) -> Result> .into(); } - Ok(-(seconds as INT).into()) + Ok(Dynamic::from(-(seconds as INT))) } else { let seconds = (ts1 - ts2).as_secs(); From db9d8b81cf9b627f143dd68e269547e112a040fc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 15 Aug 2020 12:57:47 +0800 Subject: [PATCH 049/103] Move BasicStringsPackage to plugins. --- src/packages/array_basic.rs | 5 +- src/packages/fn_basic.rs | 5 +- src/packages/math_basic.rs | 5 +- src/packages/string_basic.rs | 164 ++++++++++++++++++++++++----------- src/packages/string_more.rs | 14 +-- src/packages/time_basic.rs | 5 +- 6 files changed, 133 insertions(+), 65 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c044eddc..fc44c4c5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,8 +3,6 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; use crate::engine::{Array, Engine}; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; @@ -13,6 +11,9 @@ use crate::plugin::*; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; + use crate::stdlib::{any::TypeId, boxed::Box}; #[cfg(not(feature = "unchecked"))] diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index fd3b5052..e34cb918 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,9 +1,10 @@ use crate::def_package; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; use crate::fn_native::FnPtr; use crate::plugin::*; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; + #[export_fn] fn get_fn_name(f: &mut FnPtr) -> ImmutableString { f.get_fn_name().clone() diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index f8b905b2..aad542b1 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,6 +1,4 @@ use crate::def_package; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; use crate::parser::INT; use crate::plugin::*; @@ -10,6 +8,9 @@ use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] use crate::{result::EvalAltResult, token::Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; + #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::float::Float; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 08778150..712faed1 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,8 +1,10 @@ +#![allow(non_snake_case)] + use crate::def_package; use crate::engine::{FN_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_native::FnPtr; -use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -16,76 +18,136 @@ use crate::stdlib::{ string::ToString, }; -// Register print and debug -fn to_debug(x: &mut T) -> FuncReturn { - Ok(format!("{:?}", x).into()) -} -fn to_string(x: &mut T) -> FuncReturn { - Ok(x.to_string().into()) -} -#[cfg(not(feature = "no_object"))] -fn format_map(x: &mut Map) -> FuncReturn { - Ok(format!("#{:?}", x).into()) +type Unit = (); + +macro_rules! gen_functions { + ($root:ident => $fn_name:ident ( $($arg_type:ident),+ )) => { + pub mod $root { $( + pub mod $arg_type { + use super::super::*; + + #[export_fn] + pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { + super::super::$fn_name(x) + } + } + )* } + } } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_1_mut($op, $func::<$par>); )* - }; +macro_rules! reg_print_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { + $(set_exported_fn!($mod_name, FN_TO_STRING, $root::$arg_type::to_string_func);)* + $(set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func);)* + } +} + +macro_rules! reg_debug_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { + $(set_exported_fn!($mod_name, KEYWORD_DEBUG, $root::$arg_type::to_string_func);)* + } } def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { - reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char, FnPtr); - reg_op!(lib, FN_TO_STRING, to_string, INT, bool, char, FnPtr); - lib.set_fn_1_mut(KEYWORD_DEBUG, |f: &mut FnPtr| Ok(f.to_string())); + reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr); + set_exported_fn!(lib, KEYWORD_PRINT, print_empty_string); + set_exported_fn!(lib, KEYWORD_PRINT, print_unit); + set_exported_fn!(lib, FN_TO_STRING, print_unit); + set_exported_fn!(lib, KEYWORD_PRINT, print_string); + set_exported_fn!(lib, FN_TO_STRING, print_string); - lib.set_fn_0(KEYWORD_PRINT, || Ok("".to_string())); - lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string())); - lib.set_fn_1(FN_TO_STRING, |_: ()| Ok("".to_string())); + reg_debug_functions!(lib += debug_basic; INT, bool, Unit, char, ImmutableString); + set_exported_fn!(lib, KEYWORD_DEBUG, print_empty_string); + set_exported_fn!(lib, KEYWORD_DEBUG, debug_fn_ptr); - lib.set_fn_1(KEYWORD_PRINT, |s: ImmutableString| Ok(s)); - lib.set_fn_1(FN_TO_STRING, |s: ImmutableString| Ok(s)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_print_functions!(lib += print_numbers; i8, u8, i16, u16, i32, u32, i64, u64); + reg_debug_functions!(lib += debug_numbers; i8, u8, i16, u16, i32, u32, i64, u64); - reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); - reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); - reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); - reg_op!(lib, KEYWORD_PRINT, to_string, i64, u64); - reg_op!(lib, FN_TO_STRING, to_string, i64, u64); - reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); - reg_op!(lib, FN_TO_STRING, to_string, i128, u128); - reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); + #[cfg(not(target_arch = "wasm32"))] + { + reg_print_functions!(lib += print_num_128; i128, u128); + reg_debug_functions!(lib += debug_num_128; i128, u128); } } #[cfg(not(feature = "no_float"))] { - reg_op!(lib, KEYWORD_PRINT, to_string, f32, f64); - reg_op!(lib, FN_TO_STRING, to_string, f32, f64); - reg_op!(lib, KEYWORD_DEBUG, to_debug, f32, f64); + reg_print_functions!(lib += print_float; f32, f64); + reg_debug_functions!(lib += debug_float; f32, f64); } #[cfg(not(feature = "no_index"))] { - reg_op!(lib, KEYWORD_PRINT, to_debug, Array); - reg_op!(lib, FN_TO_STRING, to_debug, Array); - reg_op!(lib, KEYWORD_DEBUG, to_debug, Array); + reg_print_functions!(lib += print_array; Array); + reg_debug_functions!(lib += print_array; Array); } #[cfg(not(feature = "no_object"))] { - lib.set_fn_1_mut(KEYWORD_PRINT, format_map); - lib.set_fn_1_mut(FN_TO_STRING, format_map); - lib.set_fn_1_mut(KEYWORD_DEBUG, format_map); + set_exported_fn!(lib, KEYWORD_PRINT, format_map); + set_exported_fn!(lib, FN_TO_STRING, format_map); + set_exported_fn!(lib, KEYWORD_DEBUG, format_map); } - - lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch)); - lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) }); }); + +gen_functions!(print_basic => to_string(INT, bool, char, FnPtr)); +gen_functions!(debug_basic => to_debug(INT, bool, Unit, char, ImmutableString)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_functions!(print_numbers => to_string(i8, u8, i16, u16, i32, u32, i64, u64)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_functions!(debug_numbers => to_debug(i8, u8, i16, u16, i32, u32, i64, u64)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_functions!(print_num_128 => to_string(i128, u128)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_functions!(debug_num_128 => to_debug(i128, u128)); + +#[cfg(not(feature = "no_float"))] +gen_functions!(print_float => to_string(f32, f64)); + +#[cfg(not(feature = "no_float"))] +gen_functions!(debug_float => to_debug(f32, f64)); + +#[cfg(not(feature = "no_index"))] +gen_functions!(print_array => to_debug(Array)); + +// Register print and debug +#[export_fn] +fn print_empty_string() -> ImmutableString { + "".to_string().into() +} +#[export_fn] +fn print_unit(_x: ()) -> ImmutableString { + "".to_string().into() +} +#[export_fn] +fn print_string(s: ImmutableString) -> ImmutableString { + s +} +#[export_fn] +fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { + f.to_string().into() +} +fn to_string(x: &mut T) -> ImmutableString { + x.to_string().into() +} +fn to_debug(x: &mut T) -> ImmutableString { + format!("{:?}", x).into() +} +#[cfg(not(feature = "no_object"))] +#[export_fn] +fn format_map(x: &mut Map) -> ImmutableString { + format!("#{:?}", x).into() +} diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index d3b913fa..ded48378 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -2,8 +2,6 @@ use crate::any::Dynamic; use crate::def_package; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; use crate::engine::Engine; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; @@ -13,6 +11,9 @@ use crate::utils::StaticVec; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; + use crate::stdlib::{ any::TypeId, boxed::Box, fmt::Display, format, mem, string::String, string::ToString, vec::Vec, }; @@ -73,6 +74,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str set_exported_fn!(lib, "index_of", string_funcs::index_of_string); set_exported_fn!(lib, "index_of", string_funcs::index_of_string_starting_from); set_exported_fn!(lib, "append", string_funcs::append_char); + set_exported_fn!(lib, "+=", string_funcs::append_char); set_exported_fn!(lib, "append", string_funcs::append_string); set_exported_fn!(lib, "sub_string", string_funcs::sub_string); set_exported_fn!(lib, "sub_string", string_funcs::sub_string_starting_from); @@ -193,8 +195,8 @@ mod string_functions { mod string_funcs_array { use crate::engine::Array; use crate::plugin::*; - use crate::utils::ImmutableString; use crate::stdlib::string::String; + use crate::utils::ImmutableString; #[export_fn] pub fn append_array(x: &mut ImmutableString, y: Array) -> String { @@ -209,8 +211,8 @@ mod string_funcs_array { mod string_funcs { use crate::parser::INT; use crate::plugin::*; - use crate::utils::{ImmutableString, StaticVec}; use crate::stdlib::string::{String, ToString}; + use crate::utils::{ImmutableString, StaticVec}; #[export_fn] pub fn append_unit(s: ImmutableString, _x: ()) -> ImmutableString { @@ -280,11 +282,11 @@ mod string_funcs { } #[export_fn] pub fn append_char(s: &mut ImmutableString, ch: char) { - s.make_mut().push(ch); + *s += ch; } #[export_fn] pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { - s.make_mut().push_str(add.as_str()); + *s += &add; } #[export_fn] pub fn sub_string(s: ImmutableString, start: INT, len: INT) -> ImmutableString { diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 0b3cad13..55315cee 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -5,8 +5,6 @@ use super::math_basic::MAX_INT; use crate::def_package; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; use crate::plugin::*; use crate::result::EvalAltResult; @@ -20,6 +18,9 @@ use crate::parser::INT; #[cfg(not(feature = "unchecked"))] use crate::token::Position; +#[cfg(not(feature = "no_object"))] +use crate::engine::make_getter; + #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::time::Instant; From 3ccc08b6e075a92ffae26508694c993af52407c9 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 13 Aug 2020 22:06:30 -0500 Subject: [PATCH 050/103] Convert args length check to debug assert --- codegen/src/function.rs | 50 +++++++++++++---------------------------- codegen/src/module.rs | 35 +++++++++-------------------- 2 files changed, 25 insertions(+), 60 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0e4f1260..7ae34045 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -485,11 +485,9 @@ impl ExportedFn { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != #arg_count { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), #arg_count), Position::none()))); - } + debug_assert_eq!(args.len(), #arg_count, + "wrong arg count: {} != {}", + args.len(), #arg_count); #(#unpack_stmts)* #return_expr } @@ -778,11 +776,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 0usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 0usize), Position::none()))); - } + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); Ok(Dynamic::from(do_nothing())) } @@ -825,11 +820,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 1usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 1usize), Position::none()))); - } + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(do_something(arg0))) } @@ -869,11 +861,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 1usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 1usize), Position::none()))); - } + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(do_something(arg0))) } @@ -906,11 +895,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 2usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 2usize), Position::none()))); - } + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); let arg0 = mem::take(args[0usize]).clone().cast::(); let arg1 = mem::take(args[1usize]).clone().cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) @@ -956,11 +942,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 2usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 2usize), Position::none()))); - } + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); let arg1 = mem::take(args[1usize]).clone().cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) @@ -1007,11 +990,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 1usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 1usize), Position::none()))); - } + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(special_print(&arg0))) } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 9e042bb9..4f8616c3 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -314,11 +314,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 0usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 0usize), Position::none()))); - } + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); Ok(Dynamic::from(get_mystic_number())) } @@ -374,11 +371,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 1usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 1usize), Position::none()))); - } + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(add_one_to(arg0))) } @@ -436,11 +430,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 2usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 2usize), Position::none()))); - } + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); let arg0 = mem::take(args[0usize]).clone().cast::(); let arg1 = mem::take(args[1usize]).clone().cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) @@ -608,11 +599,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 1usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 1usize), Position::none()))); - } + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).clone().cast::(); Ok(Dynamic::from(print_out_to(&arg0))) } @@ -670,11 +658,8 @@ mod generate_tests { fn call(&self, args: &mut [&mut Dynamic], pos: Position ) -> Result> { - if args.len() != 1usize { - return Err(Box::new(EvalAltResult::ErrorRuntime( - format!("wrong arg count: {} != {}", - args.len(), 1usize), Position::none()))); - } + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } From bcf14025a793b9de2e505ac8698d1152f320e713 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 15 Aug 2020 21:51:14 -0500 Subject: [PATCH 051/103] Add rhai_fn nested attribute and skip fn parameter --- codegen/src/function.rs | 18 +++- codegen/src/module.rs | 90 ++++++++++++++++--- codegen/tests/test_nested.rs | 36 ++++++++ codegen/ui_tests/rhai_fn_bad_attr.rs | 27 ++++++ codegen/ui_tests/rhai_fn_bad_attr.stderr | 11 +++ codegen/ui_tests/rhai_fn_bad_value.rs | 27 ++++++ codegen/ui_tests/rhai_fn_bad_value.stderr | 11 +++ codegen/ui_tests/rhai_fn_extra_value.rs | 27 ++++++ codegen/ui_tests/rhai_fn_extra_value.stderr | 11 +++ codegen/ui_tests/rhai_fn_junk_arg.rs | 27 ++++++ codegen/ui_tests/rhai_fn_junk_arg.stderr | 11 +++ codegen/ui_tests/rhai_fn_missing_value.rs | 27 ++++++ codegen/ui_tests/rhai_fn_missing_value.stderr | 11 +++ codegen/ui_tests/rhai_fn_path_attr.rs | 27 ++++++ codegen/ui_tests/rhai_fn_path_attr.stderr | 11 +++ 15 files changed, 357 insertions(+), 15 deletions(-) create mode 100644 codegen/tests/test_nested.rs create mode 100644 codegen/ui_tests/rhai_fn_bad_attr.rs create mode 100644 codegen/ui_tests/rhai_fn_bad_attr.stderr create mode 100644 codegen/ui_tests/rhai_fn_bad_value.rs create mode 100644 codegen/ui_tests/rhai_fn_bad_value.stderr create mode 100644 codegen/ui_tests/rhai_fn_extra_value.rs create mode 100644 codegen/ui_tests/rhai_fn_extra_value.stderr create mode 100644 codegen/ui_tests/rhai_fn_junk_arg.rs create mode 100644 codegen/ui_tests/rhai_fn_junk_arg.stderr create mode 100644 codegen/ui_tests/rhai_fn_missing_value.rs create mode 100644 codegen/ui_tests/rhai_fn_missing_value.stderr create mode 100644 codegen/ui_tests/rhai_fn_path_attr.rs create mode 100644 codegen/ui_tests/rhai_fn_path_attr.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0e4f1260..1724e308 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -17,6 +17,15 @@ use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; pub(crate) struct ExportedFnParams { pub name: Option, pub return_raw: bool, + pub skip: bool, +} + +impl ExportedFnParams { + pub fn skip() -> ExportedFnParams { + let mut skip = ExportedFnParams::default(); + skip.skip = true; + skip + } } impl Parse for ExportedFnParams { @@ -68,6 +77,7 @@ impl Parse for ExportedFnParams { let mut name = None; let mut return_raw = false; + let mut skip = false; for (ident, value) in attrs.drain() { match (ident.to_string().as_ref(), value) { ("name", Some(s)) => name = Some(s.value()), @@ -76,6 +86,10 @@ impl Parse for ExportedFnParams { ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) } + ("skip", None) => skip = true, + ("skip", Some(s)) => { + return Err(syn::Error::new(s.span(), "extraneous value")) + } (attr, _) => { return Err(syn::Error::new( ident.span(), @@ -85,7 +99,7 @@ impl Parse for ExportedFnParams { } } - Ok(ExportedFnParams { name, return_raw }) + Ok(ExportedFnParams { name, return_raw, skip, ..Default::default() }) } } @@ -95,7 +109,7 @@ pub(crate) struct ExportedFn { signature: syn::Signature, is_public: bool, mut_receiver: bool, - params: ExportedFnParams, + pub params: ExportedFnParams, } impl Parse for ExportedFn { diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 9e042bb9..827607c8 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -1,7 +1,7 @@ use quote::{quote, ToTokens}; use syn::{parse::Parse, parse::ParseStream}; -use crate::function::ExportedFn; +use crate::function::{ExportedFn, ExportedFnParams}; use crate::rhai_module::ExportedConst; #[cfg(no_std)] @@ -12,6 +12,22 @@ use std::vec as new_vec; #[cfg(no_std)] use core::mem; +fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { + if let Some(rhai_fn_idx) = f.attrs.iter().position(|a| { + a.path + .get_ident() + .map(|i| i.to_string() == "rhai_fn") + .unwrap_or(false) + }) { + let rhai_fn_attr = f.attrs.remove(rhai_fn_idx); + rhai_fn_attr.parse_args() + } else if let syn::Visibility::Public(_) = f.vis { + Ok(ExportedFnParams::default()) + } else { + Ok(ExportedFnParams::skip()) + } +} + #[derive(Debug)] pub(crate) struct Module { mod_all: Option, @@ -21,25 +37,27 @@ pub(crate) struct Module { impl Parse for Module { fn parse(input: ParseStream) -> syn::Result { - let mod_all: syn::ItemMod = input.parse()?; + let mut mod_all: syn::ItemMod = input.parse()?; let fns: Vec<_>; let consts: Vec<_>; - if let Some((_, ref content)) = mod_all.content { + if let Some((_, ref mut content)) = mod_all.content { fns = content - .iter() + .iter_mut() .filter_map(|item| match item { - syn::Item::Fn(f) => { - if let syn::Visibility::Public(_) = f.vis { - Some(f) - } else { - None - } - } + syn::Item::Fn(f) => Some(f), _ => None, }) - .try_fold(Vec::new(), |mut vec, itemfn| { + .try_fold(Vec::new(), |mut vec, mut itemfn| { + let params = match inner_fn_attributes(&mut itemfn) { + Ok(p) => p, + Err(e) => return Err(e), + }; syn::parse2::(itemfn.to_token_stream()) - .map(|f| vec.push(f)) + .map(|mut f| { + f.params = params; + f + }) + .map(|f| if !f.params.skip { vec.push(f) }) .map(|_| vec) })?; consts = content @@ -217,6 +235,22 @@ mod module_tests { assert!(item_mod.consts.is_empty()); } + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + } + #[test] fn one_private_constant_module() { let input_tokens: TokenStream = quote! { @@ -552,6 +586,36 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + #[test] fn one_private_constant_module() { let input_tokens: TokenStream = quote! { diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs new file mode 100644 index 00000000..4881b927 --- /dev/null +++ b/codegen/tests/test_nested.rs @@ -0,0 +1,36 @@ +use rhai::module_resolvers::*; +use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT, INT}; + +pub mod one_fn_module_nested_attr { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + use rhai::plugin::*; + use rhai::FLOAT; + + #[rhai_fn(return_raw)] + pub fn get_mystic_number() -> Result> { + Ok(Dynamic::from(42.0 as FLOAT)) + } + } +} + +#[test] +fn one_fn_module_nested_attr_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::get_mystic_number(); + m"# + )?, + 42.0 + ); + Ok(()) +} diff --git a/codegen/ui_tests/rhai_fn_bad_attr.rs b/codegen/ui_tests/rhai_fn_bad_attr.rs new file mode 100644 index 00000000..119efdb3 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(unknown = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_bad_attr.stderr b/codegen/ui_tests/rhai_fn_bad_attr.stderr new file mode 100644 index 00000000..3fe059dd --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/rhai_fn_bad_attr.rs:11:11 + | +11 | #[rhai_fn(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_bad_attr.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_bad_value.rs b/codegen/ui_tests/rhai_fn_bad_value.rs new file mode 100644 index 00000000..307e3c0e --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(name = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_bad_value.stderr b/codegen/ui_tests/rhai_fn_bad_value.stderr new file mode 100644 index 00000000..fedaed9b --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/rhai_fn_bad_value.rs:11:18 + | +11 | #[rhai_fn(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_bad_value.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_extra_value.rs b/codegen/ui_tests/rhai_fn_extra_value.rs new file mode 100644 index 00000000..f86b2df3 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_extra_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_extra_value.stderr b/codegen/ui_tests/rhai_fn_extra_value.stderr new file mode 100644 index 00000000..0597f4c2 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/rhai_fn_extra_value.rs:11:24 + | +11 | #[rhai_fn(return_raw = "yes")] + | ^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_extra_value.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_junk_arg.rs b/codegen/ui_tests/rhai_fn_junk_arg.rs new file mode 100644 index 00000000..b84424ae --- /dev/null +++ b/codegen/ui_tests/rhai_fn_junk_arg.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn("wheeeee")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_junk_arg.stderr b/codegen/ui_tests/rhai_fn_junk_arg.stderr new file mode 100644 index 00000000..e2054eef --- /dev/null +++ b/codegen/ui_tests/rhai_fn_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/rhai_fn_junk_arg.rs:11:11 + | +11 | #[rhai_fn("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_junk_arg.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_missing_value.rs b/codegen/ui_tests/rhai_fn_missing_value.rs new file mode 100644 index 00000000..25349530 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_missing_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_missing_value.stderr b/codegen/ui_tests/rhai_fn_missing_value.stderr new file mode 100644 index 00000000..6ea8040a --- /dev/null +++ b/codegen/ui_tests/rhai_fn_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/rhai_fn_missing_value.rs:11:11 + | +11 | #[rhai_fn(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_missing_value.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_path_attr.rs b/codegen/ui_tests/rhai_fn_path_attr.rs new file mode 100644 index 00000000..5c2174a8 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_path_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_path_attr.stderr b/codegen/ui_tests/rhai_fn_path_attr.stderr new file mode 100644 index 00000000..5b471f16 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/rhai_fn_path_attr.rs:11:11 + | +11 | #[rhai_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_path_attr.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From 31a05f8c48a5df05312e80135b003e441d4471ce Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Aug 2020 18:24:42 +0800 Subject: [PATCH 052/103] Revise treatment of name parameter. --- codegen/src/function.rs | 35 +++++++++++++++-------------------- codegen/src/lib.rs | 11 +---------- codegen/src/rhai_module.rs | 8 ++++++-- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0f6a603f..4285ece7 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -87,9 +87,7 @@ impl Parse for ExportedFnParams { return Err(syn::Error::new(s.span(), "extraneous value")) } ("skip", None) => skip = true, - ("skip", Some(s)) => { - return Err(syn::Error::new(s.span(), "extraneous value")) - } + ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), (attr, _) => { return Err(syn::Error::new( ident.span(), @@ -99,7 +97,12 @@ impl Parse for ExportedFnParams { } } - Ok(ExportedFnParams { name, return_raw, skip, ..Default::default() }) + Ok(ExportedFnParams { + name, + return_raw, + skip, + ..Default::default() + }) } } @@ -260,13 +263,8 @@ impl ExportedFn { } pub fn generate(self) -> proc_macro2::TokenStream { - let name_str = if let Some(ref name) = self.params.name { - name.clone() - } else { - self.name().to_string() - }; let name: syn::Ident = - syn::Ident::new(&format!("rhai_fn_{}", name_str), self.name().span()); + syn::Ident::new(&format!("rhai_fn_{}", self.name()), self.name().span()); let impl_block = self.generate_impl("Token"); let callable_block = self.generate_callable("Token"); let input_types_block = self.generate_input_types("Token"); @@ -285,11 +283,7 @@ impl ExportedFn { } pub fn generate_dynamic_fn(&self) -> proc_macro2::TokenStream { - let name: syn::Ident = if let Some(ref name) = self.params.name { - syn::Ident::new(name, self.name().span()) - } else { - self.name().clone() - }; + let name = self.name().clone(); let mut dynamic_signature = self.signature.clone(); dynamic_signature.ident = @@ -358,10 +352,11 @@ impl ExportedFn { } pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { - let name: syn::Ident = if let Some(ref name) = self.params.name { - syn::Ident::new(name, self.name().span()) + let sig_name = self.name().clone(); + let name = if let Some(ref name) = self.params.name { + name.clone() } else { - self.name().clone() + self.name().to_string() }; let arg_count = self.arg_count(); @@ -485,11 +480,11 @@ impl ExportedFn { // This allows skipping the Dynamic::from wrap. let return_expr = if !self.params.return_raw { quote! { - Ok(Dynamic::from(#name(#(#unpack_exprs),*))) + Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) } } else { quote! { - #name(#(#unpack_exprs),*) + #sig_name(#(#unpack_exprs),*) } }; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 2e5d0860..6f25be9a 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -106,20 +106,11 @@ pub fn export_fn( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let output = proc_macro2::TokenStream::from(input.clone()); + let mut output = proc_macro2::TokenStream::from(input.clone()); let parsed_params = parse_macro_input!(args as function::ExportedFnParams); let function_def = parse_macro_input!(input as function::ExportedFn); - let mut output = if let Some(ref rename) = parsed_params.name { - // If it wasn't a function, it wouldn't have parsed earlier, so unwrap() is fine. - let mut output_fn: syn::ItemFn = syn::parse2(output.clone()).unwrap(); - let new_name = syn::Ident::new(rename, output_fn.sig.ident.span()); - output_fn.sig.ident = new_name; - output_fn.into_token_stream() - } else { - output - }; output.extend(function_def.generate_with_params(parsed_params)); proc_macro::TokenStream::from(output) } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 08c7f11f..3d238b19 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -29,8 +29,12 @@ pub(crate) fn generate_body( &format!("{}_token", function.name().to_string()), function.name().span(), ); - let fn_literal = - syn::LitStr::new(&function.name().to_string(), proc_macro2::Span::call_site()); + let reg_name = if let Some(ref name) = function.params.name { + name.clone() + } else { + function.name().to_string() + }; + let fn_literal = syn::LitStr::new(®_name, proc_macro2::Span::call_site()); let fn_input_types: Vec = function .arg_list() .map(|fnarg| match fnarg { From e75d91e9bf6514f4c47229944fc9d0eae2db372b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 16 Aug 2020 23:41:59 +0800 Subject: [PATCH 053/103] Bring more functions into modules in plugins via rhai_fn(name) attribute. --- codegen/src/lib.rs | 2 +- src/packages/array_basic.rs | 44 ++++------ src/packages/eval.rs | 8 +- src/packages/fn_basic.rs | 10 +-- src/packages/map_basic.rs | 50 +++++------ src/packages/string_more.rs | 153 ++++++++++++++------------------- src/packages/time_basic.rs | 166 +++++++++++++++++------------------- tests/plugins.rs | 12 ++- tests/time.rs | 9 ++ 9 files changed, 210 insertions(+), 244 deletions(-) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 6f25be9a..9675ac66 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -93,7 +93,7 @@ //! ``` //! -use quote::{quote, ToTokens}; +use quote::quote; use syn::parse_macro_input; mod function; diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index fc44c4c5..fee7ae4d 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -11,9 +11,6 @@ use crate::plugin::*; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; - use crate::stdlib::{any::TypeId, boxed::Box}; #[cfg(not(feature = "unchecked"))] @@ -61,8 +58,9 @@ macro_rules! reg_pad { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { lib.combine(exported_module!(array_functions)); - set_exported_fn!(lib, "+", append); - set_exported_fn!(lib, "+", concat); + + #[cfg(not(feature = "no_object"))] + lib.combine(exported_module!(object_functions)); reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); reg_pad!(lib, INT, bool, char, ImmutableString, FnPtr, Array, Unit); @@ -86,9 +84,6 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { reg_pad!(lib, f32, f64); } - #[cfg(not(feature = "no_object"))] - set_exported_fn!(lib, make_getter("len"), array_funcs::len); - // Register array iterator lib.set_iter( TypeId::of::(), @@ -96,24 +91,23 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { ); }); -#[export_fn] -fn append(x: &mut Array, y: Array) { - x.extend(y); -} -#[export_fn] -fn concat(mut x: Array, y: Array) -> Array { - x.extend(y); - x -} - #[export_module] mod array_functions { pub fn len(list: &mut Array) -> INT { - array_funcs::len(list) + list.len() as INT } pub fn append(x: &mut Array, y: Array) { x.extend(y); } + #[rhai_fn(name = "+=")] + pub fn append_operator(x: &mut Array, y: Array) { + append(x, y) + } + #[rhai_fn(name = "+")] + pub fn concat(mut x: Array, y: Array) -> Array { + x.extend(y); + x + } pub fn pop(list: &mut Array) -> Dynamic { list.pop().unwrap_or_else(|| ().into()) } @@ -143,14 +137,12 @@ mod array_functions { } } -mod array_funcs { - use crate::engine::Array; - use crate::parser::INT; - use crate::plugin::*; - - #[export_fn] +#[cfg(not(feature = "no_object"))] +#[export_module] +mod object_functions { + #[rhai_fn(name = "get$len")] pub fn len(list: &mut Array) -> INT { - list.len() as INT + array_functions::len(list) } } diff --git a/src/packages/eval.rs b/src/packages/eval.rs index 6b2b9aa9..0e2187da 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -4,11 +4,11 @@ use crate::parser::ImmutableString; use crate::plugin::*; use crate::result::EvalAltResult; +def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { + set_exported_fn!(lib, "eval", eval_override); +}); + #[export_fn(return_raw)] fn eval_override(_script: ImmutableString) -> Result> { Err("eval is evil!".into()) } - -def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { - set_exported_fn!(lib, "eval", eval_override); -}); diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index e34cb918..7523b7f6 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -5,14 +5,14 @@ use crate::plugin::*; #[cfg(not(feature = "no_object"))] use crate::engine::make_getter; -#[export_fn] -fn get_fn_name(f: &mut FnPtr) -> ImmutableString { - f.get_fn_name().clone() -} - def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { set_exported_fn!(lib, "name", get_fn_name); #[cfg(not(feature = "no_object"))] set_exported_fn!(lib, make_getter("name"), get_fn_name); }); + +#[export_fn] +fn get_fn_name(f: &mut FnPtr) -> ImmutableString { + f.get_fn_name().clone() +} diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index bb2f4f09..9c9f080d 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -2,7 +2,7 @@ use crate::any::Dynamic; use crate::def_package; -use crate::engine::{make_getter, Map}; +use crate::engine::Map; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; @@ -10,9 +10,6 @@ use crate::stdlib::vec::Vec; def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.combine(exported_module!(map_functions)); - set_exported_fn!(lib, make_getter("len"), map_funcs::len); - set_exported_fn!(lib, "+=", map_funcs::mixin); - set_exported_fn!(lib, "+", map_funcs::merge); // Register map access functions #[cfg(not(feature = "no_index"))] @@ -25,7 +22,11 @@ mod map_functions { map.contains_key(&prop) } pub fn len(map: &mut Map) -> INT { - map_funcs::len(map) + map.len() as INT + } + #[rhai_fn(name = "get$len")] + pub fn len_prop(map: &mut Map) -> INT { + len(map) } pub fn clear(map: &mut Map) { map.clear(); @@ -34,7 +35,20 @@ mod map_functions { x.remove(&name).unwrap_or_else(|| ().into()) } pub fn mixin(map1: &mut Map, map2: Map) { - map_funcs::mixin(map1, map2); + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + } + #[rhai_fn(name = "+=")] + pub fn mixin_operator(map1: &mut Map, map2: Map) { + mixin(map1, map2) + } + #[rhai_fn(name = "+")] + pub fn merge(mut map1: Map, map2: Map) -> Map { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + map1 } pub fn fill_with(map1: &mut Map, map2: Map) { map2.into_iter().for_each(|(key, value)| { @@ -45,30 +59,6 @@ mod map_functions { } } -mod map_funcs { - use crate::engine::Map; - use crate::parser::INT; - use crate::plugin::*; - - #[export_fn] - pub fn len(map: &mut Map) -> INT { - map.len() as INT - } - #[export_fn] - pub fn mixin(map1: &mut Map, map2: Map) { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - } - #[export_fn] - pub fn merge(mut map1: Map, map2: Map) -> Map { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - map1 - } -} - #[cfg(not(feature = "no_index"))] #[export_module] mod index_functions { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index ded48378..c9efcffe 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -11,9 +11,6 @@ use crate::utils::StaticVec; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; - use crate::stdlib::{ any::TypeId, boxed::Box, fmt::Display, format, mem, string::String, string::ToString, vec::Vec, }; @@ -61,29 +58,12 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str reg_functions!(lib += float; f32, f64); #[cfg(not(feature = "no_index"))] - { - set_exported_fn!(lib, "+", string_funcs_array::append_array); - set_exported_fn!(lib, "+", string_funcs_array::prepend_array); - } + lib.combine(exported_module!(index_functions)); + + #[cfg(not(feature = "no_object"))] + lib.combine(exported_module!(object_functions)); lib.combine(exported_module!(string_functions)); - set_exported_fn!(lib, "contains", string_funcs::contains_char); - set_exported_fn!(lib, "contains", string_funcs::contains_string); - set_exported_fn!(lib, "index_of", string_funcs::index_of_char); - set_exported_fn!(lib, "index_of", string_funcs::index_of_char_starting_from); - set_exported_fn!(lib, "index_of", string_funcs::index_of_string); - set_exported_fn!(lib, "index_of", string_funcs::index_of_string_starting_from); - set_exported_fn!(lib, "append", string_funcs::append_char); - set_exported_fn!(lib, "+=", string_funcs::append_char); - set_exported_fn!(lib, "append", string_funcs::append_string); - set_exported_fn!(lib, "sub_string", string_funcs::sub_string); - set_exported_fn!(lib, "sub_string", string_funcs::sub_string_starting_from); - set_exported_fn!(lib, "crop", string_funcs::crop_string); - set_exported_fn!(lib, "crop", string_funcs::crop_string_starting_from); - set_exported_fn!(lib, "replace", string_funcs::replace_string); - set_exported_fn!(lib, "replace", string_funcs::replace_char); - set_exported_fn!(lib, "replace", string_funcs::replace_string_with_char); - set_exported_fn!(lib, "replace", string_funcs::replace_char_with_string); lib.set_raw_fn( "pad", @@ -131,9 +111,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str }, ); - #[cfg(not(feature = "no_object"))] - set_exported_fn!(lib, make_getter("len"), string_funcs::len); - // Register string iterator lib.set_iter( TypeId::of::(), @@ -166,9 +143,27 @@ gen_concat_functions!(float => f32, f64); #[export_module] mod string_functions { - pub fn len(s: &mut ImmutableString) -> INT { - string_funcs::len(s) + #[rhai_fn(name = "+")] + pub fn append_unit(s: ImmutableString, _x: ()) -> ImmutableString { + s } + #[rhai_fn(name = "+")] + pub fn prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { + s + } + #[rhai_fn(name = "+")] + pub fn append_char(s: &mut ImmutableString, ch: char) { + *s += ch; + } + #[rhai_fn(name = "+")] + pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { + *s += &add; + } + + pub fn len(s: &mut ImmutableString) -> INT { + s.chars().count() as INT + } + pub fn clear(s: &mut ImmutableString) { s.make_mut().clear(); } @@ -189,52 +184,17 @@ mod string_functions { *s = trimmed.to_string().into(); } } -} -#[cfg(not(feature = "no_index"))] -mod string_funcs_array { - use crate::engine::Array; - use crate::plugin::*; - use crate::stdlib::string::String; - use crate::utils::ImmutableString; - - #[export_fn] - pub fn append_array(x: &mut ImmutableString, y: Array) -> String { - format!("{}{:?}", x, y) - } - #[export_fn] - pub fn prepend_array(x: &mut Array, y: ImmutableString) -> String { - format!("{:?}{}", x, y) - } -} - -mod string_funcs { - use crate::parser::INT; - use crate::plugin::*; - use crate::stdlib::string::{String, ToString}; - use crate::utils::{ImmutableString, StaticVec}; - - #[export_fn] - pub fn append_unit(s: ImmutableString, _x: ()) -> ImmutableString { - s - } - #[export_fn] - pub fn prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { - s - } - #[export_fn] - pub fn len(s: &mut ImmutableString) -> INT { - s.chars().count() as INT - } - #[export_fn] + #[rhai_fn(name = "contains")] pub fn contains_char(s: &mut ImmutableString, ch: char) -> bool { s.contains(ch) } - #[export_fn] + #[rhai_fn(name = "contains")] pub fn contains_string(s: &mut ImmutableString, find: ImmutableString) -> bool { s.contains(find.as_str()) } - #[export_fn] + + #[rhai_fn(name = "index_of")] pub fn index_of_char_starting_from(s: &mut ImmutableString, ch: char, start: INT) -> INT { let start = if start < 0 { 0 @@ -249,13 +209,13 @@ mod string_funcs { .map(|index| s[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } - #[export_fn] + #[rhai_fn(name = "index_of")] pub fn index_of_char(s: &mut ImmutableString, ch: char) -> INT { s.find(ch) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT) } - #[export_fn] + #[rhai_fn(name = "index_of")] pub fn index_of_string_starting_from( s: &mut ImmutableString, find: ImmutableString, @@ -274,21 +234,14 @@ mod string_funcs { .map(|index| s[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } - #[export_fn] + #[rhai_fn(name = "index_of")] pub fn index_of_string(s: &mut ImmutableString, find: ImmutableString) -> INT { s.find(find.as_str()) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT) } - #[export_fn] - pub fn append_char(s: &mut ImmutableString, ch: char) { - *s += ch; - } - #[export_fn] - pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { - *s += &add; - } - #[export_fn] + + #[rhai_fn(name = "sub_string")] pub fn sub_string(s: ImmutableString, start: INT, len: INT) -> ImmutableString { let offset = if s.is_empty() || len <= 0 { return "".to_string().into(); @@ -316,12 +269,13 @@ mod string_funcs { .collect::() .into() } - #[export_fn] + #[rhai_fn(name = "sub_string")] pub fn sub_string_starting_from(s: ImmutableString, start: INT) -> ImmutableString { let len = s.len() as INT; sub_string(s, start, len) } - #[export_fn] + + #[rhai_fn(name = "crop")] fn crop_string(s: &mut ImmutableString, start: INT, len: INT) { let offset = if s.is_empty() || len <= 0 { s.make_mut().clear(); @@ -347,24 +301,49 @@ mod string_funcs { copy.clear(); copy.extend(chars.iter().skip(offset).take(len)); } - #[export_fn] + #[rhai_fn(name = "crop")] pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) { crop_string(s, start, s.len() as INT); } - #[export_fn] + + #[rhai_fn(name = "replace")] pub fn replace_string(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) { *s = s.replace(find.as_str(), sub.as_str()).into(); } - #[export_fn] + #[rhai_fn(name = "replace")] pub fn replace_string_with_char(s: &mut ImmutableString, find: ImmutableString, sub: char) { *s = s.replace(find.as_str(), &sub.to_string()).into(); } - #[export_fn] + #[rhai_fn(name = "replace")] pub fn replace_char_with_string(s: &mut ImmutableString, find: char, sub: ImmutableString) { *s = s.replace(&find.to_string(), sub.as_str()).into(); } - #[export_fn] + #[rhai_fn(name = "replace")] pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { *s = s.replace(&find.to_string(), &sub.to_string()).into(); } } + +#[cfg(not(feature = "no_index"))] +#[export_module] +mod index_functions { + use crate::engine::Array; + + #[rhai_fn(name = "+")] + pub fn append(x: &mut ImmutableString, y: Array) -> String { + format!("{}{:?}", x, y) + } + #[rhai_fn(name = "+")] + pub fn prepend(x: &mut Array, y: ImmutableString) -> String { + format!("{:?}{}", x, y) + } +} + +#[cfg(not(feature = "no_object"))] +#[export_module] +mod object_functions { + #[rhai_fn(name = "get$len")] + pub fn len(s: &mut ImmutableString) -> INT { + string_functions::len(s) + } +} diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 55315cee..a55471ee 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -18,9 +18,6 @@ use crate::parser::INT; #[cfg(not(feature = "unchecked"))] use crate::token::Position; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; - #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::time::Instant; @@ -29,111 +26,104 @@ use instant::Instant; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions - set_exported_fn!(lib, "timestamp", create_timestamp); - set_exported_fn!(lib, "elapsed", elapsed); + lib.combine(exported_module!(time_functions)); #[cfg(not(feature = "no_object"))] - set_exported_fn!(lib, make_getter("elapsed"), elapsed); - - set_exported_fn!(lib, "-", time_diff); - - //lib.combine(exported_module!(time_compare)); - - lib.set_fn_2("<", |x:Instant, y:Instant| Ok(x < y)); - lib.set_fn_2("<=", |x:Instant, y:Instant| Ok(x <= y)); - lib.set_fn_2(">", |x:Instant, y:Instant| Ok(x > y)); - lib.set_fn_2(">=", |x:Instant, y:Instant| Ok(x >= y)); - lib.set_fn_2("==", |x:Instant, y:Instant| Ok(x == y)); - lib.set_fn_2("!=", |x:Instant, y:Instant| Ok(x != y)); + lib.set_getter_fn("elapsed", time_functions::elapsed); }); -#[export_fn] -fn create_timestamp() -> Instant { - Instant::now() -} - -#[cfg(not(feature = "no_float"))] -#[export_fn] -fn elapsed(timestamp: &mut Instant) -> FLOAT { - timestamp.elapsed().as_secs_f64() as FLOAT -} - -#[cfg(feature = "no_float")] -#[export_fn(return_raw)] -fn elapsed(timestamp: &mut Instant) -> Result> { - let seconds = timestamp.elapsed().as_secs(); - - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp.elapsed: {}", seconds), - Position::none(), - ) - .into(); - } - - Ok((seconds as INT).into()) -} - -#[cfg(not(feature = "no_float"))] -#[export_fn] -fn time_diff(ts1: Instant, ts2: Instant) -> FLOAT { - if ts2 > ts1 { - -(ts2 - ts1).as_secs_f64() as FLOAT - } else { - (ts1 - ts2).as_secs_f64() as FLOAT - } -} - -#[cfg(feature = "no_float")] -#[export_fn(return_raw)] -fn time_diff(ts1: Instant, ts2: Instant) -> Result> { - if ts2 > ts1 { - let seconds = (ts2 - ts1).as_secs(); - - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: -{}", seconds), - Position::none(), - ) - .into(); - } - - Ok(Dynamic::from(-(seconds as INT))) - } else { - let seconds = (ts1 - ts2).as_secs(); - - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: {}", seconds), - Position::none(), - ) - .into(); - } - - Ok((seconds as INT).into()) - } -} - #[export_module] -mod time_compare { +mod time_functions { + pub fn timestamp() -> Instant { + Instant::now() + } + #[rhai_fn(return_raw)] + pub fn elapsed(timestamp: &mut Instant) -> Result> { + #[cfg(not(feature = "no_float"))] + { + Ok((timestamp.elapsed().as_secs_f64() as FLOAT).into()) + } + + #[cfg(feature = "no_float")] + { + let seconds = timestamp.elapsed().as_secs(); + + #[cfg(not(feature = "unchecked"))] + if seconds > (MAX_INT as u64) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp.elapsed: {}", seconds), + Position::none(), + ) + .into(); + } + + Ok((seconds as INT).into()) + } + } + + #[rhai_fn(return_raw, name = "-")] + fn time_diff(ts1: Instant, ts2: Instant) -> Result> { + #[cfg(not(feature = "no_float"))] + { + Ok(if ts2 > ts1 { + -(ts2 - ts1).as_secs_f64() as FLOAT + } else { + (ts1 - ts2).as_secs_f64() as FLOAT + } + .into()) + } + + #[cfg(feature = "no_float")] + if ts2 > ts1 { + let seconds = (ts2 - ts1).as_secs(); + + #[cfg(not(feature = "unchecked"))] + if seconds > (MAX_INT as u64) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp duration: -{}", seconds), + Position::none(), + ) + .into(); + } + + Ok(Dynamic::from(-(seconds as INT))) + } else { + let seconds = (ts1 - ts2).as_secs(); + + #[cfg(not(feature = "unchecked"))] + if seconds > (MAX_INT as u64) { + return EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp duration: {}", seconds), + Position::none(), + ) + .into(); + } + + Ok((seconds as INT).into()) + } + } + + #[rhai_fn(name = "==")] pub fn eq(x: Instant, y: Instant) -> bool { x == y } + #[rhai_fn(name = "!=")] pub fn ne(x: Instant, y: Instant) -> bool { x != y } + #[rhai_fn(name = "<")] pub fn lt(x: Instant, y: Instant) -> bool { x < y } + #[rhai_fn(name = "<=")] pub fn lte(x: Instant, y: Instant) -> bool { x <= y } + #[rhai_fn(name = ">")] pub fn gt(x: Instant, y: Instant) -> bool { x > y } + #[rhai_fn(name = ">=")] pub fn gte(x: Instant, y: Instant) -> bool { x >= y } diff --git a/tests/plugins.rs b/tests/plugins.rs index b6399ced..3379b411 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -7,9 +7,14 @@ use rhai::{Engine, EvalAltResult, INT}; mod special_array_package { use rhai::{Array, INT}; + #[rhai_fn(name = "test")] pub fn len(array: &mut Array, mul: INT) -> INT { (array.len() as INT) * mul } + #[rhai_fn(name = "+")] + pub fn funky_add(x: INT, y: INT) -> INT { + x / 2 + y * 2 + } } macro_rules! gen_unary_functions { @@ -18,7 +23,7 @@ macro_rules! gen_unary_functions { pub mod $arg_type { use super::super::*; - #[export_fn] + #[export_fn(name="test")] pub fn single(x: $arg_type) -> $return_type { super::super::$op_fn(x) } @@ -48,9 +53,10 @@ fn test_plugins_package() -> Result<(), Box> { reg_functions!(engine += greet::single(INT, bool, char)); - assert_eq!(engine.eval::("let a = [1, 2, 3]; len(a, 2)")?, 6); + assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); + assert_eq!(engine.eval::("2 + 2")?, 5); assert_eq!( - engine.eval::("let a = [1, 2, 3]; greet(len(a, 2))")?, + engine.eval::("let a = [1, 2, 3]; greet(test(a, 2))")?, "6 kitties" ); diff --git a/tests/time.rs b/tests/time.rs index b3f6668f..ad7fc4c9 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -39,5 +39,14 @@ fn test_timestamp() -> Result<(), Box> { )? < 10 ); + assert!(engine.eval::( + r" + let time1 = timestamp(); + for x in range(0, 10000) {} + let time2 = timestamp(); + time1 <= time2 + " + )?); + Ok(()) } From 810514dd31df69031fd8a8ef18697b1542913cd7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 17 Aug 2020 00:13:52 +0800 Subject: [PATCH 054/103] Add get/set/index_get/index_set parameters to rhai_fn. --- codegen/src/function.rs | 30 +++++++++++++++++++++++------- codegen/src/rhai_module.rs | 32 +++++++++++++++++++++++++++----- src/packages/array_basic.rs | 2 +- src/packages/map_basic.rs | 2 +- src/packages/string_more.rs | 2 +- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 4285ece7..36f914d0 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -13,9 +13,15 @@ use std::collections::HashMap; use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; +use super::rhai_module::get_register_name; + #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option, + pub get: Option, + pub set: Option, + pub index_get: bool, + pub index_set: bool, pub return_raw: bool, pub skip: bool, } @@ -76,14 +82,24 @@ impl Parse for ExportedFnParams { } let mut name = None; + let mut get = None; + let mut set = None; + let mut index_get = false; + let mut index_set = false; let mut return_raw = false; let mut skip = false; for (ident, value) in attrs.drain() { match (ident.to_string().as_ref(), value) { ("name", Some(s)) => name = Some(s.value()), - ("name", None) => return Err(syn::Error::new(ident.span(), "requires value")), + ("get", Some(s)) => get = Some(s.value()), + ("set", Some(s)) => set = Some(s.value()), + ("get", None) | ("set", None) | ("name", None) => { + return Err(syn::Error::new(ident.span(), "requires value")) + } + ("index_get", None) => index_get = true, + ("index_set", None) => index_get = true, ("return_raw", None) => return_raw = true, - ("return_raw", Some(s)) => { + ("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) } ("skip", None) => skip = true, @@ -99,6 +115,10 @@ impl Parse for ExportedFnParams { Ok(ExportedFnParams { name, + get, + set, + index_get, + index_set, return_raw, skip, ..Default::default() @@ -353,11 +373,7 @@ impl ExportedFn { pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { let sig_name = self.name().clone(); - let name = if let Some(ref name) = self.params.name { - name.clone() - } else { - self.name().to_string() - }; + let name = get_register_name(self); let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 3d238b19..22c22056 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -4,6 +4,32 @@ use crate::function::ExportedFn; pub(crate) type ExportedConst = (String, syn::Expr); +pub(crate) fn get_register_name(function: &ExportedFn) -> String { + pub const FN_IDX_GET: &str = "index$get$"; + pub const FN_IDX_SET: &str = "index$set$"; + + pub fn make_getter(id: &str) -> String { + format!("get${}", id) + } + pub fn make_setter(id: &str) -> String { + format!("set${}", id) + } + + if let Some(ref name) = function.params.name { + name.clone() + } else if let Some(ref name) = function.params.get { + make_getter(name).clone() + } else if let Some(ref name) = function.params.set { + make_setter(name).clone() + } else if function.params.index_get { + FN_IDX_GET.to_string() + } else if function.params.index_set { + FN_IDX_SET.to_string() + } else { + function.name().to_string() + } +} + pub(crate) fn generate_body( fns: &Vec, consts: &Vec, @@ -29,11 +55,7 @@ pub(crate) fn generate_body( &format!("{}_token", function.name().to_string()), function.name().span(), ); - let reg_name = if let Some(ref name) = function.params.name { - name.clone() - } else { - function.name().to_string() - }; + let reg_name = get_register_name(function); let fn_literal = syn::LitStr::new(®_name, proc_macro2::Span::call_site()); let fn_input_types: Vec = function .arg_list() diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index fee7ae4d..c4590b08 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -140,7 +140,7 @@ mod array_functions { #[cfg(not(feature = "no_object"))] #[export_module] mod object_functions { - #[rhai_fn(name = "get$len")] + #[rhai_fn(get = "len")] pub fn len(list: &mut Array) -> INT { array_functions::len(list) } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 9c9f080d..12ad00e8 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -24,7 +24,7 @@ mod map_functions { pub fn len(map: &mut Map) -> INT { map.len() as INT } - #[rhai_fn(name = "get$len")] + #[rhai_fn(get = "len")] pub fn len_prop(map: &mut Map) -> INT { len(map) } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index c9efcffe..e21d039e 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -342,7 +342,7 @@ mod index_functions { #[cfg(not(feature = "no_object"))] #[export_module] mod object_functions { - #[rhai_fn(name = "get$len")] + #[rhai_fn(get = "len")] pub fn len(s: &mut ImmutableString) -> INT { string_functions::len(s) } From 371c13139553fd2300bda7fd200fff01f2fe9b33 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 17 Aug 2020 09:44:28 +0800 Subject: [PATCH 055/103] Fix CI. --- codegen/tests/test_functions.rs | 72 --------------------------------- 1 file changed, 72 deletions(-) diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index cfc73275..4a179376 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -160,78 +160,6 @@ fn mut_opaque_ref_test() -> Result<(), Box> { Ok(()) } -mod rename_fn { - use rhai::plugin::*; - use rhai::FLOAT; - - #[export_fn(name = "add_float")] - pub fn add(f1: FLOAT, f2: FLOAT) -> FLOAT { - f1 + f2 - } -} - -#[test] -fn rename_fn_test() -> Result<(), Box> { - let mut engine = Engine::new(); - engine.register_fn("get_mystic_number", || 42 as FLOAT); - let mut m = Module::new(); - rhai::set_exported_fn!(m, "add_two_floats", rename_fn::add_float); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); - engine.set_module_resolver(Some(r)); - - assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let x = get_mystic_number(); - let y = math::add_two_floats(x, 1.0); - y"# - )?, - 43.0 - ); - Ok(()) -} - -mod duplicate_fn_rename { - use rhai::plugin::*; - use rhai::{FLOAT, INT}; - - #[export_fn(name = "add_float")] - pub fn add(f1: FLOAT, f2: FLOAT) -> FLOAT { - f1 + f2 - } - - #[export_fn(name = "add_int")] - pub fn add(i1: INT, i2: INT) -> INT { - i1 + i2 - } -} - -#[test] -fn duplicate_fn_rename_test() -> Result<(), Box> { - let mut engine = Engine::new(); - engine.register_fn("get_mystic_number", || 42 as FLOAT); - let mut m = Module::new(); - rhai::set_exported_fn!(m, "add_two_floats", duplicate_fn_rename::add_float); - rhai::set_exported_fn!(m, "add_two_ints", duplicate_fn_rename::add_int); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); - engine.set_module_resolver(Some(r)); - - let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let fx = get_mystic_number(); - let fy = math::add_two_floats(fx, 1.0); - let ix = 42; - let iy = math::add_two_ints(ix, 1); - [fy, iy] - "#, - )?; - assert_eq!(&output_array[0].as_float().unwrap(), &43.0); - assert_eq!(&output_array[1].as_int().unwrap(), &43); - Ok(()) -} - pub mod raw_returning_fn { use rhai::plugin::*; use rhai::FLOAT; From 09b75ed1a331d636f0905e35dabbca2be12634e2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 17 Aug 2020 10:00:50 +0800 Subject: [PATCH 056/103] Fix bug in strings package. --- src/packages/string_more.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e21d039e..087e8e90 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -23,12 +23,12 @@ macro_rules! gen_concat_functions { #[export_fn] pub fn append_func(x: ImmutableString, y: $arg_type) -> String { - super::super::append(x, y) + super::super::add_append(x, y) } #[export_fn] pub fn prepend_func(x: $arg_type, y: ImmutableString) -> String { - super::super::prepend(x, y) + super::super::add_prepend(x, y) } } )* } @@ -120,10 +120,10 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ); }); -fn prepend(x: T, y: ImmutableString) -> String { +fn add_prepend(x: T, y: ImmutableString) -> String { format!("{}{}", x, y) } -fn append(x: ImmutableString, y: T) -> String { +fn add_append(x: ImmutableString, y: T) -> String { format!("{}{}", x, y) } @@ -144,18 +144,18 @@ gen_concat_functions!(float => f32, f64); #[export_module] mod string_functions { #[rhai_fn(name = "+")] - pub fn append_unit(s: ImmutableString, _x: ()) -> ImmutableString { + pub fn add_append_unit(s: ImmutableString, _x: ()) -> ImmutableString { s } #[rhai_fn(name = "+")] - pub fn prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { + pub fn add_prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { s } - #[rhai_fn(name = "+")] + #[rhai_fn(name = "+=")] pub fn append_char(s: &mut ImmutableString, ch: char) { *s += ch; } - #[rhai_fn(name = "+")] + #[rhai_fn(name = "+=")] pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { *s += &add; } From 767c39d974075b6e8b7d9d3e00e5b01a4350e026 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 17 Aug 2020 23:35:04 +0800 Subject: [PATCH 057/103] Simplify rhai_fn attribute. --- codegen/src/function.rs | 38 +++++++++++++++++++------------------- codegen/src/rhai_module.rs | 32 +++++--------------------------- 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 36f914d0..4e5bf139 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -13,15 +13,9 @@ use std::collections::HashMap; use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; -use super::rhai_module::get_register_name; - #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option, - pub get: Option, - pub set: Option, - pub index_get: bool, - pub index_set: bool, pub return_raw: bool, pub skip: bool, } @@ -34,6 +28,16 @@ impl ExportedFnParams { } } +pub const FN_IDX_GET: &str = "index$get$"; +pub const FN_IDX_SET: &str = "index$set$"; + +pub fn make_getter(id: &str) -> String { + format!("get${}", id) +} +pub fn make_setter(id: &str) -> String { + format!("set${}", id) +} + impl Parse for ExportedFnParams { fn parse(args: ParseStream) -> syn::Result { if args.is_empty() { @@ -82,22 +86,18 @@ impl Parse for ExportedFnParams { } let mut name = None; - let mut get = None; - let mut set = None; - let mut index_get = false; - let mut index_set = false; let mut return_raw = false; let mut skip = false; for (ident, value) in attrs.drain() { match (ident.to_string().as_ref(), value) { ("name", Some(s)) => name = Some(s.value()), - ("get", Some(s)) => get = Some(s.value()), - ("set", Some(s)) => set = Some(s.value()), + ("get", Some(s)) => name = Some(make_getter(&s.value())), + ("set", Some(s)) => name = Some(make_setter(&s.value())), ("get", None) | ("set", None) | ("name", None) => { return Err(syn::Error::new(ident.span(), "requires value")) } - ("index_get", None) => index_get = true, - ("index_set", None) => index_get = true, + ("index_get", None) => name = Some(FN_IDX_GET.to_string()), + ("index_set", None) => name = Some(FN_IDX_SET.to_string()), ("return_raw", None) => return_raw = true, ("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) @@ -115,10 +115,6 @@ impl Parse for ExportedFnParams { Ok(ExportedFnParams { name, - get, - set, - index_get, - index_set, return_raw, skip, ..Default::default() @@ -373,7 +369,11 @@ impl ExportedFn { pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { let sig_name = self.name().clone(); - let name = get_register_name(self); + let name = self + .params + .name + .clone() + .unwrap_or_else(|| self.name().to_string()); let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 22c22056..dea8a421 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -4,32 +4,6 @@ use crate::function::ExportedFn; pub(crate) type ExportedConst = (String, syn::Expr); -pub(crate) fn get_register_name(function: &ExportedFn) -> String { - pub const FN_IDX_GET: &str = "index$get$"; - pub const FN_IDX_SET: &str = "index$set$"; - - pub fn make_getter(id: &str) -> String { - format!("get${}", id) - } - pub fn make_setter(id: &str) -> String { - format!("set${}", id) - } - - if let Some(ref name) = function.params.name { - name.clone() - } else if let Some(ref name) = function.params.get { - make_getter(name).clone() - } else if let Some(ref name) = function.params.set { - make_setter(name).clone() - } else if function.params.index_get { - FN_IDX_GET.to_string() - } else if function.params.index_set { - FN_IDX_SET.to_string() - } else { - function.name().to_string() - } -} - pub(crate) fn generate_body( fns: &Vec, consts: &Vec, @@ -55,7 +29,11 @@ pub(crate) fn generate_body( &format!("{}_token", function.name().to_string()), function.name().span(), ); - let reg_name = get_register_name(function); + let reg_name = function + .params + .name + .clone() + .unwrap_or_else(|| function.name().to_string()); let fn_literal = syn::LitStr::new(®_name, proc_macro2::Span::call_site()); let fn_input_types: Vec = function .arg_list() From 772e44aa3df24e1178f71acefc3d43a1f626c2ed Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 18 Aug 2020 09:25:43 +0800 Subject: [PATCH 058/103] Test getter. --- tests/plugins.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/plugins.rs b/tests/plugins.rs index 3379b411..5331287b 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -7,6 +7,10 @@ use rhai::{Engine, EvalAltResult, INT}; mod special_array_package { use rhai::{Array, INT}; + #[rhai_fn(get = "foo", return_raw)] + pub fn foo(array: &mut Array) -> Result> { + Ok(array[0].clone()) + } #[rhai_fn(name = "test")] pub fn len(array: &mut Array, mul: INT) -> INT { (array.len() as INT) * mul @@ -53,6 +57,7 @@ fn test_plugins_package() -> Result<(), Box> { reg_functions!(engine += greet::single(INT, bool, char)); + assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::("2 + 2")?, 5); assert_eq!( From e3f2157c6aaf07eaf1f03e94acd79618496a47a8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 18 Aug 2020 22:01:13 +0800 Subject: [PATCH 059/103] Treat leading #{ in Engine::parse_json. --- RELEASES.md | 1 + doc/src/language/json.md | 31 ++++++++++++++++++++++++++++--- src/api.rs | 32 +++++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index b753bba3..89b05c36 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,7 @@ New features * Adds `Engine::register_get_result`, `Engine::register_set_result`, `Engine::register_indexer_get_result`, `Engine::register_indexer_set_result` API. * Adds `Module::combine` to combine two modules. +* `Engine::parse_json` now also accepts a JSON object starting with `#{`. Version 0.18.1 diff --git a/doc/src/language/json.md b/doc/src/language/json.md index a5aaba2a..543ddfdb 100644 --- a/doc/src/language/json.md +++ b/doc/src/language/json.md @@ -7,7 +7,7 @@ The syntax for an [object map] is extremely similar to JSON, with the exception technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a Rhai [object map] does - that's the major difference! -Use the `Engine::parse_json` method to parse a piece of JSON into an object map: +Use the `Engine::parse_json` method to parse a piece of _simple_ JSON into an object map: ```rust // JSON string - notice that JSON property names are always quoted @@ -26,7 +26,7 @@ let json = r#"{ // Set the second boolean parameter to true in order to map 'null' to '()' let map = engine.parse_json(json, true)?; -map.len() == 6; // 'map' contains all properties in the JSON string +map.len() == 6; // 'map' contains all properties in the JSON string // Put the object map into a 'Scope' let mut scope = Scope::new(); @@ -34,7 +34,7 @@ scope.push("map", map); let result = engine.eval_with_scope::(r#"map["^^^!!!"].len()"#)?; -result == 3; // the object map is successfully used in the script +result == 3; // the object map is successfully used in the script ``` Representation of Numbers @@ -45,3 +45,28 @@ the [`no_float`] feature is not used. Most common generators of JSON data disti integer and floating-point values by always serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully with Rhai [object maps]. + + +Parse JSON with Sub-Objects +-------------------------- + +`Engine::parse_json` depends on the fact that the [object map] literal syntax in Rhai is _almost_ +the same as a JSON object. However, it is _almost_ because the syntax for a sub-object in JSON +(i.e. "`{ ... }`") is different from a Rhai [object map] literal (i.e. "`#{ ... }`"). + +When `Engine::parse_json` encounters JSON with sub-objects, it fails with a syntax error. + +If it is certain that no text string in the JSON will ever contain the character '`{`', +then it is possible to parse it by first replacing all occupance of '`{`' with "`#{`". + +```rust +// JSON with sub-object 'b'. +let json = r#"{"a":1, "b":{"x":true, "y":false}}"#; + +let new_json = json.replace("{" "#{"); + +// The leading '{' will also be replaced to '#{', but parse_json can handle this. +let map = engine.parse_json(&new_json, false)?; + +map.len() == 2; // 'map' contains two properties: 'a' and 'b' +``` diff --git a/src/api.rs b/src/api.rs index 8487370f..27a2d42b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -898,21 +898,34 @@ impl Engine { /// Set `has_null` to `true` in order to map `null` values to `()`. /// Setting it to `false` will cause a _variable not found_ error during parsing. /// + /// # JSON With Sub-Objects + /// + /// This method assumes no sub-objects in the JSON string. That is because the syntax + /// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`. + /// Parsing a JSON string with sub-objects will cause a syntax error. + /// + /// If it is certain that the character `{` never appears in any text string within the JSON object, + /// then globally replace `{` with `#{` before calling this method. + /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; + /// use rhai::{Engine, Map}; /// /// let engine = Engine::new(); /// - /// let map = engine.parse_json(r#"{"a":123, "b":42, "c":false, "d":null}"#, true)?; + /// let map = engine.parse_json( + /// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"# + /// .replace("{", "#{").as_str(), true)?; /// /// assert_eq!(map.len(), 4); - /// assert_eq!(map.get("a").cloned().unwrap().cast::(), 123); - /// assert_eq!(map.get("b").cloned().unwrap().cast::(), 42); - /// assert_eq!(map.get("c").cloned().unwrap().cast::(), false); - /// assert_eq!(map.get("d").cloned().unwrap().cast::<()>(), ()); + /// assert_eq!(map["a"].as_int().unwrap(), 123); + /// assert_eq!(map["b"].as_int().unwrap(), 42); + /// assert!(map["d"].is::<()>()); + /// + /// let c = map["c"].read_lock::().unwrap(); + /// assert_eq!(c["x"].as_bool().unwrap(), false); /// # Ok(()) /// # } /// ``` @@ -921,7 +934,12 @@ impl Engine { let mut scope = Scope::new(); // Trims the JSON string and add a '#' in front - let scripts = ["#", json.trim()]; + let json = json.trim(); + let scripts = if json.starts_with(Token::MapStart.syntax().as_ref()) { + [json, ""] + } else { + ["#", json] + }; let stream = lex( &scripts, if has_null { From d2593576d2e865f14265dea02f76683c1c22498e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 18 Aug 2020 22:01:29 +0800 Subject: [PATCH 060/103] Add multiple instantiation. --- doc/src/SUMMARY.md | 3 +- doc/src/patterns/multiple.md | 89 ++++++++++++++++++++++++++++++++++++ doc/src/start/features.md | 12 +++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 doc/src/patterns/multiple.md diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 2ef05a0f..7a4fd884 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -88,6 +88,7 @@ The Rhai Scripting Language 4. [Create from AST](language/modules/ast.md) 5. [Module Resolvers](rust/modules/resolvers.md) 1. [Custom Implementation](rust/modules/imp-resolver.md) + 18. [Eval Statement](language/eval.md) 6. [Safety and Protection](safety/index.md) 1. [Checked Arithmetic](safety/checked.md) 2. [Sand-Boxing](safety/sandbox.md) @@ -119,7 +120,7 @@ The Rhai Scripting Language 1. [Disable Keywords and/or Operators](engine/disable.md) 2. [Custom Operators](engine/custom-op.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md) - 7. [Eval Statement](language/eval.md) + 7. [Multiple Instantiation](patterns/multiple.md) 8. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators and Symbols](appendix/operators.md) diff --git a/doc/src/patterns/multiple.md b/doc/src/patterns/multiple.md new file mode 100644 index 00000000..1abbe942 --- /dev/null +++ b/doc/src/patterns/multiple.md @@ -0,0 +1,89 @@ +Multiple Instantiation +====================== + +{{#include ../links.md}} + + +Background +---------- + +Rhai's [features] are not strictly additive. This is easily deduced from the [`no_std`] feature +which prepares the crate for `no-std` builds. Obviously, turning on this feature has a material +impact on how Rhai behaves. + +Many crates resolve this by going the opposite direction: build for `no-std` in default, +but add a `std` feature, included by default, which builds for the `stdlib`. + + +Rhai Language Features Are Not Additive +-------------------------------------- + +Rhai, however, is more complex. Language features cannot be easily made _additive_. + +That is because the _lack_ of a language feature is a feature by itself. + +For example, by including [`no_float`], a project sets the Rhai language to ignore floating-point math. +Floating-point numbers do not even parse under this case and will generate syntax errors. +Assume that the project expects this behavior (why? perhaps integers are all that make sense +within the project domain). + +Now, assume that a dependent crate also depends on Rhai. Under such circumstances, +unless _exact_ versioning is used and the dependent crate depends on a _different_ version +of Rhai, Cargo automatically _merges_ both dependencies, with the [`no_float`] feature turned on +because Cargo features are _additive_. + +This will break the dependent crate, which does not by itself specify [`no_float`] +and expects floating-point numbers and math to work normally. + +There is no way out of this dilemma. Reversing the [features] set with a `float` feature +causes the project to break because floating-point numbers are not rejected as expected. + + +Multiple Instantiations of Rhai Within The Same Project +------------------------------------------------------ + +The trick is to differentiate between multiple identical copies of Rhai, each having +a different [features] set, by their _sources_: + +* Different versions from [`crates.io`](https://crates.io/crates/rhai/) - The official crate. + +* Different releases from [`GitHub`](https://github.com/jonathandturner/rhai) - Crate source on GitHub. + +* Forked copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) on GitHub. + +* Local copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) downloaded form GitHub. + +Use the following configuration in `Cargo.toml` to pull in multiple copies of Rhai within the same project: + +```toml +[dependencies] +rhai = { version = "{{version}}", features = [ "no_float" ] } +rhai_github = { git = "https://github.com/jonathandturner/rhai", features = [ "unchecked" ] } +rhai_my_github = { git = "https://github.com/my_github/rhai", branch = "variation1", features = [ "serde", "no_closure" ] } +rhai_local = { path = "../rhai_copy" } +``` + +The example above creates four different modules: `rhai`, `rhai_github`, `rhai_my_github` and +`rhai_local`, each referring to a different Rhai copy with the appropriate [features] set. + +Only one crate of any particular version can be used from each source, because Cargo merges +all candidate cases within the same source, adding all [features] together. + +If more than four different instantiations of Rhai is necessary (why?), create more local repositories +or GitHub forks or branches. + + +Caveat - No Way To Avoid Dependency Conflicts +-------------------------------------------- + +Unfortunately, pulling in Rhai from different sources do not resolve the problem of +[features] conflict between dependencies. Even overriding `crates.io` via the `[patch]` manifest +section doesn't work - all dependencies will eventually find the only one copy. + +What is necessary - multiple copies of Rhai, one for each dependent crate that requires it, +together with their _unique_ [features] set intact. In other words, turning off Cargo's +crate merging feature _just for Rhai_. + +Unfortunately, as of this writing, there is no known method to achieve it. + +Therefore, moral of the story: avoid pulling in multiple crates that depend on Rhai. diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 3201aa07..3e8067fa 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -52,3 +52,15 @@ no floating-point, is `Send + Sync` (so it can be safely used across threads), a nor loading external [modules]. This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware. + + +Caveat - Features Are Not Additive +--------------------------------- + +Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities. + +In fact, most features are _subtractive_ - i.e. they _remove_ functionalities. + +There is a reason for this design, because the _lack_ of a language feature by itself is a feature. + +See [here]({{rootUrl}}/patterns/multiple.md) for more details. From c55b0d788337de8f07cb14a2167f53c6440193e8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 19 Aug 2020 12:50:23 +0800 Subject: [PATCH 061/103] Fix bug with plugin method call detection. --- src/fn_native.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/fn_native.rs b/src/fn_native.rs index 4d2cf19b..820e5dc7 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -261,7 +261,9 @@ impl CallableFunction { pub fn is_pure(&self) -> bool { match self { Self::Pure(_) => true, - Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false, + Self::Method(_) | Self::Iterator(_) => false, + + Self::Plugin(p) => !p.is_method_call(), #[cfg(not(feature = "no_function"))] Self::Script(_) => false, @@ -271,7 +273,9 @@ impl CallableFunction { pub fn is_method(&self) -> bool { match self { Self::Method(_) => true, - Self::Pure(_) | Self::Iterator(_) | Self::Plugin(_) => false, + Self::Pure(_) | Self::Iterator(_) => false, + + Self::Plugin(p) => p.is_method_call(), #[cfg(not(feature = "no_function"))] Self::Script(_) => false, From 980aba77a9f037b68531fb9bc4c8ec9d6eb7a080 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 19 Aug 2020 12:53:33 +0800 Subject: [PATCH 062/103] Use &mut. --- src/packages/string_more.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 087e8e90..99af280d 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -22,12 +22,12 @@ macro_rules! gen_concat_functions { use super::super::*; #[export_fn] - pub fn append_func(x: ImmutableString, y: $arg_type) -> String { + pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { super::super::add_append(x, y) } #[export_fn] - pub fn prepend_func(x: $arg_type, y: ImmutableString) -> String { + pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { super::super::add_prepend(x, y) } } @@ -120,10 +120,10 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ); }); -fn add_prepend(x: T, y: ImmutableString) -> String { +fn add_prepend(x: &mut T, y: ImmutableString) -> String { format!("{}{}", x, y) } -fn add_append(x: ImmutableString, y: T) -> String { +fn add_append(x: &mut ImmutableString, y: T) -> String { format!("{}{}", x, y) } From eedebf11d2336a21a0e10022c25af96c0f740980 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 19 Aug 2020 13:39:20 +0800 Subject: [PATCH 063/103] Fix plugins test. --- tests/plugins.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/plugins.rs b/tests/plugins.rs index 5331287b..51357260 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -57,7 +57,9 @@ fn test_plugins_package() -> Result<(), Box> { reg_functions!(engine += greet::single(INT, bool, char)); + #[cfg(not(feature = "no_object"))] assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); + assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::("2 + 2")?, 5); assert_eq!( From f1dc2cbf1817a0df9f45368c92628ca176678470 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 16 Aug 2020 16:53:25 -0500 Subject: [PATCH 064/103] codegen: update tests for new name attribute behavior --- codegen/tests/test_functions.rs | 2 +- codegen/tests/test_modules.rs | 43 ++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 4a179376..2f8a881e 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -1,6 +1,6 @@ use rhai::module_resolvers::*; use rhai::plugin::*; -use rhai::{Array, Engine, EvalAltResult, Module, RegisterFn, FLOAT}; +use rhai::{Engine, EvalAltResult, Module, RegisterFn, FLOAT}; pub mod raw_fn { use rhai::plugin::*; diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index 56ffb4a5..b9690a5c 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -1,5 +1,5 @@ use rhai::module_resolvers::*; -use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT, INT}; +use rhai::{Array, Engine, EvalAltResult, RegisterFn, FLOAT, INT}; pub mod empty_module { use rhai::plugin::*; @@ -180,3 +180,44 @@ fn mut_opaque_ref_test() -> Result<(), Box> { ); Ok(()) } + +mod duplicate_fn_rename { + use rhai::plugin::*; + #[export_module] + pub mod my_adds { + use rhai::{FLOAT, INT}; + + #[rhai_fn(name = "add_f")] + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + #[rhai_fn(name = "add_i")] + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + } +} + +#[test] +fn duplicate_fn_rename_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + let output_array = engine.eval::( + r#"import "Math::Advanced" as math; + let fx = get_mystic_number(); + let fy = math::add_f(fx, 1.0); + let ix = 42; + let iy = math::add_i(ix, 1); + [fy, iy] + "#, + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &43.0); + assert_eq!(&output_array[1].as_int().unwrap(), &43); + Ok(()) +} From 8efde3c7cec4cf4cff63291b63fed226a1ae6d99 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 19 Aug 2020 22:15:48 -0500 Subject: [PATCH 065/103] codegen: Rhai names cannot contain dot --- codegen/src/function.rs | 8 +++++++ codegen/ui_tests/rhai_fn_rename_dot.rs | 28 ++++++++++++++++++++++ codegen/ui_tests/rhai_fn_rename_dot.stderr | 11 +++++++++ 3 files changed, 47 insertions(+) create mode 100644 codegen/ui_tests/rhai_fn_rename_dot.rs create mode 100644 codegen/ui_tests/rhai_fn_rename_dot.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 4e5bf139..41b3aac1 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -113,6 +113,14 @@ impl Parse for ExportedFnParams { } } + // Check validity of name, if present. + if name.as_ref().filter(|n| n.contains('.')).is_some() { + return Err(syn::Error::new( + span, + "Rhai function names may not contain dot" + )) + } + Ok(ExportedFnParams { name, return_raw, diff --git a/codegen/ui_tests/rhai_fn_rename_dot.rs b/codegen/ui_tests/rhai_fn_rename_dot.rs new file mode 100644 index 00000000..9e2180d9 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_dot.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo.bar")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_dot.stderr b/codegen/ui_tests/rhai_fn_rename_dot.stderr new file mode 100644 index 00000000..f650a081 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_dot.stderr @@ -0,0 +1,11 @@ +error: Rhai function names may not contain dot + --> $DIR/rhai_fn_rename_dot.rs:12:15 + | +12 | #[rhai_fn(name = "foo.bar")] + | ^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_dot.rs:23:8 + | +23 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From c87da31328e6398981e910e84589170205345d28 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 19 Aug 2020 23:12:39 -0500 Subject: [PATCH 066/103] codegen: prevent name duplication in Rust or Rhai --- codegen/src/function.rs | 3 ++ codegen/src/module.rs | 52 +++++++++++++++++++ codegen/tests/test_modules.rs | 8 +-- codegen/ui_tests/rhai_fn_rename_collision.rs | 33 ++++++++++++ .../ui_tests/rhai_fn_rename_collision.stderr | 17 ++++++ .../rhai_fn_rename_collision_oneattr.rs | 32 ++++++++++++ .../rhai_fn_rename_collision_oneattr.stderr | 17 ++++++ 7 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 codegen/ui_tests/rhai_fn_rename_collision.rs create mode 100644 codegen/ui_tests/rhai_fn_rename_collision.stderr create mode 100644 codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs create mode 100644 codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 41b3aac1..0dba5afa 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -18,6 +18,7 @@ pub(crate) struct ExportedFnParams { pub name: Option, pub return_raw: bool, pub skip: bool, + pub span: Option, } impl ExportedFnParams { @@ -47,6 +48,7 @@ impl Parse for ExportedFnParams { let arg_list = args.call( syn::punctuated::Punctuated::::parse_separated_nonempty, )?; + let span = arg_list.span(); let mut attrs: HashMap> = HashMap::new(); for arg in arg_list { @@ -125,6 +127,7 @@ impl Parse for ExportedFnParams { name, return_raw, skip, + span: Some(span), ..Default::default() }) } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 337e38e9..bba97c2a 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -12,6 +12,8 @@ use std::vec as new_vec; #[cfg(no_std)] use core::mem; +use std::collections::HashMap; + fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { if let Some(rhai_fn_idx) = f.attrs.iter().position(|a| { a.path @@ -28,6 +30,48 @@ fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { } } +fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { + let mut renames = HashMap::::new(); + let mut names = HashMap::::new(); + for itemfn in fns.iter() { + if let Some(ref name) = itemfn.params.name { + let current_span = itemfn.params.span.as_ref().unwrap(); + let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { + let type_string: String = match fnarg { + syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"), + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => + ty.as_ref().to_token_stream().to_string(), + }; + argstr.push('.'); + argstr.extend(type_string.chars()); + argstr + }); + if let Some(other_span) = renames.insert(key, + current_span.clone()) { + let mut err = syn::Error::new(current_span.clone(), + format!("duplicate Rhai signature for '{}'", &name)); + err.combine(syn::Error::new(other_span, + format!("duplicated function renamed '{}'", &name))); + return Err(err); + } + } else { + let ident = itemfn.name(); + names.insert(ident.to_string(), ident.span()); + } + } + for (new_name, attr_span) in renames.drain() { + let new_name = new_name.split('.').next().unwrap(); + if let Some(fn_span) = names.get(new_name) { + let mut err = syn::Error::new(attr_span, + format!("duplicate Rhai signature for '{}'", &new_name)); + err.combine(syn::Error::new(fn_span.clone(), + format!("duplicated function '{}'", &new_name))); + return Err(err); + } + } + Ok(()) +} + #[derive(Debug)] pub(crate) struct Module { mod_all: Option, @@ -92,7 +136,15 @@ impl Parse for Module { impl Module { pub fn generate(self) -> proc_macro2::TokenStream { + // Check for collisions if the "name" attribute was used on inner functions. + if let Err(e) = check_rename_collisions(&self.fns) { + return e.to_compile_error(); + } + + // Perform the generation of new module items. let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts); + + // Rebuild the structure of the module, with the new content added. let Module { mod_all, .. } = self; let mut mod_all = mod_all.unwrap(); let mod_name = mod_all.ident.clone(); diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index b9690a5c..dd5acd05 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -187,12 +187,12 @@ mod duplicate_fn_rename { pub mod my_adds { use rhai::{FLOAT, INT}; - #[rhai_fn(name = "add_f")] + #[rhai_fn(name = "add")] pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { f1 + f2 } - #[rhai_fn(name = "add_i")] + #[rhai_fn(name = "add")] pub fn add_int(i1: INT, i2: INT) -> INT { i1 + i2 } @@ -211,9 +211,9 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { let output_array = engine.eval::( r#"import "Math::Advanced" as math; let fx = get_mystic_number(); - let fy = math::add_f(fx, 1.0); + let fy = math::add(fx, 1.0); let ix = 42; - let iy = math::add_i(ix, 1); + let iy = math::add(ix, 1); [fy, iy] "#, )?; diff --git a/codegen/ui_tests/rhai_fn_rename_collision.rs b/codegen/ui_tests/rhai_fn_rename_collision.rs new file mode 100644 index 00000000..38814f3d --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision.rs @@ -0,0 +1,33 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + #[rhai_fn(name = "foo")] + pub fn test_fn_2(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_collision.stderr b/codegen/ui_tests/rhai_fn_rename_collision.stderr new file mode 100644 index 00000000..19ddfd35 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision.stderr @@ -0,0 +1,17 @@ +error: duplicate Rhai signature for 'foo' + --> $DIR/rhai_fn_rename_collision.rs:17:15 + | +17 | #[rhai_fn(name = "foo")] + | ^^^^^^^^^^^^ + +error: duplicated function renamed 'foo' + --> $DIR/rhai_fn_rename_collision.rs:12:15 + | +12 | #[rhai_fn(name = "foo")] + | ^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_collision.rs:28:8 + | +28 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs new file mode 100644 index 00000000..373eab0d --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs @@ -0,0 +1,32 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + pub fn foo(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr new file mode 100644 index 00000000..6702b43a --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr @@ -0,0 +1,17 @@ +error: duplicate Rhai signature for 'foo' + --> $DIR/rhai_fn_rename_collision_oneattr.rs:12:15 + | +12 | #[rhai_fn(name = "foo")] + | ^^^^^^^^^^^^ + +error: duplicated function 'foo' + --> $DIR/rhai_fn_rename_collision_oneattr.rs:17:12 + | +17 | pub fn foo(input: Point) -> bool { + | ^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_collision_oneattr.rs:27:8 + | +27 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From 729ab99ec3850d598522c5302c3487d22a72a4c1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 20 Aug 2020 22:11:41 +0800 Subject: [PATCH 067/103] Refine packages. --- src/packages/array_basic.rs | 26 ++++---- src/packages/fn_basic.rs | 23 ++++--- src/packages/logic.rs | 125 ++++++++++++----------------------- src/packages/map_basic.rs | 6 ++ src/packages/math_basic.rs | 122 ++++++++++++++++++---------------- src/packages/string_basic.rs | 8 +++ src/packages/string_more.rs | 38 +++++++---- src/packages/time_basic.rs | 16 ++++- tests/plugins.rs | 37 ++++++----- 9 files changed, 210 insertions(+), 191 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c4590b08..433f815f 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -25,11 +25,13 @@ macro_rules! gen_array_functions { use super::super::*; #[export_fn] + #[inline(always)] pub fn push_func(list: &mut Array, item: $arg_type) { super::super::push(list, item); } #[export_fn] + #[inline(always)] pub fn insert_func(list: &mut Array, len: INT, item: $arg_type) { super::super::insert(list, len, item); } @@ -59,9 +61,6 @@ macro_rules! reg_pad { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { lib.combine(exported_module!(array_functions)); - #[cfg(not(feature = "no_object"))] - lib.combine(exported_module!(object_functions)); - reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); reg_pad!(lib, INT, bool, char, ImmutableString, FnPtr, Array, Unit); @@ -93,21 +92,31 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[export_module] mod array_functions { + #[inline(always)] pub fn len(list: &mut Array) -> INT { list.len() as INT } + #[rhai_fn(get = "len")] + #[inline(always)] + pub fn len_prop(list: &mut Array) -> INT { + len(list) + } + #[inline(always)] pub fn append(x: &mut Array, y: Array) { x.extend(y); } #[rhai_fn(name = "+=")] + #[inline(always)] pub fn append_operator(x: &mut Array, y: Array) { append(x, y) } #[rhai_fn(name = "+")] + #[inline] pub fn concat(mut x: Array, y: Array) -> Array { x.extend(y); x } + #[inline] pub fn pop(list: &mut Array) -> Dynamic { list.pop().unwrap_or_else(|| ().into()) } @@ -125,6 +134,7 @@ mod array_functions { list.remove(len as usize) } } + #[inline(always)] pub fn clear(list: &mut Array) { list.clear(); } @@ -137,16 +147,8 @@ mod array_functions { } } -#[cfg(not(feature = "no_object"))] -#[export_module] -mod object_functions { - #[rhai_fn(get = "len")] - pub fn len(list: &mut Array) -> INT { - array_functions::len(list) - } -} - // Register array utility functions +#[inline(always)] fn push(list: &mut Array, item: T) { list.push(Dynamic::from(item)); } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 7523b7f6..a115485a 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -2,17 +2,20 @@ use crate::def_package; use crate::fn_native::FnPtr; use crate::plugin::*; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; - def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { - set_exported_fn!(lib, "name", get_fn_name); - - #[cfg(not(feature = "no_object"))] - set_exported_fn!(lib, make_getter("name"), get_fn_name); + lib.combine(exported_module!(fn_ptr_functions)); }); -#[export_fn] -fn get_fn_name(f: &mut FnPtr) -> ImmutableString { - f.get_fn_name().clone() +#[export_module] +mod fn_ptr_functions { + #[inline(always)] + fn name(f: &mut FnPtr) -> ImmutableString { + f.get_fn_name().clone() + } + + #[rhai_fn(get = "name")] + #[inline(always)] + fn name_prop(f: &mut FnPtr) -> ImmutableString { + name(f) + } } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 91ae1abf..60d7d75c 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -2,25 +2,52 @@ use crate::def_package; use crate::plugin::*; macro_rules! gen_cmp_functions { - ($op_name:literal = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { - pub mod $op_fn { $( + ($root:ident => $($arg_type:ident),+) => { + mod $root { $( pub mod $arg_type { use crate::plugin::*; - pub const OP_NAME: &'static str = $op_name; - - #[export_fn] - pub fn cmp_func(x: $arg_type, y: $arg_type) -> $return_type { - super::super::super::$op_fn(x, y) + #[export_module] + pub mod functions { + #[rhai_fn(name = "<")] + #[inline(always)] + pub fn lt(x: $arg_type, y: $arg_type) -> bool { + x < y + } + #[rhai_fn(name = "<=")] + #[inline(always)] + pub fn lte(x: $arg_type, y: $arg_type) -> bool { + x <= y + } + #[rhai_fn(name = ">")] + #[inline(always)] + pub fn gt(x: $arg_type, y: $arg_type) -> bool { + x > y + } + #[rhai_fn(name = ">=")] + #[inline(always)] + pub fn gte(x: $arg_type, y: $arg_type) -> bool { + x >= y + } + #[rhai_fn(name = "==")] + #[inline(always)] + pub fn eq(x: $arg_type, y: $arg_type) -> bool { + x == y + } + #[rhai_fn(name = "!=")] + #[inline(always)] + pub fn ne(x: $arg_type, y: $arg_type) -> bool { + x != y + } } } )* } - } + }; } macro_rules! reg_functions { - ($mod_name:ident += $root:ident :: $op_name:ident ( $($arg_type:ident),+ )) => { - $(set_exported_fn!($mod_name, $root::$op_name::$arg_type::OP_NAME, $root::$op_name::$arg_type::cmp_func);)* + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { + $($mod_name.combine(exported_module!($root::$arg_type::functions));)* } } @@ -28,63 +55,18 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_functions!(lib += cmp::lt(i8, u8, i16, u16, i32, u32, u64)); - reg_functions!(lib += cmp::lte(i8, u8, i16, u16, i32, u32, u64)); - reg_functions!(lib += cmp::gt(i8, u8, i16, u16, i32, u32, u64)); - reg_functions!(lib += cmp::gte(i8, u8, i16, u16, i32, u32, u64)); - reg_functions!(lib += cmp::eq(i8, u8, i16, u16, i32, u32, u64)); - reg_functions!(lib += cmp::ne(i8, u8, i16, u16, i32, u32, u64)); + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64); #[cfg(not(target_arch = "wasm32"))] - { - reg_functions!(lib += cmp_128::lt(i128, u128)); - reg_functions!(lib += cmp_128::lte(i128, u128)); - reg_functions!(lib += cmp_128::gt(i128, u128)); - reg_functions!(lib += cmp_128::gte(i128, u128)); - reg_functions!(lib += cmp_128::eq(i128, u128)); - reg_functions!(lib += cmp_128::ne(i128, u128)); - } + reg_functions!(lib += num_128; i128, u128); } #[cfg(not(feature = "no_float"))] - { - reg_functions!(lib += cmp_float::lt(f32)); - reg_functions!(lib += cmp_float::lte(f32)); - reg_functions!(lib += cmp_float::gt(f32)); - reg_functions!(lib += cmp_float::gte(f32)); - reg_functions!(lib += cmp_float::eq(f32)); - reg_functions!(lib += cmp_float::ne(f32)); - } + reg_functions!(lib += float; f32); set_exported_fn!(lib, "!", not); }); -// Comparison operators -#[inline(always)] -pub fn lt(x: T, y: T) -> bool { - x < y -} -#[inline(always)] -pub fn lte(x: T, y: T) -> bool { - x <= y -} -#[inline(always)] -pub fn gt(x: T, y: T) -> bool { - x > y -} -#[inline(always)] -pub fn gte(x: T, y: T) -> bool { - x >= y -} -#[inline(always)] -pub fn eq(x: T, y: T) -> bool { - x == y -} -#[inline(always)] -pub fn ne(x: T, y: T) -> bool { - x != y -} - // Logic operators #[export_fn] #[inline(always)] @@ -94,33 +76,12 @@ fn not(x: bool) -> bool { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -mod cmp { - gen_cmp_functions!("<" = lt(i8, u8, i16, u16, i32, u32, u64) -> bool); - gen_cmp_functions!("<=" = lte(i8, u8, i16, u16, i32, u32, u64) -> bool); - gen_cmp_functions!(">" = gt(i8, u8, i16, u16, i32, u32, u64) -> bool); - gen_cmp_functions!(">=" = gte(i8, u8, i16, u16, i32, u32, u64) -> bool); - gen_cmp_functions!("==" = eq(i8, u8, i16, u16, i32, u32, u64) -> bool); - gen_cmp_functions!("!=" = ne(i8, u8, i16, u16, i32, u32, u64) -> bool); -} +gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_arch = "wasm32"))] -mod cmp_128 { - gen_cmp_functions!("<" = lt(i128, u128) -> bool); - gen_cmp_functions!("<=" = lte(i128, u128) -> bool); - gen_cmp_functions!(">" = gt(i128, u128) -> bool); - gen_cmp_functions!(">=" = gte(i128, u128) -> bool); - gen_cmp_functions!("==" = eq(i128, u128) -> bool); - gen_cmp_functions!("!=" = ne(i128, u128) -> bool); -} +gen_cmp_functions!(num_128 => i128, u128); #[cfg(not(feature = "no_float"))] -mod cmp_float { - gen_cmp_functions!("<" = lt(f32) -> bool); - gen_cmp_functions!("<=" = lte(f32) -> bool); - gen_cmp_functions!(">" = gt(f32) -> bool); - gen_cmp_functions!(">=" = gte(f32) -> bool); - gen_cmp_functions!("==" = eq(f32) -> bool); - gen_cmp_functions!("!=" = ne(f32) -> bool); -} +gen_cmp_functions!(float => f32); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 12ad00e8..5834e66b 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -18,19 +18,24 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { #[export_module] mod map_functions { + #[inline(always)] pub fn has(map: &mut Map, prop: ImmutableString) -> bool { map.contains_key(&prop) } + #[inline(always)] pub fn len(map: &mut Map) -> INT { map.len() as INT } #[rhai_fn(get = "len")] + #[inline(always)] pub fn len_prop(map: &mut Map) -> INT { len(map) } + #[inline(always)] pub fn clear(map: &mut Map) { map.clear(); } + #[inline] pub fn remove(x: &mut Map, name: ImmutableString) -> Dynamic { x.remove(&name).unwrap_or_else(|| ().into()) } @@ -40,6 +45,7 @@ mod map_functions { }); } #[rhai_fn(name = "+=")] + #[inline(always)] pub fn mixin_operator(map1: &mut Map, map2: Map) { mixin(map1, map2) } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index aad542b1..f8679766 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -8,9 +8,6 @@ use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_object"))] -use crate::engine::make_getter; - #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::float::Float; @@ -31,19 +28,6 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { // Floating point functions lib.combine(exported_module!(float_functions)); - // Floating point properties - #[cfg(not(feature = "no_object"))] - { - set_exported_fn!(lib, make_getter("floor"), float_funcs::floor); - set_exported_fn!(lib, make_getter("ceiling"), float_funcs::ceiling); - set_exported_fn!(lib, make_getter("round"), float_funcs::round); - set_exported_fn!(lib, make_getter("int"), float_funcs::int); - set_exported_fn!(lib, make_getter("fraction"), float_funcs::fraction); - set_exported_fn!(lib, make_getter("is_nan"), float_funcs::is_nan); - set_exported_fn!(lib, make_getter("is_finite"), float_funcs::is_finite); - set_exported_fn!(lib, make_getter("is_infinite"), float_funcs::is_infinite); - } - // Trig functions lib.combine(exported_module!(trig_functions)); @@ -125,39 +109,51 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { mod trig_functions { use crate::parser::FLOAT; + #[inline(always)] pub fn sin(x: FLOAT) -> FLOAT { x.to_radians().sin() } + #[inline(always)] pub fn cos(x: FLOAT) -> FLOAT { x.to_radians().cos() } + #[inline(always)] pub fn tan(x: FLOAT) -> FLOAT { x.to_radians().tan() } + #[inline(always)] pub fn sinh(x: FLOAT) -> FLOAT { x.to_radians().sinh() } + #[inline(always)] pub fn cosh(x: FLOAT) -> FLOAT { x.to_radians().cosh() } + #[inline(always)] pub fn tanh(x: FLOAT) -> FLOAT { x.to_radians().tanh() } + #[inline(always)] pub fn asin(x: FLOAT) -> FLOAT { x.asin().to_degrees() } + #[inline(always)] pub fn acos(x: FLOAT) -> FLOAT { x.acos().to_degrees() } + #[inline(always)] pub fn atan(x: FLOAT) -> FLOAT { x.atan().to_degrees() } + #[inline(always)] pub fn asinh(x: FLOAT) -> FLOAT { x.asinh().to_degrees() } + #[inline(always)] pub fn acosh(x: FLOAT) -> FLOAT { x.acosh().to_degrees() } + #[inline(always)] pub fn atanh(x: FLOAT) -> FLOAT { x.atanh().to_degrees() } @@ -168,84 +164,96 @@ mod trig_functions { mod float_functions { use crate::parser::FLOAT; + #[inline(always)] pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() } + #[inline(always)] pub fn exp(x: FLOAT) -> FLOAT { x.exp() } + #[inline(always)] pub fn ln(x: FLOAT) -> FLOAT { x.ln() } + #[inline(always)] pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { x.log(base) } + #[inline(always)] pub fn log10(x: FLOAT) -> FLOAT { x.log10() } - pub fn floor(x: FLOAT) -> FLOAT { - float_funcs::floor(x) - } - pub fn ceiling(x: FLOAT) -> FLOAT { - float_funcs::ceiling(x) - } - pub fn round(x: FLOAT) -> FLOAT { - float_funcs::round(x) - } - pub fn int(x: FLOAT) -> FLOAT { - float_funcs::int(x) - } - pub fn fraction(x: FLOAT) -> FLOAT { - float_funcs::fraction(x) - } - pub fn is_nan(x: FLOAT) -> bool { - float_funcs::is_nan(x) - } - pub fn is_finite(x: FLOAT) -> bool { - float_funcs::is_finite(x) - } - pub fn is_infinite(x: FLOAT) -> bool { - float_funcs::is_infinite(x) - } -} - -#[cfg(not(feature = "no_float"))] -mod float_funcs { - use crate::parser::FLOAT; - use crate::plugin::*; - #[cfg(feature = "no_std")] - use num_traits::float::Float; - - #[export_fn] + #[inline(always)] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } - #[export_fn] + #[rhai_fn(get = "floor")] + #[inline(always)] + pub fn floor_prop(x: FLOAT) -> FLOAT { + floor(x) + } + #[inline(always)] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } - #[export_fn] + #[rhai_fn(get = "ceiling")] + #[inline(always)] + pub fn ceiling_prop(x: FLOAT) -> FLOAT { + ceiling(x) + } + #[inline(always)] pub fn round(x: FLOAT) -> FLOAT { x.ceil() } - #[export_fn] + #[rhai_fn(get = "round")] + #[inline(always)] + pub fn round_prop(x: FLOAT) -> FLOAT { + ceiling(x) + } + #[inline(always)] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } - #[export_fn] + #[rhai_fn(get = "int")] + #[inline(always)] + pub fn int_prop(x: FLOAT) -> FLOAT { + int(x) + } + #[inline(always)] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } - #[export_fn] + #[rhai_fn(get = "fraction")] + #[inline(always)] + pub fn fraction_prop(x: FLOAT) -> FLOAT { + fraction(x) + } + #[inline(always)] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } - #[export_fn] + #[rhai_fn(get = "is_nan")] + #[inline(always)] + pub fn is_nan_prop(x: FLOAT) -> bool { + is_nan(x) + } + #[inline(always)] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } - #[export_fn] + #[rhai_fn(get = "is_finite")] + #[inline(always)] + pub fn is_finite_prop(x: FLOAT) -> bool { + is_finite(x) + } + #[inline(always)] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } + #[rhai_fn(get = "is_infinite")] + #[inline(always)] + pub fn is_infinite_prop(x: FLOAT) -> bool { + is_infinite(x) + } } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 712faed1..020f9292 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -27,6 +27,7 @@ macro_rules! gen_functions { use super::super::*; #[export_fn] + #[inline(always)] pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { super::super::$fn_name(x) } @@ -125,29 +126,36 @@ gen_functions!(print_array => to_debug(Array)); // Register print and debug #[export_fn] +#[inline(always)] fn print_empty_string() -> ImmutableString { "".to_string().into() } #[export_fn] +#[inline(always)] fn print_unit(_x: ()) -> ImmutableString { "".to_string().into() } #[export_fn] +#[inline(always)] fn print_string(s: ImmutableString) -> ImmutableString { s } #[export_fn] +#[inline(always)] fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { f.to_string().into() } +#[inline(always)] fn to_string(x: &mut T) -> ImmutableString { x.to_string().into() } +#[inline] fn to_debug(x: &mut T) -> ImmutableString { format!("{:?}", x).into() } #[cfg(not(feature = "no_object"))] #[export_fn] +#[inline] fn format_map(x: &mut Map) -> ImmutableString { format!("#{:?}", x).into() } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 99af280d..fbcb2acd 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -22,11 +22,13 @@ macro_rules! gen_concat_functions { use super::super::*; #[export_fn] + #[inline(always)] pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { super::super::add_append(x, y) } #[export_fn] + #[inline(always)] pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { super::super::add_prepend(x, y) } @@ -60,9 +62,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str #[cfg(not(feature = "no_index"))] lib.combine(exported_module!(index_functions)); - #[cfg(not(feature = "no_object"))] - lib.combine(exported_module!(object_functions)); - lib.combine(exported_module!(string_functions)); lib.set_raw_fn( @@ -120,9 +119,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ); }); +#[inline] fn add_prepend(x: &mut T, y: ImmutableString) -> String { format!("{}{}", x, y) } +#[inline] fn add_append(x: &mut ImmutableString, y: T) -> String { format!("{}{}", x, y) } @@ -144,26 +145,38 @@ gen_concat_functions!(float => f32, f64); #[export_module] mod string_functions { #[rhai_fn(name = "+")] + #[inline(always)] pub fn add_append_unit(s: ImmutableString, _x: ()) -> ImmutableString { s } #[rhai_fn(name = "+")] + #[inline(always)] pub fn add_prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { s } #[rhai_fn(name = "+=")] + #[inline(always)] pub fn append_char(s: &mut ImmutableString, ch: char) { *s += ch; } #[rhai_fn(name = "+=")] + #[inline(always)] pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { *s += &add; } + #[inline(always)] pub fn len(s: &mut ImmutableString) -> INT { s.chars().count() as INT } + #[rhai_fn(get = "len")] + #[inline(always)] + pub fn len_prop(s: &mut ImmutableString) -> INT { + len(s) + } + + #[inline(always)] pub fn clear(s: &mut ImmutableString) { s.make_mut().clear(); } @@ -186,10 +199,12 @@ mod string_functions { } #[rhai_fn(name = "contains")] + #[inline(always)] pub fn contains_char(s: &mut ImmutableString, ch: char) -> bool { s.contains(ch) } #[rhai_fn(name = "contains")] + #[inline(always)] pub fn contains_string(s: &mut ImmutableString, find: ImmutableString) -> bool { s.contains(find.as_str()) } @@ -270,6 +285,7 @@ mod string_functions { .into() } #[rhai_fn(name = "sub_string")] + #[inline(always)] pub fn sub_string_starting_from(s: ImmutableString, start: INT) -> ImmutableString { let len = s.len() as INT; sub_string(s, start, len) @@ -302,23 +318,28 @@ mod string_functions { copy.extend(chars.iter().skip(offset).take(len)); } #[rhai_fn(name = "crop")] + #[inline(always)] pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) { crop_string(s, start, s.len() as INT); } #[rhai_fn(name = "replace")] + #[inline(always)] pub fn replace_string(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) { *s = s.replace(find.as_str(), sub.as_str()).into(); } #[rhai_fn(name = "replace")] + #[inline(always)] pub fn replace_string_with_char(s: &mut ImmutableString, find: ImmutableString, sub: char) { *s = s.replace(find.as_str(), &sub.to_string()).into(); } #[rhai_fn(name = "replace")] + #[inline(always)] pub fn replace_char_with_string(s: &mut ImmutableString, find: char, sub: ImmutableString) { *s = s.replace(&find.to_string(), sub.as_str()).into(); } #[rhai_fn(name = "replace")] + #[inline(always)] pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { *s = s.replace(&find.to_string(), &sub.to_string()).into(); } @@ -330,20 +351,13 @@ mod index_functions { use crate::engine::Array; #[rhai_fn(name = "+")] + #[inline] pub fn append(x: &mut ImmutableString, y: Array) -> String { format!("{}{:?}", x, y) } #[rhai_fn(name = "+")] + #[inline] pub fn prepend(x: &mut Array, y: ImmutableString) -> String { format!("{:?}{}", x, y) } } - -#[cfg(not(feature = "no_object"))] -#[export_module] -mod object_functions { - #[rhai_fn(get = "len")] - pub fn len(s: &mut ImmutableString) -> INT { - string_functions::len(s) - } -} diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index a55471ee..6aff8096 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -27,13 +27,11 @@ use instant::Instant; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions lib.combine(exported_module!(time_functions)); - - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("elapsed", time_functions::elapsed); }); #[export_module] mod time_functions { + #[inline(always)] pub fn timestamp() -> Instant { Instant::now() } @@ -61,6 +59,12 @@ mod time_functions { } } + #[rhai_fn(get = "elapsed", return_raw)] + #[inline(always)] + pub fn elapsed_prop(timestamp: &mut Instant) -> Result> { + elapsed(timestamp) + } + #[rhai_fn(return_raw, name = "-")] fn time_diff(ts1: Instant, ts2: Instant) -> Result> { #[cfg(not(feature = "no_float"))] @@ -104,26 +108,32 @@ mod time_functions { } #[rhai_fn(name = "==")] + #[inline(always)] pub fn eq(x: Instant, y: Instant) -> bool { x == y } #[rhai_fn(name = "!=")] + #[inline(always)] pub fn ne(x: Instant, y: Instant) -> bool { x != y } #[rhai_fn(name = "<")] + #[inline(always)] pub fn lt(x: Instant, y: Instant) -> bool { x < y } #[rhai_fn(name = "<=")] + #[inline(always)] pub fn lte(x: Instant, y: Instant) -> bool { x <= y } #[rhai_fn(name = ">")] + #[inline(always)] pub fn gt(x: Instant, y: Instant) -> bool { x > y } #[rhai_fn(name = ">=")] + #[inline(always)] pub fn gte(x: Instant, y: Instant) -> bool { x >= y } diff --git a/tests/plugins.rs b/tests/plugins.rs index 51357260..9d40107e 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -3,21 +3,28 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT}; -#[export_module] -mod special_array_package { - use rhai::{Array, INT}; +mod test { + use rhai::plugin::*; - #[rhai_fn(get = "foo", return_raw)] - pub fn foo(array: &mut Array) -> Result> { - Ok(array[0].clone()) - } - #[rhai_fn(name = "test")] - pub fn len(array: &mut Array, mul: INT) -> INT { - (array.len() as INT) * mul - } - #[rhai_fn(name = "+")] - pub fn funky_add(x: INT, y: INT) -> INT { - x / 2 + y * 2 + #[export_module] + pub mod special_array_package { + use rhai::{Array, INT}; + + #[rhai_fn(get = "foo", return_raw)] + #[inline(always)] + pub fn foo(array: &mut Array) -> Result> { + Ok(array[0].clone()) + } + #[rhai_fn(name = "test")] + #[inline(always)] + pub fn len(array: &mut Array, mul: INT) -> INT { + (array.len() as INT) * mul + } + #[rhai_fn(name = "+")] + #[inline(always)] + pub fn funky_add(x: INT, y: INT) -> INT { + x / 2 + y * 2 + } } } @@ -52,7 +59,7 @@ gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String); fn test_plugins_package() -> Result<(), Box> { let mut engine = Engine::new(); - let m = exported_module!(special_array_package); + let m = exported_module!(test::special_array_package); engine.load_package(m); reg_functions!(engine += greet::single(INT, bool, char)); From 7962c6dc521c7b94cf6a0ecd74d68d24932cab89 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 20 Aug 2020 22:20:12 -0500 Subject: [PATCH 068/103] codegen: add rhai_mod and submodule support --- codegen/src/module.rs | 566 +++++++++++++++++- codegen/src/rhai_module.rs | 20 + codegen/tests/test_nested.rs | 36 ++ codegen/ui_tests/rhai_mod_bad_attr.rs | 29 + codegen/ui_tests/rhai_mod_bad_attr.stderr | 11 + codegen/ui_tests/rhai_mod_bad_value.rs | 29 + codegen/ui_tests/rhai_mod_bad_value.stderr | 11 + codegen/ui_tests/rhai_mod_junk_arg.rs | 29 + codegen/ui_tests/rhai_mod_junk_arg.stderr | 11 + codegen/ui_tests/rhai_mod_missing_value.rs | 29 + .../ui_tests/rhai_mod_missing_value.stderr | 11 + codegen/ui_tests/rhai_mod_path_attr.rs | 29 + codegen/ui_tests/rhai_mod_path_attr.stderr | 11 + codegen/ui_tests/rhai_mod_return_raw.rs | 29 + codegen/ui_tests/rhai_mod_return_raw.stderr | 11 + 15 files changed, 852 insertions(+), 10 deletions(-) create mode 100644 codegen/ui_tests/rhai_mod_bad_attr.rs create mode 100644 codegen/ui_tests/rhai_mod_bad_attr.stderr create mode 100644 codegen/ui_tests/rhai_mod_bad_value.rs create mode 100644 codegen/ui_tests/rhai_mod_bad_value.stderr create mode 100644 codegen/ui_tests/rhai_mod_junk_arg.rs create mode 100644 codegen/ui_tests/rhai_mod_junk_arg.stderr create mode 100644 codegen/ui_tests/rhai_mod_missing_value.rs create mode 100644 codegen/ui_tests/rhai_mod_missing_value.stderr create mode 100644 codegen/ui_tests/rhai_mod_path_attr.rs create mode 100644 codegen/ui_tests/rhai_mod_path_attr.stderr create mode 100644 codegen/ui_tests/rhai_mod_return_raw.rs create mode 100644 codegen/ui_tests/rhai_mod_return_raw.stderr diff --git a/codegen/src/module.rs b/codegen/src/module.rs index bba97c2a..c6bd6469 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -1,5 +1,5 @@ use quote::{quote, ToTokens}; -use syn::{parse::Parse, parse::ParseStream}; +use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; use crate::function::{ExportedFn, ExportedFnParams}; use crate::rhai_module::ExportedConst; @@ -12,6 +12,7 @@ use std::vec as new_vec; #[cfg(no_std)] use core::mem; +use std::borrow::Cow; use std::collections::HashMap; fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { @@ -72,11 +73,113 @@ fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { Ok(()) } +fn inner_mod_attributes(f: &mut syn::ItemMod) -> syn::Result { + if let Some(rhai_mod_idx) = f.attrs.iter().position(|a| { + a.path + .get_ident() + .map(|i| i.to_string() == "rhai_mod") + .unwrap_or(false) + }) { + let rhai_mod_attr = f.attrs.remove(rhai_mod_idx); + rhai_mod_attr.parse_args() + } else if let syn::Visibility::Public(_) = f.vis { + Ok(ExportedModParams::default()) + } else { + Ok(ExportedModParams::skip()) + } +} + +#[derive(Debug, Default)] +pub(crate) struct ExportedModParams { + pub name: Option, + pub skip: bool, +} + +impl ExportedModParams { + pub fn skip() -> ExportedModParams { + let mut skip = ExportedModParams::default(); + skip.skip = true; + skip + } +} + +impl Parse for ExportedModParams { + fn parse(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportedModParams::default()); + } + + let arg_list = args.call( + syn::punctuated::Punctuated::::parse_separated_nonempty, + )?; + + let mut attrs: HashMap> = HashMap::new(); + for arg in arg_list { + let (left, right) = match arg { + syn::Expr::Assign(syn::ExprAssign { + ref left, + ref right, + .. + }) => { + let attr_name: syn::Ident = match left.as_ref() { + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path.get_ident().cloned().ok_or_else(|| { + syn::Error::new(attr_path.span(), "expecting attribute name") + })?, + x => return Err(syn::Error::new(x.span(), "expecting attribute name")), + }; + let attr_value = match right.as_ref() { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(string), + .. + }) => string.clone(), + x => return Err(syn::Error::new(x.span(), "expecting string literal")), + }; + (attr_name, Some(attr_value)) + } + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path + .get_ident() + .cloned() + .map(|a| (a, None)) + .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, + x => return Err(syn::Error::new(x.span(), "expecting identifier")), + }; + attrs.insert(left, right); + } + + let mut name = None; + let mut skip = false; + for (ident, value) in attrs.drain() { + match (ident.to_string().as_ref(), value) { + ("name", Some(s)) => name = Some(s.value()), + ("name", None) => return Err(syn::Error::new(ident.span(), "requires value")), + ("skip", None) => skip = true, + ("skip", Some(s)) => { + return Err(syn::Error::new(s.span(), "extraneous value")) + } + (attr, _) => { + return Err(syn::Error::new( + ident.span(), + format!("unknown attribute '{}'", attr), + )) + } + } + } + + Ok(ExportedModParams { name, skip, ..Default::default() }) + } +} + #[derive(Debug)] pub(crate) struct Module { mod_all: Option, fns: Vec, consts: Vec, + submodules: Vec, + params: ExportedModParams, } impl Parse for Module { @@ -84,7 +187,9 @@ impl Parse for Module { let mut mod_all: syn::ItemMod = input.parse()?; let fns: Vec<_>; let consts: Vec<_>; + let mut submodules: Vec<_> = Vec::new(); if let Some((_, ref mut content)) = mod_all.content { + // Gather and parse functions. fns = content .iter_mut() .filter_map(|item| match item { @@ -104,6 +209,7 @@ impl Parse for Module { .map(|f| if !f.params.skip { vec.push(f) }) .map(|_| vec) })?; + // Gather and parse constants definitions. consts = content .iter() .filter_map(|item| match item { @@ -122,6 +228,34 @@ impl Parse for Module { _ => None, }) .collect(); + // Gather and parse submodule definitions. + // + // They are actually removed from the module's body, because they will need + // re-generating later when generated code is added. + submodules.reserve(content.len() - fns.len() - consts.len()); + let mut i = 0; + while i < content.len() { + if let syn::Item::Mod(_) = &content[i] { + let mut itemmod = match content.remove(i) { + syn::Item::Mod(m) => m, + _ => unreachable!(), + }; + let params = match inner_mod_attributes(&mut itemmod) { + Ok(p) => p, + Err(e) => return Err(e), + }; + let module = syn::parse2::(itemmod.to_token_stream()) + .map(|mut f| { + f.params = params; + f + })?; + if !module.params.skip { + submodules.push(module); + } + } else { + i += 1; + } + } } else { consts = new_vec![]; fns = new_vec![]; @@ -130,19 +264,49 @@ impl Parse for Module { mod_all: Some(mod_all), fns, consts, + submodules, + params: ExportedModParams::default(), }) } } impl Module { - pub fn generate(self) -> proc_macro2::TokenStream { - // Check for collisions if the "name" attribute was used on inner functions. - if let Err(e) = check_rename_collisions(&self.fns) { - return e.to_compile_error(); - } + pub fn module_name(&self) -> Option<&syn::Ident> { + self.mod_all.as_ref().map(|m| &m.ident) + } - // Perform the generation of new module items. - let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts); + pub fn exported_name(&self) -> Option> { + if let Some(ref s) = self.params.name { + Some(Cow::Borrowed(s)) + } else { + self.module_name().map(|m| Cow::Owned(m.to_string())) + } + } + + pub fn generate(self) -> proc_macro2::TokenStream { + match self.generate_inner() { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + } + } + + fn generate_inner(mut self) -> Result { + // Check for collisions if the "name" attribute was used on inner functions. + check_rename_collisions(&self.fns)?; + + // Generate new module items. + // + // This is done before inner module recursive generation, because that is destructive. + let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts, &self.submodules); + + // NB: submodules must have their new items for exporting generated in depth-first order to + // avoid issues with reparsing them. + let inner_modules: Vec = self.submodules.drain(..) + .try_fold::, _, + Result, syn::Error>>( + Vec::new(), |mut acc, m| { acc.push(m.generate_inner()?); Ok(acc) })?; + + // Generate new module items for exporting functions and constant. // Rebuild the structure of the module, with the new content added. let Module { mod_all, .. } = self; @@ -150,11 +314,23 @@ impl Module { let mod_name = mod_all.ident.clone(); let (_, orig_content) = mod_all.content.take().unwrap(); - quote! { + Ok(quote! { pub mod #mod_name { #(#orig_content)* + #(#inner_modules)* #mod_gen } + }) + } + + pub fn name(&self) -> Option<&syn::Ident> { + self.mod_all.as_ref().map(|m| &m.ident) + } + + pub fn content(&self) -> Option<&Vec> { + match self.mod_all { + Some(syn::ItemMod { content: Some((_, ref vec)), .. }) => Some(vec), + _ => None, } } } @@ -254,6 +430,68 @@ mod module_tests { ); } + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert_eq!(&item_mod.submodules[0].consts[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.submodules[0].consts[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod skip_this { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert!(item_mod.submodules[0].fns.is_empty()); + assert!(item_mod.submodules[0].consts.is_empty()); + assert!(item_mod.submodules[0].submodules.is_empty()); + } + + #[test] + fn one_skipped_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_mod(skip)] + pub mod skip_this { + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert!(item_mod.submodules.is_empty()); + } + #[test] fn one_constant_module() { let input_tokens: TokenStream = quote! { @@ -795,7 +1033,315 @@ mod generate_tests { pub fn increment_token_input_types() -> Box<[TypeId]> { increment_token().input_types() } - } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn dual_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + } + } + }; + + let expected_tokens = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("SPECIAL_CPU_NUMBER", 68000); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_sub_module("first_is", self::first_is::rhai_module_generate()); + m.set_sub_module("second_is", self::second_is::rhai_module_generate()); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn deep_tree_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + } + pub mod right { + pub const VALUE: INT = 7; + } + } + pub mod right { + pub const VALUE: INT = 3; + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + } + pub mod right { + pub const VALUE: INT = 1; + } + } + } + }; + + let expected_tokens = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 2); + m + } + } + pub mod right { + pub const VALUE: INT = 7; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 7); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 17); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } + pub mod right { + pub const VALUE: INT = 3; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 3); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 19); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 25); + m + } + } + pub mod right { + pub const VALUE: INT = 1; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 1); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 36); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 100); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } }; let item_mod = syn::parse2::(input_tokens).unwrap(); diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index dea8a421..f4f7369e 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -1,15 +1,18 @@ use quote::quote; use crate::function::ExportedFn; +use crate::module::Module; pub(crate) type ExportedConst = (String, syn::Expr); pub(crate) fn generate_body( fns: &Vec, consts: &Vec, + submodules: &Vec, ) -> proc_macro2::TokenStream { let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); + let mut add_mod_stmts: Vec = Vec::new(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); for (const_name, const_expr) in consts { @@ -22,6 +25,22 @@ pub(crate) fn generate_body( ); } + for itemmod in submodules { + let module_name: &syn::Ident = itemmod.module_name().unwrap(); + let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { + syn::LitStr::new(&name, proc_macro2::Span::call_site()) + } else { + syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) + }; + add_mod_stmts.push( + syn::parse2::(quote! { + m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate()); + }) + .unwrap(), + ); + } + + // NB: these are token streams, because reparsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { @@ -98,6 +117,7 @@ pub(crate) fn generate_body( let mut m = Module::new(); #(#set_fn_stmts)* #(#set_const_stmts)* + #(#add_mod_stmts)* m } } diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index 4881b927..bf0c0030 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -34,3 +34,39 @@ fn one_fn_module_nested_attr_test() -> Result<(), Box> { ); Ok(()) } + +pub mod one_fn_submodule_nested_attr { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + #[rhai_mod(name = "constants")] + pub mod my_module { + use rhai::plugin::*; + use rhai::FLOAT; + #[rhai_fn(return_raw)] + pub fn get_mystic_number() -> Result> { + Ok(Dynamic::from(42.0 as FLOAT)) + } + } + } +} + +#[test] +fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::constants::get_mystic_number(); + m"# + )?, + 42.0 + ); + Ok(()) +} diff --git a/codegen/ui_tests/rhai_mod_bad_attr.rs b/codegen/ui_tests/rhai_mod_bad_attr.rs new file mode 100644 index 00000000..09319ce6 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_attr.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(unknown = "thing")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_bad_attr.stderr b/codegen/ui_tests/rhai_mod_bad_attr.stderr new file mode 100644 index 00000000..87209038 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/rhai_mod_bad_attr.rs:11:12 + | +11 | #[rhai_mod(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_bad_attr.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_bad_value.rs b/codegen/ui_tests/rhai_mod_bad_value.rs new file mode 100644 index 00000000..3d30fdcc --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_value.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(name = true)] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_bad_value.stderr b/codegen/ui_tests/rhai_mod_bad_value.stderr new file mode 100644 index 00000000..765f8a1f --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/rhai_mod_bad_value.rs:11:19 + | +11 | #[rhai_mod(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_bad_value.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_junk_arg.rs b/codegen/ui_tests/rhai_mod_junk_arg.rs new file mode 100644 index 00000000..842c0bad --- /dev/null +++ b/codegen/ui_tests/rhai_mod_junk_arg.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod("wheeeee")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_junk_arg.stderr b/codegen/ui_tests/rhai_mod_junk_arg.stderr new file mode 100644 index 00000000..c2dcc28a --- /dev/null +++ b/codegen/ui_tests/rhai_mod_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/rhai_mod_junk_arg.rs:11:12 + | +11 | #[rhai_mod("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_junk_arg.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_missing_value.rs b/codegen/ui_tests/rhai_mod_missing_value.rs new file mode 100644 index 00000000..f231b65d --- /dev/null +++ b/codegen/ui_tests/rhai_mod_missing_value.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(name)] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_missing_value.stderr b/codegen/ui_tests/rhai_mod_missing_value.stderr new file mode 100644 index 00000000..62e0502f --- /dev/null +++ b/codegen/ui_tests/rhai_mod_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/rhai_mod_missing_value.rs:11:12 + | +11 | #[rhai_mod(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_missing_value.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_path_attr.rs b/codegen/ui_tests/rhai_mod_path_attr.rs new file mode 100644 index 00000000..43be9604 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_path_attr.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(rhai::name = "thing")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_path_attr.stderr b/codegen/ui_tests/rhai_mod_path_attr.stderr new file mode 100644 index 00000000..bd165324 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/rhai_mod_path_attr.rs:11:12 + | +11 | #[rhai_mod(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_path_attr.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_return_raw.rs b/codegen/ui_tests/rhai_mod_return_raw.rs new file mode 100644 index 00000000..e5fed7e0 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_return_raw.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(return_raw = "yes")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_return_raw.stderr b/codegen/ui_tests/rhai_mod_return_raw.stderr new file mode 100644 index 00000000..f767dce0 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_return_raw.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'return_raw' + --> $DIR/rhai_mod_return_raw.rs:11:12 + | +11 | #[rhai_mod(return_raw = "yes")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_return_raw.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From 08977e2a62f3aa3717160faa69c0677967ce8e01 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 21 Aug 2020 21:48:45 +0800 Subject: [PATCH 069/103] Use combine_flatten for plugin modules. --- RELEASES.md | 9 +++++++++ src/module.rs | 33 +++++++++++++++++++++++++++++++-- src/packages/array_basic.rs | 2 +- src/packages/fn_basic.rs | 2 +- src/packages/logic.rs | 2 +- src/packages/map_basic.rs | 4 ++-- src/packages/math_basic.rs | 4 ++-- src/packages/string_more.rs | 4 ++-- src/packages/time_basic.rs | 2 +- src/parser.rs | 4 ++-- src/settings.rs | 1 + tests/plugins.rs | 16 +++++++++++----- 12 files changed, 64 insertions(+), 19 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d42f41e2..fb624187 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,15 @@ Rhai Release Notes Version 0.19.0 ============== +New features +------------ + +* Adds `Module::combine_flatten` to combine two modules while flattening to the root level. + + +Version 0.18.2 +============== + Bug fixes --------- diff --git a/src/module.rs b/src/module.rs index 28c64ff0..c6e161b6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -931,6 +931,24 @@ impl Module { /// Combine another module into this module. /// The other module is consumed to merge into this module. pub fn combine(&mut self, other: Self) -> &mut Self { + self.modules.extend(other.modules.into_iter()); + self.variables.extend(other.variables.into_iter()); + self.functions.extend(other.functions.into_iter()); + self.type_iterators.extend(other.type_iterators.into_iter()); + self.all_functions.clear(); + self.all_variables.clear(); + self.indexed = false; + self + } + + /// Combine another module into this module. + /// The other module is consumed to merge into this module. + /// Sub-modules are flattened onto the root module, with higher level overriding lower level. + pub fn combine_flatten(&mut self, other: Self) -> &mut Self { + other.modules.into_iter().for_each(|(_, m)| { + self.combine_flatten(m); + }); + self.variables.extend(other.variables.into_iter()); self.functions.extend(other.functions.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter()); @@ -942,15 +960,26 @@ impl Module { /// Merge another module into this module. pub fn merge(&mut self, other: &Self) -> &mut Self { - self.merge_filtered(other, |_, _, _| true) + self.merge_filtered(other, &|_, _, _| true) } /// Merge another module into this module, with only selected script-defined functions based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, - _filter: impl Fn(FnAccess, &str, usize) -> bool, + _filter: &impl Fn(FnAccess, &str, usize) -> bool, ) -> &mut Self { + #[cfg(not(feature = "no_function"))] + for (k, v) in &other.modules { + let mut m = Self::new(); + m.merge_filtered(v, _filter); + self.modules.insert(k.clone(), m); + } + + #[cfg(feature = "no_function")] + self.modules + .extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone()))); + self.variables .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 433f815f..5b117731 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -59,7 +59,7 @@ macro_rules! reg_pad { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - lib.combine(exported_module!(array_functions)); + lib.combine_flatten(exported_module!(array_functions)); reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); reg_pad!(lib, INT, bool, char, ImmutableString, FnPtr, Array, Unit); diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index a115485a..274b85e1 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -3,7 +3,7 @@ use crate::fn_native::FnPtr; use crate::plugin::*; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { - lib.combine(exported_module!(fn_ptr_functions)); + lib.combine_flatten(exported_module!(fn_ptr_functions)); }); #[export_module] diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 60d7d75c..0c994dca 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -47,7 +47,7 @@ macro_rules! gen_cmp_functions { macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { - $($mod_name.combine(exported_module!($root::$arg_type::functions));)* + $($mod_name.combine_flatten(exported_module!($root::$arg_type::functions));)* } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 5834e66b..97e4a9a9 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -9,11 +9,11 @@ use crate::plugin::*; use crate::stdlib::vec::Vec; def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { - lib.combine(exported_module!(map_functions)); + lib.combine_flatten(exported_module!(map_functions)); // Register map access functions #[cfg(not(feature = "no_index"))] - lib.combine(exported_module!(index_functions)); + lib.combine_flatten(exported_module!(index_functions)); }); #[export_module] diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index f8679766..d5904998 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -26,10 +26,10 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] { // Floating point functions - lib.combine(exported_module!(float_functions)); + lib.combine_flatten(exported_module!(float_functions)); // Trig functions - lib.combine(exported_module!(trig_functions)); + lib.combine_flatten(exported_module!(trig_functions)); // Register conversion functions lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index fbcb2acd..a387ac40 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -60,9 +60,9 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str reg_functions!(lib += float; f32, f64); #[cfg(not(feature = "no_index"))] - lib.combine(exported_module!(index_functions)); + lib.combine_flatten(exported_module!(index_functions)); - lib.combine(exported_module!(string_functions)); + lib.combine_flatten(exported_module!(string_functions)); lib.set_raw_fn( "pad", diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 6aff8096..d1401288 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -26,7 +26,7 @@ use instant::Instant; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions - lib.combine(exported_module!(time_functions)); + lib.combine_flatten(exported_module!(time_functions)); }); #[export_module] diff --git a/src/parser.rs b/src/parser.rs index b8bb9cd8..da184a16 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -133,7 +133,7 @@ impl AST { filter: impl Fn(FnAccess, &str, usize) -> bool, ) -> Self { let mut functions: Module = Default::default(); - functions.merge_filtered(&self.1, filter); + functions.merge_filtered(&self.1, &filter); Self(Default::default(), functions) } @@ -266,7 +266,7 @@ impl AST { }; let mut functions = functions.clone(); - functions.merge_filtered(&other.1, filter); + functions.merge_filtered(&other.1, &filter); Self::new(ast, functions) } diff --git a/src/settings.rs b/src/settings.rs index 6e9e020d..4f0f7ccf 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -17,6 +17,7 @@ use crate::stdlib::boxed::Box; impl Engine { /// Load a new package into the `Engine`. + /// Anything that can be converted into a `PackageLibrary` is accepted, including a simple `Module`. /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. diff --git a/tests/plugins.rs b/tests/plugins.rs index 9d40107e..46a52ede 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -10,11 +10,16 @@ mod test { pub mod special_array_package { use rhai::{Array, INT}; - #[rhai_fn(get = "foo", return_raw)] - #[inline(always)] - pub fn foo(array: &mut Array) -> Result> { - Ok(array[0].clone()) + #[cfg(not(feature = "no_object"))] + #[rhai_mod()] + pub mod feature { + #[rhai_fn(get = "foo", return_raw)] + #[inline(always)] + pub fn foo(array: &mut Array) -> Result> { + Ok(array[0].clone()) + } } + #[rhai_fn(name = "test")] #[inline(always)] pub fn len(array: &mut Array, mul: INT) -> INT { @@ -59,7 +64,8 @@ gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String); fn test_plugins_package() -> Result<(), Box> { let mut engine = Engine::new(); - let m = exported_module!(test::special_array_package); + let mut m = Module::new(); + m.combine_flatten(exported_module!(test::special_array_package)); engine.load_package(m); reg_functions!(engine += greet::single(INT, bool, char)); From 5b3467631f856a0a98b0d6ef7809c7ad7b28bd4e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 22 Aug 2020 10:44:51 +0800 Subject: [PATCH 070/103] No need for rhai_mod if not renaming. --- tests/plugins.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/plugins.rs b/tests/plugins.rs index 46a52ede..dd56ae61 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -11,7 +11,6 @@ mod test { use rhai::{Array, INT}; #[cfg(not(feature = "no_object"))] - #[rhai_mod()] pub mod feature { #[rhai_fn(get = "foo", return_raw)] #[inline(always)] From 15a8f528aeef31736c5d2e085ddf45e4a2ac3ef0 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 21 Aug 2020 22:29:04 -0500 Subject: [PATCH 071/103] Avoid export_fn+cfg attributes in Rhai packages --- src/packages/string_basic.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 020f9292..827ba183 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -88,9 +88,9 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin #[cfg(not(feature = "no_object"))] { - set_exported_fn!(lib, KEYWORD_PRINT, format_map); - set_exported_fn!(lib, FN_TO_STRING, format_map); - set_exported_fn!(lib, KEYWORD_DEBUG, format_map); + set_exported_fn!(lib, KEYWORD_PRINT, format_map::format_map); + set_exported_fn!(lib, FN_TO_STRING, format_map::format_map); + set_exported_fn!(lib, KEYWORD_DEBUG, format_map::format_map); } }); @@ -154,8 +154,11 @@ fn to_debug(x: &mut T) -> ImmutableString { format!("{:?}", x).into() } #[cfg(not(feature = "no_object"))] -#[export_fn] -#[inline] -fn format_map(x: &mut Map) -> ImmutableString { - format!("#{:?}", x).into() +mod format_map { + use super::*; + #[inline] + #[export_fn] + pub fn format_map(x: &mut Map) -> ImmutableString { + format!("#{:?}", x).into() + } } From 708c0f385e825cb14d66846a804d8c5d007ea416 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 21 Aug 2020 22:30:10 -0500 Subject: [PATCH 072/103] Block #[cfg] attributes on exported or inner functions --- codegen/src/function.rs | 10 ++++++++++ codegen/src/module.rs | 11 +++++++++++ codegen/ui_tests/export_fn_cfg.rs | 25 +++++++++++++++++++++++++ codegen/ui_tests/export_fn_cfg.stderr | 11 +++++++++++ codegen/ui_tests/module_cfg_fn.rs | 27 +++++++++++++++++++++++++++ codegen/ui_tests/module_cfg_fn.stderr | 11 +++++++++++ 6 files changed, 95 insertions(+) create mode 100644 codegen/ui_tests/export_fn_cfg.rs create mode 100644 codegen/ui_tests/export_fn_cfg.stderr create mode 100644 codegen/ui_tests/module_cfg_fn.rs create mode 100644 codegen/ui_tests/module_cfg_fn.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0dba5afa..011ff650 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -148,6 +148,16 @@ impl Parse for ExportedFn { let entire_span = fn_all.span(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); + // #[cfg] attributes are not allowed on functions due to what is generated for them + if let Some(cfg_attr) = fn_all.attrs.iter().find(|a| { + a.path + .get_ident() + .map(|i| i.to_string() == "cfg") + .unwrap_or(false) + }) { + return Err(syn::Error::new(cfg_attr.span(), "cfg attributes not allowed on this item")); + } + // Determine if the function is public. let is_public = match fn_all.vis { syn::Visibility::Public(_) => true, diff --git a/codegen/src/module.rs b/codegen/src/module.rs index c6bd6469..d3e09eca 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -16,6 +16,17 @@ use std::borrow::Cow; use std::collections::HashMap; fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { + // #[cfg] attributes are not allowed on objects + if let Some(cfg_attr) = f.attrs.iter().find(|a| { + a.path + .get_ident() + .map(|i| i.to_string() == "cfg") + .unwrap_or(false) + }) { + return Err(syn::Error::new(cfg_attr.span(), "cfg attributes not allowed on this item")); + } + + // Find the #[rhai_fn] attribute which will turn be read for the function parameters. if let Some(rhai_fn_idx) = f.attrs.iter().position(|a| { a.path .get_ident() diff --git a/codegen/ui_tests/export_fn_cfg.rs b/codegen/ui_tests/export_fn_cfg.rs new file mode 100644 index 00000000..52d4c963 --- /dev/null +++ b/codegen/ui_tests/export_fn_cfg.rs @@ -0,0 +1,25 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[cfg(not(feature = "foo"))] +#[export_fn] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_cfg.stderr b/codegen/ui_tests/export_fn_cfg.stderr new file mode 100644 index 00000000..ac068be8 --- /dev/null +++ b/codegen/ui_tests/export_fn_cfg.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/export_fn_cfg.rs:9:1 + | +9 | #[cfg(not(feature = "foo"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_cfg.rs:20:8 + | +20 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/module_cfg_fn.rs b/codegen/ui_tests/module_cfg_fn.rs new file mode 100644 index 00000000..3613b9a5 --- /dev/null +++ b/codegen/ui_tests/module_cfg_fn.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + #[cfg(not(feature = "foo"))] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/module_cfg_fn.stderr b/codegen/ui_tests/module_cfg_fn.stderr new file mode 100644 index 00000000..80ac12f8 --- /dev/null +++ b/codegen/ui_tests/module_cfg_fn.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/module_cfg_fn.rs:11:5 + | +11 | #[cfg(not(feature = "foo"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/module_cfg_fn.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From 382e60e91a21094baf836ff3aefa35c510a836af Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 21 Aug 2020 22:49:15 -0500 Subject: [PATCH 073/103] Move UI tests to separate tests file --- codegen/src/function.rs | 9 --------- codegen/tests/ui_tests.rs | 8 ++++++++ 2 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 codegen/tests/ui_tests.rs diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 011ff650..89358274 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -1067,12 +1067,3 @@ mod generate_tests { assert_streams_eq(item_fn.generate(), expected_tokens); } } - -#[cfg(test)] -mod ui_tests { - #[test] - fn all() { - let t = trybuild::TestCases::new(); - t.compile_fail("ui_tests/*.rs"); - } -} diff --git a/codegen/tests/ui_tests.rs b/codegen/tests/ui_tests.rs new file mode 100644 index 00000000..ef7bf58f --- /dev/null +++ b/codegen/tests/ui_tests.rs @@ -0,0 +1,8 @@ +#![cfg(test)] +mod ui_tests { + #[test] + fn all() { + let t = trybuild::TestCases::new(); + t.compile_fail("ui_tests/*.rs"); + } +} From 55870e7b37c40d26f9a5406f442b3a1ddbb6e7e7 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 21 Aug 2020 23:05:18 -0500 Subject: [PATCH 074/103] Add #[cfg] support in submodules --- codegen/src/module.rs | 107 ++++++++++++++++-- codegen/src/rhai_module.rs | 17 ++- codegen/ui_tests/rhai_mod_inner_cfg_false.rs | 29 +++++ .../ui_tests/rhai_mod_inner_cfg_false.stderr | 5 + 4 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 codegen/ui_tests/rhai_mod_inner_cfg_false.rs create mode 100644 codegen/ui_tests/rhai_mod_inner_cfg_false.stderr diff --git a/codegen/src/module.rs b/codegen/src/module.rs index d3e09eca..7aa98409 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -11,6 +11,8 @@ use std::vec as new_vec; #[cfg(no_std)] use core::mem; +#[cfg(not(no_std))] +use std::mem; use std::borrow::Cow; use std::collections::HashMap; @@ -282,6 +284,10 @@ impl Parse for Module { } impl Module { + pub fn attrs(&self) -> Option<&Vec> { + self.mod_all.as_ref().map(|m| &m.attrs) + } + pub fn module_name(&self) -> Option<&syn::Ident> { self.mod_all.as_ref().map(|m| &m.ident) } @@ -324,8 +330,10 @@ impl Module { let mut mod_all = mod_all.unwrap(); let mod_name = mod_all.ident.clone(); let (_, orig_content) = mod_all.content.take().unwrap(); + let mod_attrs = mem::replace(&mut mod_all.attrs, Vec::with_capacity(0)); Ok(quote! { + #(#mod_attrs)* pub mod #mod_name { #(#orig_content)* #(#inner_modules)* @@ -1112,7 +1120,82 @@ mod generate_tests { #[allow(unused_mut)] pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_with_cfg_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[cfg(not(feature = "no_float"))] + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + #[cfg(not(feature = "no_float"))] + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + #[cfg(not(feature = "no_float"))] { + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + } m } } @@ -1150,7 +1233,7 @@ mod generate_tests { #[allow(unused_mut)] pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } m } } @@ -1202,8 +1285,8 @@ mod generate_tests { #[allow(unused_mut)] pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_sub_module("first_is", self::first_is::rhai_module_generate()); - m.set_sub_module("second_is", self::second_is::rhai_module_generate()); + { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } + { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } m } } @@ -1280,8 +1363,8 @@ mod generate_tests { pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_var("VALUE", 17); - m.set_sub_module("left", self::left::rhai_module_generate()); - m.set_sub_module("right", self::right::rhai_module_generate()); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } m } } @@ -1302,8 +1385,8 @@ mod generate_tests { pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_var("VALUE", 19); - m.set_sub_module("left", self::left::rhai_module_generate()); - m.set_sub_module("right", self::right::rhai_module_generate()); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } m } } @@ -1337,8 +1420,8 @@ mod generate_tests { pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_var("VALUE", 36); - m.set_sub_module("left", self::left::rhai_module_generate()); - m.set_sub_module("right", self::right::rhai_module_generate()); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } m } } @@ -1348,8 +1431,8 @@ mod generate_tests { pub fn rhai_module_generate() -> Module { let mut m = Module::new(); m.set_var("VALUE", 100); - m.set_sub_module("left", self::left::rhai_module_generate()); - m.set_sub_module("right", self::right::rhai_module_generate()); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } m } } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index f4f7369e..c327c71f 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -12,7 +12,7 @@ pub(crate) fn generate_body( ) -> proc_macro2::TokenStream { let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); - let mut add_mod_stmts: Vec = Vec::new(); + let mut add_mod_blocks: Vec = Vec::new(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); for (const_name, const_expr) in consts { @@ -32,9 +32,16 @@ pub(crate) fn generate_body( } else { syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) }; - add_mod_stmts.push( - syn::parse2::(quote! { - m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate()); + let cfg_attrs: Vec<&syn::Attribute> = itemmod.attrs().unwrap().iter().filter(|&a| { + a.path.get_ident() + .map(|i| i.to_string() == "cfg") + .unwrap_or(false) + }).collect(); + add_mod_blocks.push( + syn::parse2::(quote! { + #(#cfg_attrs)* { + m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate()); + } }) .unwrap(), ); @@ -117,7 +124,7 @@ pub(crate) fn generate_body( let mut m = Module::new(); #(#set_fn_stmts)* #(#set_const_stmts)* - #(#add_mod_stmts)* + #(#add_mod_blocks)* m } } diff --git a/codegen/ui_tests/rhai_mod_inner_cfg_false.rs b/codegen/ui_tests/rhai_mod_inner_cfg_false.rs new file mode 100644 index 00000000..cfb3e1b6 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_inner_cfg_false.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + #[cfg(feature = "unset_feature")] + pub mod test_mod { + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_inner_cfg_false.stderr b/codegen/ui_tests/rhai_mod_inner_cfg_false.stderr new file mode 100644 index 00000000..04068220 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_inner_cfg_false.stderr @@ -0,0 +1,5 @@ +error[E0433]: failed to resolve: could not find `test_mod` in `test_module` + --> $DIR/rhai_mod_inner_cfg_false.rs:24:21 + | +24 | if test_module::test_mod::test_fn(n) { + | ^^^^^^^^ could not find `test_mod` in `test_module` From 65009dd19301a81c873daeca299d0bb5a083cf96 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Fri, 21 Aug 2020 23:21:56 -0500 Subject: [PATCH 075/103] Block #[cfg] attributes on inner constants --- codegen/src/module.rs | 34 +++++++++++++++--------- codegen/ui_tests/module_cfg_const.rs | 31 +++++++++++++++++++++ codegen/ui_tests/module_cfg_const.stderr | 11 ++++++++ 3 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 codegen/ui_tests/module_cfg_const.rs create mode 100644 codegen/ui_tests/module_cfg_const.stderr diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 7aa98409..34b67876 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -199,7 +199,7 @@ impl Parse for Module { fn parse(input: ParseStream) -> syn::Result { let mut mod_all: syn::ItemMod = input.parse()?; let fns: Vec<_>; - let consts: Vec<_>; + let mut consts: Vec<_> = new_vec![]; let mut submodules: Vec<_> = Vec::new(); if let Some((_, ref mut content)) = mod_all.content { // Gather and parse functions. @@ -223,24 +223,33 @@ impl Parse for Module { .map(|_| vec) })?; // Gather and parse constants definitions. - consts = content - .iter() - .filter_map(|item| match item { + for item in content.iter() { + match item { syn::Item::Const(syn::ItemConst { vis, ref expr, ident, + attrs, .. }) => { - if let syn::Visibility::Public(_) = vis { - Some((ident.to_string(), expr.as_ref().clone())) - } else { - None + // #[cfg] attributes are not allowed on const declarations + if let Some(cfg_attr) = attrs.iter().find(|a| { + a.path + .get_ident() + .map(|i| i.to_string() == "cfg") + .unwrap_or(false) + }) { + return Err(syn::Error::new( + cfg_attr.span(), + "cfg attributes not allowed on this item")); } - } - _ => None, - }) - .collect(); + if let syn::Visibility::Public(_) = vis { + consts.push((ident.to_string(), expr.as_ref().clone())); + } + }, + _ => {}, + } + }; // Gather and parse submodule definitions. // // They are actually removed from the module's body, because they will need @@ -270,7 +279,6 @@ impl Parse for Module { } } } else { - consts = new_vec![]; fns = new_vec![]; } Ok(Module { diff --git a/codegen/ui_tests/module_cfg_const.rs b/codegen/ui_tests/module_cfg_const.rs new file mode 100644 index 00000000..599f52da --- /dev/null +++ b/codegen/ui_tests/module_cfg_const.rs @@ -0,0 +1,31 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + use rhai::FLOAT; + + #[cfg(feature = "foo")] + pub const MAGIC: FLOAT = 42.0 as FLOAT; + + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/module_cfg_const.stderr b/codegen/ui_tests/module_cfg_const.stderr new file mode 100644 index 00000000..4eaf1b06 --- /dev/null +++ b/codegen/ui_tests/module_cfg_const.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/module_cfg_const.rs:13:5 + | +13 | #[cfg(feature = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/module_cfg_const.rs:26:8 + | +26 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From 211ce549737532e7111a07dd1bba445183eb7be9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 22 Aug 2020 22:26:49 +0800 Subject: [PATCH 076/103] Move most packages to modules. --- Cargo.toml | 3 +- src/fn_call.rs | 52 +-- src/packages/arithmetic.rs | 765 ++++++++++++++++++------------------ src/packages/map_basic.rs | 21 +- src/packages/math_basic.rs | 150 ++++--- src/packages/string_more.rs | 30 +- 6 files changed, 511 insertions(+), 510 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ed6f6c6..ce2f3350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] -num-traits = { version = "0.2.11", default-features = false } smallvec = { version = "1.4.1", default-features = false } rhai_codegen = { version = "0.1", path = "codegen" } @@ -39,7 +38,7 @@ internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std -no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "no_closure", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" diff --git a/src/fn_call.rs b/src/fn_call.rs index 792ccd4c..4ac5a50d 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1120,7 +1120,7 @@ pub fn run_builtin_binary_op( x: &Dynamic, y: &Dynamic, ) -> Result, Box> { - use crate::packages::arithmetic::*; + use crate::packages::arithmetic::arith_basic::INT::functions::*; let args_type = x.type_id(); @@ -1134,14 +1134,14 @@ pub fn run_builtin_binary_op( if cfg!(not(feature = "unchecked")) { match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), + "+" => return add(x, y).map(Some), + "-" => return subtract(x, y).map(Some), + "*" => return multiply(x, y).map(Some), + "/" => return divide(x, y).map(Some), + "%" => return modulo(x, y).map(Some), + "~" => return power(x, y).map(Some), + ">>" => return shift_right(x, y).map(Some), + "<<" => return shift_left(x, y).map(Some), _ => (), } } else { @@ -1151,9 +1151,9 @@ pub fn run_builtin_binary_op( "*" => return Ok(Some((x * y).into())), "/" => return Ok(Some((x / y).into())), "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), + "~" => return Ok(Some(x.pow(y as u32).into())), + ">>" => return Ok(Some((x >> y).into())), + "<<" => return Ok(Some((x << y).into())), _ => (), } } @@ -1228,7 +1228,7 @@ pub fn run_builtin_binary_op( "*" => return Ok(Some((x * y).into())), "/" => return Ok(Some((x / y).into())), "%" => return Ok(Some((x % y).into())), - "~" => return pow_f_f(x, y).map(Into::into).map(Some), + "~" => return Ok(Some(x.powf(y).into())), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), ">" => return Ok(Some((x > y).into())), @@ -1248,7 +1248,7 @@ pub fn run_builtin_op_assignment( x: &mut Dynamic, y: &Dynamic, ) -> Result, Box> { - use crate::packages::arithmetic::*; + use crate::packages::arithmetic::arith_basic::INT::functions::*; let args_type = x.type_id(); @@ -1262,14 +1262,14 @@ pub fn run_builtin_op_assignment( if cfg!(not(feature = "unchecked")) { match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), + "+=" => return Ok(Some(*x = add(*x, y)?.as_int().unwrap())), + "-=" => return Ok(Some(*x = subtract(*x, y)?.as_int().unwrap())), + "*=" => return Ok(Some(*x = multiply(*x, y)?.as_int().unwrap())), + "/=" => return Ok(Some(*x = divide(*x, y)?.as_int().unwrap())), + "%=" => return Ok(Some(*x = modulo(*x, y)?.as_int().unwrap())), + "~=" => return Ok(Some(*x = power(*x, y)?.as_int().unwrap())), + ">>=" => return Ok(Some(*x = shift_right(*x, y)?.as_int().unwrap())), + "<<=" => return Ok(Some(*x = shift_left(*x, y)?.as_int().unwrap())), _ => (), } } else { @@ -1279,9 +1279,9 @@ pub fn run_builtin_op_assignment( "*=" => return Ok(Some(*x *= y)), "/=" => return Ok(Some(*x /= y)), "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + "~=" => return Ok(Some(*x = x.pow(y as u32))), + ">>=" => return Ok(Some(*x = *x << y)), + "<<=" => return Ok(Some(*x = *x << y)), _ => (), } } @@ -1322,7 +1322,7 @@ pub fn run_builtin_op_assignment( "*=" => return Ok(Some(*x *= y)), "/=" => return Ok(Some(*x /= y)), "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_f_f(*x, y)?)), + "~=" => return Ok(Some(*x = x.powf(y))), _ => (), } } diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index f40f1d11..10d33f8e 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -1,419 +1,404 @@ +#![allow(non_snake_case)] + use crate::def_package; -use crate::module::FuncReturn; use crate::parser::INT; +use crate::plugin::*; use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -use num_traits::{ - identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, - CheckedShr, CheckedSub, -}; - #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::float::Float; -use crate::stdlib::{ - fmt::Display, - format, - ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, -}; +use crate::stdlib::format; -// Checked add -pub fn add(x: T, y: T) -> FuncReturn { - x.checked_add(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Addition overflow: {} + {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked subtract -pub fn sub(x: T, y: T) -> FuncReturn { - x.checked_sub(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Subtraction underflow: {} - {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked multiply -pub fn mul(x: T, y: T) -> FuncReturn { - x.checked_mul(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Multiplication overflow: {} * {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked divide -pub fn div(x: T, y: T) -> FuncReturn -where - T: Display + CheckedDiv + PartialEq + Zero, -{ - // Detect division by zero - if y == T::zero() { - return EvalAltResult::ErrorArithmetic( - format!("Division by zero: {} / {}", x, y), - Position::none(), - ) - .into(); - } +macro_rules! gen_arithmetic_functions { + ($root:ident => $($arg_type:ident),+) => { + pub mod $root { $( + pub mod $arg_type { + use super::super::*; - x.checked_div(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Division overflow: {} / {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -pub fn neg(x: T) -> FuncReturn { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }) -} -// Checked absolute -pub fn abs(x: T) -> FuncReturn { - // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics - // when the number is ::MIN instead of returning ::MIN itself. - if x >= ::zero() { - Ok(x) - } else { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }) - } -} -// Unchecked add - may panic on overflow -fn add_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x + y) -} -// Unchecked subtract - may panic on underflow -fn sub_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x - y) -} -// Unchecked multiply - may panic on overflow -fn mul_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x * y) -} -// Unchecked divide - may panic when dividing by zero -fn div_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x / y) -} -// Unchecked negative - may panic on overflow -fn neg_u(x: T) -> FuncReturn<::Output> { - Ok(-x) -} -// Unchecked absolute - may panic on overflow -fn abs_u(x: T) -> FuncReturn<::Output> -where - T: Neg + PartialOrd + Default + Into<::Output>, -{ - // Numbers should default to zero - if x < Default::default() { - Ok(-x) - } else { - Ok(x.into()) - } -} -// Bit operators -fn binary_and(x: T, y: T) -> FuncReturn<::Output> { - Ok(x & y) -} -fn binary_or(x: T, y: T) -> FuncReturn<::Output> { - Ok(x | y) -} -fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { - Ok(x ^ y) -} -// Checked left-shift -pub fn shl(x: T, y: INT) -> FuncReturn { - // Cannot shift by a negative number of bits - if y < 0 { - return EvalAltResult::ErrorArithmetic( - format!("Left-shift by a negative number: {} << {}", x, y), - Position::none(), - ) - .into(); - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "+", return_raw)] + #[inline] + pub fn add(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_add(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Addition overflow: {} + {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x + y)) + } + } + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_sub(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Subtraction overflow: {} - {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x - y)) + } + } + #[rhai_fn(name = "*", return_raw)] + #[inline] + pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_mul(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Multiplication overflow: {} * {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x * y)) + } + } + #[rhai_fn(name = "/", return_raw)] + #[inline] + pub fn divide(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + // Detect division by zero + if y == 0 { + EvalAltResult::ErrorArithmetic( + format!("Division by zero: {} / {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_div(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Division overflow: {} / {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x / y)) + } + } + #[rhai_fn(name = "%", return_raw)] + #[inline] + pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_rem(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Modulo division by zero or overflow: {} % {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x % y)) + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn power(x: INT, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x.pow(y as u32))) + } + } - CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by too many bits: {} << {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked right-shift -pub fn shr(x: T, y: INT) -> FuncReturn { - // Cannot shift by a negative number of bits - if y < 0 { - return EvalAltResult::ErrorArithmetic( - format!("Right-shift by a negative number: {} >> {}", x, y), - Position::none(), - ) - .into(); + #[rhai_fn(name = "<<", return_raw)] + #[inline] + pub fn shift_left(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by too many bits: {} << {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by a negative number: {} << {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_shl(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by too many bits: {} << {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x << y)) + } + } + #[rhai_fn(name = ">>", return_raw)] + #[inline] + pub fn shift_right(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by too many bits: {} >> {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by a negative number: {} >> {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_shr(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by too many bits: {} >> {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x >> y)) + } + } + #[rhai_fn(name = "&")] + #[inline(always)] + fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { + x & y + } + #[rhai_fn(name = "|")] + #[inline(always)] + fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { + x | y + } + #[rhai_fn(name = "^")] + #[inline(always)] + fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { + x ^ y + } + } + } + )* } } - - CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by too many bits: {} % {}", x, y), - Position::none(), - ) - .into() - }) -} -// Unchecked left-shift - may panic if shifting by a negative number of bits -pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { - Ok(x.shl(y)) -} -// Unchecked right-shift - may panic if shifting by a negative number of bits -pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { - Ok(x.shr(y)) -} -// Checked modulo -pub fn modulo(x: T, y: T) -> FuncReturn { - x.checked_rem(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Modulo division by zero or overflow: {} % {}", x, y), - Position::none(), - ) - .into() - }) -} -// Unchecked modulo - may panic if dividing by zero -fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x % y) -} -// Checked power -pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { - if cfg!(not(feature = "only_i32")) { - if y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }) - } - } else { - if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }) - } - } -} -// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) -pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { - Ok(x.pow(y as u32)) -} -// Floating-point power - always well-defined -#[cfg(not(feature = "no_float"))] -pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { - Ok(x.powf(y)) -} -// Checked power -#[cfg(not(feature = "no_float"))] -pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { - // Raise to power that is larger than an i32 - if y > (i32::MAX as INT) { - return EvalAltResult::ErrorArithmetic( - format!("Number raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into(); - } - - Ok(x.powi(y as i32)) -} -// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(not(feature = "no_float"))] -pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { - Ok(x.powi(y as i32)) } -macro_rules! reg_unary { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_1($op, $func::<$par>); )* - }; +macro_rules! gen_signed_functions { + ($root:ident => $($arg_type:ident),+) => { + pub mod $root { $( + pub mod $arg_type { + use super::super::*; + + #[export_module] + pub mod functions { + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn neg(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_neg().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(-x)) + } + } + #[rhai_fn(return_raw)] + #[inline] + pub fn abs(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_abs().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x.abs())) + } + } + #[inline] + pub fn sign(x: $arg_type) -> INT { + if x == 0 { + 0 + } else if x < 0 { + -1 + } else { + 1 + } + } + } + } + )* } + } } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2($op, $func::<$par>); )* - }; -} -macro_rules! reg_sign { - ($lib:expr, $op:expr, $ret:ty, $($par:ty),*) => { - $( $lib.set_fn_1($op, |value: $par| -> Result<$ret, _> { - Ok(if value == (0 as $par) { - (0 as $ret) - } else if value < (0 as $par) { - (-1 as $ret) - } else { - (1 as $ret) - }) - }); )* - }; + +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { + $($mod_name.combine_flatten(exported_module!($root::$arg_type::functions));)* + } } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - if cfg!(not(feature = "unchecked")) { - // Checked basic arithmetic - reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64); - // Checked bit shifts - reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); + reg_functions!(lib += signed_basic; INT); - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "+", add, i128, u128); - reg_op!(lib, "-", sub, i128, u128); - reg_op!(lib, "*", mul, i128, u128); - reg_op!(lib, "/", div, i128, u128); - // Checked bit shifts - reg_op!(lib, "<<", shl, i128, u128); - reg_op!(lib, ">>", shr, i128, u128); - reg_op!(lib, "%", modulo, i128, u128); - } - } - - if cfg!(feature = "unchecked") { - // Unchecked basic arithmetic - reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64); - // Unchecked bit shifts - reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "+", add_u, i128, u128); - reg_op!(lib, "-", sub_u, i128, u128); - reg_op!(lib, "*", mul_u, i128, u128); - reg_op!(lib, "/", div_u, i128, u128); - // Unchecked bit shifts - reg_op!(lib, "<<", shl_u, i128, u128); - reg_op!(lib, ">>", shr_u, i128, u128); - reg_op!(lib, "%", modulo_u, i128, u128); - } - } - - reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - - if cfg!(not(target_arch = "wasm32")) { - reg_sign!(lib, "sign", INT, i128); - } - } - - // Basic arithmetic for floating-point - no need to check - if cfg!(not(feature = "no_float")) { - reg_op!(lib, "+", add_u, f32); - reg_op!(lib, "-", sub_u, f32); - reg_op!(lib, "*", mul_u, f32); - reg_op!(lib, "/", div_u, f32); - reg_sign!(lib, "sign", f32, f32); - reg_sign!(lib, "sign", f64, f64); - } - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "|", binary_or, i128, u128); - reg_op!(lib, "&", binary_and, i128, u128); - reg_op!(lib, "^", binary_xor, i128, u128); - } - } - - #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - // Checked power - if cfg!(not(feature = "unchecked")) { - lib.set_fn_2("~", pow_f_i); - } else { - lib.set_fn_2("~", pow_f_i_u); - } + reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); + reg_functions!(lib += signed_numbers; i8, i16, i32); - // Floating-point modulo and power - reg_op!(lib, "%", modulo_u, f32); - - // Floating-point unary - reg_unary!(lib, "-", neg_u, f32, f64); - reg_unary!(lib, "abs", abs_u, f32, f64); - } - - // Checked unary - if cfg!(not(feature = "unchecked")) { - reg_unary!(lib, "-", neg, INT); - reg_unary!(lib, "abs", abs, INT); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_unary!(lib, "-", neg, i8, i16, i32, i64); - reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - - if cfg!(not(target_arch = "wasm32")) { - reg_unary!(lib, "-", neg, i128); - reg_unary!(lib, "abs", abs, i128); - } + #[cfg(not(target_arch = "wasm32"))] + { + reg_functions!(lib += arith_num_128; i128, u128); + reg_functions!(lib += signed_num_128; i128); } } - // Unchecked unary - if cfg!(feature = "unchecked") { - reg_unary!(lib, "-", neg_u, INT); - reg_unary!(lib, "abs", abs_u, INT); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); - reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - - if cfg!(not(target_arch = "wasm32")) { - reg_unary!(lib, "-", neg_u, i128); - reg_unary!(lib, "abs", abs_u, i128); - } - } - } + // Basic arithmetic for floating-point + #[cfg(not(feature = "no_float"))] + lib.combine_flatten(exported_module!(float_functions)); }); + +gen_arithmetic_functions!(arith_basic => INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_arithmetic_functions!(arith_num_128 => i128, u128); + +gen_signed_functions!(signed_basic => INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_signed_functions!(signed_numbers => i8, i16, i32); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_signed_functions!(signed_num_128 => i128); + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod float_functions { + #[rhai_fn(name = "+")] + #[inline(always)] + pub fn add(x: f32, y: f32) -> f32 { + x + y + } + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn subtract(x: f32, y: f32) -> f32 { + x - y + } + #[rhai_fn(name = "*")] + #[inline(always)] + pub fn multiply(x: f32, y: f32) -> f32 { + x * y + } + #[rhai_fn(name = "/")] + #[inline(always)] + pub fn divide(x: f32, y: f32) -> f32 { + x / y + } + #[rhai_fn(name = "%")] + #[inline(always)] + pub fn modulo(x: f32, y: f32) -> f32 { + x % y + } + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn neg_f32(x: f32) -> f32 { + -x + } + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn neg_f64(x: f64) -> f64 { + -x + } + #[rhai_fn(name = "abs")] + #[inline(always)] + pub fn abs_f32(x: f32) -> f32 { + x.abs() + } + #[rhai_fn(name = "abs")] + #[inline(always)] + pub fn abs_f64(x: f64) -> f64 { + x.abs() + } + #[rhai_fn(name = "sign")] + #[inline] + pub fn sign_f32(x: f32) -> INT { + if x == 0.0 { + 0 + } else if x < 0.0 { + -1 + } else { + 1 + } + } + #[rhai_fn(name = "sign")] + #[inline] + pub fn sign_f64(x: f64) -> INT { + if x == 0.0 { + 0 + } else if x < 0.0 { + -1 + } else { + 1 + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline(always)] + pub fn pow_f_f(x: FLOAT, y: FLOAT) -> Result> { + Ok(x.powf(y).into()) + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn pow_f_i(x: FLOAT, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Number raised to too large an index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else { + Ok(x.powi(y as i32).into()) + } + } +} diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 97e4a9a9..d479fcfd 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -10,10 +10,6 @@ use crate::stdlib::vec::Vec; def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.combine_flatten(exported_module!(map_functions)); - - // Register map access functions - #[cfg(not(feature = "no_index"))] - lib.combine_flatten(exported_module!(index_functions)); }); #[export_module] @@ -63,15 +59,14 @@ mod map_functions { } }); } -} -#[cfg(not(feature = "no_index"))] -#[export_module] -mod index_functions { - pub fn keys(map: &mut Map) -> Vec { - map.iter().map(|(k, _)| k.clone().into()).collect() - } - pub fn values(map: &mut Map) -> Vec { - map.iter().map(|(_, v)| v.clone()).collect() + #[cfg(not(feature = "no_index"))] + pub mod indexing { + pub fn keys(map: &mut Map) -> Vec { + map.iter().map(|(k, _)| k.clone().into()).collect() + } + pub fn values(map: &mut Map) -> Vec { + map.iter().map(|(_, v)| v.clone()).collect() + } } } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index d5904998..3f557124 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use crate::def_package; use crate::parser::INT; use crate::plugin::*; @@ -22,6 +24,28 @@ pub const MAX_INT: INT = i32::MAX; #[cfg(not(feature = "only_i32"))] pub const MAX_INT: INT = i64::MAX; +macro_rules! gen_conversion_functions { + ($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { + pub mod $root { $( + pub mod $arg_type { + use super::super::*; + + #[export_fn] + #[inline(always)] + pub fn $func_name(x: $arg_type) -> $result_type { + x as $result_type + } + } + )* } + } +} + +macro_rules! reg_functions { + ($mod_name:ident += $root:ident :: $func_name:ident ( $($arg_type:ident),+ ) ) => { + $(set_exported_fn!($mod_name, stringify!($func_name), $root::$arg_type::$func_name);)* + } +} + def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] { @@ -31,76 +55,27 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { // Trig functions lib.combine_flatten(exported_module!(trig_functions)); - // Register conversion functions - lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); + reg_functions!(lib += basic_to_float::to_float(INT)); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u16| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: i32| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32)); - if cfg!(not(target_arch = "wasm32")) { - lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); - } + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128_to_float::to_float(i128, u128)); } } - lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); + reg_functions!(lib += basic_to_int::to_int(char)); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); - lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); - lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); - lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); - } - - if cfg!(not(feature = "only_i32")) { - lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); - lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - - if cfg!(feature = "only_i64") { - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); - } - } - - #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - if cfg!(not(feature = "unchecked")) { - lib.set_fn_1("to_int", |x: f32| { - if x > (MAX_INT as f32) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - ) - .into(); - } + reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); - Ok(x.trunc() as INT) - }); - lib.set_fn_1("to_int", |x: FLOAT| { - if x > (MAX_INT as FLOAT) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - ) - .into(); - } - - Ok(x.trunc() as INT) - }); - } - - if cfg!(feature = "unchecked") { - lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); - lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); - } + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128_to_int::to_int(i128, u128)); } }); @@ -256,4 +231,55 @@ mod float_functions { pub fn is_infinite_prop(x: FLOAT) -> bool { is_infinite(x) } + #[rhai_fn(name = "to_int", return_raw)] + #[inline] + pub fn f32_to_int(x: f32) -> Result> { + if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f32) { + EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + ) + .into() + } else { + Ok((x.trunc() as INT).into()) + } + } + #[rhai_fn(name = "to_int", return_raw)] + #[inline] + pub fn f64_to_int(x: f64) -> Result> { + if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f64) { + EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + ) + .into() + } else { + Ok((x.trunc() as INT).into()) + } + } } + +#[cfg(not(feature = "no_float"))] +gen_conversion_functions!(basic_to_float => to_float (INT) -> FLOAT); + +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_conversion_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32, u32, i64, u64) -> FLOAT); + +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_conversion_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT); + +gen_conversion_functions!(basic_to_int => to_int (char) -> INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_conversion_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32, i64, u64) -> INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index a387ac40..c0e937fa 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -59,9 +59,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str #[cfg(not(feature = "no_float"))] reg_functions!(lib += float; f32, f64); - #[cfg(not(feature = "no_index"))] - lib.combine_flatten(exported_module!(index_functions)); - lib.combine_flatten(exported_module!(string_functions)); lib.set_raw_fn( @@ -343,21 +340,20 @@ mod string_functions { pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { *s = s.replace(&find.to_string(), &sub.to_string()).into(); } -} -#[cfg(not(feature = "no_index"))] -#[export_module] -mod index_functions { - use crate::engine::Array; + #[cfg(not(feature = "no_index"))] + pub mod arrays { + use crate::engine::Array; - #[rhai_fn(name = "+")] - #[inline] - pub fn append(x: &mut ImmutableString, y: Array) -> String { - format!("{}{:?}", x, y) - } - #[rhai_fn(name = "+")] - #[inline] - pub fn prepend(x: &mut Array, y: ImmutableString) -> String { - format!("{:?}{}", x, y) + #[rhai_fn(name = "+")] + #[inline] + pub fn append(x: &mut ImmutableString, y: Array) -> String { + format!("{}{:?}", x, y) + } + #[rhai_fn(name = "+")] + #[inline] + pub fn prepend(x: &mut Array, y: ImmutableString) -> String { + format!("{:?}{}", x, y) + } } } From c6a3397b29999f8599c42b50640862c3def036b9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 23 Aug 2020 11:16:08 +0800 Subject: [PATCH 077/103] Remove dependency on num-traits. --- doc/src/about/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index d0624c2b..b88599ff 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -14,7 +14,7 @@ Easy * Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust. -* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations, and [`smallvec`](https://crates.io/crates/smallvec/)); +* Very few additional dependencies (right now only [`smallvec`](https://crates.io/crates/smallvec/)); for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. Fast From 3902e49a7da5321c3c4d042bd5a26186989d3b65 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 23 Aug 2020 18:04:19 +0800 Subject: [PATCH 078/103] Fix bug in right-shifts. --- src/fn_call.rs | 2 +- tests/compound_equality.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 4ac5a50d..f7836107 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1280,7 +1280,7 @@ pub fn run_builtin_op_assignment( "/=" => return Ok(Some(*x /= y)), "%=" => return Ok(Some(*x %= y)), "~=" => return Ok(Some(*x = x.pow(y as u32))), - ">>=" => return Ok(Some(*x = *x << y)), + ">>=" => return Ok(Some(*x = *x >> y)), "<<=" => return Ok(Some(*x = *x << y)), _ => (), } diff --git a/tests/compound_equality.rs b/tests/compound_equality.rs index b09115a0..af5c16b6 100644 --- a/tests/compound_equality.rs +++ b/tests/compound_equality.rs @@ -45,16 +45,16 @@ fn test_divide_equals() -> Result<(), Box> { } #[test] -fn test_left_shift_equals() -> Result<(), Box> { +fn test_right_shift_equals() -> Result<(), Box> { let engine = Engine::new(); assert_eq!(engine.eval::("let x = 9; x >>=1; x")?, 4); Ok(()) } #[test] -fn test_right_shift_equals() -> Result<(), Box> { +fn test_left_shift_equals() -> Result<(), Box> { let engine = Engine::new(); - assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); + assert_eq!(engine.eval::("let x = 4; x <<= 2; x")?, 16); Ok(()) } From 1dacf21a84d9d28ae6c0c7957ce671e66f5e32af Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 23 Aug 2020 18:28:55 +0800 Subject: [PATCH 079/103] Fix no_std builds by pulling in num-traits. --- Cargo.toml | 7 ++++++- src/fn_call.rs | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index aa50392c..83a2d021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std -no_std = [ "no_closure", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" @@ -51,6 +51,11 @@ version = "0.2.1" default_features = false optional = true +[dependencies.num-traits] +version = "0.2.11" +default-features = false +optional = true + [dependencies.core-error] version = "0.0.0" default_features = false diff --git a/src/fn_call.rs b/src/fn_call.rs index f7836107..e7c5fdc7 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -52,6 +52,10 @@ use crate::stdlib::{ #[cfg(not(feature = "no_function"))] use crate::stdlib::{collections::HashSet, string::String}; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; + /// Extract the property name from a getter function name. #[inline(always)] fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> { From 7c273e0aac590fbf94b9a054966d9c070d88043b Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 23 Aug 2020 14:22:49 -0500 Subject: [PATCH 080/103] Add missing skip submodule test --- codegen/src/module.rs | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 34b67876..41455360 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -924,6 +924,70 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn one_skipped_submodule() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[rhai_mod(skip)] + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[allow(non_camel_case_types)] + struct get_mystic_number_token(); + impl PluginFunction for get_mystic_number_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + #[test] fn one_private_constant_module() { let input_tokens: TokenStream = quote! { From 3fd3da6bfce1a35c234f8cf99786b2bb92f6f720 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 23 Aug 2020 17:22:29 -0500 Subject: [PATCH 081/103] Improve diagonstics on return_raw return mismatches --- codegen/src/function.rs | 25 +++++++++++++------ codegen/src/lib.rs | 7 ++++-- codegen/ui_tests/export_fn_raw_noreturn.rs | 25 +++++++++++++++++++ .../ui_tests/export_fn_raw_noreturn.stderr | 11 ++++++++ codegen/ui_tests/export_fn_raw_return.rs | 24 ++++++++++++++++++ codegen/ui_tests/export_fn_raw_return.stderr | 21 ++++++++++++++++ 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 codegen/ui_tests/export_fn_raw_noreturn.rs create mode 100644 codegen/ui_tests/export_fn_raw_noreturn.stderr create mode 100644 codegen/ui_tests/export_fn_raw_return.rs create mode 100644 codegen/ui_tests/export_fn_raw_return.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 89358274..78b26e3d 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -2,6 +2,8 @@ #[cfg(no_std)] use core::mem; +#[cfg(not(no_std))] +use std::mem; #[cfg(no_std)] use alloc::format; @@ -291,12 +293,21 @@ impl ExportedFn { } } - pub fn generate_with_params( - mut self, - mut params: ExportedFnParams, - ) -> proc_macro2::TokenStream { + pub fn set_params( + &mut self, mut params: ExportedFnParams, + ) -> syn::Result<()> { + + // Do not allow non-returning raw functions. + // + // This is caught now to avoid issues with diagnostics later. + if params.return_raw && mem::discriminant(&self.signature.output) == + mem::discriminant(&syn::ReturnType::Default) { + return Err(syn::Error::new(self.signature.span(), + "return_raw functions must return Result")); + } + self.params = params; - self.generate() + Ok(()) } pub fn generate(self) -> proc_macro2::TokenStream { @@ -353,7 +364,7 @@ impl ExportedFn { } } } else { - quote! { + quote_spanned! { self.return_type().unwrap().span()=> type EvalBox = Box; pub #dynamic_signature { super::#name(#(#arguments),*) @@ -520,7 +531,7 @@ impl ExportedFn { Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) } } else { - quote! { + quote_spanned! { self.return_type().unwrap().span()=> #sig_name(#(#unpack_exprs),*) } }; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 9675ac66..a9e43d93 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -109,9 +109,12 @@ pub fn export_fn( let mut output = proc_macro2::TokenStream::from(input.clone()); let parsed_params = parse_macro_input!(args as function::ExportedFnParams); - let function_def = parse_macro_input!(input as function::ExportedFn); + let mut function_def = parse_macro_input!(input as function::ExportedFn); + if let Err(e) = function_def.set_params(parsed_params) { + return e.to_compile_error().into(); + } - output.extend(function_def.generate_with_params(parsed_params)); + output.extend(function_def.generate()); proc_macro::TokenStream::from(output) } diff --git a/codegen/ui_tests/export_fn_raw_noreturn.rs b/codegen/ui_tests/export_fn_raw_noreturn.rs new file mode 100644 index 00000000..7c8b42e0 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_noreturn.rs @@ -0,0 +1,25 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw)] +pub fn test_fn(input: &mut Point) { + input.x += 1.0; +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + test_fn(&mut n); + if n.x >= 10.0 { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_raw_noreturn.stderr b/codegen/ui_tests/export_fn_raw_noreturn.stderr new file mode 100644 index 00000000..0687c8c6 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_noreturn.stderr @@ -0,0 +1,11 @@ +error: return_raw functions must return Result + --> $DIR/export_fn_raw_noreturn.rs:10:5 + | +10 | pub fn test_fn(input: &mut Point) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_raw_noreturn.rs:19:5 + | +19 | test_fn(&mut n); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_raw_return.rs b/codegen/ui_tests/export_fn_raw_return.rs new file mode 100644 index 00000000..9df99549 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_return.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_raw_return.stderr b/codegen/ui_tests/export_fn_raw_return.stderr new file mode 100644 index 00000000..f570fda9 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_return.stderr @@ -0,0 +1,21 @@ +error[E0308]: mismatched types + --> $DIR/export_fn_raw_return.rs:10:8 + | +9 | #[export_fn(return_raw)] + | ------------------------ expected `std::result::Result>` because of return type +10 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found `bool` + | + = note: expected enum `std::result::Result>` + found type `bool` + +error[E0308]: mismatched types + --> $DIR/export_fn_raw_return.rs:10:33 + | +9 | #[export_fn(return_raw)] + | ------------------------ expected `std::result::Result>` because of return type +10 | pub fn test_fn(input: Point) -> bool { + | ^^^^ expected enum `std::result::Result`, found `bool` + | + = note: expected enum `std::result::Result>` + found type `bool` From bb6a044182a7858a56a28627e3218d22f82442ae Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 23 Aug 2020 17:44:29 -0500 Subject: [PATCH 082/103] Add test case for overloading rename --- codegen/src/module.rs | 103 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 41455360..d2e40d3a 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -750,6 +750,109 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn two_fn_overload_module() { + let input_tokens: TokenStream = quote! { + pub mod two_fns { + #[rhai_fn(name = "add_n")] + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + #[rhai_fn(name = "add_n")] + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod two_fns { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::()], + CallableFunction::from_plugin(add_one_to_token())); + m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + CallableFunction::from_plugin(add_n_to_token())); + m + } + + #[allow(non_camel_case_types)] + struct add_one_to_token(); + impl PluginFunction for add_one_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + + #[allow(non_camel_case_types)] + struct add_n_to_token(); + impl PluginFunction for add_n_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_n_to(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_n_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_n_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_n_to_token()) + } + pub fn add_n_to_token_input_types() -> Box<[TypeId]> { + add_n_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + #[test] fn one_double_arg_fn_module() { let input_tokens: TokenStream = quote! { From 9fe3672514f3d3a520680f9dd084817a8240e823 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 23 Aug 2020 17:47:43 -0500 Subject: [PATCH 083/103] string_more: add missing pub from crop_string --- src/packages/string_more.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index fbcb2acd..03704f5a 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -292,7 +292,7 @@ mod string_functions { } #[rhai_fn(name = "crop")] - fn crop_string(s: &mut ImmutableString, start: INT, len: INT) { + pub fn crop_string(s: &mut ImmutableString, start: INT, len: INT) { let offset = if s.is_empty() || len <= 0 { s.make_mut().clear(); return; From 5498443517525e23a4d0089f8ba1b47ea52e7ef9 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 23 Aug 2020 17:53:30 -0500 Subject: [PATCH 084/103] Major refactor of attribute handling --- codegen/src/attrs.rs | 117 ++++++++ codegen/src/function.rs | 136 +++------ codegen/src/lib.rs | 6 +- codegen/src/module.rs | 321 ++++++++------------- codegen/src/rhai_module.rs | 86 +++++- codegen/ui_tests/rhai_fn_rename_dot.stderr | 4 +- 6 files changed, 363 insertions(+), 307 deletions(-) create mode 100644 codegen/src/attrs.rs diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs new file mode 100644 index 00000000..1850d294 --- /dev/null +++ b/codegen/src/attrs.rs @@ -0,0 +1,117 @@ +use syn::{parse::ParseStream, parse::Parser, spanned::Spanned}; + +pub trait ExportedParams: Sized { + fn parse_stream(args: ParseStream) -> syn::Result; + fn no_attrs() -> Self; + fn from_info(info: ExportInfo) -> syn::Result; +} + +pub struct AttrItem { + pub key: proc_macro2::Ident, + pub value: Option, +} + +pub struct ExportInfo { + pub item_span: proc_macro2::Span, + pub items: Vec, +} + +pub fn parse_attr_items(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportInfo { item_span: args.span(), items: Vec::new()}); + } + let arg_list = args + .call(syn::punctuated::Punctuated::::parse_separated_nonempty)?; + + parse_punctuated_items(arg_list) +} + +pub fn parse_punctuated_items( + arg_list: syn::punctuated::Punctuated, +) -> syn::Result { + let list_span = arg_list.span(); + + let mut attrs: Vec = Vec::new(); + for arg in arg_list { + let (key, value) = match arg { + syn::Expr::Assign(syn::ExprAssign { + ref left, + ref right, + .. + }) => { + let attr_name: syn::Ident = match left.as_ref() { + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path.get_ident().cloned().ok_or_else(|| { + syn::Error::new(attr_path.span(), "expecting attribute name") + })?, + x => return Err(syn::Error::new(x.span(), "expecting attribute name")), + }; + let attr_value = match right.as_ref() { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(string), + .. + }) => string.clone(), + x => return Err(syn::Error::new(x.span(), "expecting string literal")), + }; + (attr_name, Some(attr_value)) + } + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path + .get_ident() + .cloned() + .map(|a| (a, None)) + .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, + x => return Err(syn::Error::new(x.span(), "expecting identifier")), + }; + attrs.push(AttrItem { key, value }); + } + + Ok(ExportInfo { item_span: list_span, items: attrs }) +} + +pub(crate) fn outer_item_attributes( + args: proc_macro2::TokenStream, + _attr_name: &str, +) -> syn::Result { + if args.is_empty() { + return Ok(T::no_attrs()); + } + + let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; + let arg_list = parser.parse2(args)?; + + let export_info = parse_punctuated_items(arg_list)?; + T::from_info(export_info) +} + +pub(crate) fn inner_item_attributes( + attrs: &mut Vec, + attr_name: &str, +) -> syn::Result { + // Find the #[rhai_fn] attribute which will turn be read for the function parameters. + if let Some(rhai_fn_idx) = attrs + .iter() + .position(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false)) + { + let rhai_fn_attr = attrs.remove(rhai_fn_idx); + rhai_fn_attr.parse_args_with(T::parse_stream) + } else { + Ok(T::no_attrs()) + } +} + +pub(crate) fn deny_cfg_attr(attrs: &Vec) -> syn::Result<()> { + if let Some(cfg_attr) = attrs + .iter() + .find(|a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) + { + Err(syn::Error::new( + cfg_attr.span(), + "cfg attributes not allowed on this item", + )) + } else { + Ok(()) + } +} diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 78b26e3d..b02a657d 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -10,11 +10,11 @@ use alloc::format; #[cfg(not(no_std))] use std::format; -use std::collections::HashMap; - use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; +use crate::attrs::{ExportInfo, ExportedParams}; + #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option, @@ -23,14 +23,6 @@ pub(crate) struct ExportedFnParams { pub span: Option, } -impl ExportedFnParams { - pub fn skip() -> ExportedFnParams { - let mut skip = ExportedFnParams::default(); - skip.skip = true; - skip - } -} - pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_SET: &str = "index$set$"; @@ -47,58 +39,44 @@ impl Parse for ExportedFnParams { return Ok(ExportedFnParams::default()); } - let arg_list = args.call( - syn::punctuated::Punctuated::::parse_separated_nonempty, - )?; - let span = arg_list.span(); + let info = crate::attrs::parse_attr_items(args)?; + Self::from_info(info) + } +} - let mut attrs: HashMap> = HashMap::new(); - for arg in arg_list { - let (left, right) = match arg { - syn::Expr::Assign(syn::ExprAssign { - ref left, - ref right, - .. - }) => { - let attr_name: syn::Ident = match left.as_ref() { - syn::Expr::Path(syn::ExprPath { - path: attr_path, .. - }) => attr_path.get_ident().cloned().ok_or_else(|| { - syn::Error::new(attr_path.span(), "expecting attribute name") - })?, - x => return Err(syn::Error::new(x.span(), "expecting attribute name")), - }; - let attr_value = match right.as_ref() { - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(string), - .. - }) => string.clone(), - x => return Err(syn::Error::new(x.span(), "expecting string literal")), - }; - (attr_name, Some(attr_value)) - } - syn::Expr::Path(syn::ExprPath { - path: attr_path, .. - }) => attr_path - .get_ident() - .cloned() - .map(|a| (a, None)) - .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, - x => return Err(syn::Error::new(x.span(), "expecting identifier")), - }; - attrs.insert(left, right); - } +impl ExportedParams for ExportedFnParams { + fn parse_stream(args: ParseStream) -> syn::Result { + Self::parse(args) + } + fn no_attrs() -> Self { + Default::default() + } + + fn from_info( + info: crate::attrs::ExportInfo, + ) -> syn::Result { + let ExportInfo { item_span: span, items: attrs } = info; let mut name = None; let mut return_raw = false; let mut skip = false; - for (ident, value) in attrs.drain() { - match (ident.to_string().as_ref(), value) { - ("name", Some(s)) => name = Some(s.value()), + for attr in attrs { + let crate::attrs::AttrItem { key, value } = attr; + match (key.to_string().as_ref(), value) { + ("name", Some(s)) => { + // check validity of name + if s.value().contains('.') { + return Err(syn::Error::new( + s.span(), + "Rhai function names may not contain dot", + )); + } + name = Some(s.value()) + } ("get", Some(s)) => name = Some(make_getter(&s.value())), ("set", Some(s)) => name = Some(make_setter(&s.value())), ("get", None) | ("set", None) | ("name", None) => { - return Err(syn::Error::new(ident.span(), "requires value")) + return Err(syn::Error::new(key.span(), "requires value")) } ("index_get", None) => name = Some(FN_IDX_GET.to_string()), ("index_set", None) => name = Some(FN_IDX_SET.to_string()), @@ -110,21 +88,13 @@ impl Parse for ExportedFnParams { ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), (attr, _) => { return Err(syn::Error::new( - ident.span(), + key.span(), format!("unknown attribute '{}'", attr), )) } } } - // Check validity of name, if present. - if name.as_ref().filter(|n| n.contains('.')).is_some() { - return Err(syn::Error::new( - span, - "Rhai function names may not contain dot" - )) - } - Ok(ExportedFnParams { name, return_raw, @@ -151,20 +121,10 @@ impl Parse for ExportedFn { let str_type_path = syn::parse2::(quote! { str }).unwrap(); // #[cfg] attributes are not allowed on functions due to what is generated for them - if let Some(cfg_attr) = fn_all.attrs.iter().find(|a| { - a.path - .get_ident() - .map(|i| i.to_string() == "cfg") - .unwrap_or(false) - }) { - return Err(syn::Error::new(cfg_attr.span(), "cfg attributes not allowed on this item")); - } + crate::attrs::deny_cfg_attr(&fn_all.attrs)?; // Determine if the function is public. - let is_public = match fn_all.vis { - syn::Visibility::Public(_) => true, - _ => false, - }; + let is_public = matches!(fn_all.vis, syn::Visibility::Public(_)); // Determine whether function generates a special calling convention for a mutable // reciever. let mut_receiver = { @@ -216,10 +176,7 @@ impl Parse for ExportedFn { mutability: None, ref elem, .. - }) => match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => true, - _ => false, - }, + }) => matches!(elem.as_ref(), &syn::Type::Path(ref p) if p.path == str_type_path), &syn::Type::Verbatim(_) => false, _ => true, }; @@ -293,17 +250,18 @@ impl ExportedFn { } } - pub fn set_params( - &mut self, mut params: ExportedFnParams, - ) -> syn::Result<()> { - + pub fn set_params(&mut self, mut params: ExportedFnParams) -> syn::Result<()> { // Do not allow non-returning raw functions. // // This is caught now to avoid issues with diagnostics later. - if params.return_raw && mem::discriminant(&self.signature.output) == - mem::discriminant(&syn::ReturnType::Default) { - return Err(syn::Error::new(self.signature.span(), - "return_raw functions must return Result")); + if params.return_raw + && mem::discriminant(&self.signature.output) + == mem::discriminant(&syn::ReturnType::Default) + { + return Err(syn::Error::new( + self.signature.span(), + "return_raw functions must return Result", + )); } self.params = params; @@ -618,7 +576,7 @@ mod function_tests { &syn::parse2::(quote! { x: usize }).unwrap() ); assert_eq!( - item_fn.arg_list().skip(1).next().unwrap(), + item_fn.arg_list().nth(1).unwrap(), &syn::parse2::(quote! { y: f32 }).unwrap() ); } @@ -739,7 +697,7 @@ mod function_tests { &syn::parse2::(quote! { level: usize }).unwrap() ); assert_eq!( - item_fn.arg_list().skip(1).next().unwrap(), + item_fn.arg_list().nth(1).unwrap(), &syn::parse2::(quote! { message: &str }).unwrap() ); } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index a9e43d93..7054cbe5 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -96,6 +96,7 @@ use quote::quote; use syn::parse_macro_input; +mod attrs; mod function; mod module; mod register; @@ -108,7 +109,10 @@ pub fn export_fn( ) -> proc_macro::TokenStream { let mut output = proc_macro2::TokenStream::from(input.clone()); - let parsed_params = parse_macro_input!(args as function::ExportedFnParams); + let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") { + Ok(args) => args, + Err(err) => return proc_macro::TokenStream::from(err.to_compile_error()), + }; let mut function_def = parse_macro_input!(input as function::ExportedFn); if let Err(e) = function_def.set_params(parsed_params) { return e.to_compile_error().into(); diff --git a/codegen/src/module.rs b/codegen/src/module.rs index d2e40d3a..535ea9d7 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -1,7 +1,7 @@ use quote::{quote, ToTokens}; -use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; +use syn::{parse::Parse, parse::ParseStream}; -use crate::function::{ExportedFn, ExportedFnParams}; +use crate::function::ExportedFn; use crate::rhai_module::ExportedConst; #[cfg(no_std)] @@ -15,92 +15,9 @@ use core::mem; use std::mem; use std::borrow::Cow; -use std::collections::HashMap; -fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { - // #[cfg] attributes are not allowed on objects - if let Some(cfg_attr) = f.attrs.iter().find(|a| { - a.path - .get_ident() - .map(|i| i.to_string() == "cfg") - .unwrap_or(false) - }) { - return Err(syn::Error::new(cfg_attr.span(), "cfg attributes not allowed on this item")); - } - - // Find the #[rhai_fn] attribute which will turn be read for the function parameters. - if let Some(rhai_fn_idx) = f.attrs.iter().position(|a| { - a.path - .get_ident() - .map(|i| i.to_string() == "rhai_fn") - .unwrap_or(false) - }) { - let rhai_fn_attr = f.attrs.remove(rhai_fn_idx); - rhai_fn_attr.parse_args() - } else if let syn::Visibility::Public(_) = f.vis { - Ok(ExportedFnParams::default()) - } else { - Ok(ExportedFnParams::skip()) - } -} - -fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { - let mut renames = HashMap::::new(); - let mut names = HashMap::::new(); - for itemfn in fns.iter() { - if let Some(ref name) = itemfn.params.name { - let current_span = itemfn.params.span.as_ref().unwrap(); - let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { - let type_string: String = match fnarg { - syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"), - syn::FnArg::Typed(syn::PatType { ref ty, .. }) => - ty.as_ref().to_token_stream().to_string(), - }; - argstr.push('.'); - argstr.extend(type_string.chars()); - argstr - }); - if let Some(other_span) = renames.insert(key, - current_span.clone()) { - let mut err = syn::Error::new(current_span.clone(), - format!("duplicate Rhai signature for '{}'", &name)); - err.combine(syn::Error::new(other_span, - format!("duplicated function renamed '{}'", &name))); - return Err(err); - } - } else { - let ident = itemfn.name(); - names.insert(ident.to_string(), ident.span()); - } - } - for (new_name, attr_span) in renames.drain() { - let new_name = new_name.split('.').next().unwrap(); - if let Some(fn_span) = names.get(new_name) { - let mut err = syn::Error::new(attr_span, - format!("duplicate Rhai signature for '{}'", &new_name)); - err.combine(syn::Error::new(fn_span.clone(), - format!("duplicated function '{}'", &new_name))); - return Err(err); - } - } - Ok(()) -} - -fn inner_mod_attributes(f: &mut syn::ItemMod) -> syn::Result { - if let Some(rhai_mod_idx) = f.attrs.iter().position(|a| { - a.path - .get_ident() - .map(|i| i.to_string() == "rhai_mod") - .unwrap_or(false) - }) { - let rhai_mod_attr = f.attrs.remove(rhai_mod_idx); - rhai_mod_attr.parse_args() - } else if let syn::Visibility::Public(_) = f.vis { - Ok(ExportedModParams::default()) - } else { - Ok(ExportedModParams::skip()) - } -} +use crate::attrs::{AttrItem, ExportInfo, ExportedParams}; +use crate::function::{ExportedFnParams}; #[derive(Debug, Default)] pub(crate) struct ExportedModParams { @@ -108,81 +25,52 @@ pub(crate) struct ExportedModParams { pub skip: bool, } -impl ExportedModParams { - pub fn skip() -> ExportedModParams { - let mut skip = ExportedModParams::default(); - skip.skip = true; - skip - } -} - impl Parse for ExportedModParams { fn parse(args: ParseStream) -> syn::Result { if args.is_empty() { return Ok(ExportedModParams::default()); } - let arg_list = args.call( - syn::punctuated::Punctuated::::parse_separated_nonempty, - )?; + let info = crate::attrs::parse_attr_items(args)?; - let mut attrs: HashMap> = HashMap::new(); - for arg in arg_list { - let (left, right) = match arg { - syn::Expr::Assign(syn::ExprAssign { - ref left, - ref right, - .. - }) => { - let attr_name: syn::Ident = match left.as_ref() { - syn::Expr::Path(syn::ExprPath { - path: attr_path, .. - }) => attr_path.get_ident().cloned().ok_or_else(|| { - syn::Error::new(attr_path.span(), "expecting attribute name") - })?, - x => return Err(syn::Error::new(x.span(), "expecting attribute name")), - }; - let attr_value = match right.as_ref() { - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(string), - .. - }) => string.clone(), - x => return Err(syn::Error::new(x.span(), "expecting string literal")), - }; - (attr_name, Some(attr_value)) - } - syn::Expr::Path(syn::ExprPath { - path: attr_path, .. - }) => attr_path - .get_ident() - .cloned() - .map(|a| (a, None)) - .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, - x => return Err(syn::Error::new(x.span(), "expecting identifier")), - }; - attrs.insert(left, right); - } + Self::from_info(info) + } +} +impl ExportedParams for ExportedModParams { + fn parse_stream(args: ParseStream) -> syn::Result { + Self::parse(args) + } + + fn no_attrs() -> Self { + Default::default() + } + + fn from_info(info: ExportInfo) -> syn::Result { + let ExportInfo { items: attrs, .. } = info; let mut name = None; let mut skip = false; - for (ident, value) in attrs.drain() { - match (ident.to_string().as_ref(), value) { + for attr in attrs { + let AttrItem { key, value } = attr; + match (key.to_string().as_ref(), value) { ("name", Some(s)) => name = Some(s.value()), - ("name", None) => return Err(syn::Error::new(ident.span(), "requires value")), + ("name", None) => return Err(syn::Error::new(key.span(), "requires value")), ("skip", None) => skip = true, - ("skip", Some(s)) => { - return Err(syn::Error::new(s.span(), "extraneous value")) - } + ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), (attr, _) => { return Err(syn::Error::new( - ident.span(), + key.span(), format!("unknown attribute '{}'", attr), )) } } } - Ok(ExportedModParams { name, skip, ..Default::default() }) + Ok(ExportedModParams { + name, + skip, + ..Default::default() + }) } } @@ -209,17 +97,26 @@ impl Parse for Module { syn::Item::Fn(f) => Some(f), _ => None, }) - .try_fold(Vec::new(), |mut vec, mut itemfn| { - let params = match inner_fn_attributes(&mut itemfn) { - Ok(p) => p, - Err(e) => return Err(e), + .try_fold(Vec::new(), |mut vec, itemfn| { + // #[cfg] attributes are not allowed on functions + crate::attrs::deny_cfg_attr(&itemfn.attrs)?; + + let mut params: ExportedFnParams = + match crate::attrs::inner_item_attributes(&mut itemfn.attrs, "rhai_fn") { + Ok(p) => p, + Err(e) => return Err(e), + }; + params.skip = if let syn::Visibility::Public(_) = itemfn.vis { + params.skip + } else { + true }; syn::parse2::(itemfn.to_token_stream()) .map(|mut f| { f.params = params; f }) - .map(|f| if !f.params.skip { vec.push(f) }) + .map(|f| vec.push(f)) .map(|_| vec) })?; // Gather and parse constants definitions. @@ -233,23 +130,14 @@ impl Parse for Module { .. }) => { // #[cfg] attributes are not allowed on const declarations - if let Some(cfg_attr) = attrs.iter().find(|a| { - a.path - .get_ident() - .map(|i| i.to_string() == "cfg") - .unwrap_or(false) - }) { - return Err(syn::Error::new( - cfg_attr.span(), - "cfg attributes not allowed on this item")); - } + crate::attrs::deny_cfg_attr(&attrs)?; if let syn::Visibility::Public(_) = vis { consts.push((ident.to_string(), expr.as_ref().clone())); } - }, - _ => {}, + } + _ => {} } - }; + } // Gather and parse submodule definitions. // // They are actually removed from the module's body, because they will need @@ -257,23 +145,27 @@ impl Parse for Module { submodules.reserve(content.len() - fns.len() - consts.len()); let mut i = 0; while i < content.len() { - if let syn::Item::Mod(_) = &content[i] { + if let syn::Item::Mod(_) = &content[i] { let mut itemmod = match content.remove(i) { syn::Item::Mod(m) => m, _ => unreachable!(), }; - let params = match inner_mod_attributes(&mut itemmod) { - Ok(p) => p, - Err(e) => return Err(e), + let mut params: ExportedModParams = + match crate::attrs::inner_item_attributes(&mut itemmod.attrs, "rhai_mod") { + Ok(p) => p, + Err(e) => return Err(e), + }; + params.skip = if let syn::Visibility::Public(_) = itemmod.vis { + params.skip + } else { + true }; - let module = syn::parse2::(itemmod.to_token_stream()) - .map(|mut f| { + let module = + syn::parse2::(itemmod.to_token_stream()).map(|mut f| { f.params = params; f })?; - if !module.params.skip { - submodules.push(module); - } + submodules.push(module); } else { i += 1; } @@ -308,6 +200,10 @@ impl Module { } } + pub fn skipped(&self) -> bool { + self.params.skip + } + pub fn generate(self) -> proc_macro2::TokenStream { match self.generate_inner() { Ok(tokens) => tokens, @@ -315,39 +211,55 @@ impl Module { } } - fn generate_inner(mut self) -> Result { + fn generate_inner(self) -> Result { // Check for collisions if the "name" attribute was used on inner functions. - check_rename_collisions(&self.fns)?; + crate::rhai_module::check_rename_collisions(&self.fns)?; - // Generate new module items. - // - // This is done before inner module recursive generation, because that is destructive. - let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts, &self.submodules); - - // NB: submodules must have their new items for exporting generated in depth-first order to - // avoid issues with reparsing them. - let inner_modules: Vec = self.submodules.drain(..) - .try_fold::, _, - Result, syn::Error>>( - Vec::new(), |mut acc, m| { acc.push(m.generate_inner()?); Ok(acc) })?; - - // Generate new module items for exporting functions and constant. - - // Rebuild the structure of the module, with the new content added. - let Module { mod_all, .. } = self; + // Extract the current structure of the module. + let Module { + mod_all, + fns, + consts, + mut submodules, + params, + .. + } = self; let mut mod_all = mod_all.unwrap(); let mod_name = mod_all.ident.clone(); let (_, orig_content) = mod_all.content.take().unwrap(); let mod_attrs = mem::replace(&mut mod_all.attrs, Vec::with_capacity(0)); - Ok(quote! { - #(#mod_attrs)* - pub mod #mod_name { - #(#orig_content)* - #(#inner_modules)* - #mod_gen - } - }) + if !params.skip { + // Generate new module items. + // + // This is done before inner module recursive generation, because that is destructive. + let mod_gen = crate::rhai_module::generate_body(&fns, &consts, &submodules); + + // NB: submodules must have their new items for exporting generated in depth-first order + // to avoid issues caused by re-parsing them + let inner_modules: Vec = submodules.drain(..) + .try_fold::, _, + Result, syn::Error>>( + Vec::new(), |mut acc, m| { acc.push(m.generate_inner()?); Ok(acc) })?; + + // Regenerate the module with the new content added. + Ok(quote! { + #(#mod_attrs)* + pub mod #mod_name { + #(#orig_content)* + #(#inner_modules)* + #mod_gen + } + }) + } else { + // Regenerate the original module as-is. + Ok(quote! { + #(#mod_attrs)* + pub mod #mod_name { + #(#orig_content)* + } + }) + } } pub fn name(&self) -> Option<&syn::Ident> { @@ -356,7 +268,10 @@ impl Module { pub fn content(&self) -> Option<&Vec> { match self.mod_all { - Some(syn::ItemMod { content: Some((_, ref vec)), .. }) => Some(vec), + Some(syn::ItemMod { + content: Some((_, ref vec)), + .. + }) => Some(vec), _ => None, } } @@ -495,7 +410,8 @@ mod module_tests { assert!(item_mod.fns.is_empty()); assert!(item_mod.consts.is_empty()); assert_eq!(item_mod.submodules.len(), 1); - assert!(item_mod.submodules[0].fns.is_empty()); + assert_eq!(item_mod.submodules[0].fns.len(), 1); + assert!(item_mod.submodules[0].fns[0].params.skip); assert!(item_mod.submodules[0].consts.is_empty()); assert!(item_mod.submodules[0].submodules.is_empty()); } @@ -516,7 +432,8 @@ mod module_tests { let item_mod = syn::parse2::(input_tokens).unwrap(); assert!(item_mod.fns.is_empty()); assert!(item_mod.consts.is_empty()); - assert!(item_mod.submodules.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert!(item_mod.submodules[0].params.skip); } #[test] @@ -548,7 +465,8 @@ mod module_tests { }; let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert!(item_mod.fns[0].params.skip); assert!(item_mod.consts.is_empty()); } @@ -564,7 +482,8 @@ mod module_tests { }; let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert!(item_mod.fns[0].params.skip); assert!(item_mod.consts.is_empty()); } @@ -599,7 +518,7 @@ mod generate_tests { .zip(expected.chars()) .inspect(|_| counter += 1) .skip_while(|(a, e)| *a == *e); - let (actual_diff, expected_diff) = { + let (_actual_diff, _expected_diff) = { let mut actual_diff = String::new(); let mut expected_diff = String::new(); for (a, e) in iter.take(50) { diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index c327c71f..74144081 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -1,4 +1,6 @@ -use quote::quote; +use std::collections::HashMap; + +use quote::{quote, ToTokens}; use crate::function::ExportedFn; use crate::module::Module; @@ -6,9 +8,9 @@ use crate::module::Module; pub(crate) type ExportedConst = (String, syn::Expr); pub(crate) fn generate_body( - fns: &Vec, - consts: &Vec, - submodules: &Vec, + fns: &[ExportedFn], + consts: &[ExportedConst], + submodules: &[Module], ) -> proc_macro2::TokenStream { let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); @@ -26,17 +28,21 @@ pub(crate) fn generate_body( } for itemmod in submodules { + if itemmod.skipped() { + continue; + } let module_name: &syn::Ident = itemmod.module_name().unwrap(); let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { syn::LitStr::new(&name, proc_macro2::Span::call_site()) } else { syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) }; - let cfg_attrs: Vec<&syn::Attribute> = itemmod.attrs().unwrap().iter().filter(|&a| { - a.path.get_ident() - .map(|i| i.to_string() == "cfg") - .unwrap_or(false) - }).collect(); + let cfg_attrs: Vec<&syn::Attribute> = itemmod + .attrs() + .unwrap() + .iter() + .filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) + .collect(); add_mod_blocks.push( syn::parse2::(quote! { #(#cfg_attrs)* { @@ -47,10 +53,12 @@ pub(crate) fn generate_body( ); } - // NB: these are token streams, because reparsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { + if function.params.skip { + continue; + } let fn_token_name = syn::Ident::new( &format!("{}_token", function.name().to_string()), function.name().span(), @@ -67,24 +75,24 @@ pub(crate) fn generate_body( syn::FnArg::Receiver(_) => panic!("internal error: receiver fn outside impl!?"), syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { let arg_type = match ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. }) => match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => { + syn::Type::Path(ref p) if p.path == str_type_path => { syn::parse2::(quote! { ImmutableString }) .unwrap() } _ => panic!("internal error: non-string shared reference!?"), }, - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: Some(_), ref elem, .. }) => match elem.as_ref() { - &syn::Type::Path(ref p) => syn::parse2::(quote! { + syn::Type::Path(ref p) => syn::parse2::(quote! { #p }) .unwrap(), _ => panic!("internal error: non-string shared reference!?"), @@ -138,3 +146,53 @@ pub(crate) fn generate_body( #(#gen_fn_tokens)* } } + +pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { + let mut renames = HashMap::::new(); + let mut names = HashMap::::new(); + for itemfn in fns.iter() { + if let Some(ref name) = itemfn.params.name { + let current_span = itemfn.params.span.as_ref().unwrap(); + let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { + let type_string: String = match fnarg { + syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"), + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + ty.as_ref().to_token_stream().to_string() + } + }; + argstr.push('.'); + argstr.push_str(&type_string); + argstr + }); + if let Some(other_span) = renames.insert(key, *current_span) { + let mut err = syn::Error::new( + *current_span, + format!("duplicate Rhai signature for '{}'", &name), + ); + err.combine(syn::Error::new( + other_span, + format!("duplicated function renamed '{}'", &name), + )); + return Err(err); + } + } else { + let ident = itemfn.name(); + names.insert(ident.to_string(), ident.span()); + } + } + for (new_name, attr_span) in renames.drain() { + let new_name = new_name.split('.').next().unwrap(); + if let Some(fn_span) = names.get(new_name) { + let mut err = syn::Error::new( + attr_span, + format!("duplicate Rhai signature for '{}'", &new_name), + ); + err.combine(syn::Error::new( + *fn_span, + format!("duplicated function '{}'", &new_name), + )); + return Err(err); + } + } + Ok(()) +} diff --git a/codegen/ui_tests/rhai_fn_rename_dot.stderr b/codegen/ui_tests/rhai_fn_rename_dot.stderr index f650a081..61299e8b 100644 --- a/codegen/ui_tests/rhai_fn_rename_dot.stderr +++ b/codegen/ui_tests/rhai_fn_rename_dot.stderr @@ -1,8 +1,8 @@ error: Rhai function names may not contain dot - --> $DIR/rhai_fn_rename_dot.rs:12:15 + --> $DIR/rhai_fn_rename_dot.rs:12:22 | 12 | #[rhai_fn(name = "foo.bar")] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^ error[E0433]: failed to resolve: use of undeclared type or module `test_module` --> $DIR/rhai_fn_rename_dot.rs:23:8 From 2fbc1b791043b63b72b79049b2b7e9a3d3a32972 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 24 Aug 2020 10:38:15 +0800 Subject: [PATCH 085/103] Add missing pub to functions. --- src/packages/arithmetic.rs | 6 +++--- src/packages/fn_basic.rs | 4 ++-- src/packages/time_basic.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 10d33f8e..ae203782 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -197,17 +197,17 @@ macro_rules! gen_arithmetic_functions { } #[rhai_fn(name = "&")] #[inline(always)] - fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { + pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { x & y } #[rhai_fn(name = "|")] #[inline(always)] - fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { + pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { x | y } #[rhai_fn(name = "^")] #[inline(always)] - fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { + pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 274b85e1..bb3835d7 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -9,13 +9,13 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { #[export_module] mod fn_ptr_functions { #[inline(always)] - fn name(f: &mut FnPtr) -> ImmutableString { + pub fn name(f: &mut FnPtr) -> ImmutableString { f.get_fn_name().clone() } #[rhai_fn(get = "name")] #[inline(always)] - fn name_prop(f: &mut FnPtr) -> ImmutableString { + pub fn name_prop(f: &mut FnPtr) -> ImmutableString { name(f) } } diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index d1401288..5b8e1f21 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -66,7 +66,7 @@ mod time_functions { } #[rhai_fn(return_raw, name = "-")] - fn time_diff(ts1: Instant, ts2: Instant) -> Result> { + pub fn time_diff(ts1: Instant, ts2: Instant) -> Result> { #[cfg(not(feature = "no_float"))] { Ok(if ts2 > ts1 { From 9ab3d87cfc71cd88c92418ebbc90911a194ec86b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 24 Aug 2020 22:37:44 +0800 Subject: [PATCH 086/103] Refactor. --- src/packages/arithmetic.rs | 83 ++++++++++++++++++++++-------------- src/packages/array_basic.rs | 42 ++++++++---------- src/packages/logic.rs | 6 +-- src/packages/math_basic.rs | 6 +-- src/packages/string_basic.rs | 14 +++--- src/packages/string_more.rs | 8 ++-- 6 files changed, 85 insertions(+), 74 deletions(-) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index ae203782..b9553cd8 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -265,9 +265,9 @@ macro_rules! gen_signed_functions { } macro_rules! reg_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { - $($mod_name.combine_flatten(exported_module!($root::$arg_type::functions));)* - } + ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $( + $mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); + )* } } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { @@ -288,7 +288,10 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { // Basic arithmetic for floating-point #[cfg(not(feature = "no_float"))] - lib.combine_flatten(exported_module!(float_functions)); + { + lib.combine_flatten(exported_module!(f32_functions)); + lib.combine_flatten(exported_module!(f64_functions)); + } }); gen_arithmetic_functions!(arith_basic => INT); @@ -315,7 +318,7 @@ gen_signed_functions!(signed_num_128 => i128); #[cfg(not(feature = "no_float"))] #[export_module] -mod float_functions { +mod f32_functions { #[rhai_fn(name = "+")] #[inline(always)] pub fn add(x: f32, y: f32) -> f32 { @@ -343,38 +346,15 @@ mod float_functions { } #[rhai_fn(name = "-")] #[inline(always)] - pub fn neg_f32(x: f32) -> f32 { + pub fn neg(x: f32) -> f32 { -x } - #[rhai_fn(name = "-")] #[inline(always)] - pub fn neg_f64(x: f64) -> f64 { - -x - } - #[rhai_fn(name = "abs")] - #[inline(always)] - pub fn abs_f32(x: f32) -> f32 { + pub fn abs(x: f32) -> f32 { x.abs() } - #[rhai_fn(name = "abs")] - #[inline(always)] - pub fn abs_f64(x: f64) -> f64 { - x.abs() - } - #[rhai_fn(name = "sign")] #[inline] - pub fn sign_f32(x: f32) -> INT { - if x == 0.0 { - 0 - } else if x < 0.0 { - -1 - } else { - 1 - } - } - #[rhai_fn(name = "sign")] - #[inline] - pub fn sign_f64(x: f64) -> INT { + pub fn sign(x: f32) -> INT { if x == 0.0 { 0 } else if x < 0.0 { @@ -385,8 +365,45 @@ mod float_functions { } #[rhai_fn(name = "~", return_raw)] #[inline(always)] - pub fn pow_f_f(x: FLOAT, y: FLOAT) -> Result> { - Ok(x.powf(y).into()) + pub fn pow_f_f(x: f32, y: f32) -> Result> { + Ok(Dynamic::from(x.powf(y))) + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn pow_f_i(x: f32, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Number raised to too large an index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else { + Ok(Dynamic::from(x.powi(y as i32))) + } + } +} + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod f64_functions { + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn neg(x: f64) -> f64 { + -x + } + #[inline(always)] + pub fn abs(x: f64) -> f64 { + x.abs() + } + #[inline] + pub fn sign(x: f64) -> INT { + if x == 0.0 { + 0 + } else if x < 0.0 { + -1 + } else { + 1 + } } #[rhai_fn(name = "~", return_raw)] #[inline] diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 5b117731..a69372ae 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -11,6 +11,9 @@ use crate::plugin::*; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{any::TypeId, boxed::Box}; #[cfg(not(feature = "unchecked"))] @@ -41,47 +44,35 @@ macro_rules! gen_array_functions { } macro_rules! reg_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { - $(set_exported_fn!($mod_name, "push", $root::$arg_type::push_func);)* - $(set_exported_fn!($mod_name, "insert", $root::$arg_type::insert_func);)* - } -} + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, "push", $root::$arg_type::push_func); + set_exported_fn!($mod_name, "insert", $root::$arg_type::insert_func); -macro_rules! reg_pad { - ($lib:expr, $($par:ty),*) => { - $({ - $lib.set_raw_fn("pad", - &[TypeId::of::(), TypeId::of::(), TypeId::of::<$par>()], - pad::<$par> - ); - })* - }; + $mod_name.set_raw_fn("pad", + &[TypeId::of::(), TypeId::of::(), TypeId::of::<$arg_type>()], + pad::<$arg_type>); + )* } } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { lib.combine_flatten(exported_module!(array_functions)); reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); - reg_pad!(lib, INT, bool, char, ImmutableString, FnPtr, Array, Unit); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { reg_functions!(lib += numbers; i8, u8, i16, u16, i32, i64, u32, u64); - reg_pad!(lib, u8, i16, u16, i32, u32, i64, u64); #[cfg(not(target_arch = "wasm32"))] - { - reg_functions!(lib += num_128; i128, u128); - reg_pad!(lib, i128, u128); - } + reg_functions!(lib += num_128; i128, u128); } #[cfg(not(feature = "no_float"))] - { - reg_functions!(lib += float; f32, f64); - reg_pad!(lib, f32, f64); - } + reg_functions!(lib += float; f32, f64); + + #[cfg(not(feature = "no_object"))] + reg_functions!(lib += map; Map); // Register array iterator lib.set_iter( @@ -207,3 +198,6 @@ gen_array_functions!(num_128 => i128, u128); #[cfg(not(feature = "no_float"))] gen_array_functions!(float => f32, f64); + +#[cfg(not(feature = "no_object"))] +gen_array_functions!(map => Map); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 0c994dca..e05d8515 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -46,9 +46,9 @@ macro_rules! gen_cmp_functions { } macro_rules! reg_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { - $($mod_name.combine_flatten(exported_module!($root::$arg_type::functions));)* - } + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + $mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); + )* } } def_package!(crate:LogicPackage:"Logical operators.", lib, { diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 3f557124..f1de6c4f 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -41,9 +41,9 @@ macro_rules! gen_conversion_functions { } macro_rules! reg_functions { - ($mod_name:ident += $root:ident :: $func_name:ident ( $($arg_type:ident),+ ) ) => { - $(set_exported_fn!($mod_name, stringify!($func_name), $root::$arg_type::$func_name);)* - } + ($mod_name:ident += $root:ident :: $func_name:ident ( $($arg_type:ident),+ ) ) => { $( + set_exported_fn!($mod_name, stringify!($func_name), $root::$arg_type::$func_name); + )* } } def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 827ba183..ae5a84e0 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -37,16 +37,16 @@ macro_rules! gen_functions { } macro_rules! reg_print_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { - $(set_exported_fn!($mod_name, FN_TO_STRING, $root::$arg_type::to_string_func);)* - $(set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func);)* - } + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, FN_TO_STRING, $root::$arg_type::to_string_func); + set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func); + )* } } macro_rules! reg_debug_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { - $(set_exported_fn!($mod_name, KEYWORD_DEBUG, $root::$arg_type::to_string_func);)* - } + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, KEYWORD_DEBUG, $root::$arg_type::to_string_func); + )* } } def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 84126d86..883d4fec 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -38,10 +38,10 @@ macro_rules! gen_concat_functions { } macro_rules! reg_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { - $(set_exported_fn!($mod_name, "+", $root::$arg_type::append_func);)* - $(set_exported_fn!($mod_name, "+", $root::$arg_type::prepend_func);)* - } + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, "+", $root::$arg_type::append_func); + set_exported_fn!($mod_name, "+", $root::$arg_type::prepend_func); + )* } } def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { From 525ffe6f5a8db5990d1c11be33bd09614ede8a41 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 27 Aug 2020 22:26:05 -0500 Subject: [PATCH 087/103] Improve diagnostics for duplicated names --- codegen/src/rhai_module.rs | 12 ++++++- codegen/ui_tests/rhai_mod_name_collisions.rs | 31 ++++++++++++++++ .../ui_tests/rhai_mod_name_collisions.stderr | 17 +++++++++ codegen/ui_tests/rhai_mod_unknown_type.rs | 27 ++++++++++++++ codegen/ui_tests/rhai_mod_unknown_type.stderr | 23 ++++++++++++ .../ui_tests/rhai_mod_unknown_type_return.rs | 27 ++++++++++++++ .../rhai_mod_unknown_type_return.stderr | 5 +++ diag_test/Cargo.toml | 13 +++++++ diag_test/test_template.rs | 35 +++++++++++++++++++ 9 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 codegen/ui_tests/rhai_mod_name_collisions.rs create mode 100644 codegen/ui_tests/rhai_mod_name_collisions.stderr create mode 100644 codegen/ui_tests/rhai_mod_unknown_type.rs create mode 100644 codegen/ui_tests/rhai_mod_unknown_type.stderr create mode 100644 codegen/ui_tests/rhai_mod_unknown_type_return.rs create mode 100644 codegen/ui_tests/rhai_mod_unknown_type_return.stderr create mode 100644 diag_test/Cargo.toml create mode 100644 diag_test/test_template.rs diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 74144081..95f17ffc 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -177,7 +177,17 @@ pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn:: } } else { let ident = itemfn.name(); - names.insert(ident.to_string(), ident.span()); + if let Some(other_span) = names.insert(ident.to_string(), ident.span()) { + let mut err = syn::Error::new( + ident.span(), + format!("duplicate function '{}'", ident.to_string()), + ); + err.combine(syn::Error::new( + other_span, + format!("duplicated function '{}'", ident.to_string()), + )); + return Err(err); + } } } for (new_name, attr_span) in renames.drain() { diff --git a/codegen/ui_tests/rhai_mod_name_collisions.rs b/codegen/ui_tests/rhai_mod_name_collisions.rs new file mode 100644 index 00000000..c7709555 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_name_collisions.rs @@ -0,0 +1,31 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + pub fn test_fn(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_name_collisions.stderr b/codegen/ui_tests/rhai_mod_name_collisions.stderr new file mode 100644 index 00000000..539bd3eb --- /dev/null +++ b/codegen/ui_tests/rhai_mod_name_collisions.stderr @@ -0,0 +1,17 @@ +error: duplicate function 'test_fn' + --> $DIR/rhai_mod_name_collisions.rs:16:12 + | +16 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^ + +error: duplicated function 'test_fn' + --> $DIR/rhai_mod_name_collisions.rs:12:12 + | +12 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_name_collisions.rs:26:8 + | +26 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_unknown_type.rs b/codegen/ui_tests/rhai_mod_unknown_type.rs new file mode 100644 index 00000000..7c19ab18 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + pub fn test_fn(input: Pointer) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_unknown_type.stderr b/codegen/ui_tests/rhai_mod_unknown_type.stderr new file mode 100644 index 00000000..392f90a9 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type.stderr @@ -0,0 +1,23 @@ +error[E0412]: cannot find type `Pointer` in this scope + --> $DIR/rhai_mod_unknown_type.rs:12:27 + | +4 | pub struct Point { + | ---------------- similarly named struct `Point` defined here +... +12 | pub fn test_fn(input: Pointer) -> bool { + | ^^^^^^^ + | +help: a struct with a similar name exists + | +12 | pub fn test_fn(input: Point) -> bool { + | ^^^^^ +help: consider importing one of these items + | +11 | use core::fmt::Pointer; + | +11 | use crate::mem::fmt::Pointer; + | +11 | use std::fmt::Pointer; + | +11 | use syn::export::fmt::Pointer; + | diff --git a/codegen/ui_tests/rhai_mod_unknown_type_return.rs b/codegen/ui_tests/rhai_mod_unknown_type_return.rs new file mode 100644 index 00000000..c2287eaa --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type_return.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + pub fn test_fn(input: Point) -> boool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_unknown_type_return.stderr b/codegen/ui_tests/rhai_mod_unknown_type_return.stderr new file mode 100644 index 00000000..43f2896c --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type_return.stderr @@ -0,0 +1,5 @@ +error[E0412]: cannot find type `boool` in this scope + --> $DIR/rhai_mod_unknown_type_return.rs:12:37 + | +12 | pub fn test_fn(input: Point) -> boool { + | ^^^^^ help: a builtin type with a similar name exists: `bool` diff --git a/diag_test/Cargo.toml b/diag_test/Cargo.toml new file mode 100644 index 00000000..17998ecf --- /dev/null +++ b/diag_test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "diag_test" +version = "0.1.0" +authors = ["J Henry Waugh "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "test_template" +path = "test_template.rs" + +[dependencies] +rhai = { version = "*", path = ".." } diff --git a/diag_test/test_template.rs b/diag_test/test_template.rs new file mode 100644 index 00000000..98088637 --- /dev/null +++ b/diag_test/test_template.rs @@ -0,0 +1,35 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + #[rhai_mod(name = "bar")] + pub mod test_mod { + #[rhai_fn(name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + #[rhai_fn(return_raw)] + pub fn test_fn_raw(input: Point) -> Result> { + Ok(Dynamic::from(input.x > input.y)) + } + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} From 8de095fa361e61b8087128b1e89f971b3a1eb986 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 27 Aug 2020 22:49:56 -0500 Subject: [PATCH 088/103] Improve diagnostics for non-clonable return types --- codegen/src/function.rs | 12 +++++--- .../ui_tests/rhai_fn_non_clonable_return.rs | 27 +++++++++++++++++ .../rhai_fn_non_clonable_return.stderr | 10 +++++++ .../ui_tests/rhai_mod_non_clonable_return.rs | 29 +++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 codegen/ui_tests/rhai_fn_non_clonable_return.rs create mode 100644 codegen/ui_tests/rhai_fn_non_clonable_return.stderr create mode 100644 codegen/ui_tests/rhai_mod_non_clonable_return.rs diff --git a/codegen/src/function.rs b/codegen/src/function.rs index b02a657d..a8862d20 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -314,15 +314,17 @@ impl ExportedFn { }) .collect(); + let return_span = self.return_type().map(|r| r.span()) + .unwrap_or_else(|| proc_macro2::Span::call_site()); if !self.params.return_raw { - quote! { + quote_spanned! { return_span=> type EvalBox = Box; pub #dynamic_signature { Ok(Dynamic::from(super::#name(#(#arguments),*))) } } } else { - quote_spanned! { self.return_type().unwrap().span()=> + quote_spanned! { return_span=> type EvalBox = Box; pub #dynamic_signature { super::#name(#(#arguments),*) @@ -484,12 +486,14 @@ impl ExportedFn { // Handle "raw returns", aka cases where the result is a dynamic or an error. // // This allows skipping the Dynamic::from wrap. + let return_span = self.return_type().map(|r| r.span()) + .unwrap_or_else(|| proc_macro2::Span::call_site()); let return_expr = if !self.params.return_raw { - quote! { + quote_spanned! { return_span=> Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) } } else { - quote_spanned! { self.return_type().unwrap().span()=> + quote_spanned! { return_span=> #sig_name(#(#unpack_exprs),*) } }; diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.rs b/codegen/ui_tests/rhai_fn_non_clonable_return.rs new file mode 100644 index 00000000..e2e2d788 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: f32) -> NonClonable { + NonClonable { + a: input, + b: 10, + c: 'a', + d: true, + } +} + +fn main() { + let n = test_fn(20.0); + if n.c == 'a' { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr new file mode 100644 index 00000000..6184fd24 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied + --> $DIR/rhai_fn_non_clonable_return.rs:11:8 + | +11 | pub fn test_fn(input: f32) -> NonClonable { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` + | + ::: /home/cryptkeeper/Desktop/Software/ytdl-replacement/rhai/src/any.rs:537:30 + | +537 | pub fn from(value: T) -> Self { + | ----- required by this bound in `rhai::Dynamic::from` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.rs b/codegen/ui_tests/rhai_mod_non_clonable_return.rs new file mode 100644 index 00000000..fe8f5fff --- /dev/null +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_module] +pub mod test_mod { + pub fn test_fn(input: f32) -> NonClonable { + NonClonable { + a: input, + b: 10, + c: 'a', + d: true, + } + } +} + +fn main() { + let n = test_mod::test_fn(20.0); + if n.c == 'a' { + println!("yes"); + } else { + println!("no"); + } +} From fedc4c53385b054968275b9bb18bac2ab395b645 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 27 Aug 2020 22:59:25 -0500 Subject: [PATCH 089/103] Add exported_fn tests for rhai_fn in module --- codegen/ui_tests/export_mod_bad_attr.rs | 27 ++++++++++++++++++ codegen/ui_tests/export_mod_bad_attr.stderr | 11 ++++++++ codegen/ui_tests/export_mod_bad_value.rs | 27 ++++++++++++++++++ codegen/ui_tests/export_mod_bad_value.stderr | 11 ++++++++ codegen/ui_tests/export_mod_cfg.rs | 28 +++++++++++++++++++ codegen/ui_tests/export_mod_cfg.stderr | 11 ++++++++ codegen/ui_tests/export_mod_extra_value.rs | 27 ++++++++++++++++++ .../ui_tests/export_mod_extra_value.stderr | 11 ++++++++ codegen/ui_tests/export_mod_junk_arg.rs | 27 ++++++++++++++++++ codegen/ui_tests/export_mod_junk_arg.stderr | 11 ++++++++ codegen/ui_tests/export_mod_missing_value.rs | 27 ++++++++++++++++++ .../ui_tests/export_mod_missing_value.stderr | 11 ++++++++ codegen/ui_tests/export_mod_path_attr.rs | 27 ++++++++++++++++++ codegen/ui_tests/export_mod_path_attr.stderr | 11 ++++++++ .../rhai_mod_non_clonable_return.stderr | 10 +++++++ 15 files changed, 277 insertions(+) create mode 100644 codegen/ui_tests/export_mod_bad_attr.rs create mode 100644 codegen/ui_tests/export_mod_bad_attr.stderr create mode 100644 codegen/ui_tests/export_mod_bad_value.rs create mode 100644 codegen/ui_tests/export_mod_bad_value.stderr create mode 100644 codegen/ui_tests/export_mod_cfg.rs create mode 100644 codegen/ui_tests/export_mod_cfg.stderr create mode 100644 codegen/ui_tests/export_mod_extra_value.rs create mode 100644 codegen/ui_tests/export_mod_extra_value.stderr create mode 100644 codegen/ui_tests/export_mod_junk_arg.rs create mode 100644 codegen/ui_tests/export_mod_junk_arg.stderr create mode 100644 codegen/ui_tests/export_mod_missing_value.rs create mode 100644 codegen/ui_tests/export_mod_missing_value.stderr create mode 100644 codegen/ui_tests/export_mod_path_attr.rs create mode 100644 codegen/ui_tests/export_mod_path_attr.stderr create mode 100644 codegen/ui_tests/rhai_mod_non_clonable_return.stderr diff --git a/codegen/ui_tests/export_mod_bad_attr.rs b/codegen/ui_tests/export_mod_bad_attr.rs new file mode 100644 index 00000000..8f8f7c2b --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(unknown = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_bad_attr.stderr b/codegen/ui_tests/export_mod_bad_attr.stderr new file mode 100644 index 00000000..9704d74d --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/export_mod_bad_attr.rs:11:11 + | +11 | #[rhai_fn(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_bad_attr.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_bad_value.rs b/codegen/ui_tests/export_mod_bad_value.rs new file mode 100644 index 00000000..c513dd35 --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(name = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_bad_value.stderr b/codegen/ui_tests/export_mod_bad_value.stderr new file mode 100644 index 00000000..63b22e4e --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/export_mod_bad_value.rs:11:18 + | +11 | #[rhai_fn(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_bad_value.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_cfg.rs b/codegen/ui_tests/export_mod_cfg.rs new file mode 100644 index 00000000..49838a73 --- /dev/null +++ b/codegen/ui_tests/export_mod_cfg.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[cfg(not(feature = "foo"))] +#[rhai_fn] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_cfg.stderr b/codegen/ui_tests/export_mod_cfg.stderr new file mode 100644 index 00000000..b932ec86 --- /dev/null +++ b/codegen/ui_tests/export_mod_cfg.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/export_mod_cfg.rs:11:1 + | +11 | #[cfg(not(feature = "foo"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_cfg.rs:23:8 + | +23 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_extra_value.rs b/codegen/ui_tests/export_mod_extra_value.rs new file mode 100644 index 00000000..6636f108 --- /dev/null +++ b/codegen/ui_tests/export_mod_extra_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_extra_value.stderr b/codegen/ui_tests/export_mod_extra_value.stderr new file mode 100644 index 00000000..0e3c65d6 --- /dev/null +++ b/codegen/ui_tests/export_mod_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/export_mod_extra_value.rs:11:24 + | +11 | #[rhai_fn(return_raw = "yes")] + | ^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_extra_value.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_junk_arg.rs b/codegen/ui_tests/export_mod_junk_arg.rs new file mode 100644 index 00000000..12190cf2 --- /dev/null +++ b/codegen/ui_tests/export_mod_junk_arg.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn("wheeeee")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_junk_arg.stderr b/codegen/ui_tests/export_mod_junk_arg.stderr new file mode 100644 index 00000000..f4505bae --- /dev/null +++ b/codegen/ui_tests/export_mod_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/export_mod_junk_arg.rs:11:11 + | +11 | #[rhai_fn("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_junk_arg.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_missing_value.rs b/codegen/ui_tests/export_mod_missing_value.rs new file mode 100644 index 00000000..f57c247b --- /dev/null +++ b/codegen/ui_tests/export_mod_missing_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_missing_value.stderr b/codegen/ui_tests/export_mod_missing_value.stderr new file mode 100644 index 00000000..f479f5fa --- /dev/null +++ b/codegen/ui_tests/export_mod_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/export_mod_missing_value.rs:11:11 + | +11 | #[rhai_fn(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_missing_value.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_path_attr.rs b/codegen/ui_tests/export_mod_path_attr.rs new file mode 100644 index 00000000..a489f042 --- /dev/null +++ b/codegen/ui_tests/export_mod_path_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_path_attr.stderr b/codegen/ui_tests/export_mod_path_attr.stderr new file mode 100644 index 00000000..d96d7c8c --- /dev/null +++ b/codegen/ui_tests/export_mod_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/export_mod_path_attr.rs:11:11 + | +11 | #[rhai_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_path_attr.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr new file mode 100644 index 00000000..db880026 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied + --> $DIR/rhai_mod_non_clonable_return.rs:12:12 + | +12 | pub fn test_fn(input: f32) -> NonClonable { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` + | + ::: /home/cryptkeeper/Desktop/Software/ytdl-replacement/rhai/src/any.rs:537:30 + | +537 | pub fn from(value: T) -> Self { + | ----- required by this bound in `rhai::Dynamic::from` From 62dc142c5861e7bf8d13541ce72394efa5ba6b7e Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 27 Aug 2020 23:08:34 -0500 Subject: [PATCH 090/103] Avoid bypassing setter checks by making fn.params.skip private --- codegen/src/function.rs | 10 +++++++++- codegen/src/module.rs | 14 +++++++------- codegen/src/rhai_module.rs | 8 ++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index a8862d20..337eccab 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -111,7 +111,7 @@ pub(crate) struct ExportedFn { signature: syn::Signature, is_public: bool, mut_receiver: bool, - pub params: ExportedFnParams, + params: ExportedFnParams, } impl Parse for ExportedFn { @@ -218,6 +218,14 @@ impl Parse for ExportedFn { } impl ExportedFn { + pub(crate) fn params(&self) -> &ExportedFnParams { + &self.params + } + + pub(crate) fn skipped(&self) -> bool { + self.params.skip + } + pub(crate) fn mutable_receiver(&self) -> bool { self.mut_receiver } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 535ea9d7..b1a76f29 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -112,9 +112,9 @@ impl Parse for Module { true }; syn::parse2::(itemfn.to_token_stream()) - .map(|mut f| { - f.params = params; - f + .and_then(|mut f| { + f.set_params(params)?; + Ok(f) }) .map(|f| vec.push(f)) .map(|_| vec) @@ -411,7 +411,7 @@ mod module_tests { assert!(item_mod.consts.is_empty()); assert_eq!(item_mod.submodules.len(), 1); assert_eq!(item_mod.submodules[0].fns.len(), 1); - assert!(item_mod.submodules[0].fns[0].params.skip); + assert!(item_mod.submodules[0].fns[0].skipped()); assert!(item_mod.submodules[0].consts.is_empty()); assert!(item_mod.submodules[0].submodules.is_empty()); } @@ -433,7 +433,7 @@ mod module_tests { assert!(item_mod.fns.is_empty()); assert!(item_mod.consts.is_empty()); assert_eq!(item_mod.submodules.len(), 1); - assert!(item_mod.submodules[0].params.skip); + assert!(item_mod.submodules[0].skipped()); } #[test] @@ -466,7 +466,7 @@ mod module_tests { let item_mod = syn::parse2::(input_tokens).unwrap(); assert_eq!(item_mod.fns.len(), 1); - assert!(item_mod.fns[0].params.skip); + assert!(item_mod.fns[0].skipped()); assert!(item_mod.consts.is_empty()); } @@ -483,7 +483,7 @@ mod module_tests { let item_mod = syn::parse2::(input_tokens).unwrap(); assert_eq!(item_mod.fns.len(), 1); - assert!(item_mod.fns[0].params.skip); + assert!(item_mod.fns[0].skipped()); assert!(item_mod.consts.is_empty()); } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 95f17ffc..7e1aa106 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -56,7 +56,7 @@ pub(crate) fn generate_body( // NB: these are token streams, because reparsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { - if function.params.skip { + if function.skipped() { continue; } let fn_token_name = syn::Ident::new( @@ -64,7 +64,7 @@ pub(crate) fn generate_body( function.name().span(), ); let reg_name = function - .params + .params() .name .clone() .unwrap_or_else(|| function.name().to_string()); @@ -151,8 +151,8 @@ pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn:: let mut renames = HashMap::::new(); let mut names = HashMap::::new(); for itemfn in fns.iter() { - if let Some(ref name) = itemfn.params.name { - let current_span = itemfn.params.span.as_ref().unwrap(); + if let Some(ref name) = itemfn.params().name { + let current_span = itemfn.params().span.as_ref().unwrap(); let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { let type_string: String = match fnarg { syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"), From d8e85df9dcea9b3f1f277e1823589a044d9306b8 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 27 Aug 2020 23:11:22 -0500 Subject: [PATCH 091/103] Add return_raw tests for rhai_fn in module --- codegen/ui_tests/export_mod_raw_noreturn.rs | 28 +++++++++++++++++++ .../ui_tests/export_mod_raw_noreturn.stderr | 11 ++++++++ codegen/ui_tests/export_mod_raw_return.rs | 27 ++++++++++++++++++ codegen/ui_tests/export_mod_raw_return.stderr | 11 ++++++++ 4 files changed, 77 insertions(+) create mode 100644 codegen/ui_tests/export_mod_raw_noreturn.rs create mode 100644 codegen/ui_tests/export_mod_raw_noreturn.stderr create mode 100644 codegen/ui_tests/export_mod_raw_return.rs create mode 100644 codegen/ui_tests/export_mod_raw_return.stderr diff --git a/codegen/ui_tests/export_mod_raw_noreturn.rs b/codegen/ui_tests/export_mod_raw_noreturn.rs new file mode 100644 index 00000000..926f6286 --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_noreturn.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(return_raw)] +pub fn test_fn(input: &mut Point) { + input.x += 1.0; +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + test_mod::test_fn(&mut n); + if n.x >= 10.0 { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_raw_noreturn.stderr b/codegen/ui_tests/export_mod_raw_noreturn.stderr new file mode 100644 index 00000000..a548f339 --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_noreturn.stderr @@ -0,0 +1,11 @@ +error: return_raw functions must return Result + --> $DIR/export_mod_raw_noreturn.rs:12:5 + | +12 | pub fn test_fn(input: &mut Point) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_raw_noreturn.rs:22:5 + | +22 | test_mod::test_fn(&mut n); + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_raw_return.rs b/codegen/ui_tests/export_mod_raw_return.rs new file mode 100644 index 00000000..ae52301e --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_return.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(return_raw)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_raw_return.stderr b/codegen/ui_tests/export_mod_raw_return.stderr new file mode 100644 index 00000000..7d22f7a2 --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_return.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/export_mod_raw_return.rs:12:8 + | +9 | #[export_module] + | ---------------- expected `std::result::Result>` because of return type +... +12 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found `bool` + | + = note: expected enum `std::result::Result>` + found type `bool` From e66adaa60f44b7c426d2256f2e5f4ac51910d469 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 27 Aug 2020 23:33:42 -0500 Subject: [PATCH 092/103] Revert diagnostic tests which trigger trybuild path bug --- .../ui_tests/rhai_fn_non_clonable_return.rs | 27 ----------------- .../rhai_fn_non_clonable_return.stderr | 10 ------- .../ui_tests/rhai_mod_non_clonable_return.rs | 29 ------------------- .../rhai_mod_non_clonable_return.stderr | 10 ------- 4 files changed, 76 deletions(-) delete mode 100644 codegen/ui_tests/rhai_fn_non_clonable_return.rs delete mode 100644 codegen/ui_tests/rhai_fn_non_clonable_return.stderr delete mode 100644 codegen/ui_tests/rhai_mod_non_clonable_return.rs delete mode 100644 codegen/ui_tests/rhai_mod_non_clonable_return.stderr diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.rs b/codegen/ui_tests/rhai_fn_non_clonable_return.rs deleted file mode 100644 index e2e2d788..00000000 --- a/codegen/ui_tests/rhai_fn_non_clonable_return.rs +++ /dev/null @@ -1,27 +0,0 @@ -use rhai::plugin::*; - -struct NonClonable { - a: f32, - b: u32, - c: char, - d: bool, -} - -#[export_fn] -pub fn test_fn(input: f32) -> NonClonable { - NonClonable { - a: input, - b: 10, - c: 'a', - d: true, - } -} - -fn main() { - let n = test_fn(20.0); - if n.c == 'a' { - println!("yes"); - } else { - println!("no"); - } -} diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr deleted file mode 100644 index 6184fd24..00000000 --- a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied - --> $DIR/rhai_fn_non_clonable_return.rs:11:8 - | -11 | pub fn test_fn(input: f32) -> NonClonable { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` - | - ::: /home/cryptkeeper/Desktop/Software/ytdl-replacement/rhai/src/any.rs:537:30 - | -537 | pub fn from(value: T) -> Self { - | ----- required by this bound in `rhai::Dynamic::from` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.rs b/codegen/ui_tests/rhai_mod_non_clonable_return.rs deleted file mode 100644 index fe8f5fff..00000000 --- a/codegen/ui_tests/rhai_mod_non_clonable_return.rs +++ /dev/null @@ -1,29 +0,0 @@ -use rhai::plugin::*; - -struct NonClonable { - a: f32, - b: u32, - c: char, - d: bool, -} - -#[export_module] -pub mod test_mod { - pub fn test_fn(input: f32) -> NonClonable { - NonClonable { - a: input, - b: 10, - c: 'a', - d: true, - } - } -} - -fn main() { - let n = test_mod::test_fn(20.0); - if n.c == 'a' { - println!("yes"); - } else { - println!("no"); - } -} diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr deleted file mode 100644 index db880026..00000000 --- a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied - --> $DIR/rhai_mod_non_clonable_return.rs:12:12 - | -12 | pub fn test_fn(input: f32) -> NonClonable { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` - | - ::: /home/cryptkeeper/Desktop/Software/ytdl-replacement/rhai/src/any.rs:537:30 - | -537 | pub fn from(value: T) -> Self { - | ----- required by this bound in `rhai::Dynamic::from` From b4406f2302be6cf770e04df24f4e436b84d276c0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 28 Aug 2020 16:46:06 +0800 Subject: [PATCH 093/103] Fix docs on sign result. --- doc/src/language/num-fn.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md index a963c7e2..c9427a35 100644 --- a/doc/src/language/num-fn.md +++ b/doc/src/language/num-fn.md @@ -9,11 +9,11 @@ Integer Functions The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | Description | -| ------------ | --------------------------------------------------------------- | -| `abs` | absolute value | -| `sign` | returns -1 if the number is negative, +1 if positive, 0 if zero | -| [`to_float`] | converts an integer type to `f64` | +| Function | Description | +| ------------ | ----------------------------------------------------------------------- | +| `abs` | absolute value | +| `sign` | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | +| [`to_float`] | converts an integer type to `FLOAT` | Floating-Point Functions ----------------------- From 8736919cea69a0d5f00fcaea4737499cc9208f1f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 28 Aug 2020 23:13:38 +0800 Subject: [PATCH 094/103] Nicer formatting/less indentation of module plugin blocks. --- src/packages/arithmetic.rs | 398 +++++++++++++++++------------------ src/packages/array_basic.rs | 47 ++--- src/packages/logic.rs | 70 +++--- src/packages/math_basic.rs | 16 +- src/packages/string_basic.rs | 22 +- src/packages/string_more.rs | 39 ++-- 6 files changed, 280 insertions(+), 312 deletions(-) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index b9553cd8..33b01b57 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -17,250 +17,246 @@ use crate::stdlib::format; macro_rules! gen_arithmetic_functions { ($root:ident => $($arg_type:ident),+) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_module] - pub mod functions { - #[rhai_fn(name = "+", return_raw)] - #[inline] - pub fn add(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_add(y).ok_or_else(|| { + #[export_module] + pub mod functions { + #[rhai_fn(name = "+", return_raw)] + #[inline] + pub fn add(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_add(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Addition overflow: {} + {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x + y)) + } + } + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_sub(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Subtraction overflow: {} - {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x - y)) + } + } + #[rhai_fn(name = "*", return_raw)] + #[inline] + pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_mul(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Multiplication overflow: {} * {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x * y)) + } + } + #[rhai_fn(name = "/", return_raw)] + #[inline] + pub fn divide(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + // Detect division by zero + if y == 0 { + EvalAltResult::ErrorArithmetic( + format!("Division by zero: {} / {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_div(y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Addition overflow: {} + {}", x, y), + format!("Division overflow: {} / {}", x, y), Position::none(), ) .into() }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x + y)) } + } else { + Ok(Dynamic::from(x / y)) } - #[rhai_fn(name = "-", return_raw)] - #[inline] - pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_sub(y).ok_or_else(|| { + } + #[rhai_fn(name = "%", return_raw)] + #[inline] + pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_rem(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Modulo division by zero or overflow: {} % {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x % y)) + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn power(x: INT, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_pow(y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Subtraction overflow: {} - {}", x, y), + format!("Power overflow: {} ~ {}", x, y), Position::none(), ) .into() }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x - y)) - } - } - #[rhai_fn(name = "*", return_raw)] - #[inline] - pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_mul(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Multiplication overflow: {} * {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x * y)) - } - } - #[rhai_fn(name = "/", return_raw)] - #[inline] - pub fn divide(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - // Detect division by zero - if y == 0 { - EvalAltResult::ErrorArithmetic( - format!("Division by zero: {} / {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_div(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Division overflow: {} / {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x / y)) - } - } - #[rhai_fn(name = "%", return_raw)] - #[inline] - pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_rem(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Modulo division by zero or overflow: {} % {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x % y)) - } - } - #[rhai_fn(name = "~", return_raw)] - #[inline] - pub fn power(x: INT, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x.pow(y as u32))) } + } else { + Ok(Dynamic::from(x.pow(y as u32))) } + } - #[rhai_fn(name = "<<", return_raw)] - #[inline] - pub fn shift_left(x: $arg_type, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + #[rhai_fn(name = "<<", return_raw)] + #[inline] + pub fn shift_left(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by too many bits: {} << {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by a negative number: {} << {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_shl(y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( format!("Left-shift by too many bits: {} << {}", x, y), Position::none(), ) .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by a negative number: {} << {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_shl(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by too many bits: {} << {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x << y)) + }).map(Dynamic::from) } + } else { + Ok(Dynamic::from(x << y)) } - #[rhai_fn(name = ">>", return_raw)] - #[inline] - pub fn shift_right(x: $arg_type, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + } + #[rhai_fn(name = ">>", return_raw)] + #[inline] + pub fn shift_right(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by too many bits: {} >> {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by a negative number: {} >> {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_shr(y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( format!("Right-shift by too many bits: {} >> {}", x, y), Position::none(), ) .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by a negative number: {} >> {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_shr(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by too many bits: {} >> {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x >> y)) + }).map(Dynamic::from) } - } - #[rhai_fn(name = "&")] - #[inline(always)] - pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { - x & y - } - #[rhai_fn(name = "|")] - #[inline(always)] - pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { - x | y - } - #[rhai_fn(name = "^")] - #[inline(always)] - pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { - x ^ y + } else { + Ok(Dynamic::from(x >> y)) } } + #[rhai_fn(name = "&")] + #[inline(always)] + pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { + x & y + } + #[rhai_fn(name = "|")] + #[inline(always)] + pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { + x | y + } + #[rhai_fn(name = "^")] + #[inline(always)] + pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { + x ^ y + } } - )* } + })* } } } macro_rules! gen_signed_functions { ($root:ident => $($arg_type:ident),+) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_module] - pub mod functions { - #[rhai_fn(name = "-", return_raw)] - #[inline] - pub fn neg(x: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(-x)) - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn neg(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_neg().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(-x)) } - #[rhai_fn(return_raw)] - #[inline] - pub fn abs(x: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_abs().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x.abs())) - } + } + #[rhai_fn(return_raw)] + #[inline] + pub fn abs(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_abs().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x.abs())) } - #[inline] - pub fn sign(x: $arg_type) -> INT { - if x == 0 { - 0 - } else if x < 0 { - -1 - } else { - 1 - } + } + #[inline] + pub fn sign(x: $arg_type) -> INT { + if x == 0 { + 0 + } else if x < 0 { + -1 + } else { + 1 } } } - )* } + })* } } } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index a69372ae..66c35924 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -23,30 +23,33 @@ pub type Unit = (); macro_rules! gen_array_functions { ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn push_func(list: &mut Array, item: $arg_type) { - super::super::push(list, item); - } + #[export_fn] + #[inline(always)] + pub fn push(list: &mut Array, item: $arg_type) { + list.push(Dynamic::from(item)); + } - #[export_fn] - #[inline(always)] - pub fn insert_func(list: &mut Array, len: INT, item: $arg_type) { - super::super::insert(list, len, item); + #[export_fn] + pub fn insert(list: &mut Array, position: INT, item: $arg_type) { + if position <= 0 { + list.insert(0, Dynamic::from(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Dynamic::from(item)); } } - )* } + })* } } } macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - set_exported_fn!($mod_name, "push", $root::$arg_type::push_func); - set_exported_fn!($mod_name, "insert", $root::$arg_type::insert_func); + set_exported_fn!($mod_name, "push", $root::$arg_type::push); + set_exported_fn!($mod_name, "insert", $root::$arg_type::insert); $mod_name.set_raw_fn("pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::<$arg_type>()], @@ -138,20 +141,6 @@ mod array_functions { } } -// Register array utility functions -#[inline(always)] -fn push(list: &mut Array, item: T) { - list.push(Dynamic::from(item)); -} -fn insert(list: &mut Array, position: INT, item: T) { - if position <= 0 { - list.insert(0, Dynamic::from(item)); - } else if (position as usize) >= list.len() - 1 { - push(list, item); - } else { - list.insert(position as usize, Dynamic::from(item)); - } -} fn pad( _engine: &Engine, _: &Module, diff --git a/src/packages/logic.rs b/src/packages/logic.rs index e05d8515..48e46047 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -3,45 +3,43 @@ use crate::plugin::*; macro_rules! gen_cmp_functions { ($root:ident => $($arg_type:ident),+) => { - mod $root { $( - pub mod $arg_type { - use crate::plugin::*; + mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_module] - pub mod functions { - #[rhai_fn(name = "<")] - #[inline(always)] - pub fn lt(x: $arg_type, y: $arg_type) -> bool { - x < y - } - #[rhai_fn(name = "<=")] - #[inline(always)] - pub fn lte(x: $arg_type, y: $arg_type) -> bool { - x <= y - } - #[rhai_fn(name = ">")] - #[inline(always)] - pub fn gt(x: $arg_type, y: $arg_type) -> bool { - x > y - } - #[rhai_fn(name = ">=")] - #[inline(always)] - pub fn gte(x: $arg_type, y: $arg_type) -> bool { - x >= y - } - #[rhai_fn(name = "==")] - #[inline(always)] - pub fn eq(x: $arg_type, y: $arg_type) -> bool { - x == y - } - #[rhai_fn(name = "!=")] - #[inline(always)] - pub fn ne(x: $arg_type, y: $arg_type) -> bool { - x != y - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "<")] + #[inline(always)] + pub fn lt(x: $arg_type, y: $arg_type) -> bool { + x < y + } + #[rhai_fn(name = "<=")] + #[inline(always)] + pub fn lte(x: $arg_type, y: $arg_type) -> bool { + x <= y + } + #[rhai_fn(name = ">")] + #[inline(always)] + pub fn gt(x: $arg_type, y: $arg_type) -> bool { + x > y + } + #[rhai_fn(name = ">=")] + #[inline(always)] + pub fn gte(x: $arg_type, y: $arg_type) -> bool { + x >= y + } + #[rhai_fn(name = "==")] + #[inline(always)] + pub fn eq(x: $arg_type, y: $arg_type) -> bool { + x == y + } + #[rhai_fn(name = "!=")] + #[inline(always)] + pub fn ne(x: $arg_type, y: $arg_type) -> bool { + x != y } } - )* } + })* } }; } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index f1de6c4f..a7c17e50 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -26,17 +26,15 @@ pub const MAX_INT: INT = i64::MAX; macro_rules! gen_conversion_functions { ($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn $func_name(x: $arg_type) -> $result_type { - x as $result_type - } + #[export_fn] + #[inline(always)] + pub fn $func_name(x: $arg_type) -> $result_type { + x as $result_type } - )* } + })* } } } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index ae5a84e0..288fc115 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -22,17 +22,15 @@ type Unit = (); macro_rules! gen_functions { ($root:ident => $fn_name:ident ( $($arg_type:ident),+ )) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { - super::super::$fn_name(x) - } + #[export_fn] + #[inline(always)] + pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { + super::super::$fn_name(x) } - )* } + })* } } } @@ -128,12 +126,12 @@ gen_functions!(print_array => to_debug(Array)); #[export_fn] #[inline(always)] fn print_empty_string() -> ImmutableString { - "".to_string().into() + String::new().into() } #[export_fn] #[inline(always)] fn print_unit(_x: ()) -> ImmutableString { - "".to_string().into() + String::new().into() } #[export_fn] #[inline(always)] @@ -143,7 +141,7 @@ fn print_string(s: ImmutableString) -> ImmutableString { #[export_fn] #[inline(always)] fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { - f.to_string().into() + to_string(f) } #[inline(always)] fn to_string(x: &mut T) -> ImmutableString { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 883d4fec..3766d487 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -12,28 +12,26 @@ use crate::utils::StaticVec; use crate::{result::EvalAltResult, token::Position}; use crate::stdlib::{ - any::TypeId, boxed::Box, fmt::Display, format, mem, string::String, string::ToString, vec::Vec, + any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec, }; macro_rules! gen_concat_functions { ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { - super::super::add_append(x, y) - } - - #[export_fn] - #[inline(always)] - pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { - super::super::add_prepend(x, y) - } + #[export_fn] + #[inline] + pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { + format!("{}{}", x, y) } - )* } + + #[export_fn] + #[inline] + pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { + format!("{}{}", x, y) + } + })* } } } @@ -116,15 +114,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ); }); -#[inline] -fn add_prepend(x: &mut T, y: ImmutableString) -> String { - format!("{}{}", x, y) -} -#[inline] -fn add_append(x: &mut ImmutableString, y: T) -> String { - format!("{}{}", x, y) -} - gen_concat_functions!(basic => INT, bool, char, FnPtr); #[cfg(not(feature = "only_i32"))] From cd867b180ffd3566235d52646717207fbd79f173 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 30 Aug 2020 17:25:36 +0800 Subject: [PATCH 095/103] Restructure book chapters. --- doc/src/SUMMARY.md | 10 +++++----- doc/src/{rust => engine}/options.md | 0 doc/src/{rust => engine}/scope.md | 0 doc/src/links.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename doc/src/{rust => engine}/options.md (100%) rename doc/src/{rust => engine}/scope.md (100%) diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 59584d28..f897b61e 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -26,15 +26,14 @@ The Rhai Scripting Language 4. [Create a Rust Closure from a Rhai Function](engine/func.md) 5. [Evaluate Expressions Only](engine/expressions.md) 6. [Raw Engine](engine/raw.md) + 7. [Scope - Initializing and Maintaining State](engine/scope.md) + 8. [Engine Configuration Options](engine/options.md) 4. [Extend Rhai with Rust](rust/index.md) 1. [Traits](rust/traits.md) 2. [Register a Rust Function](rust/functions.md) 1. [String Parameters in Rust Functions](rust/strings.md) 3. [Register a Generic Rust Function](rust/generic.md) 4. [Register a Fallible Rust Function](rust/fallible.md) - 5. [Packages](rust/packages/index.md) - 1. [Built-in Packages](rust/packages/builtin.md) - 2. [Create a Custom Package](rust/packages/create.md) 6. [Override a Built-in Function](rust/override.md) 7. [Operator Overloading](rust/operators.md) 8. [Register a Custom Type and its Methods](rust/custom.md) @@ -42,8 +41,9 @@ The Rhai Scripting Language 2. [Indexers](rust/indexers.md) 3. [Disable Custom Types](rust/disable-custom.md) 4. [Printing Custom Types](rust/print-custom.md) - 9. [Scope - Initializing and Maintaining State](rust/scope.md) - 10. [Engine Configuration Options](rust/options.md) + 9. [Packages](rust/packages/index.md) + 1. [Built-in Packages](rust/packages/builtin.md) + 2. [Create a Custom Package](rust/packages/create.md) 5. [Rhai Language Reference](language/index.md) 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) diff --git a/doc/src/rust/options.md b/doc/src/engine/options.md similarity index 100% rename from doc/src/rust/options.md rename to doc/src/engine/options.md diff --git a/doc/src/rust/scope.md b/doc/src/engine/scope.md similarity index 100% rename from doc/src/rust/scope.md rename to doc/src/engine/scope.md diff --git a/doc/src/links.md b/doc/src/links.md index 953c1164..e030478a 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -33,7 +33,7 @@ [packages]: {{rootUrl}}/rust/packages/index.md [custom package]: {{rootUrl}}/rust/packages/create.md [custom packages]: {{rootUrl}}/rust/packages/create.md -[`Scope`]: {{rootUrl}}/rust/scope.md +[`Scope`]: {{rootUrl}}/engine/scope.md [`serde`]: {{rootUrl}}/rust/serde.md [`type_of()`]: {{rootUrl}}/language/type-of.md From b72a6355fd44e1324220e2072bd6f4ac731a3838 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 28 Aug 2020 16:46:06 +0800 Subject: [PATCH 096/103] Fix docs on sign result. --- doc/src/language/num-fn.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md index a963c7e2..c9427a35 100644 --- a/doc/src/language/num-fn.md +++ b/doc/src/language/num-fn.md @@ -9,11 +9,11 @@ Integer Functions The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | Description | -| ------------ | --------------------------------------------------------------- | -| `abs` | absolute value | -| `sign` | returns -1 if the number is negative, +1 if positive, 0 if zero | -| [`to_float`] | converts an integer type to `f64` | +| Function | Description | +| ------------ | ----------------------------------------------------------------------- | +| `abs` | absolute value | +| `sign` | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | +| [`to_float`] | converts an integer type to `FLOAT` | Floating-Point Functions ----------------------- From 75ca49519982b28fb9b523176cf5f63fd9e39086 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 28 Aug 2020 23:13:38 +0800 Subject: [PATCH 097/103] Nicer formatting/less indentation of module plugin blocks. --- src/packages/arithmetic.rs | 398 +++++++++++++++++------------------ src/packages/array_basic.rs | 47 ++--- src/packages/logic.rs | 70 +++--- src/packages/math_basic.rs | 16 +- src/packages/string_basic.rs | 22 +- src/packages/string_more.rs | 39 ++-- 6 files changed, 280 insertions(+), 312 deletions(-) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index b9553cd8..33b01b57 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -17,250 +17,246 @@ use crate::stdlib::format; macro_rules! gen_arithmetic_functions { ($root:ident => $($arg_type:ident),+) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_module] - pub mod functions { - #[rhai_fn(name = "+", return_raw)] - #[inline] - pub fn add(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_add(y).ok_or_else(|| { + #[export_module] + pub mod functions { + #[rhai_fn(name = "+", return_raw)] + #[inline] + pub fn add(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_add(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Addition overflow: {} + {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x + y)) + } + } + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_sub(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Subtraction overflow: {} - {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x - y)) + } + } + #[rhai_fn(name = "*", return_raw)] + #[inline] + pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_mul(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Multiplication overflow: {} * {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x * y)) + } + } + #[rhai_fn(name = "/", return_raw)] + #[inline] + pub fn divide(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + // Detect division by zero + if y == 0 { + EvalAltResult::ErrorArithmetic( + format!("Division by zero: {} / {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_div(y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Addition overflow: {} + {}", x, y), + format!("Division overflow: {} / {}", x, y), Position::none(), ) .into() }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x + y)) } + } else { + Ok(Dynamic::from(x / y)) } - #[rhai_fn(name = "-", return_raw)] - #[inline] - pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_sub(y).ok_or_else(|| { + } + #[rhai_fn(name = "%", return_raw)] + #[inline] + pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_rem(y).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Modulo division by zero or overflow: {} % {}", x, y), + Position::none(), + ) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x % y)) + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn power(x: INT, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_pow(y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Subtraction overflow: {} - {}", x, y), + format!("Power overflow: {} ~ {}", x, y), Position::none(), ) .into() }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x - y)) - } - } - #[rhai_fn(name = "*", return_raw)] - #[inline] - pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_mul(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Multiplication overflow: {} * {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x * y)) - } - } - #[rhai_fn(name = "/", return_raw)] - #[inline] - pub fn divide(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - // Detect division by zero - if y == 0 { - EvalAltResult::ErrorArithmetic( - format!("Division by zero: {} / {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_div(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Division overflow: {} / {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x / y)) - } - } - #[rhai_fn(name = "%", return_raw)] - #[inline] - pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_rem(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Modulo division by zero or overflow: {} % {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x % y)) - } - } - #[rhai_fn(name = "~", return_raw)] - #[inline] - pub fn power(x: INT, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x.pow(y as u32))) } + } else { + Ok(Dynamic::from(x.pow(y as u32))) } + } - #[rhai_fn(name = "<<", return_raw)] - #[inline] - pub fn shift_left(x: $arg_type, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + #[rhai_fn(name = "<<", return_raw)] + #[inline] + pub fn shift_left(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by too many bits: {} << {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Left-shift by a negative number: {} << {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_shl(y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( format!("Left-shift by too many bits: {} << {}", x, y), Position::none(), ) .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by a negative number: {} << {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_shl(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by too many bits: {} << {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x << y)) + }).map(Dynamic::from) } + } else { + Ok(Dynamic::from(x << y)) } - #[rhai_fn(name = ">>", return_raw)] - #[inline] - pub fn shift_right(x: $arg_type, y: INT) -> Result> { - if cfg!(not(feature = "unchecked")) { - if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + } + #[rhai_fn(name = ">>", return_raw)] + #[inline] + pub fn shift_right(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by too many bits: {} >> {}", x, y), + Position::none(), + ) + .into() + } else if y < 0 { + EvalAltResult::ErrorArithmetic( + format!("Right-shift by a negative number: {} >> {}", x, y), + Position::none(), + ) + .into() + } else { + x.checked_shr(y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( format!("Right-shift by too many bits: {} >> {}", x, y), Position::none(), ) .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by a negative number: {} >> {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_shr(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by too many bits: {} >> {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) - } - } else { - Ok(Dynamic::from(x >> y)) + }).map(Dynamic::from) } - } - #[rhai_fn(name = "&")] - #[inline(always)] - pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { - x & y - } - #[rhai_fn(name = "|")] - #[inline(always)] - pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { - x | y - } - #[rhai_fn(name = "^")] - #[inline(always)] - pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { - x ^ y + } else { + Ok(Dynamic::from(x >> y)) } } + #[rhai_fn(name = "&")] + #[inline(always)] + pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { + x & y + } + #[rhai_fn(name = "|")] + #[inline(always)] + pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { + x | y + } + #[rhai_fn(name = "^")] + #[inline(always)] + pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { + x ^ y + } } - )* } + })* } } } macro_rules! gen_signed_functions { ($root:ident => $($arg_type:ident),+) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_module] - pub mod functions { - #[rhai_fn(name = "-", return_raw)] - #[inline] - pub fn neg(x: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(-x)) - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn neg(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_neg().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(-x)) } - #[rhai_fn(return_raw)] - #[inline] - pub fn abs(x: $arg_type) -> Result> { - if cfg!(not(feature = "unchecked")) { - x.checked_abs().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }).map(Dynamic::from) - } else { - Ok(Dynamic::from(x.abs())) - } + } + #[rhai_fn(return_raw)] + #[inline] + pub fn abs(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_abs().ok_or_else(|| { + EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) + .into() + }).map(Dynamic::from) + } else { + Ok(Dynamic::from(x.abs())) } - #[inline] - pub fn sign(x: $arg_type) -> INT { - if x == 0 { - 0 - } else if x < 0 { - -1 - } else { - 1 - } + } + #[inline] + pub fn sign(x: $arg_type) -> INT { + if x == 0 { + 0 + } else if x < 0 { + -1 + } else { + 1 } } } - )* } + })* } } } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index a69372ae..66c35924 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -23,30 +23,33 @@ pub type Unit = (); macro_rules! gen_array_functions { ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn push_func(list: &mut Array, item: $arg_type) { - super::super::push(list, item); - } + #[export_fn] + #[inline(always)] + pub fn push(list: &mut Array, item: $arg_type) { + list.push(Dynamic::from(item)); + } - #[export_fn] - #[inline(always)] - pub fn insert_func(list: &mut Array, len: INT, item: $arg_type) { - super::super::insert(list, len, item); + #[export_fn] + pub fn insert(list: &mut Array, position: INT, item: $arg_type) { + if position <= 0 { + list.insert(0, Dynamic::from(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Dynamic::from(item)); } } - )* } + })* } } } macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - set_exported_fn!($mod_name, "push", $root::$arg_type::push_func); - set_exported_fn!($mod_name, "insert", $root::$arg_type::insert_func); + set_exported_fn!($mod_name, "push", $root::$arg_type::push); + set_exported_fn!($mod_name, "insert", $root::$arg_type::insert); $mod_name.set_raw_fn("pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::<$arg_type>()], @@ -138,20 +141,6 @@ mod array_functions { } } -// Register array utility functions -#[inline(always)] -fn push(list: &mut Array, item: T) { - list.push(Dynamic::from(item)); -} -fn insert(list: &mut Array, position: INT, item: T) { - if position <= 0 { - list.insert(0, Dynamic::from(item)); - } else if (position as usize) >= list.len() - 1 { - push(list, item); - } else { - list.insert(position as usize, Dynamic::from(item)); - } -} fn pad( _engine: &Engine, _: &Module, diff --git a/src/packages/logic.rs b/src/packages/logic.rs index e05d8515..48e46047 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -3,45 +3,43 @@ use crate::plugin::*; macro_rules! gen_cmp_functions { ($root:ident => $($arg_type:ident),+) => { - mod $root { $( - pub mod $arg_type { - use crate::plugin::*; + mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_module] - pub mod functions { - #[rhai_fn(name = "<")] - #[inline(always)] - pub fn lt(x: $arg_type, y: $arg_type) -> bool { - x < y - } - #[rhai_fn(name = "<=")] - #[inline(always)] - pub fn lte(x: $arg_type, y: $arg_type) -> bool { - x <= y - } - #[rhai_fn(name = ">")] - #[inline(always)] - pub fn gt(x: $arg_type, y: $arg_type) -> bool { - x > y - } - #[rhai_fn(name = ">=")] - #[inline(always)] - pub fn gte(x: $arg_type, y: $arg_type) -> bool { - x >= y - } - #[rhai_fn(name = "==")] - #[inline(always)] - pub fn eq(x: $arg_type, y: $arg_type) -> bool { - x == y - } - #[rhai_fn(name = "!=")] - #[inline(always)] - pub fn ne(x: $arg_type, y: $arg_type) -> bool { - x != y - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "<")] + #[inline(always)] + pub fn lt(x: $arg_type, y: $arg_type) -> bool { + x < y + } + #[rhai_fn(name = "<=")] + #[inline(always)] + pub fn lte(x: $arg_type, y: $arg_type) -> bool { + x <= y + } + #[rhai_fn(name = ">")] + #[inline(always)] + pub fn gt(x: $arg_type, y: $arg_type) -> bool { + x > y + } + #[rhai_fn(name = ">=")] + #[inline(always)] + pub fn gte(x: $arg_type, y: $arg_type) -> bool { + x >= y + } + #[rhai_fn(name = "==")] + #[inline(always)] + pub fn eq(x: $arg_type, y: $arg_type) -> bool { + x == y + } + #[rhai_fn(name = "!=")] + #[inline(always)] + pub fn ne(x: $arg_type, y: $arg_type) -> bool { + x != y } } - )* } + })* } }; } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index f1de6c4f..a7c17e50 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -26,17 +26,15 @@ pub const MAX_INT: INT = i64::MAX; macro_rules! gen_conversion_functions { ($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn $func_name(x: $arg_type) -> $result_type { - x as $result_type - } + #[export_fn] + #[inline(always)] + pub fn $func_name(x: $arg_type) -> $result_type { + x as $result_type } - )* } + })* } } } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index ae5a84e0..288fc115 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -22,17 +22,15 @@ type Unit = (); macro_rules! gen_functions { ($root:ident => $fn_name:ident ( $($arg_type:ident),+ )) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { - super::super::$fn_name(x) - } + #[export_fn] + #[inline(always)] + pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { + super::super::$fn_name(x) } - )* } + })* } } } @@ -128,12 +126,12 @@ gen_functions!(print_array => to_debug(Array)); #[export_fn] #[inline(always)] fn print_empty_string() -> ImmutableString { - "".to_string().into() + String::new().into() } #[export_fn] #[inline(always)] fn print_unit(_x: ()) -> ImmutableString { - "".to_string().into() + String::new().into() } #[export_fn] #[inline(always)] @@ -143,7 +141,7 @@ fn print_string(s: ImmutableString) -> ImmutableString { #[export_fn] #[inline(always)] fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { - f.to_string().into() + to_string(f) } #[inline(always)] fn to_string(x: &mut T) -> ImmutableString { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 883d4fec..3766d487 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -12,28 +12,26 @@ use crate::utils::StaticVec; use crate::{result::EvalAltResult, token::Position}; use crate::stdlib::{ - any::TypeId, boxed::Box, fmt::Display, format, mem, string::String, string::ToString, vec::Vec, + any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec, }; macro_rules! gen_concat_functions { ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $( - pub mod $arg_type { - use super::super::*; + pub mod $root { $(pub mod $arg_type { + use super::super::*; - #[export_fn] - #[inline(always)] - pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { - super::super::add_append(x, y) - } - - #[export_fn] - #[inline(always)] - pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { - super::super::add_prepend(x, y) - } + #[export_fn] + #[inline] + pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { + format!("{}{}", x, y) } - )* } + + #[export_fn] + #[inline] + pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { + format!("{}{}", x, y) + } + })* } } } @@ -116,15 +114,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ); }); -#[inline] -fn add_prepend(x: &mut T, y: ImmutableString) -> String { - format!("{}{}", x, y) -} -#[inline] -fn add_append(x: &mut ImmutableString, y: T) -> String { - format!("{}{}", x, y) -} - gen_concat_functions!(basic => INT, bool, char, FnPtr); #[cfg(not(feature = "only_i32"))] From 4d9aad816cdc10513bca4d9ec2ec511c89ddd59e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 30 Aug 2020 23:13:47 +0800 Subject: [PATCH 098/103] Add writeup on plugins. --- doc/src/SUMMARY.md | 8 +- doc/src/links.md | 6 + doc/src/plugins/function.md | 78 ++++++++ doc/src/plugins/index.md | 11 ++ doc/src/plugins/module.md | 194 +++++++++++++++++++ doc/src/rust/modules/{index.md => create.md} | 16 +- doc/src/rust/packages/create.md | 51 ++++- doc/src/rust/packages/index.md | 20 +- doc/src/rust/packages/plugin.md | 41 ++++ 9 files changed, 416 insertions(+), 9 deletions(-) create mode 100644 doc/src/plugins/function.md create mode 100644 doc/src/plugins/index.md create mode 100644 doc/src/plugins/module.md rename doc/src/rust/modules/{index.md => create.md} (81%) create mode 100644 doc/src/rust/packages/plugin.md diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index f897b61e..c6457b52 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -43,7 +43,11 @@ The Rhai Scripting Language 4. [Printing Custom Types](rust/print-custom.md) 9. [Packages](rust/packages/index.md) 1. [Built-in Packages](rust/packages/builtin.md) - 2. [Create a Custom Package](rust/packages/create.md) + 2. [Load a Plugin Module as a Package](rust/packages/plugin.md) + 3. [Manually Create a Custom Package](rust/packages/create.md) + 10. [Plugins](plugins/index.md) + 1. [Create a Plugin Module](plugins/module.md) + 2. [Create a Plugin Function](plugins/function.md) 5. [Rhai Language Reference](language/index.md) 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) @@ -84,7 +88,7 @@ The Rhai Scripting Language 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) - 3. [Create from Rust](rust/modules/index.md) + 3. [Create from Rust](rust/modules/create.md) 4. [Create from AST](language/modules/ast.md) 5. [Module Resolvers](rust/modules/resolvers.md) 1. [Custom Implementation](rust/modules/imp-resolver.md) diff --git a/doc/src/links.md b/doc/src/links.md index e030478a..1f666afc 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -33,6 +33,12 @@ [packages]: {{rootUrl}}/rust/packages/index.md [custom package]: {{rootUrl}}/rust/packages/create.md [custom packages]: {{rootUrl}}/rust/packages/create.md +[plugin]: {{rootUrl}}/plugins/index.md +[plugins]: {{rootUrl}}/plugins/index.md +[plugin module]: {{rootUrl}}/plugins/module.md +[plugin modules]: {{rootUrl}}/plugins/module.md +[plugin function]: {{rootUrl}}/plugins/function.md +[plugin functions]: {{rootUrl}}/plugins/function.md [`Scope`]: {{rootUrl}}/engine/scope.md [`serde`]: {{rootUrl}}/rust/serde.md diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md new file mode 100644 index 00000000..f138d5c4 --- /dev/null +++ b/doc/src/plugins/function.md @@ -0,0 +1,78 @@ +Create a Plugin Function +======================== + +{{#include ../links.md}} + + +Sometimes only a few ad hoc functions are required and it is simpler to register +individual functions instead of a full-blown [plugin module]. + + +Macros +------ + +| Macro | Apply to | Behavior | +| ------------------------ | ------------------------------------------------------------- | --------------------------------------------------------- | +| `#[export_fn]` | Rust function defined in module | Export the function | +| `#[rhai_fn(return_raw)]` | Rust function returning `Result>` | Specify that this is a fallible function | +| `register_exported_fn!` | [`Engine`] instance, register name, function name | Register function with the [`Engine`] under specific name | +| `set_exported_fn!` | [`Module`], register name, function name | Register function with the [`Module`] under specific name | + + +`#[export_fn]` and `register_exported_fn!` +----------------------------------------- + +Apply `#[export_fn]` onto a function defined at _module level_ to convert it into a Rhai plugin function. + +The function cannot be nested inside another function - it can only be defined directly under a module. + +To register the plugin function, simply call `register_exported_fn!`. The name of the function can be +any text string, so it is possible to register _overloaded_ functions as well as operators. + +```rust +use rhai::plugins::*; // import macros + +#[export_fn] +fn increment(num: &mut i64) { + *num += 1; +} + +fn main() { + let mut engine = Engine::new(); + + // 'register_exported_fn!' registers the function as 'inc' with the Engine. + register_exported_fn!(engine, "inc", increment); +} +``` + + +Fallible Functions +------------------ + +To register [fallible functions] (i.e. functions that may return errors), apply the +`#[rhai_fn(return_raw)]` attribute on plugin functions that return `Result>`. + +A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not +have the appropriate return type. + +```rust +use rhai::plugins::*; // import macros + +#[export_fn] +#[rhai_fn(return_raw)] +pub fn double_and_divide(x: i64, y: i64) -> Result> { + if y == 0 { + Err("Division by zero!".into()) + } else { + let result = (x * 2) / y; + Ok(result.into()) + } +} + +fn main() { + let mut engine = Engine::new(); + + // Overloads the operator '+' with the Engine. + register_exported_fn!(engine, "+", double_and_divide); +} +``` diff --git a/doc/src/plugins/index.md b/doc/src/plugins/index.md new file mode 100644 index 00000000..353dedd3 --- /dev/null +++ b/doc/src/plugins/index.md @@ -0,0 +1,11 @@ +Plugins +======= + +{{#include ../links.md}} + +Rhai contains a robust _plugin_ system that greatly simplifies registration of custom functions. + +Instead of the large `Engine::register_XXX` API, and the parallel `Module::set_fn_XXX` API, +a _plugin_ simplifies the work of creating and registering multiple functions into an [`Engine`]. + +Plugins are processed via a set of procedural macros under the `rhai::plugins` module. diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md new file mode 100644 index 00000000..ac558c61 --- /dev/null +++ b/doc/src/plugins/module.md @@ -0,0 +1,194 @@ +Create a Plugin Module +====================== + +{{#include ../links.md}} + + +The core of creating a plugin [module] is the `#[export_module]` attribute. + +When applied on a module definition, `#[export_module]` automatically generates Rhai-acceptable +functions from all `pub` functions defined within. + +The resulting module can then be loaded into an [`Engine`] as a normal [module], +or as a [custom package]. + + +Macros +------ + +| Macro | Apply to | Behavior | +| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- | +| `#[export_module]` | Rust module | Export all `pub` functions | +| `#[rhai_fn(skip)]` | Function in Rust module | Do not export this function | +| `#[rhai_fn(return_raw)]` | `pub` function in Rust module returning `Result>` | Specify that this is a fallible function | +| `#[rhai_fn(name = "...")]` | `pub` function in Rust module | Register function under specific name | +| `#[rhai_fn(get = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property getter under specific name | +| `#[rhai_fn(set = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property setter under specific name | +| `#[rhai_fn(index_get]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a index getter | +| `#[rhai_fn(index_set)]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a index setter | +| `#[rhai_mod(name = "...")]` | `pub` sub-module in Rust module | Export the sub-module under specific name | +| `exported_module!` | Rust module name | Create a [module] containing exported functions | + + +`#[export_module]` and `exported_module!` +---------------------------------------- + +Apply `#[export_module]` onto a standard module to convert all `pub` functions +into Rhai plugin functions. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This function will be registered as 'greet'. + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + // This function will be registered as 'get_num'. + pub fn get_num() -> i64 { + mystic_number() + } + // This function will be registered as 'increment'. + pub fn increment(num: &mut i64) { + *num += 1; + } + // This function is NOT registered. + fn mystic_number() -> i64 { + 42 + } +} + +fn main() { + let mut engine = Engine::new(); + + // 'exported_module!' creates the plugin module. + let module = exported_module!(my_module); + + // A module can simply be loaded as a custom package. + engine.load_package(module); +} +``` + +The above automatically defines a plugin module named `my_module` which can be converted into +a Rhai [module] via `exported_module!`. The functions contained within the module definition +(i.e. `greet`, `get_num` and `increment`) are automatically registered into the [`Engine`] when +`Engine::load_package` is called. + +```rust +let x = greet("world"); +x == "hello, world!"; + +let x = greet(get_num().to_string()); +x == "hello, 42!"; + +let x = get_num(); +x == 42; + +increment(x); +x == 43; +``` + + +Getters, Setters and Indexers +----------------------------- + +Functions can be marked as [getters/setters] and [indexers] for [custom types] via the `#[rhai_fn]` +attribute, which is applied on a function level. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This is a normal function 'greet'. + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + // This is a getter for 'MyType::prop'. + #[rhai_fn(get = "prop")] + pub fn get_prop(obj: &mut MyType) -> i64 { + obj.prop + } + // This is a setter for 'MyType::prop'. + #[rhai_fn(set = "prop")] + pub fn set_prop(obj: &mut MyType, value: i64) { + obj.prop = value; + } + // This is an index getter for 'MyType'. + #[rhai_fn(index_get)] + pub fn get_index(obj: &mut MyType, index: i64) -> bool { + obj.list[index] + } + // This is an index setter for 'MyType'. + #[rhai_fn(index_get)] + pub fn get_index(obj: &mut MyType, index: i64, state: bool) { + obj.list[index] = state; + } +} +``` + + +Function Overloading and Operators +--------------------------------- + +Operators and overloaded functions can be specified via `#[rhai_fn(name = "...")]` applied upon +individual functions. + +The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with +the [`Engine`], disregarding the actual name of the function. + +With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai. + +Operators (which require function names that are not valid for Rust) can also be registered this way. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This is the '+' operator for 'MyType'. + #[rhai_fn(name = "+")] + pub fn add(obj: &mut MyType, value: i64) { + obj.prop += value; + } + // This function is 'calc (i64)'. + #[rhai_fn(name = "calc")] + pub fn calc_with_default(num: i64) -> i64 { + ... + } + // This function is 'calc (i64, bool)'. + #[rhai_fn(name = "calc")] + pub fn calc_with_option(num: i64, option: bool) -> i64 { + ... + } +} +``` + + +Fallible Functions +------------------ + +To register [fallible functions] (i.e. functions that may return errors), apply the +`#[rhai_fn(return_raw)]` attribute on functions that return `Result>`. + +A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not +have the appropriate return type. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This overloads the '/' operator for i64. + #[rhai_fn(name = "/", return_raw)] + pub fn double_and_divide(x: i64, y: i64) -> Result> { + if y == 0 { + Err("Division by zero!".into()) + } else { + let result = (x * 2) / y; + Ok(result.into()) + } + } +} +``` diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/create.md similarity index 81% rename from doc/src/rust/modules/index.md rename to doc/src/rust/modules/create.md index be6576bb..26441067 100644 --- a/doc/src/rust/modules/index.md +++ b/doc/src/rust/modules/create.md @@ -3,13 +3,23 @@ Create a Module from Rust {{#include ../../links.md}} -Manually creating a [`Module`] is possible via the `Module` API. + +Create via Plugin +----------------- + +By far the simplest way to create a [module] is via a [plugin module]. + + +Create via `Module` API +----------------------- + +Manually creating a [module] is possible via the `Module` API. For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online. -Make the Module Available to the Engine --------------------------------------- +Make the `Module` Available to the `Engine` +------------------------------------------ In order to _use_ a custom module, there must be a [module resolver]. diff --git a/doc/src/rust/packages/create.md b/doc/src/rust/packages/create.md index 89f0e306..d212d38f 100644 --- a/doc/src/rust/packages/create.md +++ b/doc/src/rust/packages/create.md @@ -1,5 +1,5 @@ -Create a Custom Package -====================== +Manually Create a Custom Package +=============================== {{#include ../../links.md}} @@ -13,7 +13,7 @@ Loading a package into an [`Engine`] is functionally equivalent to calling `Engi on _each_ of the functions inside the package. But because packages are _shared_, loading an existing package is _much_ cheaper than registering all the functions one by one. -The macro `rhai::def_package!` is used to create a new custom package. +The macro `rhai::def_package!` can be used to create a new custom package. Macro Parameters @@ -53,3 +53,48 @@ def_package!(rhai:MyPackage:"My own personal super package", module, { }); }); ``` + + +Create a Custom Package from a Plugin Module +------------------------------------------- + +By far the easiest way to create a custom module is to call `Module::merge_flatten` from within +`rhai::def_package!` which simply merges in all the functions defined within a [plugin module]. + +In fact, this exactly is how Rhai's built-in packages, such as `BasicMathPackage`, are implemented. + +`rhai::plugins::exported_module!` generates a module from the [plugins][plugin module] definition, +and `Module::merge_flatten` consumes its, adding all its registered functions into the package itself. + +```rust +// Import necessary types and traits. +use rhai::{ + def_package, + packages::Package, + packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage} +}; +use rhai::plugin::*; + +// Define plugin module. +#[export_module] +mod my_module { + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + pub fn get_num() -> i64 { + 42 + } +} + +// Define the package 'MyPackage'. +def_package!(rhai:MyPackage:"My own personal super package", module, { + // Aggregate existing packages simply by calling 'init' on each. + ArithmeticPackage::init(module); + LogicPackage::init(module); + BasicArrayPackage::init(module); + BasicMapPackage::init(module); + + // Merge the plugin module into the custom package. + module.merge_flatten(exported_module!(my_module)); +}); +``` diff --git a/doc/src/rust/packages/index.md b/doc/src/rust/packages/index.md index 0b41656f..ee0daa37 100644 --- a/doc/src/rust/packages/index.md +++ b/doc/src/rust/packages/index.md @@ -11,7 +11,7 @@ packages to be used. Packages typically contain Rust functions that are callable within a Rhai script. All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). -Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`], +Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) among multiple instances of [`Engine`], even across threads (under [`sync`]). Therefore, a package only has to be created _once_. ```rust @@ -36,3 +36,21 @@ namespace alias specified in an [`import`] statement (see also [modules]). A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with the `import` statement). + + +Load a Module as a Package +-------------------------- + +Stand-alone [modules] can be loaded directly into an [`Engine`] as a package via the same `Engine::load_package` API. + +```rust +let mut module = Module::new(); + : + // add functions into module + : + +engine.load_package(module); +``` + +[Modules], however, are not _shared_, so use a [custom package] if it must be shared among multiple +instances of [`Engine`]. diff --git a/doc/src/rust/packages/plugin.md b/doc/src/rust/packages/plugin.md new file mode 100644 index 00000000..42bc9519 --- /dev/null +++ b/doc/src/rust/packages/plugin.md @@ -0,0 +1,41 @@ +Load a Plugin Module as a Package +================================ + +{{#include ../../links.md}} + +[Plugin modules] can be loaded as a package just like a normal [module]. + +```rust +use rhai::Engine; +use rhai::plugin::*; + +// Define plugin module. +#[export_module] +mod my_module { + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + pub fn get_num() -> i64 { + 42 + } +} + +fn main() { + let mut engine = Engine::new(); + + // Create plugin module. + let module = exported_module!(my_module); + + // Make the 'greet' and 'get_num' functions available to scripts. + engine.load_package(module); +} +``` + + +Share a Package Among `Engine`s +------------------------------ + +Loading a [module] via `Engine::load_package` consumes the module and it cannot be used again. + +If the functions are needed for multiple instances of [`Engine`], create a [custom package] from the +[plugin module], which can then be shared with `Package::get`. From 32985c948069c060034e89e1e2002812e299428b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 30 Aug 2020 23:18:47 +0800 Subject: [PATCH 099/103] Fix string package for no_std. --- src/packages/string_basic.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 288fc115..d6f57c02 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -126,12 +126,12 @@ gen_functions!(print_array => to_debug(Array)); #[export_fn] #[inline(always)] fn print_empty_string() -> ImmutableString { - String::new().into() + "".to_string().into() } #[export_fn] #[inline(always)] fn print_unit(_x: ()) -> ImmutableString { - String::new().into() + "".to_string().into() } #[export_fn] #[inline(always)] From ef9b90c3efcc8c39ca2c13bac8b137c4b4e5b5ce Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 31 Aug 2020 11:23:11 +0800 Subject: [PATCH 100/103] Add entry on plugins. --- README.md | 1 + RELEASES.md | 9 +++++++++ doc/src/about/features.md | 2 ++ 3 files changed, 12 insertions(+) diff --git a/README.md b/README.md index faee7e32..fbd2f3d2 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Standard features * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). +* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros. Protection against attacks -------------------------- diff --git a/RELEASES.md b/RELEASES.md index 150461eb..da91ff68 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 0.19.0 +============== + +New features +------------ + +* Plugins support via procedural macros. + + Version 0.18.3 ============== diff --git a/doc/src/about/features.md b/doc/src/about/features.md index b88599ff..cacc1885 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -66,6 +66,8 @@ Flexible * Supports [most build targets](targets.md) including `no-std` and [WASM]. +* [Plugins] system powered by procedural macros simplifies custom API development. + * Surgically [disable keywords and operators] to restrict the language. * Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators] From ee3781e86e0c000abf04585b6113ce71580901f1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 31 Aug 2020 11:46:32 +0800 Subject: [PATCH 101/103] Simplify error handling code. --- src/packages/arithmetic.rs | 139 ++++++++----------------------------- src/packages/time_basic.rs | 55 ++++++--------- 2 files changed, 52 insertions(+), 142 deletions(-) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 33b01b57..7a3b78f9 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -15,6 +15,11 @@ use num_traits::float::Float; use crate::stdlib::format; +#[inline(always)] +pub fn make_err(msg: String) -> Box { + EvalAltResult::ErrorArithmetic(msg, Position::none()).into() +} + macro_rules! gen_arithmetic_functions { ($root:ident => $($arg_type:ident),+) => { pub mod $root { $(pub mod $arg_type { @@ -26,13 +31,7 @@ macro_rules! gen_arithmetic_functions { #[inline] pub fn add(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { - x.checked_add(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Addition overflow: {} + {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))).map(Dynamic::from) } else { Ok(Dynamic::from(x + y)) } @@ -41,13 +40,7 @@ macro_rules! gen_arithmetic_functions { #[inline] pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { - x.checked_sub(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Subtraction overflow: {} - {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))).map(Dynamic::from) } else { Ok(Dynamic::from(x - y)) } @@ -56,13 +49,7 @@ macro_rules! gen_arithmetic_functions { #[inline] pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { - x.checked_mul(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Multiplication overflow: {} * {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))).map(Dynamic::from) } else { Ok(Dynamic::from(x * y)) } @@ -73,19 +60,9 @@ macro_rules! gen_arithmetic_functions { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == 0 { - EvalAltResult::ErrorArithmetic( - format!("Division by zero: {} / {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Division by zero: {} / {}", x, y))) } else { - x.checked_div(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Division overflow: {} / {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))).map(Dynamic::from) } } else { Ok(Dynamic::from(x / y)) @@ -95,13 +72,7 @@ macro_rules! gen_arithmetic_functions { #[inline] pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { - x.checked_rem(y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Modulo division by zero or overflow: {} % {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))).map(Dynamic::from) } else { Ok(Dynamic::from(x % y)) } @@ -111,25 +82,11 @@ macro_rules! gen_arithmetic_functions { pub fn power(x: INT, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y))) } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y))) } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Power overflow: {} ~ {}", x, y))).map(Dynamic::from) } } else { Ok(Dynamic::from(x.pow(y as u32))) @@ -141,25 +98,11 @@ macro_rules! gen_arithmetic_functions { pub fn shift_left(x: $arg_type, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by too many bits: {} << {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y))) } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by a negative number: {} << {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Left-shift by a negative number: {} << {}", x, y))) } else { - x.checked_shl(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by too many bits: {} << {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {} << {}", x, y))).map(Dynamic::from) } } else { Ok(Dynamic::from(x << y)) @@ -170,25 +113,11 @@ macro_rules! gen_arithmetic_functions { pub fn shift_right(x: $arg_type, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by too many bits: {} >> {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by a negative number: {} >> {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!("Right-shift by a negative number: {} >> {}", x, y))) } else { - x.checked_shr(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by too many bits: {} >> {}", x, y), - Position::none(), - ) - .into() - }).map(Dynamic::from) + x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {} >> {}", x, y))).map(Dynamic::from) } } else { Ok(Dynamic::from(x >> y)) @@ -225,10 +154,7 @@ macro_rules! gen_signed_functions { #[inline] pub fn neg(x: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }).map(Dynamic::from) + x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from) } else { Ok(Dynamic::from(-x)) } @@ -237,10 +163,7 @@ macro_rules! gen_signed_functions { #[inline] pub fn abs(x: $arg_type) -> Result> { if cfg!(not(feature = "unchecked")) { - x.checked_abs().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }).map(Dynamic::from) + x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from) } else { Ok(Dynamic::from(x.abs())) } @@ -368,11 +291,10 @@ mod f32_functions { #[inline] pub fn pow_f_i(x: f32, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Number raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!( + "Number raised to too large an index: {} ~ {}", + x, y + ))) } else { Ok(Dynamic::from(x.powi(y as i32))) } @@ -405,11 +327,10 @@ mod f64_functions { #[inline] pub fn pow_f_i(x: FLOAT, y: INT) -> Result> { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Number raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() + Err(make_err(format!( + "Number raised to too large an index: {} ~ {}", + x, y + ))) } else { Ok(x.powi(y as i32).into()) } diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 5b8e1f21..3ea2b255 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,8 +1,7 @@ #![cfg(not(feature = "no_std"))] #[cfg(feature = "no_float")] -#[cfg(not(feature = "unchecked"))] -use super::math_basic::MAX_INT; +use super::{arithmetic::make_err, math_basic::MAX_INT}; use crate::def_package; use crate::plugin::*; @@ -14,10 +13,6 @@ use crate::parser::FLOAT; #[cfg(feature = "no_float")] use crate::parser::INT; -#[cfg(feature = "no_float")] -#[cfg(not(feature = "unchecked"))] -use crate::token::Position; - #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::time::Instant; @@ -46,16 +41,14 @@ mod time_functions { { let seconds = timestamp.elapsed().as_secs(); - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp.elapsed: {}", seconds), - Position::none(), - ) - .into(); + if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + Err(make_err(format!( + "Integer overflow for timestamp.elapsed: {}", + seconds + ))) + } else { + Ok((seconds as INT).into()) } - - Ok((seconds as INT).into()) } } @@ -81,29 +74,25 @@ mod time_functions { if ts2 > ts1 { let seconds = (ts2 - ts1).as_secs(); - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: -{}", seconds), - Position::none(), - ) - .into(); + if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + Err(make_err(format!( + "Integer overflow for timestamp duration: -{}", + seconds + ))) + } else { + Ok(Dynamic::from(-(seconds as INT))) } - - Ok(Dynamic::from(-(seconds as INT))) } else { let seconds = (ts1 - ts2).as_secs(); - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: {}", seconds), - Position::none(), - ) - .into(); + if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + Err(make_err(format!( + "Integer overflow for timestamp duration: {}", + seconds + ))) + } else { + Ok((seconds as INT).into()) } - - Ok((seconds as INT).into()) } } From f4e49589739b45c8286d644f940f4c35f8bcd894 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 31 Aug 2020 12:03:30 +0800 Subject: [PATCH 102/103] Fix typos. --- doc/src/links.md | 1 + doc/src/plugins/function.md | 6 +++--- doc/src/plugins/module.md | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/src/links.md b/doc/src/links.md index 1f666afc..0231689b 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -85,6 +85,7 @@ [function]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md [function overloading]: {{rootUrl}}/rust/functions.md#function-overloading +[fallible function]: {{rootUrl}}/rust/fallible.md [fallible functions]: {{rootUrl}}/rust/fallible.md [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index f138d5c4..43af9e13 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -14,9 +14,9 @@ Macros | Macro | Apply to | Behavior | | ------------------------ | ------------------------------------------------------------- | --------------------------------------------------------- | | `#[export_fn]` | Rust function defined in module | Export the function | -| `#[rhai_fn(return_raw)]` | Rust function returning `Result>` | Specify that this is a fallible function | -| `register_exported_fn!` | [`Engine`] instance, register name, function name | Register function with the [`Engine`] under specific name | -| `set_exported_fn!` | [`Module`], register name, function name | Register function with the [`Module`] under specific name | +| `#[rhai_fn(return_raw)]` | Rust function returning `Result>` | Specify that this is a [fallible function] | +| `register_exported_fn!` | [`Engine`] instance, register name, function name | Register function into the [`Engine`] under specific name | +| `set_exported_fn!` | [`Module`], register name, function name | Register function into the [`Module`] under specific name | `#[export_fn]` and `register_exported_fn!` diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index ac558c61..22380425 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -20,12 +20,12 @@ Macros | --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- | | `#[export_module]` | Rust module | Export all `pub` functions | | `#[rhai_fn(skip)]` | Function in Rust module | Do not export this function | -| `#[rhai_fn(return_raw)]` | `pub` function in Rust module returning `Result>` | Specify that this is a fallible function | +| `#[rhai_fn(return_raw)]` | `pub` function in Rust module returning `Result>` | Specify that this is a [fallible function] | | `#[rhai_fn(name = "...")]` | `pub` function in Rust module | Register function under specific name | | `#[rhai_fn(get = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property getter under specific name | | `#[rhai_fn(set = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property setter under specific name | -| `#[rhai_fn(index_get]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a index getter | -| `#[rhai_fn(index_set)]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a index setter | +| `#[rhai_fn(index_get]` | `pub` function in Rust module (first parameter must be `&mut`) | Register an index getter | +| `#[rhai_fn(index_set)]` | `pub` function in Rust module (first parameter must be `&mut`) | Register an index setter | | `#[rhai_mod(name = "...")]` | `pub` sub-module in Rust module | Export the sub-module under specific name | | `exported_module!` | Rust module name | Create a [module] containing exported functions | @@ -121,7 +121,7 @@ mod my_module { obj.list[index] } // This is an index setter for 'MyType'. - #[rhai_fn(index_get)] + #[rhai_fn(index_set)] pub fn get_index(obj: &mut MyType, index: i64, state: bool) { obj.list[index] = state; } From 91b4f8a6bc7dcfeeb81137d271efc20da224cbff Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 31 Aug 2020 12:09:52 +0800 Subject: [PATCH 103/103] Fix no-std build. --- src/packages/arithmetic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 7a3b78f9..d72e2e8b 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -13,7 +13,7 @@ use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] use num_traits::float::Float; -use crate::stdlib::format; +use crate::stdlib::{format, string::String}; #[inline(always)] pub fn make_err(msg: String) -> Box {