From cf36dc5a57d8d060e41e59e3093ea8059356781e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Jul 2020 17:14:55 +0800 Subject: [PATCH 01/14] Support for anonymous functions. --- RELEASES.md | 5 + doc/src/language/fn-ptr.md | 57 +++++++- doc/src/language/object-maps-oop.md | 16 +- doc/src/language/oop.md | 21 ++- doc/src/links.md | 2 + src/any.rs | 1 + src/engine.rs | 23 ++- src/error.rs | 3 + src/module.rs | 2 +- src/optimize.rs | 20 ++- src/parser.rs | 218 ++++++++++++++++++++++------ 11 files changed, 294 insertions(+), 74 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 9a48b709..b09e10ce 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,12 +4,17 @@ Rhai Release Notes Version 0.18.0 ============== +This version adds: + +* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions. + New features ------------ * `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. * Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. +* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. Version 0.17.0 diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index 57f2f24f..1552a46a 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -163,4 +163,59 @@ x == 42; Beware that this only works for _method-call_ style. Normal function-call style cannot bind the `this` pointer (for syntactic reasons). -Therefore, obviously, binding the `this` pointer is unsupported under [`no_function`]. +Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`]. + + +Anonymous Functions +------------------- + +Sometimes it gets tedious to define separate functions only to dispatch them via single function pointers. +This scenario is especially common when simulating object-oriented programming ([OOP]). + +```rust +// Define object +let obj = #{ + data: 42, + increment: Fn("inc_obj"), // use function pointers to + decrement: Fn("dec_obj"), // refer to method functions + print: Fn("print_obj") +}; + +// Define method functions one-by-one +fn inc_obj(x) { this.data += x; } +fn dec_obj(x) { this.data -= x; } +fn print_obj() { print(this.data); } +``` + +The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures +(but they are **NOT** closures, merely syntactic sugar): + +```rust +let obj = #{ + data: 42, + increment: |x| this.data += x, // one-liner + decrement: |x| this.data -= x, + print_obj: || { print(this.data); } // full function body +}; +``` + +The anonymous functions will be expanded into separate functions in the global namespace: + +```rust +let obj = #{ + data: 42, + increment: Fn("anon_fn_1000"), + decrement: Fn("anon_fn_1001"), + print: Fn("anon_fn_1002") +}; + +fn anon_fn_1000(x) { this.data += x; } +fn anon_fn_1001(x) { this.data -= x; } +fn anon_fn_1002() { print this.data; } +``` + +### WARNING - NOT Closures + +Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves +**not** closures. In particular, they do not capture their running environment. They are more like +Rust's function pointers. diff --git a/doc/src/language/object-maps-oop.md b/doc/src/language/object-maps-oop.md index 4727871b..c93f6847 100644 --- a/doc/src/language/object-maps-oop.md +++ b/doc/src/language/object-maps-oop.md @@ -20,17 +20,21 @@ to the [object map] before the function is called. There is no way to simulate via a normal function-call syntax because all scripted function arguments are passed by value. ```rust -fn do_action(x) { print(this.data + x); } // 'this' binds to the object when called +fn do_action(x) { this.data += x; } // 'this' binds to the object when called let obj = #{ data: 40, - action: Fn("do_action") // 'action' holds a function pointer to 'do_action' + action: Fn("do_action") // 'action' holds a function pointer to 'do_action' }; -obj.action(2); // Short-hand syntax: prints 42 +obj.action(2); // Calls 'do_action' with `this` bound to 'obj' -// To achieve the above with normal function pointer calls: -fn do_action(map, x) { print(map.data + x); } +obj.call(obj.action, 2); // The above de-sugars to this -obj.action.call(obj, 2); // this call cannot mutate 'obj' +obj.data == 42; + +// To achieve the above with normal function pointer call will fail. +fn do_action(map, x) { map.data += x; } // 'map' is a copy + +obj.action.call(obj, 2); // 'obj' is passed as a copy by value ``` diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md index 120b42d0..e34a8ab2 100644 --- a/doc/src/language/oop.md +++ b/doc/src/language/oop.md @@ -17,23 +17,22 @@ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-m | [Object map] properties holding values | properties | | [Object map] properties that hold [function pointers] | methods | +When a property of an [object map] is called like a method function, and if it happens to hold +a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be +dispatched to the actual function with `this` binding to the [object map] itself. Examples -------- ```rust // Define the object -let obj = #{ - data: 0, - increment: Fn("add"), // when called, 'this' binds to 'obj' - update: Fn("update"), // when called, 'this' binds to 'obj' - action: Fn("action") // when called, 'this' binds to 'obj' - }; - -// Define functions -fn add(x) { this.data += x; } // update using 'this' -fn update(x) { this.data = x; } // update using 'this' -fn action() { print(this.data); } // access properties of 'this' +let obj = + #{ + data: 0, + increment: |x| this.data += x, // when called, 'this' binds to 'obj' + update: |x| this.data = x, // when called, 'this' binds to 'obj' + action: || print(this.data) // when called, 'this' binds to 'obj' + }; // Use the object obj.increment(1); diff --git a/doc/src/links.md b/doc/src/links.md index 82ceab3b..4cdb83e8 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -78,6 +78,8 @@ [function pointers]: {{rootUrl}}/language/fn-ptr.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md +[anonymous function]: {{rootUrl}}/language/fn-ptr.md#anonymous-functions +[anonymous functions]: {{rootUrl}}/language/fn-ptr.md#anonymous-functions [`Module`]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md diff --git a/src/any.rs b/src/any.rs index 5182da08..4a330cff 100644 --- a/src/any.rs +++ b/src/any.rs @@ -18,6 +18,7 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, fmt, + hash::Hash, string::String, vec::Vec, }; diff --git a/src/engine.rs b/src/engine.rs index dd1879cc..16205a2f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -87,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$"; +pub const FN_ANONYMOUS: &str = "anon$"; #[cfg(feature = "internals")] pub const MARKER_EXPR: &str = "$expr$"; @@ -654,7 +655,7 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, is_method: bool, - def_val: Option<&Dynamic>, + def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; @@ -815,7 +816,7 @@ impl Engine { // Return default value (if any) if let Some(val) = def_val { - return Ok((val.clone(), false)); + return Ok((val.into(), false)); } // Getter function not found? @@ -976,7 +977,7 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, is_method: bool, - def_val: Option<&Dynamic>, + def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. @@ -1081,7 +1082,6 @@ impl Engine { let is_ref = target.is_ref(); let is_value = target.is_value(); - let def_val = def_val.as_ref(); // Get a reference to the mutation target Dynamic let obj = target.as_mut(); @@ -1100,7 +1100,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, def_val, level, + state, lib, fn_name, *native, hash, args, false, false, *def_val, level, ) } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { // FnPtr call on object @@ -1120,7 +1120,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, *native, hash, args, is_ref, true, def_val, level, + state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, ) } else { let redirected: Option; @@ -1146,7 +1146,7 @@ impl Engine { let args = arg_values.as_mut(); self.exec_fn_call( - state, lib, fn_name, *native, hash, args, is_ref, true, def_val, level, + state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level, ) } .map_err(|err| err.new_position(*pos))?; @@ -1694,13 +1694,12 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value)) => { let op = "=="; - let def_value = false.into(); let mut scope = Scope::new(); // Call the `==` operator to compare each value for value in rhs_value.iter_mut() { + let def_value = Some(false); let args = &mut [&mut lhs_value.clone(), value]; - let def_value = Some(&def_value); let hashes = ( // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. @@ -1782,6 +1781,7 @@ impl Engine { Expr::FloatConstant(x) => Ok(x.0.into()), Expr::StringConstant(x) => Ok(x.0.to_string().into()), Expr::CharConstant(x) => Ok(x.0.into()), + Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()), Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { if let Some(ref val) = this_ptr { Ok((*val).clone()) @@ -1941,7 +1941,6 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); - let def_val = def_val.as_ref(); // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { @@ -2072,7 +2071,7 @@ impl Engine { let args = args.as_mut(); self.exec_fn_call( - state, lib, name, *native, hash, args, is_ref, false, def_val, level, + state, lib, name, *native, hash, args, is_ref, false, *def_val, level, ) .map(|(v, _)| v) .map_err(|err| err.new_position(*pos)) @@ -2166,7 +2165,7 @@ impl Engine { .map_err(|err| err.new_position(*pos)), Err(err) => match *err { EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { - Ok(def_val.clone().unwrap()) + Ok(def_val.unwrap().into()) } EvalAltResult::ErrorFunctionNotFound(_, _) => { Err(Box::new(EvalAltResult::ErrorFunctionNotFound( diff --git a/src/error.rs b/src/error.rs index 1f4b08bf..86c49091 100644 --- a/src/error.rs +++ b/src/error.rs @@ -206,6 +206,9 @@ impl fmt::Display for ParseErrorType { Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s), + Self::FnMissingBody(s) if s.is_empty() => { + f.write_str("Expecting body statement block for anonymous function") + } Self::FnMissingBody(s) => { write!(f, "Expecting body statement block for function '{}'", s) } diff --git a/src/module.rs b/src/module.rs index 5a6a604c..3ee25995 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1098,7 +1098,7 @@ impl Module { /// /// A `StaticVec` is used because most module-level access contains only one level, /// and it is wasteful to always allocate a `Vec` with one element. -#[derive(Clone, Eq, PartialEq, Default)] +#[derive(Clone, Eq, PartialEq, Default, Hash)] pub struct ModuleRef(StaticVec<(String, Position)>, Option); impl fmt::Debug for ModuleRef { diff --git a/src/optimize.rs b/src/optimize.rs index 3da46ffa..cbfbffe0 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,9 +1,12 @@ use crate::any::Dynamic; use crate::calc_fn_hash; -use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; +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::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; +use crate::token::is_valid_identifier; use crate::utils::StaticVec; #[cfg(feature = "internals")] @@ -528,6 +531,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { Expr::FnCall(x) } + // Fn("...") + Expr::FnCall(x) + if x.1.is_none() + && (x.0).0 == KEYWORD_FN_PTR + && x.3.len() == 1 + && matches!(x.3[0], Expr::StringConstant(_)) + => { + match &x.3[0] { + Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()), + _ => Expr::FnCall(x) + } + } + // Eagerly call functions Expr::FnCall(mut x) if x.1.is_none() // Non-qualified @@ -571,7 +587,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { Some(arg_for_type_of.to_string().into()) } else { // Otherwise use the default value, if any - def_value.clone() + def_value.map(|v| v.into()) } }) .and_then(|result| map_dynamic_to_expr(result, *pos)) diff --git a/src/parser.rs b/src/parser.rs index a256deef..610bc6e2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,7 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{make_getter, make_setter, Engine, KEYWORD_THIS}; +use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; @@ -35,6 +35,12 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_std"))] +use crate::stdlib::collections::hash_map::DefaultHasher; + +#[cfg(feature = "no_std")] +use ahash::AHasher; + /// The system integer type. /// /// If the `only_i32` feature is enabled, this will be `i32` instead. @@ -598,21 +604,34 @@ impl Hash for CustomExpr { } } +#[cfg(not(feature = "no_float"))] +#[derive(Debug, PartialEq, PartialOrd, Clone)] +pub struct FloatWrapper(pub FLOAT, pub Position); + +impl Hash for FloatWrapper { + fn hash(&self, state: &mut H) { + state.write(&self.0.to_le_bytes()); + self.1.hash(state); + } +} + /// An expression. /// /// Each variant is at most one pointer in size (for speed), /// with everything being allocated together in one single tuple. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub enum Expr { /// Integer constant. IntegerConstant(Box<(INT, Position)>), /// Floating-point constant. #[cfg(not(feature = "no_float"))] - FloatConstant(Box<(FLOAT, Position)>), + FloatConstant(Box), /// Character constant. CharConstant(Box<(char, Position)>), /// String constant. StringConstant(Box<(ImmutableString, Position)>), + /// FnPtr constant. + FnPointer(Box<(ImmutableString, Position)>), /// Variable access - ((variable name, position), optional modules, hash, optional index) Variable( Box<( @@ -637,7 +656,7 @@ pub enum Expr { Option>, u64, StaticVec, - Option, + Option, )>, ), /// expr op= expr @@ -673,18 +692,6 @@ impl Default for Expr { } } -impl Hash for Expr { - fn hash(&self, state: &mut H) { - match self { - Self::FloatConstant(x) => { - state.write(&x.0.to_le_bytes()); - x.1.hash(state); - } - _ => self.hash(state), - } - } -} - impl Expr { /// Get the `Dynamic` value of a constant expression. /// @@ -758,6 +765,7 @@ impl Expr { Self::IntegerConstant(x) => x.1, Self::CharConstant(x) => x.1, Self::StringConstant(x) => x.1, + Self::FnPointer(x) => x.1, Self::Array(x) => x.1, Self::Map(x) => x.1, Self::Property(x) => x.1, @@ -791,6 +799,7 @@ impl Expr { Self::IntegerConstant(x) => x.1 = new_pos, Self::CharConstant(x) => x.1 = new_pos, Self::StringConstant(x) => x.1 = new_pos, + Self::FnPointer(x) => x.1 = new_pos, Self::Array(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos, Self::Variable(x) => (x.0).1 = new_pos, @@ -847,6 +856,7 @@ impl Expr { Self::IntegerConstant(_) | Self::CharConstant(_) | Self::StringConstant(_) + | Self::FnPointer(_) | Self::True(_) | Self::False(_) | Self::Unit(_) => true, @@ -878,6 +888,7 @@ impl Expr { Self::IntegerConstant(_) | Self::CharConstant(_) + | Self::FnPointer(_) | Self::In(_) | Self::And(_) | Self::Or(_) @@ -1476,7 +1487,7 @@ fn parse_primary( let mut root_expr = match token { Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), #[cfg(not(feature = "no_float"))] - Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))), + Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { @@ -1616,7 +1627,10 @@ fn parse_unary( .map(|i| Expr::IntegerConstant(Box::new((i, pos)))) .or_else(|| { #[cfg(not(feature = "no_float"))] - return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos)))); + return Some(Expr::FloatConstant(Box::new(FloatWrapper( + -(x.0 as FLOAT), + pos, + )))); #[cfg(feature = "no_float")] return None; }) @@ -1625,7 +1639,9 @@ fn parse_unary( // Negative float #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))), + Expr::FloatConstant(x) => { + Ok(Expr::FloatConstant(Box::new(FloatWrapper(-x.0, x.1)))) + } // Call negative function expr => { @@ -1664,9 +1680,38 @@ fn parse_unary( None, hash, args, - Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false + Some(false), // NOT operator, when operating on invalid operand, defaults to false )))) } + // | ... + #[cfg(not(feature = "no_function"))] + Token::Pipe | Token::Or => { + let mut state = ParseState::new( + state.engine, + state.max_function_expr_depth, + state.max_function_expr_depth, + ); + + let settings = ParseSettings { + allow_if_expr: true, + allow_stmt_expr: true, + allow_anonymous_fn: true, + is_global: false, + is_function_scope: true, + is_breakable: false, + level: 0, + pos: *token_pos, + }; + + let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?; + + // Qualifiers (none) + function name + number of arguments. + let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); + + lib.insert(hash, func); + + Ok(expr) + } // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens @@ -2018,7 +2063,7 @@ fn parse_binary_op( settings.pos = pos; settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let cmp_def = Some(false.into()); + let cmp_def = Some(false); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, 2, empty()); let op = (op, true, pos); @@ -2041,7 +2086,7 @@ fn parse_binary_op( | Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))), // '!=' defaults to true when passed invalid operands - Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))), + Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true)))), // Comparison operators default to false when passed invalid operands Token::EqualsTo @@ -2762,32 +2807,28 @@ fn parse_fn( let mut params = Vec::new(); if !match_token(input, Token::RightParen)? { - let end_err = format!("to close the parameters list of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name); loop { - match input.peek().unwrap() { - (Token::RightParen, _) => (), - _ => match input.next().unwrap() { - (Token::Identifier(s), pos) => { - state.stack.push((s.clone(), ScopeEntryType::Normal)); - params.push((s, pos)) - } - (Token::LexError(err), pos) => return Err(err.into_err(pos)), - (_, pos) => { - return Err( - PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos) - ) - } - }, + match input.next().unwrap() { + (Token::RightParen, _) => break, + (Token::Identifier(s), pos) => { + state.stack.push((s.clone(), ScopeEntryType::Normal)); + params.push((s, pos)) + } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken( + Token::RightParen.into(), + format!("to close the parameters list of function '{}'", name), + ) + .into_err(pos)) + } } match input.next().unwrap() { (Token::RightParen, _) => break, (Token::Comma, _) => (), - (Token::Identifier(_), pos) => { - return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) - } (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) @@ -2831,6 +2872,101 @@ fn parse_fn( }) } +/// Parse an anonymous function definition. +#[cfg(not(feature = "no_function"))] +fn parse_anon_fn( + input: &mut TokenStream, + state: &mut ParseState, + lib: &mut FunctionsLib, + mut settings: ParseSettings, +) -> Result<(Expr, ScriptFnDef), ParseError> { + settings.ensure_level_within_max_limit(state.max_expr_depth)?; + + let mut params = Vec::new(); + + if input.next().unwrap().0 != Token::Or { + if !match_token(input, Token::Pipe)? { + loop { + match input.next().unwrap() { + (Token::Pipe, _) => break, + (Token::Identifier(s), pos) => { + state.stack.push((s.clone(), ScopeEntryType::Normal)); + params.push((s, pos)) + } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken( + Token::Pipe.into(), + "to close the parameters list of anonymous function".into(), + ) + .into_err(pos)) + } + } + + match input.next().unwrap() { + (Token::Pipe, _) => break, + (Token::Comma, _) => (), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), + (_, pos) => { + return Err(PERR::MissingToken( + Token::Comma.into(), + "to separate the parameters of anonymous function".into(), + ) + .into_err(pos)) + } + } + } + } + } + + // Check for duplicating parameters + params + .iter() + .enumerate() + .try_for_each(|(i, (p1, _))| { + params + .iter() + .skip(i + 1) + .find(|(p2, _)| p2 == p1) + .map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos))) + }) + .map_err(|(p, pos)| PERR::FnDuplicatedParam("".to_string(), p.to_string()).into_err(pos))?; + + // Parse function body + settings.is_breakable = false; + let pos = input.peek().unwrap().1; + let body = parse_stmt(input, state, lib, settings.level_up()) + .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; + + let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + + // Calculate hash + #[cfg(feature = "no_std")] + let mut s: AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] + let mut s = DefaultHasher::new(); + + s.write_usize(params.len()); + params.iter().for_each(|a| a.hash(&mut s)); + body.hash(&mut s); + let hash = s.finish(); + + // Create unique function name + let fn_name = format!("{}{}", FN_ANONYMOUS, hash); + + let script = ScriptFnDef { + name: fn_name.clone(), + access: FnAccess::Public, + params, + body, + pos: settings.pos, + }; + + let expr = Expr::FnPointer(Box::new((fn_name.into(), settings.pos))); + + Ok((expr, script)) +} + impl Engine { pub(crate) fn parse_global_expr( &self, @@ -2951,7 +3087,7 @@ impl Engine { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { match value.0 { #[cfg(not(feature = "no_float"))] - Union::Float(value) => Some(Expr::FloatConstant(Box::new((value, pos)))), + Union::Float(value) => Some(Expr::FloatConstant(Box::new(FloatWrapper(value, pos)))), Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), From 686d40d4ae10005480e8cb34f827d582dc46483e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Jul 2020 21:18:13 +0800 Subject: [PATCH 02/14] Move anonymous function to own chapter. --- doc/src/SUMMARY.md | 1 + doc/src/language/fn-anon.md | 57 +++++++++++++++++++++++++++++++++++++ doc/src/language/fn-ptr.md | 55 ----------------------------------- doc/src/links.md | 4 +-- 4 files changed, 60 insertions(+), 57 deletions(-) create mode 100644 doc/src/language/fn-anon.md diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index e344c306..d86aae6a 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -75,6 +75,7 @@ The Rhai Scripting Language 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) 15. [Print and Debug](language/print-debug.md) 16. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md new file mode 100644 index 00000000..21ceb7a3 --- /dev/null +++ b/doc/src/language/fn-anon.md @@ -0,0 +1,57 @@ +Anonymous Functions +=================== + +{{#include ../links.md}} + +Sometimes it gets tedious to define separate functions only to dispatch them via single [function pointers]. +This scenario is especially common when simulating object-oriented programming ([OOP]). + +```rust +// Define object +let obj = #{ + data: 42, + increment: Fn("inc_obj"), // use function pointers to + decrement: Fn("dec_obj"), // refer to method functions + print: Fn("print_obj") +}; + +// Define method functions one-by-one +fn inc_obj(x) { this.data += x; } +fn dec_obj(x) { this.data -= x; } +fn print_obj() { print(this.data); } +``` + +The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures +(but they are **NOT** closures, merely syntactic sugar): + +```rust +let obj = #{ + data: 42, + increment: |x| this.data += x, // one-liner + decrement: |x| this.data -= x, + print_obj: || { print(this.data); } // full function body +}; +``` + +The anonymous functions will be hoisted into separate functions in the global namespace. +The above is equivalent to: + +```rust +let obj = #{ + data: 42, + increment: Fn("anon_fn_1000"), + decrement: Fn("anon_fn_1001"), + print: Fn("anon_fn_1002") +}; + +fn anon_fn_1000(x) { this.data += x; } +fn anon_fn_1001(x) { this.data -= x; } +fn anon_fn_1002() { print this.data; } +``` + +WARNING - NOT Closures +---------------------- + +Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves +**not** closures. In particular, they do not capture their running environment. They are more like +Rust's function pointers. diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index 1552a46a..833cfaa2 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -164,58 +164,3 @@ Beware that this only works for _method-call_ style. Normal function-call style the `this` pointer (for syntactic reasons). Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`]. - - -Anonymous Functions -------------------- - -Sometimes it gets tedious to define separate functions only to dispatch them via single function pointers. -This scenario is especially common when simulating object-oriented programming ([OOP]). - -```rust -// Define object -let obj = #{ - data: 42, - increment: Fn("inc_obj"), // use function pointers to - decrement: Fn("dec_obj"), // refer to method functions - print: Fn("print_obj") -}; - -// Define method functions one-by-one -fn inc_obj(x) { this.data += x; } -fn dec_obj(x) { this.data -= x; } -fn print_obj() { print(this.data); } -``` - -The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures -(but they are **NOT** closures, merely syntactic sugar): - -```rust -let obj = #{ - data: 42, - increment: |x| this.data += x, // one-liner - decrement: |x| this.data -= x, - print_obj: || { print(this.data); } // full function body -}; -``` - -The anonymous functions will be expanded into separate functions in the global namespace: - -```rust -let obj = #{ - data: 42, - increment: Fn("anon_fn_1000"), - decrement: Fn("anon_fn_1001"), - print: Fn("anon_fn_1002") -}; - -fn anon_fn_1000(x) { this.data += x; } -fn anon_fn_1001(x) { this.data -= x; } -fn anon_fn_1002() { print this.data; } -``` - -### WARNING - NOT Closures - -Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves -**not** closures. In particular, they do not capture their running environment. They are more like -Rust's function pointers. diff --git a/doc/src/links.md b/doc/src/links.md index 4cdb83e8..a26a34e8 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -78,8 +78,8 @@ [function pointers]: {{rootUrl}}/language/fn-ptr.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md -[anonymous function]: {{rootUrl}}/language/fn-ptr.md#anonymous-functions -[anonymous functions]: {{rootUrl}}/language/fn-ptr.md#anonymous-functions +[anonymous function]: {{rootUrl}}/language/fn-anon.md +[anonymous functions]: {{rootUrl}}/language/fn-anon.md [`Module`]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md From 3b9422ea3c93fa42f1e493e39a80d1e57d7fa0e1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Jul 2020 21:36:16 +0800 Subject: [PATCH 03/14] Adddefault_features = false for no_std. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index fe72ee21..f6ff2b03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ optional = true [dependencies.core-error] version = "0.0.0" +default_features = false features = ["alloc"] optional = true From c6caef7285b878df8c3248ebc551de0a2fbff943 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Jul 2020 22:44:00 +0800 Subject: [PATCH 04/14] Set default features of serde to false. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f6ff2b03..46c208c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,9 +67,9 @@ features = ["compile-time-rng"] optional = true [dependencies.serde] -package = "serde" version = "1.0.111" -features = ["derive"] +default_features = false +features = ["derive", "alloc"] optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] From 700060b6e70f44a552051135511d9586625ca2af Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Jul 2020 23:32:16 +0800 Subject: [PATCH 05/14] Remove no_std example. --- doc/src/start/examples/rust.md | 1 - examples/no_std.rs | 23 ----------------------- 2 files changed, 24 deletions(-) delete mode 100644 examples/no_std.rs diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md index b7b6a5a5..3784778d 100644 --- a/doc/src/start/examples/rust.md +++ b/doc/src/start/examples/rust.md @@ -10,7 +10,6 @@ A number of examples can be found in the `examples` folder: | [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. | | [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. | | [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. | -| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds.
The [`no_std`] feature is required to build in `no-std`. | | [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. | | [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. | | [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).
The [`serde`] feature is required to run. | diff --git a/examples/no_std.rs b/examples/no_std.rs deleted file mode 100644 index a6902f72..00000000 --- a/examples/no_std.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![cfg_attr(feature = "no_std", no_std)] - -use rhai::{Engine, EvalAltResult, INT}; - -#[cfg(feature = "no_std")] -extern crate alloc; - -#[cfg(feature = "no_std")] -use alloc::boxed::Box; - -fn main() -> Result<(), Box> { - let engine = Engine::new(); - - let result = engine.eval::("40 + 2")?; - - #[cfg(not(feature = "no_std"))] - println!("Answer: {}", result); - - #[cfg(feature = "no_std")] - assert_eq!(result, 42); - - Ok(()) -} From a5fa8322e915e405d5f3120f7ac6d1caf6a96697 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 20 Jul 2020 20:23:35 +0800 Subject: [PATCH 06/14] Avoid pulling in std for no-std. --- Cargo.toml | 1 + src/packages/arithmetic.rs | 3 +++ src/packages/math_basic.rs | 3 +++ src/parser.rs | 1 + 4 files changed, 8 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 46c208c3..bf4e0dc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ codegen-units = 1 [dependencies.libm] version = "0.2.1" +default_features = false optional = true [dependencies.core-error] diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 7a548ff0..8aeb1590 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -7,6 +7,9 @@ use crate::token::Position; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; +#[cfg(not(feature = "no_float"))] +use num_traits::*; + use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index e7ce43b7..ca0bc374 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -6,6 +6,9 @@ use crate::token::Position; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; +#[cfg(not(feature = "no_float"))] +use num_traits::*; + use crate::stdlib::{boxed::Box, format, i32, i64}; #[cfg(feature = "only_i32")] diff --git a/src/parser.rs b/src/parser.rs index 610bc6e2..ce0eb686 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -608,6 +608,7 @@ impl Hash for CustomExpr { #[derive(Debug, PartialEq, PartialOrd, Clone)] pub struct FloatWrapper(pub FLOAT, pub Position); +#[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { fn hash(&self, state: &mut H) { state.write(&self.0.to_le_bytes()); From e8b6d0143dd271ea843b35de7ecb12e5bd7d895b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 20 Jul 2020 23:23:12 +0800 Subject: [PATCH 07/14] Small fixups. --- src/any.rs | 1 - src/engine.rs | 2 +- src/module.rs | 1 + src/packages/arithmetic.rs | 1 + src/packages/math_basic.rs | 1 + tests/call_fn.rs | 3 +-- 6 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/any.rs b/src/any.rs index 4a330cff..5182da08 100644 --- a/src/any.rs +++ b/src/any.rs @@ -18,7 +18,6 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, fmt, - hash::Hash, string::String, vec::Vec, }; diff --git a/src/engine.rs b/src/engine.rs index 16205a2f..0a5ec70a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -710,7 +710,7 @@ impl Engine { /// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { if let Some(this_pointer) = old_this_ptr { - mem::replace(args.get_mut(0).unwrap(), this_pointer); + args[0] = this_pointer; } } diff --git a/src/module.rs b/src/module.rs index 3ee25995..7216c248 100644 --- a/src/module.rs +++ b/src/module.rs @@ -30,6 +30,7 @@ use crate::stdlib::{ }; #[cfg(not(feature = "no_std"))] +#[cfg(feature = "sync")] use crate::stdlib::sync::RwLock; /// Return type of module-level Rust function. diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 8aeb1590..5e43c05b 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -8,6 +8,7 @@ use crate::token::Position; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] +#[cfg(feature = "no_std")] use num_traits::*; use num_traits::{ diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index ca0bc374..aed7e5b7 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -7,6 +7,7 @@ use crate::token::Position; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] +#[cfg(feature = "no_std")] use num_traits::*; use crate::stdlib::{boxed::Box, format, i32, i64}; diff --git a/tests/call_fn.rs b/tests/call_fn.rs index a04bd614..97767df7 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,7 +1,6 @@ #![cfg(not(feature = "no_function"))] use rhai::{ - Dynamic, Engine, EvalAltResult, FnPtr, Func, ImmutableString, Module, ParseError, - ParseErrorType, Scope, INT, + Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT, }; #[test] From ec3074106e1fd39b39ba5c7e711ef52cbb4d30b0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 21 Jul 2020 20:58:53 +0800 Subject: [PATCH 08/14] Display type name when printing Dynamic values that have no built-in format. --- src/any.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/any.rs b/src/any.rs index 5182da08..8ff20298 100644 --- a/src/any.rs +++ b/src/any.rs @@ -243,12 +243,15 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] - Union::Map(value) => write!(f, "#{:?}", value), + Union::Map(value) => { + f.write_str("#")?; + fmt::Debug::fmt(value, f) + } Union::FnPtr(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), - Union::Variant(_) => write!(f, "?"), + Union::Variant(value) => write!(f, "{}", (*value).type_name()), } } } @@ -266,12 +269,15 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] - Union::Map(value) => write!(f, "#{:?}", value), + Union::Map(value) => { + f.write_str("#")?; + fmt::Debug::fmt(value, f) + } Union::FnPtr(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), - Union::Variant(_) => write!(f, ""), + Union::Variant(value) => write!(f, "{}", (*value).type_name()), } } } From e6c3f8134ddabdbfc047589b3e161ed2bb7d30e5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 21 Jul 2020 20:59:12 +0800 Subject: [PATCH 09/14] Avoid copying property name for map property access. --- src/engine.rs | 40 +++++++++++++++------------------------- src/optimize.rs | 2 +- src/parser.rs | 6 +++--- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 0a5ec70a..1466e015 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -188,7 +188,7 @@ impl Target<'_> { } } /// Update the value of the `Target`. - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Self::Ref(r) => **r = new_val, @@ -419,12 +419,9 @@ pub fn make_getter(id: &str) -> String { fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { #[cfg(not(feature = "no_object"))] if fn_name.starts_with(FN_GET) { - Some(&fn_name[FN_GET.len()..]) - } else { - None + return Some(&fn_name[FN_GET.len()..]); } - #[cfg(feature = "no_object")] None } @@ -437,12 +434,9 @@ pub fn make_setter(id: &str) -> String { fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { #[cfg(not(feature = "no_object"))] if fn_name.starts_with(FN_SET) { - Some(&fn_name[FN_SET.len()..]) - } else { - None + return Some(&fn_name[FN_SET.len()..]); } - #[cfg(feature = "no_object")] None } @@ -454,7 +448,7 @@ fn default_print(s: &str) { } /// Search for a module within an imports stack. -/// Position in `EvalAltResult` is None and must be set afterwards. +/// Position in `EvalAltResult` is `None` and must be set afterwards. fn search_imports<'s>( mods: &'s Imports, state: &mut State, @@ -487,7 +481,7 @@ fn search_imports<'s>( } /// Search for a module within an imports stack. -/// Position in `EvalAltResult` is None and must be set afterwards. +/// Position in `EvalAltResult` is `None` and must be set afterwards. fn search_imports_mut<'s>( mods: &'s mut Imports, state: &mut State, @@ -637,7 +631,7 @@ impl Engine { } /// Universal method for calling functions either registered with the `Engine` or written in Rhai. - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING /// @@ -878,7 +872,7 @@ impl Engine { } /// Call a script-defined function. - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING /// @@ -960,7 +954,7 @@ impl Engine { } /// Perform an actual function call, taking care of special functions - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING /// @@ -1023,7 +1017,7 @@ impl Engine { } /// Evaluate a text string as a script - used primarily for 'eval'. - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. fn eval_script_expr( &self, scope: &mut Scope, @@ -1161,7 +1155,7 @@ impl Engine { } /// Chain-evaluate a dot/index chain. - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. fn eval_dot_index_chain_helper( &self, state: &mut State, @@ -1210,14 +1204,14 @@ impl Engine { } // xxx[rhs] = new_val _ if new_val.is_some() => { + let mut new_val = new_val.unwrap(); let mut idx_val2 = idx_val.clone(); match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { // Indexed value is an owned value - the only possibility is an indexer // Try to call an index setter Ok(obj_ptr) if obj_ptr.is_value() => { - let args = - &mut [target.as_mut(), &mut idx_val2, &mut new_val.unwrap()]; + let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, @@ -1236,17 +1230,13 @@ impl Engine { // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { obj_ptr - .set_value(new_val.unwrap()) + .set_value(new_val) .map_err(|err| err.new_position(rhs.position()))?; } Err(err) => match *err { // No index getter - try to call an index setter EvalAltResult::ErrorIndexingType(_, _) => { - let args = &mut [ - target.as_mut(), - &mut idx_val2, - &mut new_val.unwrap(), - ]; + let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; self.exec_fn_call( state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, @@ -2632,7 +2622,7 @@ impl Engine { } /// Check if the number of operations stay within limit. - /// Position in `EvalAltResult` is None and must be set afterwards. + /// Position in `EvalAltResult` is `None` and must be set afterwards. fn inc_operations(&self, state: &mut State) -> Result<(), Box> { state.operations += 1; diff --git a/src/optimize.rs b/src/optimize.rs index cbfbffe0..945040ea 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -400,7 +400,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name.as_str() == prop) + m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) .map(|(_, expr)| expr.set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } diff --git a/src/parser.rs b/src/parser.rs index ce0eb686..b3ed0756 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -643,7 +643,7 @@ pub enum Expr { )>, ), /// Property access. - Property(Box<((String, String, String), Position)>), + Property(Box<((ImmutableString, String, String), Position)>), /// { stmt } Stmt(Box<(Stmt, Position)>), /// Wrapped expression - should not be optimized away. @@ -937,7 +937,7 @@ impl Expr { let (name, pos) = x.0; let getter = make_getter(&name); let setter = make_setter(&name); - Self::Property(Box::new(((name.clone(), getter, setter), pos))) + Self::Property(Box::new(((name.into(), getter, setter), pos))) } _ => self, } @@ -1834,7 +1834,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result Date: Tue, 21 Jul 2020 22:32:24 +0800 Subject: [PATCH 10/14] Add no-std example. --- doc/src/start/examples/rust.md | 11 ++++++++- doc/src/start/examples/scripts.md | 2 +- no_std/no_std_win/Cargo.toml | 24 ++++++++++++++++++ no_std/no_std_win/README.md | 18 ++++++++++++++ no_std/no_std_win/src/main.rs | 41 +++++++++++++++++++++++++++++++ src/engine.rs | 21 +++++++--------- src/optimize.rs | 9 +++---- 7 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 no_std/no_std_win/Cargo.toml create mode 100644 no_std/no_std_win/README.md create mode 100644 no_std/no_std_win/src/main.rs diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md index 3784778d..b32d5529 100644 --- a/doc/src/start/examples/rust.md +++ b/doc/src/start/examples/rust.md @@ -3,7 +3,7 @@ Rust Examples {{#include ../../links.md}} -A number of examples can be found in the `examples` folder: +A number of examples can be found in the `examples` directory: | Example | Description | | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | @@ -29,3 +29,12 @@ Examples can be run with the following command: ```bash cargo run --example {example_name} ``` + +`no-std` Samples +---------------- + +To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory: + +| Example | Environment | +| ------------------------------------------------------------------------------------- | :---------: | +| [`no_std_win`](https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_win) | Windows API | diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md index 20836aef..222b3604 100644 --- a/doc/src/start/examples/scripts.md +++ b/doc/src/start/examples/scripts.md @@ -6,7 +6,7 @@ Example Scripts Language Feature Scripts ----------------------- -There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder: +There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` directory: | Script | Description | | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | diff --git a/no_std/no_std_win/Cargo.toml b/no_std/no_std_win/Cargo.toml new file mode 100644 index 00000000..46140968 --- /dev/null +++ b/no_std/no_std_win/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "no_std_win" +version = "0.1.0" +edition = "2018" +authors = ["Stephen Chung"] +description = "no-std test application for the Windows API" +homepage = "https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_win" +repository = "https://github.com/jonathandturner/rhai" + +[dependencies] +rhai = { path = "../../", features = [ "no_std" ], default_features = false } +wee_alloc = { version = "0.4.5", default_features = false } + +[profile.dev] +panic = "abort" + +[profile.release] +opt-level = "z" # optimize for size +debug = false +rpath = false +lto = "fat" +debug-assertions = false +codegen-units = 1 +panic = "abort" diff --git a/no_std/no_std_win/README.md b/no_std/no_std_win/README.md new file mode 100644 index 00000000..22af28b6 --- /dev/null +++ b/no_std/no_std_win/README.md @@ -0,0 +1,18 @@ +`no-std` Sample for Windows API +============================== + +This sample application is a bare-bones `no-std` build for the Windows API. + +[`wee_alloc`](https://crates.io/crates/wee_alloc) is used as the allocator. + + +To Compile +---------- + +The nightly compiler is required: + +```bash +cargo +nightly build --release +``` + +The release build is optimized for size. It can be changed to optimize on speed instead. diff --git a/no_std/no_std_win/src/main.rs b/no_std/no_std_win/src/main.rs new file mode 100644 index 00000000..4df87b2b --- /dev/null +++ b/no_std/no_std_win/src/main.rs @@ -0,0 +1,41 @@ +//! This is a `no-std` application for the Windows API that evaluates +//! a simple expression and uses the result as the return value. + +#![no_std] +#![feature(alloc_error_handler, start, core_intrinsics, lang_items)] + +extern crate alloc; +extern crate wee_alloc; + +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +use rhai::{Engine, INT}; + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + let engine = Engine::new(); + + // Evaluate a simple expression: 40 + 2 + engine.eval::("40 + 2").unwrap() as isize +} + +#[alloc_error_handler] +fn foo(_: core::alloc::Layout) -> ! { + core::intrinsics::abort(); +} + +#[panic_handler] +#[lang = "panic_impl"] +extern "C" fn rust_begin_panic(_: &core::panic::PanicInfo) -> ! { + core::intrinsics::abort(); +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} + +#[no_mangle] +extern "C" fn rust_eh_register_frames() {} + +#[no_mangle] +extern "C" fn rust_eh_unregister_frames() {} diff --git a/src/engine.rs b/src/engine.rs index 1466e015..25cbd2ca 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1773,8 +1773,8 @@ impl Engine { Expr::CharConstant(x) => Ok(x.0.into()), Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()), Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { - if let Some(ref val) = this_ptr { - Ok((*val).clone()) + if let Some(val) = this_ptr { + Ok(val.clone()) } else { Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) } @@ -1829,15 +1829,16 @@ impl Engine { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = let hash = calc_fn_hash(empty(), op, 2, empty()); + // Clone the LHS value let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; - - // Set variable value - *lhs_ptr = self + // Run function + let (value, _) = self .exec_fn_call( state, lib, op, true, hash, args, false, false, None, level, ) - .map(|(v, _)| v) .map_err(|err| err.new_position(*op_pos))?; + // Set value to LHS + *lhs_ptr = value; } Ok(Default::default()) } @@ -2491,12 +2492,8 @@ impl Engine { for ((id, id_pos), rename) in list.iter() { // Mark scope variables as public if let Some(index) = scope.get_index(id).map(|(i, _)| i) { - let alias = rename - .as_ref() - .map(|(n, _)| n.clone()) - .unwrap_or_else(|| id.clone()); - - scope.set_entry_alias(index, alias); + let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); + scope.set_entry_alias(index, alias.clone()); } else { return Err(Box::new(EvalAltResult::ErrorVariableNotFound( id.into(), diff --git a/src/optimize.rs b/src/optimize.rs index 945040ea..d47188a1 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -434,7 +434,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => { // String literal indexing - get the character state.set_dirty(); - Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).expect("should get char"), s.1))) + Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1))) } // lhs[rhs] (lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))), @@ -614,7 +614,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); // Replace constant with value - state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos) + state.find_constant(&name).unwrap().clone().set_position(pos) } // Custom syntax @@ -655,10 +655,7 @@ fn optimize( && expr.as_ref().map(|v| v.is_constant()).unwrap_or(false) }) .for_each(|ScopeEntry { name, expr, .. }| { - state.push_constant( - name.as_ref(), - (**expr.as_ref().expect("should be Some(expr)")).clone(), - ) + state.push_constant(name.as_ref(), expr.as_ref().unwrap().as_ref().clone()) }); let orig_constants_len = state.constants.len(); From 16e8ef454fa3517207bfccbea61857d0199dc0c2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 21 Jul 2020 23:08:46 +0800 Subject: [PATCH 11/14] Refine no-std sample. --- doc/src/about/non-design.md | 4 +- doc/src/engine/raw.md | 2 +- doc/src/start/builds/no-std.md | 6 +++ doc/src/start/examples/rust.md | 16 ++++-- doc/src/start/examples/scripts.md | 52 +++++++++---------- no_std/{no_std_win => no_std_test}/Cargo.toml | 6 +-- no_std/{no_std_win => no_std_test}/README.md | 6 +-- .../{no_std_win => no_std_test}/src/main.rs | 2 +- 8 files changed, 55 insertions(+), 39 deletions(-) rename no_std/{no_std_win => no_std_test}/Cargo.toml (84%) rename no_std/{no_std_win => no_std_test}/README.md (66%) rename no_std/{no_std_win => no_std_test}/src/main.rs (92%) diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md index da3747df..52888164 100644 --- a/doc/src/about/non-design.md +++ b/doc/src/about/non-design.md @@ -25,12 +25,12 @@ It doesn't attempt to be a new language. For example: * No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md). * 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 programs. + to be extremely _fast_, but to make it as easy as possible to integrate with native Rust applications. 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 program logic entirely in Rhai - that use case is best fulfilled by +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. diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md index ed7154ed..ce61aa91 100644 --- a/doc/src/engine/raw.md +++ b/doc/src/engine/raw.md @@ -7,7 +7,7 @@ Raw `Engine` `Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). In many controlled embedded environments, however, these may not be needed and unnecessarily occupy -program code storage space. +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. diff --git a/doc/src/start/builds/no-std.md b/doc/src/start/builds/no-std.md index 46a8e986..75395ed6 100644 --- a/doc/src/start/builds/no-std.md +++ b/doc/src/start/builds/no-std.md @@ -13,3 +13,9 @@ Nightly Required ---------------- Currently, [`no_std`] requires the nightly compiler due to the crates that it uses. + + +Samples +------- + +Check out the [`no-std` sample applications](../examples/rust.md#no-std-samples) for different operating environments. diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md index b32d5529..276b3d15 100644 --- a/doc/src/start/examples/rust.md +++ b/doc/src/start/examples/rust.md @@ -35,6 +35,16 @@ cargo run --example {example_name} To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory: -| Example | Environment | -| ------------------------------------------------------------------------------------- | :---------: | -| [`no_std_win`](https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_win) | Windows API | +| Sample | Description | Optimization | Allocator | Panics | +| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: | +| [`no_std_test`](https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_test) | Bare-bones test application that evaluates a Rhai expression and sets the result as the return value. | Size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | Abort | + +`cargo run` cannot be used to run a `no-std` sample. It must first be built: + +```bash +cd no_std/no_std_test + +cargo +nightly build --release + +./target/release/no_std_test +``` diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md index 222b3604..0875f869 100644 --- a/doc/src/start/examples/scripts.md +++ b/doc/src/start/examples/scripts.md @@ -8,25 +8,25 @@ Language Feature Scripts There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` directory: -| Script | Description | -| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] | -| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations | -| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments | -| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`](#for-loop) loops | -| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] | -| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters | -| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters | -| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters | -| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`](#if-statement) example | -| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop | -| [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | -| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition | -| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication | -| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis | -| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations | -| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations | -| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`](#while-loop) loop | +| Script | Description | +| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] | +| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations | +| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments | +| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops | +| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] | +| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters | +| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters | +| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters | +| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | +| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | +| [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | +| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition | +| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication | +| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis | +| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations | +| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations | +| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop | Benchmark Scripts @@ -34,18 +34,18 @@ Benchmark Scripts The following scripts are for benchmarking the speed of Rhai: -| Scripts | Description | -| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | -| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple program to measure the speed of Rhai's interpreter (1 million iterations). | -| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | -| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | -| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | +| Scripts | Description | +| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple application to measure the speed of Rhai's interpreter (1 million iterations). | +| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | +| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | +| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | Running Example Scripts ---------------------- -To run the scripts, either make a tiny program or use of the `rhai_runner` example: +The [`rhai_runner`](../examples/rust.md) example can be used to run the scripts: ```bash cargo run --example rhai_runner scripts/any_script.rhai diff --git a/no_std/no_std_win/Cargo.toml b/no_std/no_std_test/Cargo.toml similarity index 84% rename from no_std/no_std_win/Cargo.toml rename to no_std/no_std_test/Cargo.toml index 46140968..f22daed0 100644 --- a/no_std/no_std_win/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "no_std_win" +name = "no_std_test" version = "0.1.0" edition = "2018" authors = ["Stephen Chung"] -description = "no-std test application for the Windows API" -homepage = "https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_win" +description = "no-std test application" +homepage = "https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_test" repository = "https://github.com/jonathandturner/rhai" [dependencies] diff --git a/no_std/no_std_win/README.md b/no_std/no_std_test/README.md similarity index 66% rename from no_std/no_std_win/README.md rename to no_std/no_std_test/README.md index 22af28b6..e7edfc51 100644 --- a/no_std/no_std_win/README.md +++ b/no_std/no_std_test/README.md @@ -1,7 +1,7 @@ -`no-std` Sample for Windows API -============================== +`no-std` Test Sample +==================== -This sample application is a bare-bones `no-std` build for the Windows API. +This sample application is a bare-bones `no-std` build for testing. [`wee_alloc`](https://crates.io/crates/wee_alloc) is used as the allocator. diff --git a/no_std/no_std_win/src/main.rs b/no_std/no_std_test/src/main.rs similarity index 92% rename from no_std/no_std_win/src/main.rs rename to no_std/no_std_test/src/main.rs index 4df87b2b..88ba8b02 100644 --- a/no_std/no_std_win/src/main.rs +++ b/no_std/no_std_test/src/main.rs @@ -1,4 +1,4 @@ -//! This is a `no-std` application for the Windows API that evaluates +//! This is a `no-std` application for the that evaluates //! a simple expression and uses the result as the return value. #![no_std] From 839738b97eb5cb8f4a8294c7a975e0e6e6521c73 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 22 Jul 2020 11:43:58 +0800 Subject: [PATCH 12/14] Change no_std sample to bare-bones. --- no_std/no_std_test/Cargo.toml | 2 +- no_std/no_std_test/src/main.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index f22daed0..4b151047 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -8,7 +8,7 @@ homepage = "https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_te repository = "https://github.com/jonathandturner/rhai" [dependencies] -rhai = { path = "../../", features = [ "no_std" ], default_features = false } +rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "no_module" ], default_features = false } wee_alloc = { version = "0.4.5", default_features = false } [profile.dev] diff --git a/no_std/no_std_test/src/main.rs b/no_std/no_std_test/src/main.rs index 88ba8b02..829802f8 100644 --- a/no_std/no_std_test/src/main.rs +++ b/no_std/no_std_test/src/main.rs @@ -1,4 +1,4 @@ -//! This is a `no-std` application for the that evaluates +//! This is a bare-bones `no-std` application that evaluates //! a simple expression and uses the result as the return value. #![no_std] @@ -14,10 +14,12 @@ use rhai::{Engine, INT}; #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - let engine = Engine::new(); + // Notice that this is a _raw_ engine. + // To do anything useful, load a few packages from `rhai::packages`. + let engine = Engine::new_raw(); // Evaluate a simple expression: 40 + 2 - engine.eval::("40 + 2").unwrap() as isize + engine.eval_expression::("40 + 2").unwrap() as isize } #[alloc_error_handler] From 187824e684f8e9ff1600d3488914200fb983c98f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 22 Jul 2020 13:05:24 +0800 Subject: [PATCH 13/14] Improve inlining of Dynamic methods. --- src/any.rs | 320 +++++++++++++++++++++++++++++++++++++------------- src/unsafe.rs | 3 + src/utils.rs | 26 ---- 3 files changed, 240 insertions(+), 109 deletions(-) diff --git a/src/any.rs b/src/any.rs index 8ff20298..897bc922 100644 --- a/src/any.rs +++ b/src/any.rs @@ -203,6 +203,7 @@ impl Dynamic { } /// Map the name of a standard type into a friendly form. +#[inline] pub(crate) fn map_std_type_name(name: &str) -> &str { if name == type_name::() { "string" @@ -340,30 +341,47 @@ impl Dynamic { /// assert_eq!(new_result.type_name(), "string"); /// assert_eq!(new_result.to_string(), "hello"); /// ``` + #[inline(always)] pub fn from(value: T) -> Self { - if let Some(result) = ::downcast_ref::<()>(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } + let type_id = TypeId::of::(); + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } #[cfg(not(feature = "no_float"))] - if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == 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, @@ -384,6 +402,11 @@ impl Dynamic { } } + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { + Ok(d) => return *d, + Err(val) => val, + }; + Self(Union::Variant(Box::new(boxed))) } @@ -401,30 +424,80 @@ impl Dynamic { /// /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` + #[inline(always)] pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + if type_id == TypeId::of::() { + return match self.0 { + Union::Int(value) => unsafe_try_cast(value), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if type_id == TypeId::of::() { + return match self.0 { + Union::Float(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Bool(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Str(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Str(value) => unsafe_try_cast(value.into_owned()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Char(value) => unsafe_try_cast(value), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if type_id == TypeId::of::() { + return match self.0 { + Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] + if type_id == TypeId::of::() { + return match self.0 { + Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::FnPtr(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::<()>() { + return match self.0 { + Union::Unit(value) => unsafe_try_cast(value), + _ => None, + }; + } if type_id == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } match self.0 { - Union::Unit(value) => unsafe_try_cast(value), - Union::Bool(value) => unsafe_try_cast(value), - Union::Str(value) if type_id == TypeId::of::() => { - unsafe_try_cast(value) - } - Union::Str(value) => unsafe_try_cast(value.into_owned()), - Union::Char(value) => unsafe_try_cast(value), - Union::Int(value) => unsafe_try_cast(value), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => unsafe_try_cast(value), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), - Union::FnPtr(value) => unsafe_try_cast(value), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), + _ => None, } } @@ -444,81 +517,162 @@ impl Dynamic { /// /// assert_eq!(x.cast::(), 42); /// ``` + #[inline(always)] pub fn cast(self) -> T { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { - return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap(); - } - - match self.0 { - Union::Unit(value) => unsafe_try_cast(value).unwrap(), - Union::Bool(value) => unsafe_try_cast(value).unwrap(), - Union::Str(value) if type_id == TypeId::of::() => { - unsafe_try_cast(value).unwrap() - } - Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(), - Union::Char(value) => unsafe_try_cast(value).unwrap(), - Union::Int(value) => unsafe_try_cast(value).unwrap(), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => unsafe_try_cast(value).unwrap(), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(), - Union::FnPtr(value) => unsafe_try_cast(value).unwrap(), - Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(), - } + self.try_cast::().unwrap() } /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. + #[inline(always)] pub fn downcast_ref(&self) -> Option<&T> { - if TypeId::of::() == TypeId::of::() { + let type_id = TypeId::of::(); + + if type_id == TypeId::of::() { + return match &self.0 { + Union::Int(value) => ::downcast_ref::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if type_id == TypeId::of::() { + return match &self.0 { + Union::Float(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Bool(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Str(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Str(value) => ::downcast_ref::(value.as_ref()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Char(value) => ::downcast_ref::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if type_id == 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::() { + return match &self.0 { + Union::Map(value) => ::downcast_ref::(value.as_ref()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::FnPtr(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::<()>() { + return match &self.0 { + Union::Unit(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { return ::downcast_ref::(self); } match &self.0 { - Union::Unit(value) => ::downcast_ref::(value), - Union::Bool(value) => ::downcast_ref::(value), - Union::Str(value) => ::downcast_ref::(value) - .or_else(|| ::downcast_ref::(value.as_ref())), - Union::Char(value) => ::downcast_ref::(value), - Union::Int(value) => ::downcast_ref::(value), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => ::downcast_ref::(value), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => ::downcast_ref::(value.as_ref()), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => ::downcast_ref::(value.as_ref()), - Union::FnPtr(value) => ::downcast_ref::(value), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), + _ => None, } } /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. /// Returns `None` if the cast fails. + #[inline(always)] pub fn downcast_mut(&mut self) -> Option<&mut T> { - if TypeId::of::() == TypeId::of::() { + let type_id = TypeId::of::(); + + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Int(value) => ::downcast_mut::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Float(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Bool(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Str(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Char(value) => ::downcast_mut::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if type_id == 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::() { + return match &mut self.0 { + Union::Map(value) => ::downcast_mut::(value.as_mut()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::FnPtr(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::<()>() { + return match &mut self.0 { + Union::Unit(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { return ::downcast_mut::(self); } match &mut self.0 { - Union::Unit(value) => ::downcast_mut::(value), - Union::Bool(value) => ::downcast_mut::(value), - Union::Str(value) => ::downcast_mut::(value), - Union::Char(value) => ::downcast_mut::(value), - Union::Int(value) => ::downcast_mut::(value), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => ::downcast_mut::(value), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => ::downcast_mut::(value.as_mut()), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => ::downcast_mut::(value.as_mut()), - Union::FnPtr(value) => ::downcast_mut::(value), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), + _ => None, } } diff --git a/src/unsafe.rs b/src/unsafe.rs index fa5b271f..86c571f1 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -12,6 +12,7 @@ use crate::stdlib::{ }; /// Cast a type into another type. +#[inline(always)] pub fn unsafe_try_cast(a: A) -> Option { if TypeId::of::() == a.type_id() { // SAFETY: Just checked we have the right type. We explicitly forget the @@ -28,6 +29,7 @@ pub fn unsafe_try_cast(a: A) -> Option { } /// Cast a Boxed type into another type. +#[inline(always)] pub fn unsafe_cast_box(item: Box) -> Result, Box> { // Only allow casting to the exact same type if TypeId::of::() == TypeId::of::() { @@ -51,6 +53,7 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// /// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves /// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. +#[inline] pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> { // If not at global level, we can force-cast if state.scope_level > 0 { diff --git a/src/utils.rs b/src/utils.rs index 01fdd26b..12add69d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,11 +36,9 @@ use ahash::AHasher; pub struct StraightHasher(u64); impl Hasher for StraightHasher { - #[inline(always)] fn finish(&self) -> u64 { self.0 } - #[inline] fn write(&mut self, bytes: &[u8]) { let mut key = [0_u8; 8]; key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes @@ -50,7 +48,6 @@ impl Hasher for StraightHasher { impl StraightHasher { /// Create a `StraightHasher`. - #[inline(always)] pub fn new() -> Self { Self(0) } @@ -63,7 +60,6 @@ pub struct StraightHasherBuilder; impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; - #[inline(always)] fn build_hasher(&self) -> Self::Hasher { StraightHasher::new() } @@ -150,7 +146,6 @@ pub struct StaticVec { const MAX_STATIC_VEC: usize = 4; impl Drop for StaticVec { - #[inline(always)] fn drop(&mut self) { self.clear(); } @@ -233,7 +228,6 @@ impl IntoIterator for StaticVec { impl StaticVec { /// Create a new `StaticVec`. - #[inline(always)] pub fn new() -> Self { Default::default() } @@ -249,7 +243,6 @@ impl StaticVec { self.len = 0; } /// Extract a `MaybeUninit` into a concrete initialized type. - #[inline(always)] fn extract(value: MaybeUninit) -> T { unsafe { value.assume_init() } } @@ -311,7 +304,6 @@ impl StaticVec { ); } /// Is data stored in fixed-size storage? - #[inline(always)] fn is_fixed_storage(&self) -> bool { self.len <= MAX_STATIC_VEC } @@ -418,12 +410,10 @@ impl StaticVec { } } /// Get the number of items in this `StaticVec`. - #[inline(always)] pub fn len(&self) -> usize { self.len } /// Is this `StaticVec` empty? - #[inline(always)] pub fn is_empty(&self) -> bool { self.len == 0 } @@ -664,48 +654,41 @@ pub struct ImmutableString(Shared); impl Deref for ImmutableString { type Target = String; - #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for ImmutableString { - #[inline(always)] fn as_ref(&self) -> &String { &self.0 } } impl Borrow for ImmutableString { - #[inline(always)] fn borrow(&self) -> &str { self.0.as_str() } } impl From<&str> for ImmutableString { - #[inline(always)] fn from(value: &str) -> Self { Self(value.to_string().into()) } } impl From for ImmutableString { - #[inline(always)] fn from(value: String) -> Self { Self(value.into()) } } impl From> for ImmutableString { - #[inline(always)] fn from(value: Box) -> Self { Self(value.into()) } } impl From for String { - #[inline(always)] fn from(value: ImmutableString) -> Self { value.into_owned() } @@ -714,49 +697,42 @@ impl From for String { impl FromStr for ImmutableString { type Err = (); - #[inline(always)] fn from_str(s: &str) -> Result { Ok(Self(s.to_string().into())) } } impl FromIterator for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator<&'a char> for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().cloned().collect::().into()) } } impl<'a> FromIterator<&'a str> for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl fmt::Display for ImmutableString { - #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.0.as_str(), f) } } impl fmt::Debug for ImmutableString { - #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.0.as_str(), f) } @@ -891,7 +867,6 @@ impl Add for &ImmutableString { } impl AddAssign for ImmutableString { - #[inline(always)] fn add_assign(&mut self, rhs: char) { self.make_mut().push(rhs); } @@ -906,7 +881,6 @@ impl ImmutableString { } /// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references). /// Then return a mutable reference to the `String`. - #[inline(always)] pub fn make_mut(&mut self) -> &mut String { shared_make_mut(&mut self.0) } From 35374f5b3bedf76d07abb544c103acc8d3c78355 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 22 Jul 2020 13:08:51 +0800 Subject: [PATCH 14/14] Simplify custom syntax. --- RELEASES.md | 5 ++++ doc/src/engine/custom-syntax.md | 52 +++++++++++---------------------- src/engine.rs | 27 ++++++++++++----- src/lib.rs | 4 +++ src/syntax.rs | 31 +++++++------------- tests/syntax.rs | 19 ++++-------- 6 files changed, 62 insertions(+), 76 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index b09e10ce..6248f124 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -16,6 +16,11 @@ New features * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. * Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. +Breaking changes +---------------- + +* Function signature for defining custom syntax is simplified. + Version 0.17.0 ============== diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 0bd9fb30..f97e4203 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -126,32 +126,21 @@ Any custom syntax must include an _implementation_ of it. The function signature of an implementation is: ```rust -Fn( - engine: &Engine, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - inputs: &[Expression], - level: usize -) -> Result> +Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) + -> Result> ``` where: -* `engine : &Engine` - reference to the current [`Engine`]. -* `scope : &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. -* `mods : &mut Imports` - mutable reference to the current collection of imported [`Module`]'s; **do not touch**. -* `state : &mut State` - mutable reference to the current evaluation state; **do not touch**. -* `lib : &Module` - reference to the current collection of script-defined functions. -* `this_ptr : &mut Option<&mut Dynamic>` - mutable reference to the current binding of the `this` pointer; **do not touch**. -* `inputs : &[Expression]` - a list of input expression trees. -* `level : usize` - the current function call level. +* `engine: &Engine` - reference to the current [`Engine`]. +* `context: &mut EvalContext` - mutable reference to the current evaluation _context_; **do not touch**. +* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. +* `inputs: &[Expression]` - a list of input expression trees. -There are a lot of parameters, most of which should not be touched or Bad Things Happen™. -They represent the running _content_ of a script evaluation and should simply be passed -straight-through the the [`Engine`]. +#### WARNING - Lark's Vomit + +The `context` parameter contains the evaluation _context_ and should not be touched or Bad Things Happen™. +It should simply be passed straight-through the the [`Engine`]. ### Access Arguments @@ -172,7 +161,7 @@ Use the `engine::eval_expression_tree` method to evaluate an expression tree. ```rust let expr = inputs.get(0).unwrap(); -let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?; +let result = engine.eval_expression_tree(context, scope, expr)?; ``` As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`. @@ -210,13 +199,9 @@ The syntax is passed simply as a slice of `&str`. // Custom syntax implementation fn implementation_func( engine: &Engine, + context: &mut EvalContext, scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - inputs: &[Expression], - level: usize + inputs: &[Expression] ) -> Result> { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); @@ -227,15 +212,12 @@ fn implementation_func( loop { // Evaluate the statement block - engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; + engine.eval_expression_tree(context, scope, stmt)?; // Evaluate the condition expression - let stop = !engine - .eval_expression_tree(scope, mods, state, lib, this_ptr, condition, level)? - .as_bool() - .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch( - "do-while".into(), expr.position() - ))?; + let stop = !engine.eval_expression_tree(context, scope, condition)? + .as_bool().map_err(|_| EvalAltResult::ErrorBooleanArgMismatch( + "do-while".into(), expr.position()))?; if stop { break; diff --git a/src/engine.rs b/src/engine.rs index 25cbd2ca..ffd1a3eb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,7 +18,7 @@ use crate::utils::StaticVec; use crate::parser::FLOAT; #[cfg(feature = "internals")] -use crate::syntax::CustomSyntax; +use crate::syntax::{CustomSyntax, EvalContext}; use crate::stdlib::{ any::{type_name, TypeId}, @@ -1738,15 +1738,19 @@ impl Engine { #[deprecated(note = "this method is volatile and may change")] pub fn eval_expression_tree( &self, + context: &mut EvalContext, scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, expr: &Expression, - level: usize, ) -> Result> { - self.eval_expr(scope, mods, state, lib, this_ptr, expr.expr(), level) + self.eval_expr( + scope, + context.mods, + context.state, + context.lib, + context.this_ptr, + expr.expr(), + context.level, + ) } /// Evaluate an expression @@ -2215,7 +2219,14 @@ impl Engine { Expr::Custom(x) => { let func = (x.0).1.as_ref(); let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect(); - func(self, scope, mods, state, lib, this_ptr, ep.as_ref(), level) + let mut context = EvalContext { + mods, + state, + lib, + this_ptr, + level, + }; + func(self, &mut context, scope, ep.as_ref()) } _ => unreachable!(), diff --git a/src/lib.rs b/src/lib.rs index a3edc8ca..8bebc0a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,6 +171,10 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt}; #[deprecated(note = "this type is volatile and may change")] pub use engine::{Expression, Imports, State as EvalState}; +#[cfg(feature = "internals")] +#[deprecated(note = "this type is volatile and may change")] +pub use syntax::EvalContext; + #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] pub use module::ModuleRef; diff --git a/src/syntax.rs b/src/syntax.rs index 9bd5fad1..78bf94ff 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -22,26 +22,13 @@ use crate::stdlib::{ #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxEval = dyn Fn( &Engine, + &mut EvalContext, &mut Scope, - &mut Imports, - &mut State, - &Module, - &mut Option<&mut Dynamic>, &[Expression], - usize, ) -> Result>; /// A general function trail object. #[cfg(feature = "sync")] -pub type FnCustomSyntaxEval = dyn Fn( - &Engine, - &mut Scope, - &mut Imports, - &mut State, - &Module, - &mut Option<&mut Dynamic>, - &[Expression], - usize, - ) -> Result> +pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result> + Send + Sync; @@ -58,6 +45,14 @@ impl fmt::Debug for CustomSyntax { } } +pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> { + pub(crate) mods: &'a mut Imports<'b>, + pub(crate) state: &'s mut State, + pub(crate) lib: &'m Module, + pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>, + pub(crate) level: usize, +} + impl Engine { pub fn register_custom_syntax + ToString>( &mut self, @@ -65,13 +60,9 @@ impl Engine { scope_delta: isize, func: impl Fn( &Engine, + &mut EvalContext, &mut Scope, - &mut Imports, - &mut State, - &Module, - &mut Option<&mut Dynamic>, &[Expression], - usize, ) -> Result> + SendSync + 'static, diff --git a/tests/syntax.rs b/tests/syntax.rs index d4737b71..11074c6a 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -1,8 +1,5 @@ #![cfg(feature = "internals")] -use rhai::{ - Dynamic, Engine, EvalAltResult, EvalState, Expression, Imports, LexError, Module, ParseError, - ParseErrorType, Scope, INT, -}; +use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT}; #[test] fn test_custom_syntax() -> Result<(), Box> { @@ -28,13 +25,9 @@ fn test_custom_syntax() -> Result<(), Box> { ], 1, |engine: &Engine, + context: &mut EvalContext, scope: &mut Scope, - mods: &mut Imports, - state: &mut EvalState, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - inputs: &[Expression], - level: usize| { + inputs: &[Expression]| { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); let expr = inputs.get(2).unwrap(); @@ -42,10 +35,10 @@ fn test_custom_syntax() -> Result<(), Box> { scope.push(var_name, 0 as INT); loop { - engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; + engine.eval_expression_tree(context, scope, stmt)?; if !engine - .eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expression_tree(context, scope, expr)? .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch( @@ -78,7 +71,7 @@ fn test_custom_syntax() -> Result<(), Box> { // The first symbol must be an identifier assert!(matches!( - *engine.register_custom_syntax(&["!"], 0, |_, _, _, _, _, _, _, _| Ok(().into())).expect_err("should error"), + *engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"), LexError::ImproperSymbol(s) if s == "!" ));