Merge pull request #589 from schungx/master

Add module documentation feature.
This commit is contained in:
Stephen Chung 2022-07-25 16:43:21 +08:00 committed by GitHub
commit 602efc7042
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 764 additions and 409 deletions

View File

@ -6,10 +6,17 @@ Version 1.9.0
The minimum Rust version is now `1.60.0` in order to use the `dep:` syntax for dependencies. The minimum Rust version is now `1.60.0` in order to use the `dep:` syntax for dependencies.
Bug fixes
---------
* `switch` cases with conditions that evaluate to constant `()` no longer optimize to `false` (should raise a type error during runtime).
* Fixes concatenation of BLOB's and strings, where the BLOB's should be interpreted as UTF-8 encoded strings.
New features New features
------------ ------------
* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most). * A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most).
* Comment lines beginning with `//!` (requires the `metadata` feature) are now collected as the script file's _module documentation_.
Enhancements Enhancements
------------ ------------
@ -25,6 +32,8 @@ Enhancements
* `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block. * `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block.
* A new `range` function variant that takes an exclusive range with a step. * A new `range` function variant that takes an exclusive range with a step.
* `as_string` is added to BLOB's to convert it into a string by interpreting it as a UTF-8 byte stream.
* `FnAccess::is_private`, `FnAccess::is_public`, `FnNamespace::is_module_namespace` and `FnNameSpace::is_global_namespace` are added for convenience.
Version 1.8.0 Version 1.8.0

View File

@ -6,6 +6,7 @@ name = "rhai"
version = "1.8.0" version = "1.8.0"
rust-version = "1.60.0" rust-version = "1.60.0"
edition = "2018" edition = "2018"
resolver = "2"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
homepage = "https://rhai.rs" homepage = "https://rhai.rs"

View File

@ -200,7 +200,7 @@ pub fn generate_body(
let ns_str = syn::Ident::new( let ns_str = syn::Ident::new(
match namespace { match namespace {
FnNamespaceAccess::Unset => unreachable!(), FnNamespaceAccess::Unset => unreachable!("`namespace` should be set"),
FnNamespaceAccess::Global => "Global", FnNamespaceAccess::Global => "Global",
FnNamespaceAccess::Internal => "Internal", FnNamespaceAccess::Internal => "Internal",
}, },

View File

@ -1,4 +1,4 @@
// Implementation of the Event Handler With State Pattern - JS Style //! Implementation of the Event Handler With State Pattern - JS Style
/// Initialize user-provided state. /// Initialize user-provided state.
fn init() { fn init() {

View File

@ -1,4 +1,4 @@
// Implementation of the Event Handler With State Pattern - Main Style //! Implementation of the Event Handler With State Pattern - Main Style
/// Initialize user-provided state (shadows system-provided state, if any). /// Initialize user-provided state (shadows system-provided state, if any).
fn init() { fn init() {

View File

@ -1,4 +1,4 @@
// Implementation of the Event Handler With State Pattern - Map Style //! Implementation of the Event Handler With State Pattern - Map Style
/// Initialize user-provided state. /// Initialize user-provided state.
/// State is stored inside an object map bound to 'state'. /// State is stored inside an object map bound to 'state'.

View File

@ -1,4 +1,4 @@
// This script contains a single assignment statement. //! This script contains a single assignment statement.
let x = 78; let x = 78;

View File

@ -1,3 +1,5 @@
//! This script illustrates how to put doc-comments on functions.
/// The function `foo`, which prints `hello, world!` and a magic number, /// The function `foo`, which prints `hello, world!` and a magic number,
/// accepts three parameters. /// accepts three parameters.
/// ///

View File

@ -1,5 +1,5 @@
// This script calculates the n-th Fibonacci number using a really dumb algorithm //! This script calculates the n-th Fibonacci number using a really dumb algorithm
// to test the speed of the scripting engine. //! to test the speed of the scripting engine.
const TARGET = 28; const TARGET = 28;
const REPEAT = 5; const REPEAT = 5;

View File

@ -1,4 +1,4 @@
// This script runs for-loops. //! This script runs for-loops.
let arr = [1, true, 123.456, "hello", 3, 42]; let arr = [1, true, 123.456, "hello", 3, 42];

View File

@ -1,4 +1,4 @@
// This script runs for-loops //! This script runs for-loops
const MAX = 1_000_000; const MAX = 1_000_000;

View File

@ -1,4 +1,4 @@
// This script runs for-loops with closures. //! This script runs for-loops with closures.
const MAX = 100; const MAX = 100;
const CHECK = ((MAX - 1) ** 2) * MAX; const CHECK = ((MAX - 1) ** 2) * MAX;

View File

@ -1,4 +1,4 @@
// This script defines a function and calls it. //! This script defines a function and calls it.
fn call_me() { fn call_me() {
return 3; return 3;

View File

@ -1,4 +1,4 @@
// This script defines a function with two parameters and local variables. //! This script defines a function with two parameters and local variables.
let a = 3; let a = 3;

View File

@ -1,5 +1,4 @@
// This script defines a function with many parameters. //! This script defines a function with many parameters.
//
const KEY = 38; const KEY = 38;

View File

@ -1,4 +1,4 @@
// This script defines a function that acts as a method. //! This script defines a function that acts as a method.
// Use 'this' to refer to the object of a method call // Use 'this' to refer to the object of a method call
fn action(x, y) { fn action(x, y) {

View File

@ -1,4 +1,4 @@
// This script runs if statements. //! This script runs if statements.
let a = 42; let a = 42;
let b = 123; let b = 123;

View File

@ -1,4 +1,4 @@
// This script runs an if expression. //! This script runs an if expression.
let a = 42; let a = 42;
let b = 123; let b = 123;

View File

@ -1,4 +1,4 @@
// This script runs an infinite loop, ending it with a break statement. //! This script runs an infinite loop, ending it with a break statement.
let x = 10; let x = 10;

View File

@ -1,4 +1,4 @@
// This script simulates multi-dimensional matrix calculations. //! This script simulates multi-dimensional matrix calculations.
const SIZE = 50; const SIZE = 50;

View File

@ -1,4 +1,4 @@
// This script imports an external script as a module. //! This script imports an external script as a module.
import "loop" as x; import "loop" as x;

View File

@ -1,4 +1,4 @@
// This script simulates object-oriented programming (OOP) techniques using closures. //! This script simulates object-oriented programming (OOP) techniques using closures.
// External variable that will be captured. // External variable that will be captured.
let last_value = (); let last_value = ();

View File

@ -1,4 +1,4 @@
// This script runs a single expression. //! This script runs a single expression.
print("The result should be 46:"); print("The result should be 46:");

View File

@ -1,4 +1,4 @@
// This script runs a complex expression. //! This script runs a complex expression.
print("The result should be 182:"); print("The result should be 182:");

View File

@ -1,4 +1,4 @@
// This script runs a complex expression. //! This script runs a complex expression.
print("The result should be 230:"); print("The result should be 230:");

View File

@ -1,4 +1,4 @@
// This script uses the Sieve of Eratosthenes to calculate prime numbers. //! This script uses the Sieve of Eratosthenes to calculate prime numbers.
let now = timestamp(); let now = timestamp();

View File

@ -1,4 +1,4 @@
// This script runs 1 million iterations to test the speed of the scripting engine. //! This script runs 1 million iterations to test the speed of the scripting engine.
let now = timestamp(); let now = timestamp();
let x = 1_000_000; let x = 1_000_000;

170
scripts/static.d.rhai Normal file
View File

@ -0,0 +1,170 @@
/// This definition file extends the scope of all scripts.
///
/// The items defined here simply exist and are available.
/// everywhere.
///
/// These definitions should be used for built-in functions and
/// local domain-specific environment-provided values.
module static;
/// Display any data to the standard output.
///
/// # Example
///
/// ```rhai
/// let answer = 42;
///
/// print(`The Answer is ${answer}`);
/// ```
fn print(data: ?);
/// Display any data to the standard output in debug format.
///
/// # Example
///
/// ```rhai
/// let answer = 42;
///
/// debug(answer);
/// ```
fn debug(data: ?);
/// Get the type of a value.
///
/// # Example
///
/// ```rhai
/// let x = "hello, world!";
///
/// print(x.type_of()); // prints "string"
/// ```
fn type_of(data: ?) -> String;
/// Create a function pointer to a named function.
///
/// If the specified name is not a valid function name, an error is raised.
///
/// # Example
///
/// ```rhai
/// let f = Fn("foo"); // function pointer to 'foo'
///
/// f.call(42); // call: foo(42)
/// ```
fn Fn(fn_name: String) -> FnPtr;
/// Call a function pointed to by a function pointer,
/// passing following arguments to the function call.
///
/// If an appropriate function is not found, an error is raised.
///
/// # Example
///
/// ```rhai
/// let f = Fn("foo"); // function pointer to 'foo'
///
/// f.call(1, 2, 3); // call: foo(1, 2, 3)
/// ```
fn call(fn_ptr: FnPtr, ...args: ?) -> ?;
/// Call a function pointed to by a function pointer, binding the `this` pointer
/// to the object of the method call, and passing on following arguments to the function call.
///
/// If an appropriate function is not found, an error is raised.
///
/// # Example
///
/// ```rhai
/// fn add(x) {
/// this + x
/// }
///
/// let f = Fn("add"); // function pointer to 'add'
///
/// let x = 41;
///
/// let r = x.call(f, 1); // call: add(1) with 'this' = 'x'
///
/// print(r); // prints 42
/// ```
fn call(obj: ?, fn_ptr: FnPtr, ...args: ?) -> ?;
/// Curry a number of arguments into a function pointer and return it as a new function pointer.
///
/// # Example
///
/// ```rhai
/// fn foo(x, y, z) {
/// x + y + z
/// }
///
/// let f = Fn("foo");
///
/// let g = f.curry(1, 2); // curried arguments: 1, 2
///
/// g.call(3); // call: foo(1, 2, 3)
/// ```
fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr;
/// Return `true` if a script-defined function exists with a specified name and
/// number of parameters.
///
/// # Example
///
/// ```rhai
/// fn foo(x) { }
///
/// print(is_def_fn("foo", 1)); // prints true
/// print(is_def_fn("foo", 2)); // prints false
/// print(is_def_fn("foo", 0)); // prints false
/// print(is_def_fn("bar", 1)); // prints false
/// ```
fn is_def_fn(fn_name: String, num_params: i64) -> bool;
/// Return `true` if a variable matching a specified name is defined.
///
/// # Example
///
/// ```rhai
/// let x = 42;
///
/// print(is_def_var("x")); // prints true
/// print(is_def_var("foo")); // prints false
///
/// {
/// let y = 1;
/// print(is_def_var("y")); // prints true
/// }
///
/// print(is_def_var("y")); // prints false
/// ```
fn is_def_var(var_name: String) -> bool;
/// Return `true` if the variable is shared.
///
/// # Example
///
/// ```rhai
/// let x = 42;
///
/// print(is_shared(x)); // prints false
///
/// let f = || x; // capture 'x', making it shared
///
/// print(is_shared(x)); // prints true
/// ```
fn is_shared(variable: ?) -> bool;
/// Evaluate a text script within the current scope.
///
/// # Example
///
/// ```rhai
/// let x = 42;
///
/// eval("let y = x; x = 123;");
///
/// print(x); // prints 123
/// print(y); // prints 42
/// ```
fn eval(script: String) -> ?;

View File

@ -1,4 +1,4 @@
// This script tests string operations. //! This script tests string operations.
print("hello"); print("hello");
print("this\nis \\ nice"); // escape sequences print("this\nis \\ nice"); // escape sequences

View File

@ -1,4 +1,4 @@
// This script tests object maps and strings. //! This script tests object maps and strings.
print("Ready... Go!"); print("Ready... Go!");

View File

@ -1,4 +1,4 @@
// This script runs a switch statement in a for-loop. //! This script runs a switch statement in a for-loop.
let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4]; let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4];

View File

@ -1,4 +1,4 @@
// This script runs a while loop. //! This script runs a while loop.
let x = 10; let x = 10;

View File

@ -222,7 +222,10 @@ impl Engine {
self.token_mapper.as_ref().map(<_>::as_ref), self.token_mapper.as_ref().map(<_>::as_ref),
); );
let mut state = ParseState::new(self, scope, tokenizer_control); let mut state = ParseState::new(self, scope, tokenizer_control);
self.parse(&mut stream.peekable(), &mut state, optimization_level) let mut ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?;
#[cfg(feature = "metadata")]
ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n"));
Ok(ast)
} }
/// Compile a string containing an expression into an [`AST`], /// Compile a string containing an expression into an [`AST`],
/// which can be used later for evaluation. /// which can be used later for evaluation.

View File

@ -78,7 +78,7 @@ impl Expression<'_> {
/// ///
/// The following option is available: /// The following option is available:
/// ///
/// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] /// * whether to rewind the [`Scope`][crate::Scope] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock]
/// ///
/// # WARNING - Unstable API /// # WARNING - Unstable API
/// ///

View File

@ -318,7 +318,7 @@ impl crate::Expression<'_> {
/// ///
/// # Deprecated /// # Deprecated
/// ///
/// This method is deprecated. Use [`get_string_value`][Expression::get_string_value] instead. /// This method is deprecated. Use [`get_string_value`][crate::Expression::get_string_value] instead.
/// ///
/// This method will be removed in the next major version. /// This method will be removed in the next major version.
#[deprecated(since = "1.4.0", note = "use `get_string_value` instead")] #[deprecated(since = "1.4.0", note = "use `get_string_value` instead")]

View File

@ -2,6 +2,7 @@
#![cfg(not(feature = "no_optimize"))] #![cfg(not(feature = "no_optimize"))]
use crate::{Engine, OptimizationLevel, Scope, AST}; use crate::{Engine, OptimizationLevel, Scope, AST};
use std::mem;
impl Engine { impl Engine {
/// Control whether and how the [`Engine`] will optimize an [`AST`] after compilation. /// Control whether and how the [`Engine`] will optimize an [`AST`] after compilation.
@ -59,13 +60,18 @@ impl Engine {
.map(|f| f.func.get_script_fn_def().unwrap().clone()) .map(|f| f.func.get_script_fn_def().unwrap().clone())
.collect(); .collect();
crate::optimizer::optimize_into_ast( let mut new_ast = crate::optimizer::optimize_into_ast(
self, self,
scope, scope,
ast.take_statements(), ast.take_statements(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib, lib,
optimization_level, optimization_level,
) );
#[cfg(feature = "metadata")]
new_ast.set_doc(mem::take(ast.doc_mut()));
new_ast
} }
} }

View File

@ -25,9 +25,7 @@ impl Engine {
let (stream, tokenizer_control) = let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let mut state = ParseState::new(self, scope, tokenizer_control); let mut state = ParseState::new(self, scope, tokenizer_control);
let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?;
self.run_ast_with_scope(scope, &ast) self.run_ast_with_scope(scope, &ast)
} }
/// Evaluate an [`AST`], returning any error (if any). /// Evaluate an [`AST`], returning any error (if any).

View File

@ -1,7 +1,7 @@
//! Module defining the AST (abstract syntax tree). //! Module defining the AST (abstract syntax tree).
use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer}; use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer};
use crate::{Dynamic, FnNamespace, Identifier, Position}; use crate::{Dynamic, FnNamespace, Identifier, Position, SmartString};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -21,6 +21,9 @@ pub struct AST {
/// Source of the [`AST`]. /// Source of the [`AST`].
/// No source if string is empty. /// No source if string is empty.
source: Identifier, source: Identifier,
/// [`AST`] documentation.
#[cfg(feature = "metadata")]
doc: SmartString,
/// Global statements. /// Global statements.
body: StmtBlock, body: StmtBlock,
/// Script-defined functions. /// Script-defined functions.
@ -42,13 +45,11 @@ impl fmt::Debug for AST {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fp = f.debug_struct("AST"); let mut fp = f.debug_struct("AST");
if !self.source.is_empty() { fp.field("source", &self.source);
fp.field("source: ", &self.source); #[cfg(feature = "metadata")]
} fp.field("doc", &self.doc);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(ref resolver) = self.resolver { fp.field("resolver", &self.resolver);
fp.field("resolver: ", resolver);
}
fp.field("body", &self.body.as_slice()); fp.field("body", &self.body.as_slice());
@ -74,6 +75,8 @@ impl AST {
) -> Self { ) -> Self {
Self { Self {
source: Identifier::new_const(), source: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
body: StmtBlock::new(statements, Position::NONE, Position::NONE), body: StmtBlock::new(statements, Position::NONE, Position::NONE),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib: functions.into(), lib: functions.into(),
@ -92,6 +95,8 @@ impl AST {
) -> Self { ) -> Self {
Self { Self {
source: Identifier::new_const(), source: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
body: StmtBlock::new(statements, Position::NONE, Position::NONE), body: StmtBlock::new(statements, Position::NONE, Position::NONE),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib: functions.into(), lib: functions.into(),
@ -140,6 +145,8 @@ impl AST {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
source: Identifier::new_const(), source: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
body: StmtBlock::NONE, body: StmtBlock::NONE,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib: crate::Module::new().into(), lib: crate::Module::new().into(),
@ -180,6 +187,41 @@ impl AST {
self.source.clear(); self.source.clear();
self self
} }
/// Get the documentation (if any).
/// Exported under the `metadata` feature only.
///
/// Documentation is a collection of all comment lines beginning with `//!`.
///
/// Leading white-spaces are stripped, and each line always starts with `//!`.
#[cfg(feature = "metadata")]
#[inline(always)]
pub fn doc(&self) -> &str {
&self.doc
}
/// Clear the documentation.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
#[inline(always)]
pub fn clear_doc(&mut self) -> &mut Self {
self.doc.clear();
self
}
/// Get a mutable reference to the documentation.
///
/// Only available under `metadata`.
#[cfg(feature = "metadata")]
#[inline(always)]
pub(crate) fn doc_mut(&mut self) -> &mut SmartString {
&mut self.doc
}
/// Set the documentation.
///
/// Only available under `metadata`.
#[cfg(feature = "metadata")]
#[inline(always)]
pub(crate) fn set_doc(&mut self, doc: impl Into<SmartString>) {
self.doc = doc.into();
}
/// Get the statements. /// Get the statements.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
@ -292,6 +334,8 @@ impl AST {
lib.merge_filtered(&self.lib, &filter); lib.merge_filtered(&self.lib, &filter);
Self { Self {
source: self.source.clone(), source: self.source.clone(),
#[cfg(feature = "metadata")]
doc: self.doc.clone(),
body: StmtBlock::NONE, body: StmtBlock::NONE,
lib: lib.into(), lib: lib.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -305,6 +349,8 @@ impl AST {
pub fn clone_statements_only(&self) -> Self { pub fn clone_statements_only(&self) -> Self {
Self { Self {
source: self.source.clone(), source: self.source.clone(),
#[cfg(feature = "metadata")]
doc: self.doc.clone(),
body: self.body.clone(), body: self.body.clone(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
lib: crate::Module::new().into(), lib: crate::Module::new().into(),
@ -543,6 +589,14 @@ impl AST {
} }
} }
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !_ast.doc.is_empty() {
_ast.doc.push('\n');
}
_ast.doc.push_str(other.doc());
}
_ast _ast
} }
/// Combine one [`AST`] with another. The second [`AST`] is consumed. /// Combine one [`AST`] with another. The second [`AST`] is consumed.
@ -636,6 +690,14 @@ impl AST {
crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter); crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter);
} }
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !self.doc.is_empty() {
self.doc.push('\n');
}
self.doc.push_str(&other.doc);
}
self self
} }
/// Filter out the functions, retaining only some based on a filter predicate. /// Filter out the functions, retaining only some based on a filter predicate.

View File

@ -14,6 +14,27 @@ pub enum FnAccess {
Public, Public,
} }
impl FnAccess {
/// Is this function private?
#[inline(always)]
#[must_use]
pub fn is_private(self) -> bool {
match self {
Self::Private => true,
Self::Public => false,
}
}
/// Is this function public?
#[inline(always)]
#[must_use]
pub fn is_public(self) -> bool {
match self {
Self::Private => false,
Self::Public => true,
}
}
}
bitflags! { bitflags! {
/// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options. /// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.

View File

@ -22,8 +22,8 @@ pub use script_fn::EncapsulatedEnviron;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
pub use stmt::{ pub use stmt::{
CaseBlocksList, ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock, CaseBlocksList, ConditionalExpr, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer,
StmtBlockContainer, SwitchCasesCollection, TryCatchBlock, SwitchCasesCollection, TryCatchBlock,
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

View File

@ -45,6 +45,16 @@ pub struct ScriptFnDef {
pub params: StaticVec<Identifier>, pub params: StaticVec<Identifier>,
/// _(metadata)_ Function doc-comments (if any). /// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
///
/// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`,
/// placed immediately before a function definition.
///
/// 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 `/**`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: Box<[Box<str>]>, pub comments: Box<[Box<str>]>,
} }
@ -85,13 +95,15 @@ pub struct ScriptFnMetadata<'a> {
/// _(metadata)_ Function doc-comments (if any). /// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
/// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`,
/// placed immediately before a function definition.
///
/// Block doc-comments are kept in a single string slice with line-breaks within. /// 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. /// 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 /// Leading white-spaces are stripped, and each string slice always starts with the
/// corresponding doc-comment leader: `///` or `/**`. /// corresponding doc-comment leader: `///` or `/**`.
/// Function access mode.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: Vec<&'a str>, pub comments: Vec<&'a str>,
} }

View File

@ -122,33 +122,54 @@ impl fmt::Debug for OpAssignment {
} }
} }
/// A statements block with a condition. /// An expression with a condition.
/// ///
/// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition. /// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition.
#[derive(Debug, Clone, Default, Hash)] #[derive(Debug, Clone, Default, Hash)]
pub struct ConditionalStmtBlock { pub struct ConditionalExpr {
/// Condition. /// Condition.
pub condition: Expr, pub condition: Expr,
/// Statements block. /// Expression.
pub statements: StmtBlock, pub expr: Expr,
} }
impl<B: Into<StmtBlock>> From<B> for ConditionalStmtBlock { impl<E: Into<Expr>> From<E> for ConditionalExpr {
#[inline(always)] #[inline(always)]
fn from(value: B) -> Self { fn from(value: E) -> Self {
Self { Self {
condition: Expr::BoolConstant(true, Position::NONE), condition: Expr::BoolConstant(true, Position::NONE),
statements: value.into(), expr: value.into(),
} }
} }
} }
impl<B: Into<StmtBlock>> From<(Expr, B)> for ConditionalStmtBlock { impl<E: Into<Expr>> From<(Expr, E)> for ConditionalExpr {
#[inline(always)] #[inline(always)]
fn from(value: (Expr, B)) -> Self { fn from(value: (Expr, E)) -> Self {
Self { Self {
condition: value.0, condition: value.0,
statements: value.1.into(), expr: value.1.into(),
}
}
}
impl ConditionalExpr {
/// Is the condition always `true`?
#[inline(always)]
#[must_use]
pub fn is_always_true(&self) -> bool {
match self.condition {
Expr::BoolConstant(true, ..) => true,
_ => false,
}
}
/// Is the condition always `false`?
#[inline(always)]
#[must_use]
pub fn is_always_false(&self) -> bool {
match self.condition {
Expr::BoolConstant(false, ..) => true,
_ => false,
} }
} }
} }
@ -262,14 +283,14 @@ pub type CaseBlocksList = smallvec::SmallVec<[usize; 1]>;
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct SwitchCasesCollection { pub struct SwitchCasesCollection {
/// List of [`ConditionalStmtBlock`]'s. /// List of [`ConditionalExpr`]'s.
pub case_blocks: StaticVec<ConditionalStmtBlock>, pub expressions: StaticVec<ConditionalExpr>,
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s. /// Dictionary mapping value hashes to [`ConditionalExpr`]'s.
pub cases: BTreeMap<u64, CaseBlocksList>, pub cases: BTreeMap<u64, CaseBlocksList>,
/// Statements block for the default case (there can be no condition for the default case).
pub def_case: usize,
/// List of range cases. /// List of range cases.
pub ranges: StaticVec<RangeCase>, pub ranges: StaticVec<RangeCase>,
/// Statements block for the default case (there can be no condition for the default case).
pub def_case: Option<usize>,
} }
/// _(internals)_ A `try-catch` block. /// _(internals)_ A `try-catch` block.
@ -757,17 +778,15 @@ impl Stmt {
let (expr, sw) = &**x; let (expr, sw) = &**x;
expr.is_pure() expr.is_pure()
&& sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| { && sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| {
let block = &sw.case_blocks[c]; let block = &sw.expressions[c];
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure) block.condition.is_pure() && block.expr.is_pure()
}) })
&& sw.ranges.iter().all(|r| { && sw.ranges.iter().all(|r| {
let block = &sw.case_blocks[r.index()]; let block = &sw.expressions[r.index()];
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure) block.condition.is_pure() && block.expr.is_pure()
}) })
&& sw.case_blocks[sw.def_case] && sw.def_case.is_some()
.statements && sw.expressions[sw.def_case.unwrap()].expr.is_pure()
.iter()
.all(Stmt::is_pure)
} }
// Loops that exit can be pure because it can never be infinite. // Loops that exit can be pure because it can never be infinite.
@ -910,32 +929,28 @@ impl Stmt {
} }
for (.., blocks) in &sw.cases { for (.., blocks) in &sw.cases {
for &b in blocks { for &b in blocks {
let block = &sw.case_blocks[b]; let block = &sw.expressions[b];
if !block.condition.walk(path, on_node) { if !block.condition.walk(path, on_node) {
return false; return false;
} }
for s in &block.statements { if !block.expr.walk(path, on_node) {
if !s.walk(path, on_node) {
return false;
}
}
}
}
for r in &sw.ranges {
let block = &sw.case_blocks[r.index()];
if !block.condition.walk(path, on_node) {
return false;
}
for s in &block.statements {
if !s.walk(path, on_node) {
return false; return false;
} }
} }
} }
for s in &sw.case_blocks[sw.def_case].statements { for r in &sw.ranges {
if !s.walk(path, on_node) { let block = &sw.expressions[r.index()];
if !block.condition.walk(path, on_node) {
return false;
}
if !block.expr.walk(path, on_node) {
return false;
}
}
if let Some(index) = sw.def_case {
if !sw.expressions[index].expr.walk(path, on_node) {
return false; return false;
} }
} }

View File

@ -156,13 +156,11 @@ impl GlobalRuntimeState<'_> {
pub fn find_import(&self, name: &str) -> Option<usize> { pub fn find_import(&self, name: &str) -> Option<usize> {
let len = self.keys.len(); let len = self.keys.len();
self.keys.iter().rev().enumerate().find_map(|(i, key)| { self.keys
if key == name { .iter()
Some(len - 1 - i) .rev()
} else { .position(|key| key == name)
None .map(|i| len - 1 - i)
}
})
} }
/// Push an imported [module][crate::Module] onto the stack. /// Push an imported [module][crate::Module] onto the stack.
/// ///

View File

@ -394,7 +394,7 @@ impl Engine {
let ( let (
expr, expr,
SwitchCasesCollection { SwitchCasesCollection {
case_blocks, expressions,
cases, cases,
def_case, def_case,
ranges, ranges,
@ -405,7 +405,7 @@ impl Engine {
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level); self.eval_expr(scope, global, caches, lib, this_ptr, expr, level);
if let Ok(value) = value_result { if let Ok(value) = value_result {
let stmt_block_result = if value.is_hashable() { let expr_result = if value.is_hashable() {
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
@ -417,7 +417,7 @@ impl Engine {
let mut result = Ok(None); let mut result = Ok(None);
for &index in case_blocks_list { for &index in case_blocks_list {
let block = &case_blocks[index]; let block = &expressions[index];
let cond_result = match block.condition { let cond_result = match block.condition {
Expr::BoolConstant(b, ..) => Ok(b), Expr::BoolConstant(b, ..) => Ok(b),
@ -435,7 +435,7 @@ impl Engine {
match cond_result { match cond_result {
Ok(true) => { Ok(true) => {
result = Ok(Some(&block.statements)); result = Ok(Some(&block.expr));
break; break;
} }
Ok(false) => (), Ok(false) => (),
@ -453,7 +453,7 @@ impl Engine {
let mut result = Ok(None); let mut result = Ok(None);
for r in ranges.iter().filter(|r| r.contains(value)) { for r in ranges.iter().filter(|r| r.contains(value)) {
let block = &case_blocks[r.index()]; let block = &expressions[r.index()];
let cond_result = match block.condition { let cond_result = match block.condition {
Expr::BoolConstant(b, ..) => Ok(b), Expr::BoolConstant(b, ..) => Ok(b),
@ -470,7 +470,7 @@ impl Engine {
}; };
match cond_result { match cond_result {
Ok(true) => result = Ok(Some(&block.statements)), Ok(true) => result = Ok(Some(&block.expr)),
Ok(false) => continue, Ok(false) => continue,
_ => result = cond_result.map(|_| None), _ => result = cond_result.map(|_| None),
} }
@ -488,27 +488,18 @@ impl Engine {
Ok(None) Ok(None)
}; };
if let Ok(Some(statements)) = stmt_block_result { if let Ok(Some(expr)) = expr_result {
if !statements.is_empty() { self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)
self.eval_stmt_block( } else if let Ok(None) = expr_result {
scope, global, caches, lib, this_ptr, statements, true, level,
)
} else {
Ok(Dynamic::UNIT)
}
} else if let Ok(None) = stmt_block_result {
// Default match clause // Default match clause
let def_case = &case_blocks[*def_case].statements; if let Some(index) = def_case {
let def_expr = &expressions[*index].expr;
if !def_case.is_empty() { self.eval_expr(scope, global, caches, lib, this_ptr, def_expr, level)
self.eval_stmt_block(
scope, global, caches, lib, this_ptr, def_case, true, level,
)
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} else { } else {
stmt_block_result.map(|_| Dynamic::UNIT) expr_result.map(|_| Dynamic::UNIT)
} }
} else { } else {
value_result value_result

View File

@ -293,10 +293,15 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
_ => None, _ => None,
}; };
} }
if type1 == type2 { if type2 == TypeId::of::<char>() {
return match op { return match op {
"==" => Some(impl_op!(Blob == Blob)), "+" => Some(|_, args| {
"!=" => Some(impl_op!(Blob != Blob)), let mut buf = [0_u8; 4];
let mut blob = args[0].read_lock::<Blob>().expect(BUILTIN).clone();
let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf);
blob.extend(x.as_bytes());
Ok(Dynamic::from_blob(blob))
}),
_ => None, _ => None,
}; };
} }
@ -503,6 +508,33 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
}; };
} }
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
return match op {
"+" => Some(|_, args| {
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN);
Ok(Dynamic::from_blob(if !blob2.is_empty() {
if blob1.is_empty() {
blob2.clone()
} else {
let mut blob = blob1.clone();
blob.extend(blob2);
blob
}
} else {
blob1.clone()
}))
}),
"==" => Some(impl_op!(Blob == Blob)),
"!=" => Some(impl_op!(Blob != Blob)),
_ => None,
};
}
if type1 == TypeId::of::<()>() { if type1 == TypeId::of::<()>() {
return match op { return match op {
"==" => Some(|_, _| Ok(Dynamic::TRUE)), "==" => Some(|_, _| Ok(Dynamic::TRUE)),
@ -684,71 +716,39 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
{ {
// string op= blob use crate::Blob;
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<crate::Blob>()) {
return match op {
"+=" => Some(|_, args| {
let buf = {
let x = args[1].read_lock::<crate::Blob>().expect(BUILTIN);
if x.is_empty() {
return Ok(Dynamic::UNIT);
}
let s = args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let mut buf = crate::SmartString::from(s.as_str());
buf.push_str(&String::from_utf8_lossy(&x));
buf
};
let mut s = args[0].write_lock::<ImmutableString>().expect(BUILTIN);
*s = buf.into();
Ok(Dynamic::UNIT)
}),
_ => None,
};
}
// blob op= int
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<INT>()) {
use crate::Blob;
// blob op= int
if types_pair == (TypeId::of::<Blob>(), TypeId::of::<INT>()) {
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8; let x = args[1].as_int().expect("`INT`");
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN); let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(blob.push(x).into()) Ok(crate::packages::blob_basic::blob_functions::push(blob, x).into())
}), }),
_ => None, _ => None,
}; };
} }
// blob op= char // blob op= char
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<char>()) { if types_pair == (TypeId::of::<Blob>(), TypeId::of::<char>()) {
use crate::Blob;
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let mut buf = [0_u8; 4]; let x = args[1].as_char().expect("`char`");
let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf); let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN); Ok(crate::packages::blob_basic::blob_functions::append_char(blob, x).into())
Ok(blob.extend(x.as_bytes()).into())
}), }),
_ => None, _ => None,
}; };
} }
// blob op= string // blob op= string
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<ImmutableString>()) { if types_pair == (TypeId::of::<Blob>(), TypeId::of::<ImmutableString>()) {
use crate::Blob;
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let s: crate::Blob = { let s = std::mem::take(args[1]).cast::<ImmutableString>();
let s = args[1].read_lock::<ImmutableString>().expect(BUILTIN); let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
if s.is_empty() { Ok(crate::packages::blob_basic::blob_functions::append_str(blob, &s).into())
return Ok(Dynamic::UNIT);
}
s.as_bytes().into()
};
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(blob.extend(s).into())
}), }),
_ => None, _ => None,
}; };
@ -838,14 +838,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() { if type1 == TypeId::of::<crate::Blob>() {
use crate::packages::blob_basic::blob_functions::*;
use crate::Blob; use crate::Blob;
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let blob2 = std::mem::take(args[1]).cast::<Blob>(); let blob2 = std::mem::take(args[1]).cast::<Blob>();
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN); let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(append(blob1, blob2).into()) Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
}), }),
_ => None, _ => None,
}; };

View File

@ -284,7 +284,7 @@ pub use parser::ParseState;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use ast::{ pub use ast::{
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident, ASTFlags, ASTNode, BinaryExpr, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock,
}; };

View File

@ -32,6 +32,27 @@ pub enum FnNamespace {
Global, Global,
} }
impl FnNamespace {
/// Is this a module namespace?
#[inline(always)]
#[must_use]
pub fn is_module_namespace(self) -> bool {
match self {
Self::Internal => true,
Self::Global => false,
}
}
/// Is this a global namespace?
#[inline(always)]
#[must_use]
pub fn is_global_namespace(self) -> bool {
match self {
Self::Internal => false,
Self::Global => true,
}
}
}
/// A type containing all metadata for a registered function. /// A type containing all metadata for a registered function.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive] #[non_exhaustive]

View File

@ -213,9 +213,9 @@ fn optimize_stmt_block(
// Flatten blocks // Flatten blocks
loop { loop {
if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s { if let Some(n) = statements.iter().position(|s| match s {
Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => Some(i), Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => true,
_ => None, _ => false,
}) { }) {
let (first, second) = statements.split_at_mut(n); let (first, second) = statements.split_at_mut(n);
let stmt = mem::take(&mut second[0]); let stmt = mem::take(&mut second[0]);
@ -527,7 +527,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
let ( let (
match_expr, match_expr,
SwitchCasesCollection { SwitchCasesCollection {
case_blocks, expressions,
cases, cases,
ranges, ranges,
def_case, def_case,
@ -544,36 +544,36 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match &case_blocks_list[..] { match &case_blocks_list[..] {
[] => (), [] => (),
[index] => { [index] => {
let mut b = mem::take(&mut case_blocks[*index]); let mut b = mem::take(&mut expressions[*index]);
cases.clear(); cases.clear();
match b.condition { if b.is_always_true() {
Expr::BoolConstant(true, ..) => { // Promote the matched case
// Promote the matched case let mut statements = Stmt::Expr(mem::take(&mut b.expr).into());
let statements: StmtBlockContainer = mem::take(&mut b.statements); optimize_stmt(&mut statements, state, true);
let statements = *stmt = statements;
optimize_stmt_block(statements, state, true, true, false); } else {
*stmt = (statements, b.statements.span()).into(); // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
} optimize_expr(&mut b.condition, state, false);
ref mut condition => {
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(condition, state, false);
let def_case = &mut case_blocks[*def_case].statements; let else_stmt = if let Some(index) = def_case {
let def_span = def_case.span_or_else(*pos, Position::NONE); let mut def_stmt =
let def_case: StmtBlockContainer = mem::take(def_case); Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
let def_stmt = optimize_stmt(&mut def_stmt, state, true);
optimize_stmt_block(def_case, state, true, true, false); def_stmt.into()
*stmt = Stmt::If( } else {
( StmtBlock::NONE
mem::take(condition), };
mem::take(&mut b.statements),
StmtBlock::new_with_span(def_stmt, def_span), *stmt = Stmt::If(
) (
.into(), mem::take(&mut b.condition),
match_expr.start_position(), Stmt::Expr(mem::take(&mut b.expr).into()).into(),
); else_stmt,
} )
.into(),
match_expr.start_position(),
);
} }
state.set_dirty(); state.set_dirty();
@ -581,20 +581,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
_ => { _ => {
for &index in case_blocks_list { for &index in case_blocks_list {
let mut b = mem::take(&mut case_blocks[index]); let mut b = mem::take(&mut expressions[index]);
match b.condition { if b.is_always_true() {
Expr::BoolConstant(true, ..) => { // Promote the matched case
// Promote the matched case let mut statements = Stmt::Expr(mem::take(&mut b.expr).into());
let statements: StmtBlockContainer = optimize_stmt(&mut statements, state, true);
mem::take(&mut b.statements); *stmt = statements;
let statements = state.set_dirty();
optimize_stmt_block(statements, state, true, true, false); return;
*stmt = (statements, b.statements.span()).into();
state.set_dirty();
return;
}
_ => (),
} }
} }
} }
@ -607,47 +602,42 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Only one range or all ranges without conditions // Only one range or all ranges without conditions
if ranges.len() == 1 if ranges.len() == 1
|| ranges.iter().all(|r| { || ranges
matches!( .iter()
case_blocks[r.index()].condition, .all(|r| expressions[r.index()].is_always_true())
Expr::BoolConstant(true, ..)
)
})
{ {
for r in ranges.iter().filter(|r| r.contains(value)) { for r in ranges.iter().filter(|r| r.contains(value)) {
let condition = mem::take(&mut case_blocks[r.index()].condition); let range_block = &mut expressions[r.index()];
match condition { if range_block.is_always_true() {
Expr::BoolConstant(true, ..) => { // Promote the matched case
// Promote the matched case let block = &mut expressions[r.index()];
let block = &mut case_blocks[r.index()]; let mut statements = Stmt::Expr(mem::take(&mut block.expr).into());
let statements = mem::take(&mut *block.statements); optimize_stmt(&mut statements, state, true);
let statements = *stmt = statements;
optimize_stmt_block(statements, state, true, true, false); } else {
*stmt = (statements, block.statements.span()).into(); let mut condition = mem::take(&mut range_block.condition);
}
mut condition => {
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false);
let def_case = &mut case_blocks[*def_case].statements; // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
let def_span = def_case.span_or_else(*pos, Position::NONE); optimize_expr(&mut condition, state, false);
let def_case: StmtBlockContainer = mem::take(def_case);
let def_stmt =
optimize_stmt_block(def_case, state, true, true, false);
let statements = mem::take(&mut case_blocks[r.index()].statements); let else_stmt = if let Some(index) = def_case {
let mut def_stmt =
Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
optimize_stmt(&mut def_stmt, state, true);
def_stmt.into()
} else {
StmtBlock::NONE
};
*stmt = Stmt::If( let if_stmt =
( Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into())
condition, .into();
statements,
StmtBlock::new_with_span(def_stmt, def_span), *stmt = Stmt::If(
) (condition, if_stmt, else_stmt).into(),
.into(), match_expr.start_position(),
match_expr.start_position(), );
);
}
} }
state.set_dirty(); state.set_dirty();
@ -669,20 +659,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
for r in &*ranges { for r in &*ranges {
let b = &mut case_blocks[r.index()]; let b = &mut expressions[r.index()];
let statements = mem::take(&mut *b.statements);
*b.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
optimize_expr(&mut b.condition, state, false); optimize_expr(&mut b.condition, state, false);
optimize_expr(&mut b.expr, state, false);
match b.condition {
Expr::Unit(pos) => {
b.condition = Expr::BoolConstant(true, pos);
state.set_dirty()
}
_ => (),
}
} }
return; return;
} }
@ -690,18 +669,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Promote the default case // Promote the default case
state.set_dirty(); state.set_dirty();
let def_case = &mut case_blocks[*def_case].statements;
let def_span = def_case.span_or_else(*pos, Position::NONE); if let Some(index) = def_case {
let def_case: StmtBlockContainer = mem::take(def_case); let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
let def_stmt = optimize_stmt_block(def_case, state, true, true, false); optimize_stmt(&mut def_stmt, state, true);
*stmt = (def_stmt, def_span).into(); *stmt = def_stmt;
} else {
*stmt = StmtBlock::empty(*pos).into();
}
} }
// switch // switch
Stmt::Switch(x, ..) => { Stmt::Switch(x, ..) => {
let ( let (
match_expr, match_expr,
SwitchCasesCollection { SwitchCasesCollection {
case_blocks, expressions,
cases, cases,
ranges, ranges,
def_case, def_case,
@ -712,78 +694,76 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
optimize_expr(match_expr, state, false); optimize_expr(match_expr, state, false);
// Optimize blocks // Optimize blocks
for b in case_blocks.iter_mut() { for b in expressions.iter_mut() {
let statements = mem::take(&mut *b.statements);
*b.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
optimize_expr(&mut b.condition, state, false); optimize_expr(&mut b.condition, state, false);
optimize_expr(&mut b.expr, state, false);
match b.condition { if b.is_always_false() {
Expr::Unit(pos) => { if !b.expr.is_unit() {
b.condition = Expr::BoolConstant(true, pos); b.expr = Expr::Unit(b.expr.position());
state.set_dirty(); state.set_dirty();
} }
Expr::BoolConstant(false, ..) => {
if !b.statements.is_empty() {
b.statements = StmtBlock::NONE;
state.set_dirty();
}
}
_ => (),
} }
} }
// Remove false cases // Remove false cases
let cases_len = cases.len();
cases.retain(|_, list| { cases.retain(|_, list| {
// Remove all entries that have false conditions // Remove all entries that have false conditions
list.retain(|index| match case_blocks[*index].condition { list.retain(|index| {
Expr::BoolConstant(false, ..) => false, if expressions[*index].is_always_false() {
_ => true, state.set_dirty();
false
} else {
true
}
}); });
// Remove all entries after a `true` condition // Remove all entries after a `true` condition
if let Some(n) = list if let Some(n) = list
.iter() .iter()
.position(|&index| match case_blocks[index].condition { .position(|&index| expressions[index].is_always_true())
Expr::BoolConstant(true, ..) => true,
_ => false,
})
{ {
list.truncate(n + 1); if n + 1 < list.len() {
state.set_dirty();
list.truncate(n + 1);
}
} }
// Remove if no entry left // Remove if no entry left
!list.is_empty() match list.is_empty() {
true => {
state.set_dirty();
false
}
false => true,
}
}); });
if cases.len() != cases_len {
state.set_dirty();
}
// Remove false ranges // Remove false ranges
ranges.retain(|r| match case_blocks[r.index()].condition { ranges.retain(|r| {
Expr::BoolConstant(false, ..) => { if expressions[r.index()].is_always_false() {
state.set_dirty(); state.set_dirty();
false false
} else {
true
} }
_ => true,
}); });
let def_stmt_block = &mut case_blocks[*def_case].statements; if let Some(index) = def_case {
let def_block = mem::take(&mut **def_stmt_block); optimize_expr(&mut expressions[*index].expr, state, false);
**def_stmt_block = optimize_stmt_block(def_block, state, preserve_result, true, false); }
// Remove unused block statements // Remove unused block statements
for index in 0..case_blocks.len() { for index in 0..expressions.len() {
if *def_case == index if *def_case == Some(index)
|| cases.values().flat_map(|c| c.iter()).any(|&n| n == index) || cases.values().flat_map(|c| c.iter()).any(|&n| n == index)
|| ranges.iter().any(|r| r.index() == index) || ranges.iter().any(|r| r.index() == index)
{ {
continue; continue;
} }
let b = &mut case_blocks[index]; let b = &mut expressions[index];
if !b.statements.is_empty() { if !b.expr.is_unit() {
b.statements = StmtBlock::NONE; b.expr = Expr::Unit(b.expr.position());
state.set_dirty(); state.set_dirty();
} }
} }
@ -904,6 +884,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false); optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
} }
// expr(stmt)
Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => {
state.set_dirty();
match expr.as_mut() {
Expr::Stmt(block) if !block.is_empty() => {
let mut stmt_block = *mem::take(block);
*stmt_block =
optimize_stmt_block(mem::take(&mut *stmt_block), state, true, true, false);
*stmt = stmt_block.into();
}
Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
_ => unreachable!("`Expr::Stmt`"),
}
}
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
optimize_expr(expr, state, false); optimize_expr(expr, state, false);
@ -948,6 +943,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
match expr { match expr {
// {} // {}
Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
Expr::Stmt(x) if x.len() == 1 && matches!(x.statements()[0], Stmt::Expr(..)) => {
state.set_dirty();
match x.take_statements().remove(0) {
Stmt::Expr(mut e) => {
optimize_expr(&mut e, state, false);
*expr = *e;
}
_ => unreachable!("`Expr::Stmt`")
}
}
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x) => { Expr::Stmt(x) => {
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false); ***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false);

View File

@ -8,7 +8,7 @@ use crate::{
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{any::TypeId, mem}; use std::{any::TypeId, borrow::Cow, mem};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::{FLOAT, FLOAT_BYTES}; use crate::{FLOAT, FLOAT_BYTES};
@ -104,6 +104,27 @@ pub mod blob_functions {
pub fn to_array(blob: &mut Blob) -> Array { pub fn to_array(blob: &mut Blob) -> Array {
blob.iter().map(|&ch| (ch as INT).into()).collect() blob.iter().map(|&ch| (ch as INT).into()).collect()
} }
/// Convert the BLOB into a string.
///
/// The byte stream must be valid UTF-8, otherwise an error is raised.
///
/// # Example
///
/// ```rhai
/// let b = blob(5, 0x42);
///
/// let x = b.as_string();
///
/// print(x); // prints "FFFFF"
/// ```
pub fn as_string(blob: Blob) -> String {
let s = String::from_utf8_lossy(&blob);
match s {
Cow::Borrowed(_) => String::from_utf8(blob).unwrap(),
Cow::Owned(_) => s.into_owned(),
}
}
/// Return the length of the BLOB. /// Return the length of the BLOB.
/// ///
/// # Example /// # Example
@ -200,6 +221,7 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[42]" /// print(b); // prints "[42]"
/// ``` /// ```
#[rhai_fn(name = "push", name = "append")]
pub fn push(blob: &mut Blob, value: INT) { pub fn push(blob: &mut Blob, value: INT) {
blob.push((value & 0x000000ff) as u8); blob.push((value & 0x000000ff) as u8);
} }
@ -235,13 +257,13 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[424242424268656c 6c6f]" /// print(b); // prints "[424242424268656c 6c6f]"
/// ``` /// ```
#[rhai_fn(name = "+=", name = "append")] #[rhai_fn(name = "append")]
pub fn append_str(blob: &mut Blob, string: &str) { pub fn append_str(blob: &mut Blob, string: &str) {
if !string.is_empty() { if !string.is_empty() {
blob.extend(string.as_bytes()); blob.extend(string.as_bytes());
} }
} }
/// Add a string (as UTF-8 encoded byte-stream) to the end of the BLOB /// Add a character (as UTF-8 encoded byte-stream) to the end of the BLOB
/// ///
/// # Example /// # Example
/// ///
@ -252,38 +274,12 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[424242424221]" /// print(b); // prints "[424242424221]"
/// ``` /// ```
#[rhai_fn(name = "+=", name = "append")] #[rhai_fn(name = "append")]
pub fn append_char(blob: &mut Blob, character: char) { pub fn append_char(blob: &mut Blob, character: char) {
let mut buf = [0_u8; 4]; let mut buf = [0_u8; 4];
let x = character.encode_utf8(&mut buf); let x = character.encode_utf8(&mut buf);
blob.extend(x.as_bytes()); blob.extend(x.as_bytes());
} }
/// Add another BLOB to the end of the BLOB, returning it as a new BLOB.
///
/// # Example
///
/// ```rhai
/// let b1 = blob(5, 0x42);
/// let b2 = blob(3, 0x11);
///
/// print(b1 + b2); // prints "[4242424242111111]"
///
/// print(b1); // prints "[4242424242]"
/// ```
#[rhai_fn(name = "+")]
pub fn concat(blob1: Blob, blob2: Blob) -> Blob {
if !blob2.is_empty() {
if blob1.is_empty() {
blob2
} else {
let mut blob = blob1;
blob.extend(blob2);
blob
}
} else {
blob1
}
}
/// Add a byte `value` to the BLOB at a particular `index` position. /// Add a byte `value` to the BLOB at a particular `index` position.
/// ///
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte).

View File

@ -105,7 +105,7 @@ impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
self.dir = 0; self.dir = 0;
} }
} else { } else {
unreachable!(); unreachable!("`dir` != 0");
} }
Some(v) Some(v)

View File

@ -4,20 +4,20 @@ use crate::{Module, Shared};
pub(crate) mod arithmetic; pub(crate) mod arithmetic;
pub(crate) mod array_basic; pub(crate) mod array_basic;
mod bit_field; pub(crate) mod bit_field;
pub(crate) mod blob_basic; pub(crate) mod blob_basic;
mod debugging; pub(crate) mod debugging;
mod fn_basic; pub(crate) mod fn_basic;
pub(crate) mod iter_basic; pub(crate) mod iter_basic;
mod lang_core; pub(crate) mod lang_core;
mod logic; pub(crate) mod logic;
mod map_basic; pub(crate) mod map_basic;
mod math_basic; pub(crate) mod math_basic;
mod pkg_core; pub(crate) mod pkg_core;
mod pkg_std; pub(crate) mod pkg_std;
mod string_basic; pub(crate) mod string_basic;
mod string_more; pub(crate) mod string_more;
mod time_basic; pub(crate) mod time_basic;
pub use arithmetic::ArithmeticPackage; pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]

View File

@ -6,9 +6,6 @@ use std::{any::TypeId, mem};
use super::string_basic::{print_with_func, FUNC_TO_STRING}; use super::string_basic::{print_with_func, FUNC_TO_STRING};
#[cfg(not(feature = "no_index"))]
use crate::Blob;
def_package! { def_package! {
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage] /// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
pub MoreStringPackage(lib) { pub MoreStringPackage(lib) {
@ -86,26 +83,50 @@ mod string_functions {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub mod blob_functions { pub mod blob_functions {
use crate::Blob;
#[rhai_fn(name = "+", pure)] #[rhai_fn(name = "+", pure)]
pub fn add_append_blob(string: &mut ImmutableString, utf8: Blob) -> ImmutableString { pub fn add_append(string: &mut ImmutableString, utf8: Blob) -> ImmutableString {
if utf8.is_empty() { if utf8.is_empty() {
string.clone() return string.clone();
} else if string.is_empty() { }
String::from_utf8_lossy(&utf8).into_owned().into()
let s = String::from_utf8_lossy(&utf8);
if string.is_empty() {
match s {
std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(),
std::borrow::Cow::Owned(_) => s.into_owned(),
}
.into()
} else { } else {
let mut s = crate::SmartString::from(string.as_str()); let mut x = SmartString::from(string.as_str());
s.push_str(&String::from_utf8_lossy(&utf8)); x.push_str(s.as_ref());
s.into() x.into()
} }
} }
#[rhai_fn(name = "append")] #[rhai_fn(name = "+=", name = "append")]
pub fn add_blob(string: &mut ImmutableString, utf8: Blob) { pub fn add(string: &mut ImmutableString, utf8: Blob) {
let mut s = crate::SmartString::from(string.as_str()); let mut s = crate::SmartString::from(string.as_str());
if !utf8.is_empty() { if !utf8.is_empty() {
s.push_str(&String::from_utf8_lossy(&utf8)); s.push_str(&String::from_utf8_lossy(&utf8));
*string = s.into(); *string = s.into();
} }
} }
#[rhai_fn(name = "+")]
pub fn add_prepend(utf8: Blob, string: ImmutableString) -> ImmutableString {
let s = String::from_utf8_lossy(&utf8);
let mut s = match s {
std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(),
std::borrow::Cow::Owned(_) => s.into_owned(),
};
if !string.is_empty() {
s.push_str(&string);
}
s.into()
}
} }
/// Return the length of the string, in number of characters. /// Return the length of the string, in number of characters.

View File

@ -3,9 +3,9 @@
use crate::api::events::VarDefInfo; use crate::api::events::VarDefInfo;
use crate::api::options::LangOptions; use crate::api::options::LangOptions;
use crate::ast::{ use crate::ast::{
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCasesCollection, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer,
TryCatchBlock, SwitchCasesCollection, TryCatchBlock,
}; };
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::eval::GlobalRuntimeState; use crate::eval::GlobalRuntimeState;
@ -798,7 +798,7 @@ impl Engine {
match token { match token {
Token::LeftBracket => ASTFlags::NONE, Token::LeftBracket => ASTFlags::NONE,
Token::QuestionBracket => ASTFlags::NEGATED, Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!(), _ => unreachable!("`[` or `?[`"),
}, },
false, false,
settings.level_up(), settings.level_up(),
@ -1055,11 +1055,11 @@ impl Engine {
} }
} }
let mut case_blocks = StaticVec::<ConditionalStmtBlock>::new(); let mut expressions = StaticVec::<ConditionalExpr>::new();
let mut cases = BTreeMap::<u64, CaseBlocksList>::new(); let mut cases = BTreeMap::<u64, CaseBlocksList>::new();
let mut ranges = StaticVec::<RangeCase>::new(); let mut ranges = StaticVec::<RangeCase>::new();
let mut def_stmt_pos = Position::NONE; let mut def_case = None;
let mut def_stmt_index = None; let mut def_case_pos = Position::NONE;
loop { loop {
const MISSING_RBRACE: &str = "to end this switch block"; const MISSING_RBRACE: &str = "to end this switch block";
@ -1075,8 +1075,8 @@ impl Engine {
.into_err(*pos), .into_err(*pos),
) )
} }
(Token::Underscore, pos) if def_stmt_index.is_none() => { (Token::Underscore, pos) if def_case.is_none() => {
def_stmt_pos = *pos; def_case_pos = *pos;
eat_token(input, Token::Underscore); eat_token(input, Token::Underscore);
let (if_clause, if_pos) = match_token(input, Token::If); let (if_clause, if_pos) = match_token(input, Token::If);
@ -1087,8 +1087,8 @@ impl Engine {
(Default::default(), Expr::BoolConstant(true, Position::NONE)) (Default::default(), Expr::BoolConstant(true, Position::NONE))
} }
_ if def_stmt_index.is_some() => { _ if def_case.is_some() => {
return Err(PERR::WrongSwitchDefaultCase.into_err(def_stmt_pos)) return Err(PERR::WrongSwitchDefaultCase.into_err(def_case_pos))
} }
_ => { _ => {
@ -1142,8 +1142,9 @@ impl Engine {
let need_comma = !stmt.is_self_terminated(); let need_comma = !stmt.is_self_terminated();
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..)); let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
case_blocks.push((condition, stmt).into()); let stmt_block: StmtBlock = stmt.into();
let index = case_blocks.len() - 1; expressions.push((condition, Expr::Stmt(stmt_block.into())).into());
let index = expressions.len() - 1;
if !case_expr_list.is_empty() { if !case_expr_list.is_empty() {
for expr in case_expr_list { for expr in case_expr_list {
@ -1197,7 +1198,7 @@ impl Engine {
.or_insert_with(|| [index].into()); .or_insert_with(|| [index].into());
} }
} else { } else {
def_stmt_index = Some(index); def_case = Some(index);
} }
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
@ -1223,13 +1224,8 @@ impl Engine {
} }
} }
let def_case = def_stmt_index.unwrap_or_else(|| {
case_blocks.push(Default::default());
case_blocks.len() - 1
});
let cases = SwitchCasesCollection { let cases = SwitchCasesCollection {
case_blocks, expressions,
cases, cases,
def_case, def_case,
ranges, ranges,
@ -1408,9 +1404,7 @@ impl Engine {
} }
// Make sure to parse the following as text // Make sure to parse the following as text
let mut control = state.tokenizer_control.get(); state.tokenizer_control.borrow_mut().is_within_text = true;
control.is_within_text = true;
state.tokenizer_control.set(control);
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => { (Token::StringConstant(s), pos) => {
@ -1697,7 +1691,7 @@ impl Engine {
let opt = match token { let opt = match token {
Token::LeftBracket => ASTFlags::NONE, Token::LeftBracket => ASTFlags::NONE,
Token::QuestionBracket => ASTFlags::NEGATED, Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!(), _ => unreachable!("`[` or `?[`"),
}; };
self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())? self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())?
} }
@ -1721,7 +1715,7 @@ impl Engine {
let op_flags = match op { let op_flags = match op {
Token::Period => ASTFlags::NONE, Token::Period => ASTFlags::NONE,
Token::Elvis => ASTFlags::NEGATED, Token::Elvis => ASTFlags::NEGATED,
_ => unreachable!(), _ => unreachable!("`.` or `?.`"),
}; };
Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)? Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)?
} }

View File

@ -10,7 +10,7 @@ use crate::{Engine, Identifier, LexError, SmartString, StaticVec, INT, UNSIGNED_
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
borrow::Cow, borrow::Cow,
cell::Cell, cell::RefCell,
char, fmt, char, fmt,
iter::{FusedIterator, Peekable}, iter::{FusedIterator, Peekable},
num::NonZeroUsize, num::NonZeroUsize,
@ -20,11 +20,14 @@ use std::{
}; };
/// _(internals)_ A type containing commands to control the tokenizer. /// _(internals)_ A type containing commands to control the tokenizer.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct TokenizerControlBlock { pub struct TokenizerControlBlock {
/// Is the current tokenizer position within an interpolated text string? /// Is the current tokenizer position within an interpolated text string?
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream. /// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
pub is_within_text: bool, pub is_within_text: bool,
/// Collection of global comments.
#[cfg(feature = "metadata")]
pub global_comments: Vec<SmartString>,
} }
impl TokenizerControlBlock { impl TokenizerControlBlock {
@ -34,12 +37,14 @@ impl TokenizerControlBlock {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
is_within_text: false, is_within_text: false,
#[cfg(feature = "metadata")]
global_comments: Vec::new(),
} }
} }
} }
/// _(internals)_ A shared object that allows control of the tokenizer from outside. /// _(internals)_ A shared object that allows control of the tokenizer from outside.
pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>; pub type TokenizerControl = Rc<RefCell<TokenizerControlBlock>>;
type LERR = LexError; type LERR = LexError;
@ -248,7 +253,7 @@ impl fmt::Debug for Position {
} }
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
unreachable!(); unreachable!("no position");
} }
} }
} }
@ -1098,12 +1103,14 @@ impl From<Token> for String {
/// _(internals)_ State of the tokenizer. /// _(internals)_ State of the tokenizer.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct TokenizeState { pub struct TokenizeState {
/// Maximum length of a string. /// Maximum length of a string.
pub max_string_size: Option<NonZeroUsize>, pub max_string_size: Option<NonZeroUsize>,
/// Can the next token be a unary operator? /// Can the next token be a unary operator?
pub next_token_cannot_be_unary: bool, pub next_token_cannot_be_unary: bool,
/// Shared object to allow controlling the tokenizer externally.
pub tokenizer_control: TokenizerControl,
/// Is the tokenizer currently inside a block comment? /// Is the tokenizer currently inside a block comment?
pub comment_level: usize, pub comment_level: usize,
/// Include comments? /// Include comments?
@ -1866,6 +1873,11 @@ fn get_next_token_inner(
_ => Some("///".into()), _ => Some("///".into()),
} }
} }
#[cfg(feature = "metadata")]
Some('!') => {
eat_next(stream, pos);
Some("//!".into())
}
_ if state.include_comments => Some("//".into()), _ if state.include_comments => Some("//".into()),
_ => None, _ => None,
}; };
@ -1890,7 +1902,15 @@ fn get_next_token_inner(
} }
if let Some(comment) = comment { if let Some(comment) = comment {
return Some((Token::Comment(comment), start_pos)); match comment {
#[cfg(feature = "metadata")]
_ if comment.starts_with("//!") => state
.tokenizer_control
.borrow_mut()
.global_comments
.push(comment),
_ => return Some((Token::Comment(comment), start_pos)),
}
} }
} }
('/', '*') => { ('/', '*') => {
@ -2300,8 +2320,6 @@ pub struct TokenIterator<'a> {
pub state: TokenizeState, pub state: TokenizeState,
/// Current position. /// Current position.
pub pos: Position, pub pos: Position,
/// Shared object to allow controlling the tokenizer externally.
pub tokenizer_control: TokenizerControl,
/// Input character stream. /// Input character stream.
pub stream: MultiInputsStream<'a>, pub stream: MultiInputsStream<'a>,
/// A processor function that maps a token to another. /// A processor function that maps a token to another.
@ -2312,14 +2330,15 @@ impl<'a> Iterator for TokenIterator<'a> {
type Item = (Token, Position); type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut control = self.tokenizer_control.get(); {
let control = &mut *self.state.tokenizer_control.borrow_mut();
if control.is_within_text { if control.is_within_text {
// Switch to text mode terminated by back-tick // Switch to text mode terminated by back-tick
self.state.is_within_text_terminated_by = Some('`'); self.state.is_within_text_terminated_by = Some('`');
// Reset it // Reset it
control.is_within_text = false; control.is_within_text = false;
self.tokenizer_control.set(control); }
} }
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) { let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
@ -2450,7 +2469,7 @@ impl Engine {
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>, input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
token_mapper: Option<&'a OnParseTokenCallback>, token_mapper: Option<&'a OnParseTokenCallback>,
) -> (TokenIterator<'a>, TokenizerControl) { ) -> (TokenIterator<'a>, TokenizerControl) {
let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into(); let buffer: TokenizerControl = RefCell::new(TokenizerControlBlock::new()).into();
let buffer2 = buffer.clone(); let buffer2 = buffer.clone();
( (
@ -2462,12 +2481,12 @@ impl Engine {
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
max_string_size: None, max_string_size: None,
next_token_cannot_be_unary: false, next_token_cannot_be_unary: false,
tokenizer_control: buffer,
comment_level: 0, comment_level: 0,
include_comments: false, include_comments: false,
is_within_text_terminated_by: None, is_within_text_terminated_by: None,
}, },
pos: Position::new(1, 0), pos: Position::new(1, 0),
tokenizer_control: buffer,
stream: MultiInputsStream { stream: MultiInputsStream {
buf: None, buf: None,
streams: input streams: input

View File

@ -68,6 +68,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(feature = "metadata")]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
@ -79,30 +80,39 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(123 @ 1:53)] }"); assert_eq!(
format!("{:?}", ast),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"#
);
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"# r#"AST { source: "", doc: "", resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
); );
let ast = engine.compile("if 1 == 2 { 42 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert_eq!(format!("{:?}", ast), "AST { body: [] }"); assert_eq!(
format!("{:?}", ast),
r#"AST { source: "", doc: "", resolver: None, body: [] }"#
);
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("abs(-42)")?; let ast = engine.compile("abs(-42)")?;
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }"); assert_eq!(
format!("{:?}", ast),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
);
let ast = engine.compile("NUMBER")?; let ast = engine.compile("NUMBER")?;
assert_eq!( assert_eq!(
format!("{:?}", ast), format!("{:?}", ast),
"AST { body: [Expr(Variable(NUMBER) @ 1:1)] }" r#"AST { source: "", doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"#
); );
let mut module = Module::new(); let mut module = Module::new();
@ -112,7 +122,10 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("NUMBER")?; let ast = engine.compile("NUMBER")?;
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }"); assert_eq!(
format!("{:?}", ast),
r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
);
Ok(()) Ok(())
} }