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.
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
------------
* 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
------------
@ -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.
* 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

View File

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

View File

@ -200,7 +200,7 @@ pub fn generate_body(
let ns_str = syn::Ident::new(
match namespace {
FnNamespaceAccess::Unset => unreachable!(),
FnNamespaceAccess::Unset => unreachable!("`namespace` should be set"),
FnNamespaceAccess::Global => "Global",
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.
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).
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.
/// 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;

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,
/// accepts three parameters.
///

View File

@ -1,5 +1,5 @@
// This script calculates the n-th Fibonacci number using a really dumb algorithm
// to test the speed of the scripting engine.
//! This script calculates the n-th Fibonacci number using a really dumb algorithm
//! to test the speed of the scripting engine.
const TARGET = 28;
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];

View File

@ -1,4 +1,4 @@
// This script runs for-loops
//! This script runs for-loops
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 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() {
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;

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;

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
fn action(x, y) {

View File

@ -1,4 +1,4 @@
// This script runs if statements.
//! This script runs if statements.
let a = 42;
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 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;

View File

@ -1,4 +1,4 @@
// This script simulates multi-dimensional matrix calculations.
//! This script simulates multi-dimensional matrix calculations.
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;

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.
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:");

View File

@ -1,4 +1,4 @@
// This script runs a complex expression.
//! This script runs a complex expression.
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:");

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();

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 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("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!");

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];

View File

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

View File

@ -222,7 +222,10 @@ impl Engine {
self.token_mapper.as_ref().map(<_>::as_ref),
);
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`],
/// which can be used later for evaluation.

View File

@ -78,7 +78,7 @@ impl Expression<'_> {
///
/// 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
///

View File

@ -318,7 +318,7 @@ impl crate::Expression<'_> {
///
/// # 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.
#[deprecated(since = "1.4.0", note = "use `get_string_value` instead")]

View File

@ -2,6 +2,7 @@
#![cfg(not(feature = "no_optimize"))]
use crate::{Engine, OptimizationLevel, Scope, AST};
use std::mem;
impl Engine {
/// 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())
.collect();
crate::optimizer::optimize_into_ast(
let mut new_ast = crate::optimizer::optimize_into_ast(
self,
scope,
ast.take_statements(),
#[cfg(not(feature = "no_function"))]
lib,
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) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
let mut state = ParseState::new(self, scope, tokenizer_control);
let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?;
self.run_ast_with_scope(scope, &ast)
}
/// Evaluate an [`AST`], returning any error (if any).

View File

@ -1,7 +1,7 @@
//! Module defining the AST (abstract syntax tree).
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")]
use std::prelude::v1::*;
use std::{
@ -21,6 +21,9 @@ pub struct AST {
/// Source of the [`AST`].
/// No source if string is empty.
source: Identifier,
/// [`AST`] documentation.
#[cfg(feature = "metadata")]
doc: SmartString,
/// Global statements.
body: StmtBlock,
/// Script-defined functions.
@ -42,13 +45,11 @@ impl fmt::Debug for AST {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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"))]
if let Some(ref resolver) = self.resolver {
fp.field("resolver: ", resolver);
}
fp.field("resolver", &self.resolver);
fp.field("body", &self.body.as_slice());
@ -74,6 +75,8 @@ impl AST {
) -> Self {
Self {
source: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
#[cfg(not(feature = "no_function"))]
lib: functions.into(),
@ -92,6 +95,8 @@ impl AST {
) -> Self {
Self {
source: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
#[cfg(not(feature = "no_function"))]
lib: functions.into(),
@ -140,6 +145,8 @@ impl AST {
pub fn empty() -> Self {
Self {
source: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
body: StmtBlock::NONE,
#[cfg(not(feature = "no_function"))]
lib: crate::Module::new().into(),
@ -180,6 +187,41 @@ impl AST {
self.source.clear();
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.
#[cfg(not(feature = "internals"))]
#[inline(always)]
@ -292,6 +334,8 @@ impl AST {
lib.merge_filtered(&self.lib, &filter);
Self {
source: self.source.clone(),
#[cfg(feature = "metadata")]
doc: self.doc.clone(),
body: StmtBlock::NONE,
lib: lib.into(),
#[cfg(not(feature = "no_module"))]
@ -305,6 +349,8 @@ impl AST {
pub fn clone_statements_only(&self) -> Self {
Self {
source: self.source.clone(),
#[cfg(feature = "metadata")]
doc: self.doc.clone(),
body: self.body.clone(),
#[cfg(not(feature = "no_function"))]
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
}
/// 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);
}
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !self.doc.is_empty() {
self.doc.push('\n');
}
self.doc.push_str(&other.doc);
}
self
}
/// Filter out the functions, retaining only some based on a filter predicate.

View File

@ -14,6 +14,27 @@ pub enum FnAccess {
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! {
/// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options.
/// Exported under the `internals` feature only.

View File

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

View File

@ -45,6 +45,16 @@ pub struct ScriptFnDef {
pub params: StaticVec<Identifier>,
/// _(metadata)_ Function doc-comments (if any).
/// 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")]
pub comments: Box<[Box<str>]>,
}
@ -85,13 +95,15 @@ pub struct ScriptFnMetadata<'a> {
/// _(metadata)_ Function doc-comments (if any).
/// 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 `/**`.
/// Function access mode.
#[cfg(feature = "metadata")]
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.
#[derive(Debug, Clone, Default, Hash)]
pub struct ConditionalStmtBlock {
pub struct ConditionalExpr {
/// Condition.
pub condition: Expr,
/// Statements block.
pub statements: StmtBlock,
/// Expression.
pub expr: Expr,
}
impl<B: Into<StmtBlock>> From<B> for ConditionalStmtBlock {
impl<E: Into<Expr>> From<E> for ConditionalExpr {
#[inline(always)]
fn from(value: B) -> Self {
fn from(value: E) -> Self {
Self {
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)]
fn from(value: (Expr, B)) -> Self {
fn from(value: (Expr, E)) -> Self {
Self {
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.
#[derive(Debug, Clone, Hash)]
pub struct SwitchCasesCollection {
/// List of [`ConditionalStmtBlock`]'s.
pub case_blocks: StaticVec<ConditionalStmtBlock>,
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
/// List of [`ConditionalExpr`]'s.
pub expressions: StaticVec<ConditionalExpr>,
/// Dictionary mapping value hashes to [`ConditionalExpr`]'s.
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.
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.
@ -757,17 +778,15 @@ impl Stmt {
let (expr, sw) = &**x;
expr.is_pure()
&& sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| {
let block = &sw.case_blocks[c];
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
let block = &sw.expressions[c];
block.condition.is_pure() && block.expr.is_pure()
})
&& sw.ranges.iter().all(|r| {
let block = &sw.case_blocks[r.index()];
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
let block = &sw.expressions[r.index()];
block.condition.is_pure() && block.expr.is_pure()
})
&& sw.case_blocks[sw.def_case]
.statements
.iter()
.all(Stmt::is_pure)
&& sw.def_case.is_some()
&& sw.expressions[sw.def_case.unwrap()].expr.is_pure()
}
// Loops that exit can be pure because it can never be infinite.
@ -910,32 +929,28 @@ impl Stmt {
}
for (.., blocks) in &sw.cases {
for &b in blocks {
let block = &sw.case_blocks[b];
let block = &sw.expressions[b];
if !block.condition.walk(path, on_node) {
return false;
}
for s in &block.statements {
if !s.walk(path, on_node) {
if !block.expr.walk(path, on_node) {
return false;
}
}
}
}
for r in &sw.ranges {
let block = &sw.case_blocks[r.index()];
let block = &sw.expressions[r.index()];
if !block.condition.walk(path, on_node) {
return false;
}
for s in &block.statements {
if !s.walk(path, on_node) {
if !block.expr.walk(path, on_node) {
return false;
}
}
}
for s in &sw.case_blocks[sw.def_case].statements {
if !s.walk(path, on_node) {
if let Some(index) = sw.def_case {
if !sw.expressions[index].expr.walk(path, on_node) {
return false;
}
}

View File

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

View File

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

View File

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

View File

@ -284,7 +284,7 @@ pub use parser::ParseState;
#[cfg(feature = "internals")]
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,
};

View File

@ -32,6 +32,27 @@ pub enum FnNamespace {
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.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive]

View File

@ -213,9 +213,9 @@ fn optimize_stmt_block(
// Flatten blocks
loop {
if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s {
Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => Some(i),
_ => None,
if let Some(n) = statements.iter().position(|s| match s {
Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => true,
_ => false,
}) {
let (first, second) = statements.split_at_mut(n);
let stmt = mem::take(&mut second[0]);
@ -527,7 +527,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
let (
match_expr,
SwitchCasesCollection {
case_blocks,
expressions,
cases,
ranges,
def_case,
@ -544,58 +544,53 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match &case_blocks_list[..] {
[] => (),
[index] => {
let mut b = mem::take(&mut case_blocks[*index]);
let mut b = mem::take(&mut expressions[*index]);
cases.clear();
match b.condition {
Expr::BoolConstant(true, ..) => {
if b.is_always_true() {
// Promote the matched case
let statements: StmtBlockContainer = mem::take(&mut b.statements);
let statements =
optimize_stmt_block(statements, state, true, true, false);
*stmt = (statements, b.statements.span()).into();
}
ref mut condition => {
let mut statements = Stmt::Expr(mem::take(&mut b.expr).into());
optimize_stmt(&mut statements, state, true);
*stmt = statements;
} else {
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(condition, state, false);
optimize_expr(&mut b.condition, state, false);
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
};
let def_case = &mut case_blocks[*def_case].statements;
let def_span = def_case.span_or_else(*pos, Position::NONE);
let def_case: StmtBlockContainer = mem::take(def_case);
let def_stmt =
optimize_stmt_block(def_case, state, true, true, false);
*stmt = Stmt::If(
(
mem::take(condition),
mem::take(&mut b.statements),
StmtBlock::new_with_span(def_stmt, def_span),
mem::take(&mut b.condition),
Stmt::Expr(mem::take(&mut b.expr).into()).into(),
else_stmt,
)
.into(),
match_expr.start_position(),
);
}
}
state.set_dirty();
return;
}
_ => {
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 {
Expr::BoolConstant(true, ..) => {
if b.is_always_true() {
// Promote the matched case
let statements: StmtBlockContainer =
mem::take(&mut b.statements);
let statements =
optimize_stmt_block(statements, state, true, true, false);
*stmt = (statements, b.statements.span()).into();
let mut statements = Stmt::Expr(mem::take(&mut b.expr).into());
optimize_stmt(&mut statements, state, true);
*stmt = statements;
state.set_dirty();
return;
}
_ => (),
}
}
}
}
@ -607,48 +602,43 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Only one range or all ranges without conditions
if ranges.len() == 1
|| ranges.iter().all(|r| {
matches!(
case_blocks[r.index()].condition,
Expr::BoolConstant(true, ..)
)
})
|| ranges
.iter()
.all(|r| expressions[r.index()].is_always_true())
{
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 {
Expr::BoolConstant(true, ..) => {
if range_block.is_always_true() {
// Promote the matched case
let block = &mut case_blocks[r.index()];
let statements = mem::take(&mut *block.statements);
let statements =
optimize_stmt_block(statements, state, true, true, false);
*stmt = (statements, block.statements.span()).into();
}
mut condition => {
let block = &mut expressions[r.index()];
let mut statements = Stmt::Expr(mem::take(&mut block.expr).into());
optimize_stmt(&mut statements, state, true);
*stmt = statements;
} else {
let mut condition = mem::take(&mut range_block.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;
let def_span = def_case.span_or_else(*pos, Position::NONE);
let def_case: StmtBlockContainer = mem::take(def_case);
let def_stmt =
optimize_stmt_block(def_case, state, true, true, false);
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
};
let statements = mem::take(&mut case_blocks[r.index()].statements);
let if_stmt =
Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into())
.into();
*stmt = Stmt::If(
(
condition,
statements,
StmtBlock::new_with_span(def_stmt, def_span),
)
.into(),
(condition, if_stmt, else_stmt).into(),
match_expr.start_position(),
);
}
}
state.set_dirty();
return;
@ -669,20 +659,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
}
for r in &*ranges {
let b = &mut case_blocks[r.index()];
let statements = mem::take(&mut *b.statements);
*b.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
let b = &mut expressions[r.index()];
optimize_expr(&mut b.condition, state, false);
match b.condition {
Expr::Unit(pos) => {
b.condition = Expr::BoolConstant(true, pos);
state.set_dirty()
}
_ => (),
}
optimize_expr(&mut b.expr, state, false);
}
return;
}
@ -690,18 +669,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Promote the default case
state.set_dirty();
let def_case = &mut case_blocks[*def_case].statements;
let def_span = def_case.span_or_else(*pos, Position::NONE);
let def_case: StmtBlockContainer = mem::take(def_case);
let def_stmt = optimize_stmt_block(def_case, state, true, true, false);
*stmt = (def_stmt, def_span).into();
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);
*stmt = def_stmt;
} else {
*stmt = StmtBlock::empty(*pos).into();
}
}
// switch
Stmt::Switch(x, ..) => {
let (
match_expr,
SwitchCasesCollection {
case_blocks,
expressions,
cases,
ranges,
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 blocks
for b in case_blocks.iter_mut() {
let statements = mem::take(&mut *b.statements);
*b.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
for b in expressions.iter_mut() {
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);
if b.is_always_false() {
if !b.expr.is_unit() {
b.expr = Expr::Unit(b.expr.position());
state.set_dirty();
}
Expr::BoolConstant(false, ..) => {
if !b.statements.is_empty() {
b.statements = StmtBlock::NONE;
state.set_dirty();
}
}
_ => (),
}
}
// Remove false cases
let cases_len = cases.len();
cases.retain(|_, list| {
// Remove all entries that have false conditions
list.retain(|index| match case_blocks[*index].condition {
Expr::BoolConstant(false, ..) => false,
_ => true,
list.retain(|index| {
if expressions[*index].is_always_false() {
state.set_dirty();
false
} else {
true
}
});
// Remove all entries after a `true` condition
if let Some(n) = list
.iter()
.position(|&index| match case_blocks[index].condition {
Expr::BoolConstant(true, ..) => true,
_ => false,
})
.position(|&index| expressions[index].is_always_true())
{
if n + 1 < list.len() {
state.set_dirty();
list.truncate(n + 1);
}
// Remove if no entry left
!list.is_empty()
});
if cases.len() != cases_len {
state.set_dirty();
}
// Remove false ranges
ranges.retain(|r| match case_blocks[r.index()].condition {
Expr::BoolConstant(false, ..) => {
// Remove if no entry left
match list.is_empty() {
true => {
state.set_dirty();
false
}
_ => true,
false => true,
}
});
let def_stmt_block = &mut case_blocks[*def_case].statements;
let def_block = mem::take(&mut **def_stmt_block);
**def_stmt_block = optimize_stmt_block(def_block, state, preserve_result, true, false);
// Remove false ranges
ranges.retain(|r| {
if expressions[r.index()].is_always_false() {
state.set_dirty();
false
} else {
true
}
});
if let Some(index) = def_case {
optimize_expr(&mut expressions[*index].expr, state, false);
}
// Remove unused block statements
for index in 0..case_blocks.len() {
if *def_case == index
for index in 0..expressions.len() {
if *def_case == Some(index)
|| cases.values().flat_map(|c| c.iter()).any(|&n| n == index)
|| ranges.iter().any(|r| r.index() == index)
{
continue;
}
let b = &mut case_blocks[index];
let b = &mut expressions[index];
if !b.statements.is_empty() {
b.statements = StmtBlock::NONE;
if !b.expr.is_unit() {
b.expr = Expr::Unit(b.expr.position());
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);
}
// 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) => {
optimize_expr(expr, state, false);
@ -948,6 +943,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
match expr {
// {}
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
Expr::Stmt(x) => {
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false);

View File

@ -8,7 +8,7 @@ use crate::{
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, mem};
use std::{any::TypeId, borrow::Cow, mem};
#[cfg(not(feature = "no_float"))]
use crate::{FLOAT, FLOAT_BYTES};
@ -104,6 +104,27 @@ pub mod blob_functions {
pub fn to_array(blob: &mut Blob) -> Array {
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.
///
/// # Example
@ -200,6 +221,7 @@ pub mod blob_functions {
///
/// print(b); // prints "[42]"
/// ```
#[rhai_fn(name = "push", name = "append")]
pub fn push(blob: &mut Blob, value: INT) {
blob.push((value & 0x000000ff) as u8);
}
@ -235,13 +257,13 @@ pub mod blob_functions {
///
/// print(b); // prints "[424242424268656c 6c6f]"
/// ```
#[rhai_fn(name = "+=", name = "append")]
#[rhai_fn(name = "append")]
pub fn append_str(blob: &mut Blob, string: &str) {
if !string.is_empty() {
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
///
@ -252,38 +274,12 @@ pub mod blob_functions {
///
/// print(b); // prints "[424242424221]"
/// ```
#[rhai_fn(name = "+=", name = "append")]
#[rhai_fn(name = "append")]
pub fn append_char(blob: &mut Blob, character: char) {
let mut buf = [0_u8; 4];
let x = character.encode_utf8(&mut buf);
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.
///
/// * 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;
}
} else {
unreachable!();
unreachable!("`dir` != 0");
}
Some(v)

View File

@ -4,20 +4,20 @@ use crate::{Module, Shared};
pub(crate) mod arithmetic;
pub(crate) mod array_basic;
mod bit_field;
pub(crate) mod bit_field;
pub(crate) mod blob_basic;
mod debugging;
mod fn_basic;
pub(crate) mod debugging;
pub(crate) mod fn_basic;
pub(crate) mod iter_basic;
mod lang_core;
mod logic;
mod map_basic;
mod math_basic;
mod pkg_core;
mod pkg_std;
mod string_basic;
mod string_more;
mod time_basic;
pub(crate) mod lang_core;
pub(crate) mod logic;
pub(crate) mod map_basic;
pub(crate) mod math_basic;
pub(crate) mod pkg_core;
pub(crate) mod pkg_std;
pub(crate) mod string_basic;
pub(crate) mod string_more;
pub(crate) mod time_basic;
pub use arithmetic::ArithmeticPackage;
#[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};
#[cfg(not(feature = "no_index"))]
use crate::Blob;
def_package! {
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
pub MoreStringPackage(lib) {
@ -86,26 +83,50 @@ mod string_functions {
#[cfg(not(feature = "no_index"))]
pub mod blob_functions {
use crate::Blob;
#[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() {
string.clone()
} else if string.is_empty() {
String::from_utf8_lossy(&utf8).into_owned().into()
return string.clone();
}
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 {
let mut s = crate::SmartString::from(string.as_str());
s.push_str(&String::from_utf8_lossy(&utf8));
s.into()
let mut x = SmartString::from(string.as_str());
x.push_str(s.as_ref());
x.into()
}
}
#[rhai_fn(name = "append")]
pub fn add_blob(string: &mut ImmutableString, utf8: Blob) {
#[rhai_fn(name = "+=", name = "append")]
pub fn add(string: &mut ImmutableString, utf8: Blob) {
let mut s = crate::SmartString::from(string.as_str());
if !utf8.is_empty() {
s.push_str(&String::from_utf8_lossy(&utf8));
*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.

View File

@ -3,9 +3,9 @@
use crate::api::events::VarDefInfo;
use crate::api::options::LangOptions;
use crate::ast::{
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes,
Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCasesCollection,
TryCatchBlock,
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer,
SwitchCasesCollection, TryCatchBlock,
};
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::eval::GlobalRuntimeState;
@ -798,7 +798,7 @@ impl Engine {
match token {
Token::LeftBracket => ASTFlags::NONE,
Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!(),
_ => unreachable!("`[` or `?[`"),
},
false,
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 ranges = StaticVec::<RangeCase>::new();
let mut def_stmt_pos = Position::NONE;
let mut def_stmt_index = None;
let mut def_case = None;
let mut def_case_pos = Position::NONE;
loop {
const MISSING_RBRACE: &str = "to end this switch block";
@ -1075,8 +1075,8 @@ impl Engine {
.into_err(*pos),
)
}
(Token::Underscore, pos) if def_stmt_index.is_none() => {
def_stmt_pos = *pos;
(Token::Underscore, pos) if def_case.is_none() => {
def_case_pos = *pos;
eat_token(input, Token::Underscore);
let (if_clause, if_pos) = match_token(input, Token::If);
@ -1087,8 +1087,8 @@ impl Engine {
(Default::default(), Expr::BoolConstant(true, Position::NONE))
}
_ if def_stmt_index.is_some() => {
return Err(PERR::WrongSwitchDefaultCase.into_err(def_stmt_pos))
_ if def_case.is_some() => {
return Err(PERR::WrongSwitchDefaultCase.into_err(def_case_pos))
}
_ => {
@ -1142,8 +1142,9 @@ impl Engine {
let need_comma = !stmt.is_self_terminated();
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
case_blocks.push((condition, stmt).into());
let index = case_blocks.len() - 1;
let stmt_block: StmtBlock = stmt.into();
expressions.push((condition, Expr::Stmt(stmt_block.into())).into());
let index = expressions.len() - 1;
if !case_expr_list.is_empty() {
for expr in case_expr_list {
@ -1197,7 +1198,7 @@ impl Engine {
.or_insert_with(|| [index].into());
}
} else {
def_stmt_index = Some(index);
def_case = Some(index);
}
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 {
case_blocks,
expressions,
cases,
def_case,
ranges,
@ -1408,9 +1404,7 @@ impl Engine {
}
// Make sure to parse the following as text
let mut control = state.tokenizer_control.get();
control.is_within_text = true;
state.tokenizer_control.set(control);
state.tokenizer_control.borrow_mut().is_within_text = true;
match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => {
@ -1697,7 +1691,7 @@ impl Engine {
let opt = match token {
Token::LeftBracket => ASTFlags::NONE,
Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!(),
_ => unreachable!("`[` or `?[`"),
};
self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())?
}
@ -1721,7 +1715,7 @@ impl Engine {
let op_flags = match op {
Token::Period => ASTFlags::NONE,
Token::Elvis => ASTFlags::NEGATED,
_ => unreachable!(),
_ => unreachable!("`.` or `?.`"),
};
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::{
borrow::Cow,
cell::Cell,
cell::RefCell,
char, fmt,
iter::{FusedIterator, Peekable},
num::NonZeroUsize,
@ -20,11 +20,14 @@ use std::{
};
/// _(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 {
/// Is the current tokenizer position within an interpolated text string?
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
pub is_within_text: bool,
/// Collection of global comments.
#[cfg(feature = "metadata")]
pub global_comments: Vec<SmartString>,
}
impl TokenizerControlBlock {
@ -34,12 +37,14 @@ impl TokenizerControlBlock {
pub const fn new() -> Self {
Self {
is_within_text: false,
#[cfg(feature = "metadata")]
global_comments: Vec::new(),
}
}
}
/// _(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;
@ -248,7 +253,7 @@ impl fmt::Debug for Position {
}
#[cfg(feature = "no_position")]
unreachable!();
unreachable!("no position");
}
}
}
@ -1098,12 +1103,14 @@ impl From<Token> for String {
/// _(internals)_ State of the tokenizer.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TokenizeState {
/// Maximum length of a string.
pub max_string_size: Option<NonZeroUsize>,
/// Can the next token be a unary operator?
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?
pub comment_level: usize,
/// Include comments?
@ -1866,6 +1873,11 @@ fn get_next_token_inner(
_ => Some("///".into()),
}
}
#[cfg(feature = "metadata")]
Some('!') => {
eat_next(stream, pos);
Some("//!".into())
}
_ if state.include_comments => Some("//".into()),
_ => None,
};
@ -1890,7 +1902,15 @@ fn get_next_token_inner(
}
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,
/// Current position.
pub pos: Position,
/// Shared object to allow controlling the tokenizer externally.
pub tokenizer_control: TokenizerControl,
/// Input character stream.
pub stream: MultiInputsStream<'a>,
/// A processor function that maps a token to another.
@ -2312,14 +2330,15 @@ impl<'a> Iterator for TokenIterator<'a> {
type Item = (Token, Position);
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 {
// Switch to text mode terminated by back-tick
self.state.is_within_text_terminated_by = Some('`');
// Reset it
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) {
@ -2450,7 +2469,7 @@ impl Engine {
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
token_mapper: Option<&'a OnParseTokenCallback>,
) -> (TokenIterator<'a>, TokenizerControl) {
let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into();
let buffer: TokenizerControl = RefCell::new(TokenizerControlBlock::new()).into();
let buffer2 = buffer.clone();
(
@ -2462,12 +2481,12 @@ impl Engine {
#[cfg(feature = "unchecked")]
max_string_size: None,
next_token_cannot_be_unary: false,
tokenizer_control: buffer,
comment_level: 0,
include_comments: false,
is_within_text_terminated_by: None,
},
pos: Position::new(1, 0),
tokenizer_control: buffer,
stream: MultiInputsStream {
buf: None,
streams: input

View File

@ -68,6 +68,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[cfg(feature = "metadata")]
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
#[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 } }")?;
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 }")?;
assert_eq!(
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 }")?;
assert_eq!(format!("{:?}", ast), "AST { body: [] }");
assert_eq!(
format!("{:?}", ast),
r#"AST { source: "", doc: "", resolver: None, body: [] }"#
);
engine.set_optimization_level(OptimizationLevel::Full);
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")?;
assert_eq!(
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();
@ -112,7 +122,10 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
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(())
}