diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb7b1e2..8f4dbfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ New features * An API is added to automatically generate definition files from a fully-configured `Engine`, for use with the Rhai Language Server. +### Short-hand to function pointers + +* Using a script-defined function's name (in place of a variable) implicitly creates a function pointer to the function. + Enhancements ------------ diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 2a9ed2c2..d384c210 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "rhai_codegen" -version = "1.4.1" +version = "1.4.2" edition = "2018" +resolver = "2" authors = ["jhwgh1968", "Stephen Chung"] description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" homepage = "https://rhai.rs/book/plugins/index.html" @@ -15,11 +16,11 @@ proc-macro = true default = [] metadata = [] -[dev-dependencies] -rhai = { path = "..", version = "1.6", features = ["metadata"] } -trybuild = "1" - [dependencies] proc-macro2 = "1" syn = { version = "1.0", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] } quote = "1" + +[dev-dependencies] +rhai = { path = "..", version = "1.6", features = ["metadata"] } +trybuild = "1" diff --git a/examples/definitions/.rhai/definitions/__static__.d.rhai b/examples/definitions/.rhai/definitions/__static__.d.rhai index ab0d2888..108ab58e 100644 --- a/examples/definitions/.rhai/definitions/__static__.d.rhai +++ b/examples/definitions/.rhai/definitions/__static__.d.rhai @@ -5208,6 +5208,16 @@ fn tan(x: float) -> float; fn tanh(x: float) -> float; /// Create a timestamp containing the current system time. +/// +/// # Example +/// +/// ```rhai +/// let now = timestamp(); +/// +/// sleep(10.0); // sleep for 10 seconds +/// +/// print(now.elapsed); // prints 10.??? +/// ``` fn timestamp() -> Instant; /// Convert the BLOB into an array of integers. diff --git a/scripts/static.d.rhai b/scripts/static.d.rhai deleted file mode 100644 index b6c104dc..00000000 --- a/scripts/static.d.rhai +++ /dev/null @@ -1,170 +0,0 @@ -/// This definition file extends the scope of all scripts. -/// -/// The items defined here simply exist and are available. -/// everywhere. -/// -/// These definitions should be used for built-in functions and -/// local domain-specific environment-provided values. -module static; - -/// Display any data to the standard output. -/// -/// # Example -/// -/// ```rhai -/// let answer = 42; -/// -/// print(`The Answer is ${answer}`); -/// ``` -fn print(data: ?); - -/// Display any data to the standard output in debug format. -/// -/// # Example -/// -/// ```rhai -/// let answer = 42; -/// -/// debug(answer); -/// ``` -fn debug(data: ?); - -/// Get the type of a value. -/// -/// # Example -/// -/// ```rhai -/// let x = "hello, world!"; -/// -/// print(x.type_of()); // prints "string" -/// ``` -fn type_of(data: ?) -> String; - -/// Create a function pointer to a named function. -/// -/// If the specified name is not a valid function name, an error is raised. -/// -/// # Example -/// -/// ```rhai -/// let f = Fn("foo"); // function pointer to 'foo' -/// -/// f.call(42); // call: foo(42) -/// ``` -fn Fn(fn_name: String) -> FnPtr; - -/// Call a function pointed to by a function pointer, -/// passing following arguments to the function call. -/// -/// If an appropriate function is not found, an error is raised. -/// -/// # Example -/// -/// ```rhai -/// let f = Fn("foo"); // function pointer to 'foo' -/// -/// f.call(1, 2, 3); // call: foo(1, 2, 3) -/// ``` -fn call(fn_ptr: FnPtr, ...args: ?) -> ?; - -/// Call a function pointed to by a function pointer, binding the `this` pointer -/// to the object of the method call, and passing on following arguments to the function call. -/// -/// If an appropriate function is not found, an error is raised. -/// -/// # Example -/// -/// ```rhai -/// fn add(x) { -/// this + x -/// } -/// -/// let f = Fn("add"); // function pointer to 'add' -/// -/// let x = 41; -/// -/// let r = x.call(f, 1); // call: add(1) with 'this' = 'x' -/// -/// print(r); // prints 42 -/// ``` -fn call(obj: ?, fn_ptr: FnPtr, ...args: ?) -> ?; - -/// Curry a number of arguments into a function pointer and return it as a new function pointer. -/// -/// # Example -/// -/// ```rhai -/// fn foo(x, y, z) { -/// x + y + z -/// } -/// -/// let f = Fn("foo"); -/// -/// let g = f.curry(1, 2); // curried arguments: 1, 2 -/// -/// g.call(3); // call: foo(1, 2, 3) -/// ``` -fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr; - -/// Return `true` if a script-defined function exists with a specified name and -/// number of parameters. -/// -/// # Example -/// -/// ```rhai -/// fn foo(x) { } -/// -/// print(is_def_fn("foo", 1)); // prints true -/// print(is_def_fn("foo", 2)); // prints false -/// print(is_def_fn("foo", 0)); // prints false -/// print(is_def_fn("bar", 1)); // prints false -/// ``` -fn is_def_fn(fn_name: String, num_params: i64) -> bool; - -/// Return `true` if a variable matching a specified name is defined. -/// -/// # Example -/// -/// ```rhai -/// let x = 42; -/// -/// print(is_def_var("x")); // prints true -/// print(is_def_var("foo")); // prints false -/// -/// { -/// let y = 1; -/// print(is_def_var("y")); // prints true -/// } -/// -/// print(is_def_var("y")); // prints false -/// ``` -fn is_def_var(var_name: String) -> bool; - -/// Return `true` if the variable is shared. -/// -/// # Example -/// -/// ```rhai -/// let x = 42; -/// -/// print(is_shared(x)); // prints false -/// -/// let f = || x; // capture 'x', making it shared -/// -/// print(is_shared(x)); // prints true -/// ``` -fn is_shared(variable: ?) -> bool; - -/// Evaluate a text script within the current scope. -/// -/// # Example -/// -/// ```rhai -/// let x = 42; -/// -/// eval("let y = x; x = 123;"); -/// -/// print(x); // prints 123 -/// print(y); // prints 42 -/// ``` -fn eval(script: String) -> ?; diff --git a/src/api/options.rs b/src/api/options.rs index 4397e066..821a98c5 100644 --- a/src/api/options.rs +++ b/src/api/options.rs @@ -57,7 +57,6 @@ impl Engine { } /// Set whether `if`-expression is allowed. #[inline(always)] - #[must_use] pub fn set_allow_if_expression(&mut self, enable: bool) { self.options.set(LangOptions::IF_EXPR, enable); } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index b46367df..071aa45b 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -149,6 +149,18 @@ impl Engine { } _ if global.always_search_scope => (0, expr.start_position()), Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos), + // Scripted function with the same name + #[cfg(not(feature = "no_function"))] + Expr::Variable(v, None, pos) + if lib + .iter() + .flat_map(|&m| m.iter_script_fn()) + .any(|(_, _, f, ..)| f == v.3) => + { + let val: Dynamic = + crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into(); + return Ok((val.into(), *pos)); + } Expr::Variable(v, None, pos) => (v.0.map_or(0, NonZeroUsize::get), *pos), _ => unreachable!("Expr::Variable expected but gets {:?}", expr), }; diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index bb7ac905..86a3d0a9 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -244,12 +244,12 @@ impl Engine { // We shouldn't do this for too many variants because, soon or later, the added comparisons // will cost more than the mis-predicted `match` branch. if let Stmt::Assignment(x, ..) = stmt { + let (op_info, BinaryExpr { lhs, rhs }) = &**x; + #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, stmt.position())?; - let result = if x.1.lhs.is_variable_access(false) { - let (op_info, BinaryExpr { lhs, rhs }) = &**x; - + let result = if let Expr::Variable(x, ..) = lhs { let rhs_result = self .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) .map(Dynamic::flatten); @@ -261,7 +261,7 @@ impl Engine { if let Ok(search_val) = search_result { let (mut lhs_ptr, pos) = search_val; - let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`"); + let var_name = x.3.as_str(); #[cfg(not(feature = "no_closure"))] // Also handle case where target is a `Dynamic` shared value diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 4f51f8fe..b8bfa175 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -26,6 +26,16 @@ def_package! { #[export_module] mod time_functions { /// Create a timestamp containing the current system time. + /// + /// # Example + /// + /// ```rhai + /// let now = timestamp(); + /// + /// sleep(10.0); // sleep for 10 seconds + /// + /// print(now.elapsed); // prints 10.??? + /// ``` pub fn timestamp() -> Instant { Instant::now() } diff --git a/src/parser.rs b/src/parser.rs index ec13dd3e..16065b48 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -159,17 +159,34 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `ParseState::stack::len()`, /// i.e. the top element of [`ParseState`]'s variables stack is offset 1. /// - /// Return `None` when the variable name is not found in the `stack`. + /// # Return value: `(index, is_func)` + /// + /// * `index`: `None` when the variable name is not found in the `stack`, + /// otherwise the index value. + /// + /// * `is_func`: `true` if the variable is actually the name of a function + /// (in which case it will be converted into a function pointer). #[inline] #[must_use] - pub fn access_var(&mut self, name: &str, pos: Position) -> Option { + pub fn access_var( + &mut self, + name: &str, + lib: &FnLib, + pos: Position, + ) -> (Option, bool) { let _pos = pos; let (index, hit_barrier) = self.find_var(name); + #[cfg(not(feature = "no_function"))] + let is_func = lib.values().any(|f| f.name == name); + + #[cfg(feature = "no_function")] + let is_func = false; + #[cfg(not(feature = "no_closure"))] if self.allow_capture { - if index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) { + if !is_func && index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) { self.external_vars.push(crate::ast::Ident { name: name.into(), pos: _pos, @@ -179,11 +196,13 @@ impl<'e> ParseState<'e> { self.allow_capture = true; } - if hit_barrier { + let index = if hit_barrier { None } else { NonZeroUsize::new(index) - } + }; + + (index, is_func) } /// Find a module by name in the [`ParseState`], searching in reverse. @@ -1366,12 +1385,13 @@ impl Engine { #[cfg(not(feature = "no_closure"))] new_state.external_vars.iter().try_for_each( |crate::ast::Ident { name, pos }| { - let index = state.access_var(name, *pos); + let (index, is_func) = state.access_var(name, lib, *pos); if settings.options.contains(LangOptions::STRICT_VAR) && !settings.is_closure_scope && index.is_none() && !state.scope.contains(name) + && !is_func { // If the parent scope is not inside another capturing closure // then we can conclude that the captured variable doesn't exist. @@ -1512,11 +1532,12 @@ impl Engine { } // Normal variable access _ => { - let index = state.access_var(&s, settings.pos); + let (index, is_func) = state.access_var(&s, lib, settings.pos); if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() && !state.scope.contains(&s) + && !is_func { return Err( PERR::VariableUndefined(s.to_string()).into_err(settings.pos) diff --git a/src/serde/de.rs b/src/serde/de.rs index 747b0597..aa6bbd66 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,9 +1,7 @@ //! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. use crate::types::dynamic::Union; -use crate::{ - Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, SmartString, ERR, -}; +use crate::{Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, ERR}; use serde::de::{Error, IntoDeserializer, Visitor}; use serde::{Deserialize, Deserializer}; #[cfg(feature = "no_std")] @@ -422,7 +420,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { || self.type_error(), |map| { _visitor.visit_map(IterateMap::new( - map.keys().map(SmartString::as_str), + map.keys().map(crate::SmartString::as_str), map.values(), )) }, diff --git a/tests/call_fn.rs b/tests/call_fn.rs index fa333a62..30a02cc1 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -186,7 +186,7 @@ fn test_fn_ptr_raw() -> Result<(), Box> { fn foo(x) { this += x; } let x = 41; - x.bar(Fn("foo"), 1); + x.bar(foo, 1); x "# )?, diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index b7174efa..1e2baa84 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -77,6 +77,20 @@ fn test_fn_ptr() -> Result<(), Box> { if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..)) )); + #[cfg(not(feature = "no_function"))] + assert_eq!( + engine.eval::( + r#" + fn foo(x) { x + 1 } + let f = foo; + let g = 42; + g = foo; + call(f, 39) + call(g, 1) + "# + )?, + 42 + ); + Ok(()) } diff --git a/tests/options.rs b/tests/options.rs index cbe0f70a..27ff3ca2 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -104,6 +104,10 @@ fn test_options_strict_var() -> Result<(), Box> { #[cfg(not(feature = "no_function"))] { + assert_eq!( + engine.eval_with_scope::(&mut scope, "fn foo(z) { z } let f = foo; call(f, x)")?, + 42 + ); assert!(engine.compile("let f = |y| x * y;").is_err()); #[cfg(not(feature = "no_closure"))] {