diff --git a/README.md b/README.md index 8893d420..b812b5c4 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Rhai - Embedded Scripting for Rust [![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/HquqbYFcZ9) [![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit)](https://www.reddit.com/r/Rhai) +![Rhai logo](https://schungx.github.io/rhai/images/logo/rhai-banner-transparent-colour.svg) + Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. diff --git a/RELEASES.md b/RELEASES.md index d442c231..bc08d9dc 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -16,6 +16,7 @@ Bug fixes * Constants are no longer propagated by the optimizer if shadowed by a non-constant variable. * Constants passed as the `this` parameter to Rhai functions now throws an error if assigned to. * Generic type parameter of `Engine::register_iterator` is `IntoIterator` instead of `Iterator`. +* Fixes parsing of block comments ending with `**/` or inner blocks starting with `//*`. Breaking changes ---------------- diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index 7a750123..1df6e6a2 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -489,7 +489,7 @@ mod generate_tests { }; let expected_tokens = quote! { - impl PluginFunction for MyType { + impl PluginFunction for TestStruct { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -499,7 +499,7 @@ mod generate_tests { fn is_method_call(&self) -> bool { false } fn is_variadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(MyType()) } + fn clone_boxed(&self) -> Box { Box::new(TestStruct()) } fn input_names(&self) -> Box<[&'static str]> { new_vec!["x: usize"].into_boxed_slice() } @@ -513,7 +513,7 @@ mod generate_tests { }; let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); + assert_streams_eq(item_fn.generate_impl("TestStruct"), expected_tokens); } #[test] diff --git a/doc/src/about/index.md b/doc/src/about/index.md index 869016b9..5932e802 100644 --- a/doc/src/about/index.md +++ b/doc/src/about/index.md @@ -3,7 +3,7 @@ What is Rhai {{#include ../links.md}} -![Rhai Logo]({{rootUrl}}/images/rhai_logo.png) +![Rhai Logo]({{rootUrl}}/images/logo/rhai-banner-transparent-colour.svg) Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. @@ -35,9 +35,12 @@ and, in particular, a popular kind of milk tea consumed in India. Later, when the novel implementation technique behind ChaiScript was ported from C++ to Rust, logically the `C` was changed to an `R` to make it "RhaiScript", or just "Rhai". -### On the origin of the temporary Rhai logo +### On the origin of the semi-official Rhai logo -One of Rhai's maintainers, Stephen Chung, was thinking about a logo when he accidentally -came across a copy of _Catcher in the Rye_ in a restaurant. The rest was history. +One of Rhai's maintainers, [Stephen Chung](https://github.com/schungx), was thinking about a logo when he accidentally +came across a copy of _Catcher in the Rye_ in a restaurant, and drew the first version +of the logo. -It is temporary until it becomes official, that is... +Then [`@semirix`](https://github.com/semirix) refined it to the current version. + +The plan is to make the logo official together with a `1.0` release. diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md index 0ca26be5..71e94965 100644 --- a/doc/src/appendix/operators.md +++ b/doc/src/appendix/operators.md @@ -10,16 +10,16 @@ Operators | Operator | Description | Binary? | Binding direction | | :-----------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: | | `+` | add | yes | left | -| `-` | 1) subtract
2) negative | yes
no | left
right | +| `-` | 1) subtract
2) negative (prefix) | yes
no | left
right | | `*` | multiply | yes | left | | `/` | divide | yes | left | | `%` | modulo | yes | left | | `~` | power | yes | left | | `>>` | right bit-shift | yes | left | | `<<` | left bit-shift | yes | left | -| `&` | 1) bit-wise _And_
2) boolean _And_ | yes | left | -| \| | 1) bit-wise _Or_
2) boolean _Or_ | yes | left | -| `^` | 1) bit-wise _Xor_
2) boolean _Xor_ | yes | left | +| `&` | 1) bit-wise _AND_
2) boolean _AND_ | yes | left | +| \| | 1) bit-wise _OR_
2) boolean _OR_ | yes | left | +| `^` | 1) bit-wise _XOR_
2) boolean _XOR_ | yes | left | | `=`, `+=`, `-=`, `*=`, `/=`,
`~=`, `%=`, `<<=`, `>>=`, `&=`,
\|=, `^=` | assignments | yes | right | | `==` | equals to | yes | left | | `~=` | not equals to | yes | left | @@ -27,9 +27,9 @@ Operators | `>=` | greater than or equals to | yes | left | | `<` | less than | yes | left | | `<=` | less than or equals to | yes | left | -| `&&` | boolean _And_ (short-circuits) | yes | left | -| \|\| | boolean _Or_ (short-circuits) | yes | left | -| `!` | boolean _Not_ | no | left | +| `&&` | boolean _AND_ (short-circuits) | yes | left | +| \|\| | boolean _OR_ (short-circuits) | yes | left | +| `!` | boolean _NOT_ | no | left | | `[` .. `]` | indexing | yes | right | | `.` | 1) property access
2) method call | yes | right | diff --git a/doc/src/engine/compile.md b/doc/src/engine/compile.md index f4ee0e65..07c172c1 100644 --- a/doc/src/engine/compile.md +++ b/doc/src/engine/compile.md @@ -3,7 +3,10 @@ Compile a Script (to AST) {{#include ../links.md}} -To repeatedly evaluate a script, _compile_ it first into an `AST` (abstract syntax tree) form: +To repeatedly evaluate a script, _compile_ it first with `Engine::compile` into an `AST` +(abstract syntax tree) form. + +`Engine::eval_ast` evaluates a pre-compiled `AST`. ```rust // Compile to an AST and store it for later evaluations @@ -16,7 +19,8 @@ for _ in 0..42 { } ``` -Compiling a script file is also supported (not available under [`no_std`] or in [WASM] builds): +Compiling a script file is also supported with `Engine::compile_file` +(not available under [`no_std`] or in [WASM] builds): ```rust let ast = engine.compile_file("hello_world.rhai".into())?; diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 85d385aa..856392b0 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -116,16 +116,17 @@ The function signature of an implementation is: where: -| Parameter | Type | Description | -| ----------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------- | -| `context` | `&mut EvalContext` | mutable reference to the current evaluation _context_ | -| - `context.scope` | `&mut Scope` | mutable reference to the current [`Scope`]; variables can be added to/removed from it | -| - `context.engine()` | `&Engine` | reference to the current [`Engine`] | -| - `context.imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements | -| - `context.iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| - `context.this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | -| - `context.call_level()` | `usize` | the current nesting level of function calls | -| `inputs` | `&[Expression]` | a list of input expression trees | +| Parameter | Type | Description | +| -------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------- | +| `context` | `&mut EvalContext` | mutable reference to the current evaluation _context_ | +| • `scope()` | `&Scope` | reference to the current [`Scope`] | +| • `scope_mut()` | `&mut Scope` | mutable reference to the current [`Scope`]; variables can be added to/removed from it | +| • `engine()` | `&Engine` | reference to the current [`Engine`] | +| • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements | +| • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | +| • `this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | +| • `call_level()` | `usize` | the current nesting level of function calls | +| `inputs` | `&[Expression]` | a list of input expression trees | ### Return Value @@ -168,7 +169,7 @@ In other words, any [`Scope`] calls that change the list of must come _before_ a let var_name = inputs[0].get_variable_name().unwrap(); let expression = inputs.get(1).unwrap(); -context.scope.push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'! +context.scope_mut().push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'! let result = context.eval_expression_tree(expression)?; ``` @@ -195,7 +196,7 @@ fn implementation_func( let condition = inputs.get(2).unwrap(); // Push one new variable into the scope BEFORE 'context.eval_expression_tree' - context.scope.push(var_name, 0 as INT); + context.scope_mut().push(var_name, 0 as INT); loop { // Evaluate the statement block @@ -219,7 +220,7 @@ fn implementation_func( Ok(Dynamic::UNIT) } -// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0; +// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0 engine.register_custom_syntax( &[ "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax 1, // the number of new variables declared within this custom syntax @@ -227,6 +228,20 @@ engine.register_custom_syntax( )?; ``` +Remember that a custom syntax acts as an _expression_, so it can show up practically anywhere: + +```rust +// Use as an expression: +let foo = (exec |x| -> { x += 1 } while x < 0) * 100; + +// Use as a function call argument: +do_something(exec |x| -> { x += 1 } while x < 0, 24, true); + +// Use as a statement: +exec |x| -> { x += 1 } while x < 0; +// ^ terminate statement with ';' +``` + Step Four - Disable Unneeded Statement Types ------------------------------------------- @@ -258,8 +273,8 @@ Step Six - Profit! ------------------ -Really Advanced - Low Level Custom Syntax API --------------------------------------------- +Really Advanced - Custom Parsers +------------------------------- Sometimes it is desirable to have multiple custom syntax starting with the same symbol. This is especially common for _command-style_ syntax where the @@ -276,30 +291,67 @@ perform add something; // Add something to the system perform remove something; // Delete something from the system ``` -For even more flexibility, there is a _low level_ API for custom syntax that -allows the registration of an entire mini-parser. +Alternatively, a custom syntax may have variable length, with a termination symbol: + +```rust +// The following is a variable-length list terminated by '>' +tags < "foo", "bar", 123, ... , x+y, true > +``` + +For even more flexibility in order to handle these advanced use cases, there is a +_low level_ API for custom syntax that allows the registration of an entire mini-parser. Use `Engine::register_custom_syntax_raw` to register a custom syntax _parser_ -together with the implementation function: +together with the implementation function. + +### How Custom Parsers Work + +A custom parser takes as input parameters two pieces of information: + +* The symbols parsed so far; `$ident$` is replaced with the actual identifier parsed, + while `$expr$` and `$block$` stay as they were. + + The custom parser can inspect this symbols stream to determine the next symbol to parse. + +* The _look-ahead_ symbol, which is the symbol that will be parsed _next_. + + If the look-ahead is an expected symbol, the customer parser just returns it to continue parsing, + or it can return `$ident$` to parse it as an identifier, or even `$expr$` to start parsing + an expression. + + If the look-ahead is '`{`', then the custom parser may also return `$block$` to start parsing a + statements block. + + If the look-ahead is unexpected, the custom parser should then return the symbol expected + and Rhai will fail with a parse error containing information about the expected symbol. + +A custom parser always returns the _next_ symbol expected, which can also be `$ident$`, +`$expr$` or `$block$`, or `None` if parsing should terminate (_without_ reading the +look-ahead symbol). + + +### Example ```rust engine.register_custom_syntax_raw( "perform", - |stream| match stream.len() { + // The custom parser implementation - always returns the next symbol expected + // 'look_ahead' is the next symbol about to be read + |symbols, look_ahead| match symbols.len() { // perform ... 1 => Ok(Some("$ident$".to_string())), // perform command ... - 2 => match stream[1].as_str() { - "action" => Ok(Some("$expr$".to_string())), - "hello" => Ok(Some("world".to_string())), - "update" | "check" | "add" | "remove" => Ok(Some("$ident$".to_string())), + 2 => match symbols[1].as_str() { + "action" => Ok(Some("$expr$".into())), + "hello" => Ok(Some("world".into())), + "update" | "check" | "add" | "remove" => Ok(Some("$ident$".into())), "cleanup" => Ok(None), cmd => Err(ParseError(Box::new(ParseErrorType::BadInput( LexError::ImproperSymbol(format!("Improper command: {}", cmd)) )), Position::NONE)), }, // perform command arg ... - 3 => match (stream[1].as_str(), stream[2].as_str()) { + 3 => match (symbols[1].as_str(), symbols[2].as_str()) { ("action", _) => Ok(None), ("hello", "world") => Ok(None), ("update", arg) if arg == "system" => Ok(None), @@ -315,7 +367,9 @@ engine.register_custom_syntax_raw( }, _ => unreachable!(), }, - 0, // the number of new variables declared within this custom syntax + // Number of new variables declared by this custom syntax + 0, + // Implementation function implementation_func ); ``` @@ -324,20 +378,24 @@ engine.register_custom_syntax_raw( The custom syntax parser has the following signature: -> `Fn(stream: &[String]) -> Result, ParseError>` +> `Fn(symbols: &[ImmutableString], look_ahead: &str) -> Result, ParseError>` where: -| Parameter | Type | Description | -| --------- | :---------: | -------------------------------------------------------------------------------------------------- | -| `stream` | `&[String]` | a slice of symbols that have been parsed so far, possibly containing `"$expr$"` and/or `"$block$"` | +| Parameter | Type | Description | +| ------------ | :------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `symbols` | `&[ImmutableString]` | a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`; `$ident$` is replaced by the actual identifier | +| `look_ahead` | `&str` | a string slice containing the next symbol that is about to be read | + +Most strings are [`ImmutableString`][string]'s so it is usually more efficient to just `clone` the appropriate one +(if any matches, or keep an internal cache for commonly-used symbols) as the return value. ### Return Value -The return value is `Result, ParseError>` where: +The return value is `Result, ParseError>` where: -| Value | Description | -| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ok(None)` | parsing complete and there are no more symbols to match | -| `Ok(Some(symbol))` | next symbol to match, which can also be `"$expr$"`, `"$ident$"` or `"$block$"` | -| `Err(ParseError)` | error that is reflected back to the [`Engine`].
Normally this is `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate that there is a syntax error, but it can be any `ParseError`. | +| Value | Description | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Ok(None)` | parsing complete and there are no more symbols to match | +| `Ok(Some(symbol))` | the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$` | +| `Err(ParseError)` | error that is reflected back to the [`Engine`] - normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate that there is a syntax error, but it can be any `ParseError`. | diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md index e5add617..017814a5 100644 --- a/doc/src/engine/raw.md +++ b/doc/src/engine/raw.md @@ -17,7 +17,7 @@ To add more functionalities to a _raw_ `Engine`, load [packages] into it. Built-in Operators ------------------ -| Operators | Assignment operators | Supported for types (see [standard types]) | +| Operators | Assignment operators | Supported for types
(see [standard types]) | | ------------------------- | ---------------------------- | ----------------------------------------------------------------------------- | | `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `char`, `ImmutableString` | | `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) | diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md index f21a5754..86226b49 100644 --- a/doc/src/engine/var.md +++ b/doc/src/engine/var.md @@ -24,7 +24,7 @@ engine.on_var(|name, index, context| { EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE) )), // Silently maps 'chameleon' into 'innocent'. - "chameleon" => context.scope.get_value("innocent").map(Some).ok_or_else(|| Box::new( + "chameleon" => context.scope().get_value("innocent").map(Some).ok_or_else(|| Box::new( EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE) )), // Return Ok(None) to continue with the normal variable resolution process. @@ -67,17 +67,17 @@ The function signature passed to `Engine::on_var` takes the following form: where: -| Parameter | Type | Description | -| ----------------------------- | :-----------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `&str` | variable name | -| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the variable is supposed to reside.
Offsets start from 1, with 1 meaning the last variable in the current [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.
If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. | -| `context` | `&EvalContext` | reference to the current evaluation _context_ | -| - `context.scope` | `&Scope` | reference to the current [`Scope`] containing all variables up to the current evaluation position | -| - `context.engine()` | `&Engine` | reference to the current [`Engine`] | -| - `context.imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements | -| - `context.iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| - `context.this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | -| - `context.call_level()` | `usize` | the current nesting level of function calls | +| Parameter | Type | Description | +| -------------------------- | :-----------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `&str` | variable name | +| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the variable is supposed to reside.
Offsets start from 1, with 1 meaning the last variable in the current [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.
If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. | +| `context` | `&EvalContext` | reference to the current evaluation _context_ | +| • `scope()` | `&Scope` | reference to the current [`Scope`] | +| • `engine()` | `&Engine` | reference to the current [`Engine`] | +| • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements | +| • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | +| • `this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | +| • `call_level()` | `usize` | the current nesting level of function calls | ### Return Value diff --git a/doc/src/images/logo/favicon.png b/doc/src/images/logo/favicon.png new file mode 100644 index 00000000..f5cf9cac Binary files /dev/null and b/doc/src/images/logo/favicon.png differ diff --git a/doc/src/images/logo/favicon.svg b/doc/src/images/logo/favicon.svg new file mode 100644 index 00000000..16aeb468 --- /dev/null +++ b/doc/src/images/logo/favicon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-banner-transparent-colour.png b/doc/src/images/logo/rhai-banner-transparent-colour.png new file mode 100644 index 00000000..8d478bdc Binary files /dev/null and b/doc/src/images/logo/rhai-banner-transparent-colour.png differ diff --git a/doc/src/images/logo/rhai-banner-transparent-colour.svg b/doc/src/images/logo/rhai-banner-transparent-colour.svg new file mode 100644 index 00000000..57ee1693 --- /dev/null +++ b/doc/src/images/logo/rhai-banner-transparent-colour.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-colour-black.png b/doc/src/images/logo/rhai-colour-black.png new file mode 100644 index 00000000..b874a27e Binary files /dev/null and b/doc/src/images/logo/rhai-colour-black.png differ diff --git a/doc/src/images/logo/rhai-colour-black.svg b/doc/src/images/logo/rhai-colour-black.svg new file mode 100644 index 00000000..2a2469f1 --- /dev/null +++ b/doc/src/images/logo/rhai-colour-black.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-colour-white.png b/doc/src/images/logo/rhai-colour-white.png new file mode 100644 index 00000000..db83cee0 Binary files /dev/null and b/doc/src/images/logo/rhai-colour-white.png differ diff --git a/doc/src/images/logo/rhai-colour-white.svg b/doc/src/images/logo/rhai-colour-white.svg new file mode 100644 index 00000000..9008b4dd --- /dev/null +++ b/doc/src/images/logo/rhai-colour-white.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-icon-colour-black.png b/doc/src/images/logo/rhai-icon-colour-black.png new file mode 100644 index 00000000..55c7d714 Binary files /dev/null and b/doc/src/images/logo/rhai-icon-colour-black.png differ diff --git a/doc/src/images/logo/rhai-icon-colour-black.svg b/doc/src/images/logo/rhai-icon-colour-black.svg new file mode 100644 index 00000000..05db75f0 --- /dev/null +++ b/doc/src/images/logo/rhai-icon-colour-black.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-icon-colour-white.png b/doc/src/images/logo/rhai-icon-colour-white.png new file mode 100644 index 00000000..4af8351f Binary files /dev/null and b/doc/src/images/logo/rhai-icon-colour-white.png differ diff --git a/doc/src/images/logo/rhai-icon-colour-white.svg b/doc/src/images/logo/rhai-icon-colour-white.svg new file mode 100644 index 00000000..fde472a9 --- /dev/null +++ b/doc/src/images/logo/rhai-icon-colour-white.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-icon-transparent-black.png b/doc/src/images/logo/rhai-icon-transparent-black.png new file mode 100644 index 00000000..c1d0c712 Binary files /dev/null and b/doc/src/images/logo/rhai-icon-transparent-black.png differ diff --git a/doc/src/images/logo/rhai-icon-transparent-black.svg b/doc/src/images/logo/rhai-icon-transparent-black.svg new file mode 100644 index 00000000..f02f7a47 --- /dev/null +++ b/doc/src/images/logo/rhai-icon-transparent-black.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-icon-transparent-colour.png b/doc/src/images/logo/rhai-icon-transparent-colour.png new file mode 100644 index 00000000..c7a54fc4 Binary files /dev/null and b/doc/src/images/logo/rhai-icon-transparent-colour.png differ diff --git a/doc/src/images/logo/rhai-icon-transparent-colour.svg b/doc/src/images/logo/rhai-icon-transparent-colour.svg new file mode 100644 index 00000000..a8408964 --- /dev/null +++ b/doc/src/images/logo/rhai-icon-transparent-colour.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-icon-transparent-white.png b/doc/src/images/logo/rhai-icon-transparent-white.png new file mode 100644 index 00000000..cd63ff1e Binary files /dev/null and b/doc/src/images/logo/rhai-icon-transparent-white.png differ diff --git a/doc/src/images/logo/rhai-icon-transparent-white.svg b/doc/src/images/logo/rhai-icon-transparent-white.svg new file mode 100644 index 00000000..6648a7ae --- /dev/null +++ b/doc/src/images/logo/rhai-icon-transparent-white.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-black.png b/doc/src/images/logo/rhai-logo-transparent-colour-black.png new file mode 100644 index 00000000..9d2f9efb Binary files /dev/null and b/doc/src/images/logo/rhai-logo-transparent-colour-black.png differ diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-black.svg b/doc/src/images/logo/rhai-logo-transparent-colour-black.svg new file mode 100644 index 00000000..f7e36f24 --- /dev/null +++ b/doc/src/images/logo/rhai-logo-transparent-colour-black.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-white.png b/doc/src/images/logo/rhai-logo-transparent-colour-white.png new file mode 100644 index 00000000..028f6ad6 Binary files /dev/null and b/doc/src/images/logo/rhai-logo-transparent-colour-white.png differ diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-white.svg b/doc/src/images/logo/rhai-logo-transparent-colour-white.svg new file mode 100644 index 00000000..3055938d --- /dev/null +++ b/doc/src/images/logo/rhai-logo-transparent-colour-white.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-black.png b/doc/src/images/logo/rhai-logo-transparent-sil-black.png new file mode 100644 index 00000000..97b7d122 Binary files /dev/null and b/doc/src/images/logo/rhai-logo-transparent-sil-black.png differ diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-black.svg b/doc/src/images/logo/rhai-logo-transparent-sil-black.svg new file mode 100644 index 00000000..565c24ba --- /dev/null +++ b/doc/src/images/logo/rhai-logo-transparent-sil-black.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-white.png b/doc/src/images/logo/rhai-logo-transparent-sil-white.png new file mode 100644 index 00000000..99049d18 Binary files /dev/null and b/doc/src/images/logo/rhai-logo-transparent-sil-white.png differ diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-white.svg b/doc/src/images/logo/rhai-logo-transparent-sil-white.svg new file mode 100644 index 00000000..9eabc0d7 --- /dev/null +++ b/doc/src/images/logo/rhai-logo-transparent-sil-white.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-sil-black.png b/doc/src/images/logo/rhai-sil-black.png new file mode 100644 index 00000000..92b0b7e6 Binary files /dev/null and b/doc/src/images/logo/rhai-sil-black.png differ diff --git a/doc/src/images/logo/rhai-sil-black.svg b/doc/src/images/logo/rhai-sil-black.svg new file mode 100644 index 00000000..8d4c9b32 --- /dev/null +++ b/doc/src/images/logo/rhai-sil-black.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/logo/rhai-sil-white.png b/doc/src/images/logo/rhai-sil-white.png new file mode 100644 index 00000000..c1466588 Binary files /dev/null and b/doc/src/images/logo/rhai-sil-white.png differ diff --git a/doc/src/images/logo/rhai-sil-white.svg b/doc/src/images/logo/rhai-sil-white.svg new file mode 100644 index 00000000..bdb09519 --- /dev/null +++ b/doc/src/images/logo/rhai-sil-white.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/images/rhai.png b/doc/src/images/rhai.png deleted file mode 100644 index 73d16a99..00000000 Binary files a/doc/src/images/rhai.png and /dev/null differ diff --git a/doc/src/images/rhai_logo.png b/doc/src/images/rhai_logo.png deleted file mode 100644 index af45aa7f..00000000 Binary files a/doc/src/images/rhai_logo.png and /dev/null differ diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md index 3e25d5a0..edda3d4e 100644 --- a/doc/src/language/constants.md +++ b/doc/src/language/constants.md @@ -61,8 +61,8 @@ r" ``` -Constants Can be Modified via Rust ---------------------------------- +Caveat - Constants Can be Modified via Rust +------------------------------------------ A custom type stored as a constant cannot be modified via script, but _can_ be modified via a registered Rust function that takes a first `&mut` parameter - because there is no way for @@ -76,9 +76,15 @@ x.increment(); // call 'increment' defined in Rust with '&mut' first parame x == 43; // value of 'x' is changed! fn double() { - this *= 2; // function squares 'this' + this *= 2; // function doubles 'this' } +let y = 1; // 'y' is not constant and mutable + +y.double(); // double it... + +y == 2; // value of 'y' is changed as expected + x.double(); // <- error: cannot modify constant 'this' x == 43; // value of 'x' is unchanged by script diff --git a/doc/src/language/dynamic.md b/doc/src/language/dynamic.md index a89c9446..a143d519 100644 --- a/doc/src/language/dynamic.md +++ b/doc/src/language/dynamic.md @@ -15,7 +15,7 @@ it is usually used to perform type-specific actions based on the actual value's ```c let mystery = get_some_dynamic_value(); -switch mystery { +switch type_of(mystery) { "i64" => print("Hey, I got an integer here!"), "f64" => print("Hey, I got a float here!"), "string" => print("Hey, I got a string here!"), diff --git a/doc/src/patterns/events.md b/doc/src/patterns/events.md index cdea89c5..e7c5d946 100644 --- a/doc/src/patterns/events.md +++ b/doc/src/patterns/events.md @@ -134,29 +134,33 @@ impl Handler { // Say there are three events: 'start', 'end', 'update'. // In a real application you'd be handling errors... pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Result<(), Error> { + let engine = &self.engine; + let scope = &mut self.scope; + let ast = &self.ast; + match event_name { // The 'start' event maps to function 'start'. // In a real application you'd be handling errors... - "start" => self.engine.call_fn(&mut self.scope, &self.ast, "start", (event_data,))?, + "start" => engine.call_fn(scope, ast, "start", (event_data,))?, // The 'end' event maps to function 'end'. // In a real application you'd be handling errors... - "end" => self.engine.call_fn(&mut self.scope, &self.ast, "end", (event_data,))?, + "end" => engine.call_fn(scope, ast, "end", (event_data,))?, // The 'update' event maps to function 'update'. // This event provides a default implementation when the scripted function // is not found. - "update" => self.engine - .call_fn(&mut self.scope, &self.ast, "update", (event_data,)) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "update" => { - // Default implementation of 'update' event handler - self.scope.set_value("state2", SomeType::new(42)); - // Turn function-not-found into a success - Ok(Dynamic::UNIT) - } - _ => Err(err.into()) - })? + "update" => + engine.call_fn(scope, ast, "update", (event_data,)) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "update" => { + // Default implementation of 'update' event handler + self.scope.set_value("state2", SomeType::new(42)); + // Turn function-not-found into a success + Ok(Dynamic::UNIT) + } + _ => Err(err.into()) + })? } } } diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index ac0c9f80..f2edc3d2 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -271,9 +271,9 @@ use rhai::plugin::*; // a "prelude" import for macros #[export_module] mod my_module { - // This is the '+' operator for 'MyType'. + // This is the '+' operator for 'TestStruct'. #[rhai_fn(name = "+")] - pub fn add(obj: &mut MyType, value: i64) { + pub fn add(obj: &mut TestStruct, value: i64) { obj.prop += value; } // This function is 'calc (i64)'. @@ -305,24 +305,24 @@ mod my_module { pub fn greet(name: &str) -> String { format!("hello, {}!", name) } - // This is a getter for 'MyType::prop'. + // This is a getter for 'TestStruct::prop'. #[rhai_fn(get = "prop")] - pub fn get_prop(obj: &mut MyType) -> i64 { + pub fn get_prop(obj: &mut TestStruct) -> i64 { obj.prop } - // This is a setter for 'MyType::prop'. + // This is a setter for 'TestStruct::prop'. #[rhai_fn(set = "prop")] - pub fn set_prop(obj: &mut MyType, value: i64) { + pub fn set_prop(obj: &mut TestStruct, value: i64) { obj.prop = value; } - // This is an index getter for 'MyType'. + // This is an index getter for 'TestStruct'. #[rhai_fn(index_get)] - pub fn get_index(obj: &mut MyType, index: i64) -> bool { + pub fn get_index(obj: &mut TestStruct, index: i64) -> bool { obj.list[index] } - // This is an index setter for 'MyType'. + // This is an index setter for 'TestStruct'. #[rhai_fn(index_set)] - pub fn get_index(obj: &mut MyType, index: i64, state: bool) { + pub fn get_index(obj: &mut TestStruct, index: i64, state: bool) { obj.list[index] = state; } } @@ -344,7 +344,7 @@ use rhai::plugin::*; // a "prelude" import for macros mod my_module { // This function can be called in five ways #[rhai_fn(name = "get_prop_value", name = "prop", name = "+", set = "prop", index_get)] - pub fn prop_function(obj: &mut MyType, index: i64) -> i64 { + pub fn prop_function(obj: &mut TestStruct, index: i64) -> i64 { obj.prop[index] } } diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 36394e3a..1b0bec44 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -140,14 +140,14 @@ with a special "pretty-print" name, [`type_of()`] will return that name instead. engine .register_type::() .register_fn("new_ts1", TestStruct1::new) - .register_type_with_name::("MyType") + .register_type_with_name::("TestStruct") .register_fn("new_ts2", TestStruct2::new); let ts1_type = engine.eval::(r#"let x = new_ts1(); x.type_of()"#)?; let ts2_type = engine.eval::(r#"let x = new_ts2(); x.type_of()"#)?; println!("{}", ts1_type); // prints 'path::to::TestStruct' -println!("{}", ts1_type); // prints 'MyType' +println!("{}", ts1_type); // prints 'TestStruct' ``` diff --git a/doc/src/rust/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md index 32a4361e..0b8f789c 100644 --- a/doc/src/rust/modules/imp-resolver.md +++ b/doc/src/rust/modules/imp-resolver.md @@ -13,7 +13,7 @@ which contains only one function: `resolve`. When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name of the _module path_ (i.e. the path specified in the [`import`] statement). -* Upon success, it should return an [`Rc`][module] (or `Arc` under [`sync`]). +* Upon success, it should return an [`Rc`][module] (or [`Arc`][module] under [`sync`]). The module should call `Module::build_index` on the target module before returning. This method flattens the entire module tree and _indexes_ it for fast function name resolution. @@ -66,7 +66,7 @@ engine.set_module_resolver(Some(MyModuleResolver {})); engine.consume(r#" import "hello" as foo; // this 'import' statement will call - // 'MyModuleResolver::resolve' with "hello" as `path` + // 'MyModuleResolver::resolve' with "hello" as 'path' foo:bar(); "#)?; ``` diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 4d326e78..78137c30 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -65,14 +65,14 @@ The function signature passed to `Engine::register_raw_fn` takes the following f where: -| Parameter | Type | Description | -| ----------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `T` | `impl Clone` | return type of the function | -| `context` | `NativeCallContext` | the current _native call context_ | -| - `context.engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | -| - `context.imports()` | `Option<&Imports>` | reference to the current stack of [modules] imported via `import` statements (if any) | -| - `context.iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_. | +| Parameter | Type | Description | +| -------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `T` | `impl Clone` | return type of the function | +| `context` | `NativeCallContext` | the current _native call context_ | +| • `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | +| • `imports()` | `Option<&Imports>` | reference to the current stack of [modules] imported via `import` statements (if any) | +| • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | +| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_. | ### Return value diff --git a/doc/src/safety/progress.md b/doc/src/safety/progress.md index ff294976..749ac95b 100644 --- a/doc/src/safety/progress.md +++ b/doc/src/safety/progress.md @@ -46,5 +46,7 @@ Operations Count vs. Progress Percentage Notice that the _operations count_ value passed into the closure does not indicate the _percentage_ of work already done by the script (and thus it is not real _progress_ tracking), because it is impossible to determine -how long a script may run. It is possible, however, to calculate this percentage based on an estimated -total number of operations for a typical run. +how long a script may run. + +It is possible, however, to calculate this percentage based on an estimated total number of operations +for a typical run. diff --git a/doc/theme/favicon.png b/doc/theme/favicon.png new file mode 100644 index 00000000..f5cf9cac Binary files /dev/null and b/doc/theme/favicon.png differ diff --git a/doc/theme/favicon.svg b/doc/theme/favicon.svg new file mode 100644 index 00000000..4d5eaf02 --- /dev/null +++ b/doc/theme/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/ast.rs b/src/ast.rs index e9823156..75435825 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -83,8 +83,8 @@ pub struct ScriptFnDef { /// Access to external variables. #[cfg(not(feature = "no_closure"))] pub externals: Vec, - /// Comment block for function. - pub fn_comments: Vec, + /// Function doc-comments (if any). + pub comments: Vec, } impl fmt::Display for ScriptFnDef { @@ -107,16 +107,29 @@ impl fmt::Display for ScriptFnDef { } } -/// A type containing a script-defined function's metadata. -#[derive(Debug, Clone, Hash)] -pub struct ScriptFnMetadata { - pub comments: Vec, +/// A type containing the metadata of a script-defined function. +/// +/// Created by [`AST::iter_functions`]. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct ScriptFnMetadata<'a> { + /// Function doc-comments (if any). + /// + /// Block doc-comments are kept in a single string slice with line-breaks within. + /// + /// Line doc-comments are kept in one string slice per line without the termination line-break. + /// + /// Leading white-spaces are stripped, and each string slice always starts with the corresponding + /// doc-comment leader: `///` or `/**`. + pub comments: Vec<&'a str>, + /// Function access mode. pub access: FnAccess, - pub fn_name: ImmutableString, - pub params: Vec, + /// Function name. + pub name: &'a str, + /// Function parameters (if any). + pub params: Vec<&'a str>, } -impl fmt::Display for ScriptFnMetadata { +impl fmt::Display for ScriptFnMetadata<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -126,23 +139,19 @@ impl fmt::Display for ScriptFnMetadata { } else { "" }, - self.fn_name, - self.params - .iter() - .map(|p| p.as_str()) - .collect::>() - .join(", ") + self.name, + self.params.iter().cloned().collect::>().join(", ") ) } } -impl Into for &ScriptFnDef { - fn into(self) -> ScriptFnMetadata { +impl<'a> Into> for &'a ScriptFnDef { + fn into(self) -> ScriptFnMetadata<'a> { ScriptFnMetadata { - comments: self.fn_comments.clone(), + comments: self.comments.iter().map(|s| s.as_str()).collect(), access: self.access, - fn_name: self.name.clone(), - params: self.params.iter().cloned().collect(), + name: &self.name, + params: self.params.iter().map(|s| s.as_str()).collect(), } } } @@ -538,7 +547,7 @@ impl AST { } self } - /// Iterate through all functions + /// Iterate through all function definitions. #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { diff --git a/src/dynamic.rs b/src/dynamic.rs index 41f2756e..c6efb8b8 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -503,7 +503,7 @@ impl Clone for Dynamic { /// /// ## WARNING /// - /// The cloned copy is marked [`AccessType::Normal`] even if the original is constant. + /// The cloned copy is marked read-write even if the original is read-only. fn clone(&self) -> Self { match self.0 { Union::Unit(value, _) => Self(Union::Unit(value, AccessMode::ReadWrite)), diff --git a/src/optimize.rs b/src/optimize.rs index 2ef94938..53be762e 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -868,7 +868,7 @@ pub fn optimize_into_ast( lib: None, #[cfg(not(feature = "no_module"))] mods: Default::default(), - fn_comments: Default::default(), + comments: Default::default(), }) .for_each(|fn_def| { lib2.set_script_fn(fn_def); diff --git a/src/parser.rs b/src/parser.rs index afd0dd5f..f0003abd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2409,14 +2409,14 @@ fn parse_stmt( ) -> Result, ParseError> { use AccessMode::{ReadOnly, ReadWrite}; - let mut fn_comments: Vec = Default::default(); - let mut fn_comments_pos = Position::NONE; + let mut comments: Vec = Default::default(); + let mut comments_pos = Position::NONE; - // Handle doc-comment. + // Handle doc-comments. #[cfg(not(feature = "no_function"))] while let (Token::Comment(ref comment), comment_pos) = input.peek().unwrap() { - if fn_comments_pos.is_none() { - fn_comments_pos = *comment_pos; + if comments_pos.is_none() { + comments_pos = *comment_pos; } if !is_doc_comment(comment) { @@ -2424,16 +2424,16 @@ fn parse_stmt( } if !settings.is_global { - return Err(PERR::WrongDocComment.into_err(fn_comments_pos)); + return Err(PERR::WrongDocComment.into_err(comments_pos)); } if let Token::Comment(comment) = input.next().unwrap().0 { - fn_comments.push(comment); + comments.push(comment); match input.peek().unwrap() { (Token::Fn, _) | (Token::Private, _) => break, (Token::Comment(_), _) => (), - _ => return Err(PERR::WrongDocComment.into_err(fn_comments_pos)), + _ => return Err(PERR::WrongDocComment.into_err(comments_pos)), } } else { unreachable!(); @@ -2492,7 +2492,7 @@ fn parse_stmt( pos: pos, }; - let func = parse_fn(input, &mut new_state, lib, access, settings, fn_comments)?; + let func = parse_fn(input, &mut new_state, lib, access, settings, comments)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()); @@ -2651,7 +2651,7 @@ fn parse_fn( lib: &mut FunctionsLib, access: FnAccess, mut settings: ParseSettings, - fn_comments: Vec, + comments: Vec, ) -> Result { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2737,7 +2737,7 @@ fn parse_fn( lib: None, #[cfg(not(feature = "no_module"))] mods: Default::default(), - fn_comments, + comments, }) } @@ -2894,7 +2894,7 @@ fn parse_anon_fn( lib: None, #[cfg(not(feature = "no_module"))] mods: Default::default(), - fn_comments: Default::default(), + comments: Default::default(), }; let expr = Expr::FnPointer(fn_name, settings.pos); diff --git a/src/token.rs b/src/token.rs index f1c00d14..e6f3588a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -918,41 +918,41 @@ fn eat_next(stream: &mut impl InputStream, pos: &mut Position) -> Option { } /// Scan for a block comment until the end. -fn scan_comment( +fn scan_block_comment( stream: &mut impl InputStream, mut level: usize, pos: &mut Position, - comment: &mut String, + comment: &mut Option, ) -> usize { while let Some(c) = stream.get_next() { pos.advance(); - if !comment.is_empty() { + if let Some(ref mut comment) = comment { comment.push(c); } match c { '/' => { - if let Some(c2) = stream.get_next() { - if !comment.is_empty() { - comment.push(c2); - } + if let Some(c2) = stream.peek_next() { if c2 == '*' { + eat_next(stream, pos); + if let Some(ref mut comment) = comment { + comment.push(c2); + } level += 1; } } - pos.advance(); } '*' => { - if let Some(c2) = stream.get_next() { - if !comment.is_empty() { - comment.push(c2); - } + if let Some(c2) = stream.peek_next() { if c2 == '/' { + eat_next(stream, pos); + if let Some(ref mut comment) = comment { + comment.push(c2); + } level -= 1; } } - pos.advance(); } '\n' => pos.new_line(), _ => (), @@ -1032,11 +1032,16 @@ fn get_next_token_inner( // Still inside a comment? if state.comment_level > 0 { let start_pos = *pos; - let mut comment = String::new(); - state.comment_level = scan_comment(stream, state.comment_level, pos, &mut comment); + let mut comment = if state.include_comments { + Some(String::new()) + } else { + None + }; - if state.include_comments || is_doc_comment(&comment) { - return Some((Token::Comment(comment), start_pos)); + state.comment_level = scan_block_comment(stream, state.comment_level, pos, &mut comment); + + if state.include_comments || is_doc_comment(comment.as_ref().unwrap()) { + return Some((Token::Comment(comment.unwrap()), start_pos)); } } @@ -1282,10 +1287,13 @@ fn get_next_token_inner( ('/', '/') => { eat_next(stream, pos); - let mut comment = match stream.peek_next().unwrap() { - '/' => "///".to_string(), - _ if state.include_comments => "//".to_string(), - _ => String::new(), + let mut comment = match stream.peek_next() { + Some('/') => { + eat_next(stream, pos); + Some("///".to_string()) + } + _ if state.include_comments => Some("//".to_string()), + _ => None, }; while let Some(c) = stream.get_next() { @@ -1293,30 +1301,33 @@ fn get_next_token_inner( pos.new_line(); break; } - - if !comment.is_empty() { + if let Some(ref mut comment) = comment { comment.push(c); } pos.advance(); } - if state.include_comments || is_doc_comment(&comment) { + if let Some(comment) = comment { return Some((Token::Comment(comment), start_pos)); } } ('/', '*') => { state.comment_level = 1; - eat_next(stream, pos); - let mut comment = match stream.peek_next().unwrap() { - '*' => "/**".to_string(), - _ if state.include_comments => "/*".to_string(), - _ => String::new(), + let mut comment = match stream.peek_next() { + Some('*') => { + eat_next(stream, pos); + Some("/**".to_string()) + } + _ if state.include_comments => Some("/*".to_string()), + _ => None, }; - state.comment_level = scan_comment(stream, state.comment_level, pos, &mut comment); - if state.include_comments || is_doc_comment(&comment) { + state.comment_level = + scan_block_comment(stream, state.comment_level, pos, &mut comment); + + if let Some(comment) = comment { return Some((Token::Comment(comment), start_pos)); } } diff --git a/tests/closures.rs b/tests/closures.rs index fd5f6c56..aa3db7f4 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -195,7 +195,7 @@ fn test_closures_data_race() -> Result<(), Box> { Ok(()) } -type MyType = Rc>; +type TestStruct = Rc>; #[test] #[cfg(not(feature = "no_object"))] @@ -203,18 +203,18 @@ type MyType = Rc>; fn test_closures_shared_obj() -> Result<(), Box> { let mut engine = Engine::new(); - // Register API on MyType + // Register API on TestStruct engine - .register_type_with_name::("MyType") + .register_type_with_name::("TestStruct") .register_get_set( "data", - |p: &mut MyType| *p.borrow(), - |p: &mut MyType, value: INT| *p.borrow_mut() = value, + |p: &mut TestStruct| *p.borrow(), + |p: &mut TestStruct, value: INT| *p.borrow_mut() = value, ) - .register_fn("+=", |p1: &mut MyType, p2: MyType| { + .register_fn("+=", |p1: &mut TestStruct, p2: TestStruct| { *p1.borrow_mut() += *p2.borrow() }) - .register_fn("-=", |p1: &mut MyType, p2: MyType| { + .register_fn("-=", |p1: &mut TestStruct, p2: TestStruct| { *p1.borrow_mut() -= *p2.borrow() }); @@ -234,7 +234,7 @@ fn test_closures_shared_obj() -> Result<(), Box> { let res = engine.eval_ast::(&ast)?; // Make closure - let f = move |p1: MyType, p2: MyType| -> Result<(), Box> { + let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box> { let action_ptr = res["action"].clone().cast::(); let name = action_ptr.fn_name(); engine.call_fn::<_, ()>(&mut Scope::new(), &ast, name, (p1, p2)) diff --git a/tests/comments.rs b/tests/comments.rs index d7525960..17746a94 100644 --- a/tests/comments.rs +++ b/tests/comments.rs @@ -21,5 +21,62 @@ fn test_comments() -> Result<(), Box> { 42 ); + assert_eq!(engine.eval::<()>("/* Hello world */")?, ()); + + Ok(()) +} + +#[cfg(not(feature = "no_function"))] +#[test] +fn test_comments_doc() -> Result<(), Box> { + let engine = Engine::new(); + + let ast = engine.compile( + r" + /// Hello world + + + fn foo() {} + ", + )?; + + assert_eq!( + ast.iter_functions().next().unwrap().comments[0], + "/// Hello world" + ); + + assert!(engine + .compile( + r" + /// Hello world + let x = 42; + " + ) + .is_err()); + + let ast = engine.compile( + r" + /** Hello world + ** how are you? + **/ + + fn foo() {} + ", + )?; + + assert_eq!( + ast.iter_functions().next().unwrap().comments[0], + "/** Hello world\n ** how are you?\n **/" + ); + + assert!(engine + .compile( + r" + /** Hello world */ + let x = 42; + " + ) + .is_err()); + Ok(()) } diff --git a/tests/syntax.rs b/tests/syntax.rs index f2049e86..55272f54 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -29,8 +29,11 @@ fn test_custom_syntax() -> Result<(), Box> { context.scope_mut().push(var_name, 0 as INT); + let mut count: INT = 0; + loop { context.eval_expression_tree(stmt)?; + count += 1; let stop = !context .eval_expression_tree(condition)? @@ -48,10 +51,20 @@ fn test_custom_syntax() -> Result<(), Box> { } } - Ok(Dynamic::UNIT) + Ok(count.into()) }, )?; + assert_eq!( + engine.eval::( + r" + let x = 0; + let foo = (exec |x| -> { x += 2 } while x < 42) * 10; + foo + " + )?, + 210 + ); assert_eq!( engine.eval::( r"