diff --git a/README.md b/README.md index ad4a5037..89da8bad 100644 --- a/README.md +++ b/README.md @@ -1122,6 +1122,7 @@ Comments -------- Comments are C-style, including '`/*` ... `*/`' pairs and '`//`' for comments to the end of the line. +Comments can be nested. ```rust let /* intruder comment */ name = "Bob"; @@ -1138,14 +1139,35 @@ let /* intruder comment */ name = "Bob"; */ ``` +Keywords +-------- + +The following are reserved keywords in Rhai: + +| Keywords | Usage | Not available under feature | +| ------------------------------------------------- | --------------------- | :-------------------------: | +| `true`, `false` | Boolean constants | | +| `let`, `const` | Variable declarations | | +| `if`, `else` | Control flow | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | | +| `fn`, `private` | Functions | [`no_function`] | +| `return` | Return values | | +| `throw` | Return errors | | +| `import`, `export`, `as` | Modules | [`no_module`] | + +Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled. +For example, `fn` is a valid variable name if the [`no_function`] feature is used. + Statements ---------- -Statements are terminated by semicolons '`;`' - they are mandatory, except for the _last_ statement where it can be omitted. +Statements are terminated by semicolons '`;`' and they are mandatory, +except for the _last_ statement in a _block_ (enclosed by '`{`' .. '`}`' pairs) where it can be omitted. -A statement can be used anywhere where an expression is expected. The _last_ statement of a statement block -(enclosed by '`{`' .. '`}`' pairs) is always the return value of the statement. If a statement has no return value -(e.g. variable definitions, assignments) then the value will be [`()`]. +A statement can be used anywhere where an expression is expected. These are called, for lack of a more +creative name, "statement expressions." The _last_ statement of a statement block is _always_ the block's +return value when used as a statement. +If the last statement has no return value (e.g. variable definitions, assignments) then it is assumed to be [`()`]. ```rust let a = 42; // normal assignment statement @@ -1153,16 +1175,17 @@ let a = foo(42); // normal function call statement foo < 42; // normal expression as statement let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which is the value of the last statement -// ^ notice that the last statement does not require a terminating semicolon (although it also works with it) -// ^ notice that a semicolon is required here to terminate the assignment statement; it is syntax error without it +// ^ the last statement does not require a terminating semicolon (although it also works with it) +// ^ semicolon required here to terminate the assignment statement; it is a syntax error without it -4 * 10 + 2 // this is also a statement, which is an expression, with no ending semicolon because - // it is the last statement of the whole block +4 * 10 + 2 // a statement which is just one expression; no ending semicolon is OK + // because it is the last statement of the whole block ``` Variables --------- +[variable]: #variables [variables]: #variables Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`'). @@ -1307,8 +1330,8 @@ Strings and Chars [strings]: #strings-and-chars [char]: #strings-and-chars -String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and -hex ('`\x`_xx_') escape sequences. +String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') +and hex ('`\x`_xx_') escape sequences. Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points. @@ -1388,7 +1411,7 @@ record == "Bob X. Davis: age 42 ❤\n"; ### Built-in functions -The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings: +The following standard methods (mostly defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings: | Function | Parameter(s) | Description | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | @@ -1463,7 +1486,7 @@ Arrays are disabled via the [`no_index`] feature. ### Built-in functions -The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays: +The following methods (mostly defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays: | Function | Parameter(s) | Description | | ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | @@ -1963,6 +1986,9 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, Functions --------- +[function]: #functions +[functions]: #functions + Rhai supports defining functions in script (unless disabled with [`no_function`]): ```rust @@ -2007,8 +2033,9 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type). It is important to remember that all arguments are passed by _value_, so all functions are _pure_ -(i.e. they never modifytheir arguments). -Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful. +(i.e. they never modify their arguments). +Any update to an argument will **not** be reflected back to the caller. +This can introduce subtle bugs, if not careful, especially when using the _method-call_ style. ```rust fn change(s) { // 's' is passed by value @@ -2016,7 +2043,7 @@ fn change(s) { // 's' is passed by value } let x = 500; -x.change(); // de-sugars to change(x) +x.change(); // de-sugars to 'change(x)' x == 500; // 'x' is NOT changed! ``` diff --git a/src/any.rs b/src/any.rs index 3bcbb049..f685c303 100644 --- a/src/any.rs +++ b/src/any.rs @@ -630,6 +630,14 @@ impl From> for Dynamic { ))) } } +#[cfg(not(feature = "no_index"))] +impl From<&[T]> for Dynamic { + fn from(value: &[T]) -> Self { + Self(Union::Array(Box::new( + value.iter().cloned().map(Dynamic::from).collect(), + ))) + } +} #[cfg(not(feature = "no_object"))] impl From> for Dynamic { fn from(value: HashMap) -> Self { diff --git a/src/engine.rs b/src/engine.rs index 80b84110..b843c85d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1550,14 +1550,8 @@ impl Engine { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); let def_val = def_val.as_ref(); - let mut arg_values = args_expr - .iter() - .map(|expr| self.eval_expr(scope, state, lib, expr, level)) - .collect::, _>>()?; - - let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - - if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::() { + // Handle eval + if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); @@ -1567,7 +1561,8 @@ impl Engine { let pos = args_expr.get(0).position(); // Evaluate the text string as a script - let result = self.eval_script_expr(scope, state, lib, args.pop(), pos); + let script = self.eval_expr(scope, state, lib, args_expr.get(0), level)?; + let result = self.eval_script_expr(scope, state, lib, &script, pos); if scope.len() != prev_len { // IMPORTANT! If the eval defines new variables in the current scope, @@ -1580,6 +1575,46 @@ impl Engine { } // Normal function call - except for eval (handled above) + let mut arg_values: StaticVec; + let mut args: StaticVec<_>; + + if args_expr.is_empty() { + // No arguments + args = Default::default(); + } else { + // See if the first argument is a variable, if so, convert to method-call style + // in order to leverage potential &mut first argument and avoid cloning the value + match args_expr.get(0) { + // func(x, ...) -> x.func(...) + lhs @ Expr::Variable(_) => { + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, state, lib, expr, level)) + .collect::>()?; + + let (target, _, typ, pos) = search_scope(scope, state, lhs)?; + self.inc_operations(state, pos)?; + + match typ { + ScopeEntryType::Module => unreachable!(), + ScopeEntryType::Constant | ScopeEntryType::Normal => (), + } + + args = once(target).chain(arg_values.iter_mut()).collect(); + } + // func(..., ...) + _ => { + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, state, lib, expr, level)) + .collect::>()?; + + args = arg_values.iter_mut().collect(); + } + } + } + let args = args.as_mut(); self.exec_fn_call( state, lib, name, *native, *hash, args, false, def_val, *pos, level, @@ -2012,8 +2047,8 @@ fn run_builtin_binary_op( } if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap().clone(); - let y = y.downcast_ref::().unwrap().clone(); + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); #[cfg(not(feature = "unchecked"))] match op { @@ -2054,8 +2089,8 @@ fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap().clone(); - let y = y.downcast_ref::().unwrap().clone(); + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); match op { "&" => return Ok(Some((x && y).into())), @@ -2079,8 +2114,8 @@ fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap().clone(); - let y = y.downcast_ref::().unwrap().clone(); + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); match op { "==" => return Ok(Some((x == y).into())), @@ -2102,8 +2137,8 @@ fn run_builtin_binary_op( #[cfg(not(feature = "no_float"))] { if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap().clone(); - let y = y.downcast_ref::().unwrap().clone(); + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); match op { "+" => return Ok(Some((x + y).into())), @@ -2142,7 +2177,7 @@ fn run_builtin_op_assignment( if args_type == TypeId::of::() { let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap().clone(); + let y = *y.downcast_ref::().unwrap(); #[cfg(not(feature = "unchecked"))] match op { @@ -2178,7 +2213,7 @@ fn run_builtin_op_assignment( } } else if args_type == TypeId::of::() { let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap().clone(); + let y = *y.downcast_ref::().unwrap(); match op { "&=" => return Ok(Some(*x = *x && y)), @@ -2199,7 +2234,7 @@ fn run_builtin_op_assignment( { if args_type == TypeId::of::() { let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap().clone(); + let y = *y.downcast_ref::().unwrap(); match op { "+=" => return Ok(Some(*x += y)), diff --git a/src/module.rs b/src/module.rs index 457f1b1c..57bca2e3 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1044,6 +1044,9 @@ mod stat { /// Module resolution service that serves modules added into it. /// + /// `StaticModuleResolver` is a smart pointer to a `HashMap`. + /// It can simply be treated as `&HashMap`. + /// /// # Examples /// /// ``` diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index c3c22c31..6f3cbc40 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -101,22 +101,22 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str #[cfg(not(feature = "no_index"))] { - lib.set_fn_2("+", |x: ImmutableString, y: Array| Ok(format!("{}{:?}", x, y))); + lib.set_fn_2_mut("+", |x: &mut ImmutableString, y: Array| Ok(format!("{}{:?}", x, y))); lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y))); } - lib.set_fn_1("len", |s: ImmutableString| Ok(s.chars().count() as INT)); - lib.set_fn_2( + lib.set_fn_1_mut("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT)); + lib.set_fn_2_mut( "contains", - |s: ImmutableString, ch: char| Ok(s.contains(ch)), + |s: &mut ImmutableString, ch: char| Ok(s.contains(ch)), ); - lib.set_fn_2( + lib.set_fn_2_mut( "contains", - |s: ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())), + |s: &mut ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())), ); - lib.set_fn_3( + lib.set_fn_3_mut( "index_of", - |s: ImmutableString, ch: char, start: INT| { + |s: &mut ImmutableString, ch: char, start: INT| { let start = if start < 0 { 0 } else if (start as usize) >= s.chars().count() { @@ -131,17 +131,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str .unwrap_or(-1 as INT)) }, ); - lib.set_fn_2( + lib.set_fn_2_mut( "index_of", - |s: ImmutableString, ch: char| { + |s: &mut ImmutableString, ch: char| { Ok(s.find(ch) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT)) }, ); - lib.set_fn_3( + lib.set_fn_3_mut( "index_of", - |s: ImmutableString, find: ImmutableString, start: INT| { + |s: &mut ImmutableString, find: ImmutableString, start: INT| { let start = if start < 0 { 0 } else if (start as usize) >= s.chars().count() { @@ -156,9 +156,9 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str .unwrap_or(-1 as INT)) }, ); - lib.set_fn_2( + lib.set_fn_2_mut( "index_of", - |s: ImmutableString, find: ImmutableString| { + |s: &mut ImmutableString, find: ImmutableString| { Ok(s.find(find.as_str()) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT))