Merge pull request #589 from schungx/master
Add module documentation feature.
This commit is contained in:
commit
602efc7042
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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'.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script contains a single assignment statement.
|
||||
//! This script contains a single assignment statement.
|
||||
|
||||
let x = 78;
|
||||
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs for-loops.
|
||||
//! This script runs for-loops.
|
||||
|
||||
let arr = [1, true, 123.456, "hello", 3, 42];
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs for-loops
|
||||
//! This script runs for-loops
|
||||
|
||||
const MAX = 1_000_000;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
// This script defines a function with many parameters.
|
||||
//
|
||||
//! This script defines a function with many parameters.
|
||||
|
||||
const KEY = 38;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs if statements.
|
||||
//! This script runs if statements.
|
||||
|
||||
let a = 42;
|
||||
let b = 123;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs an if expression.
|
||||
//! This script runs an if expression.
|
||||
|
||||
let a = 42;
|
||||
let b = 123;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script simulates multi-dimensional matrix calculations.
|
||||
//! This script simulates multi-dimensional matrix calculations.
|
||||
|
||||
const SIZE = 50;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 = ();
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs a single expression.
|
||||
//! This script runs a single expression.
|
||||
|
||||
print("The result should be 46:");
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs a complex expression.
|
||||
//! This script runs a complex expression.
|
||||
|
||||
print("The result should be 182:");
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs a complex expression.
|
||||
//! This script runs a complex expression.
|
||||
|
||||
print("The result should be 230:");
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
170
scripts/static.d.rhai
Normal 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) -> ?;
|
@ -1,4 +1,4 @@
|
||||
// This script tests string operations.
|
||||
//! This script tests string operations.
|
||||
|
||||
print("hello");
|
||||
print("this\nis \\ nice"); // escape sequences
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script tests object maps and strings.
|
||||
//! This script tests object maps and strings.
|
||||
|
||||
print("Ready... Go!");
|
||||
|
||||
|
@ -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];
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs a while loop.
|
||||
//! This script runs a while loop.
|
||||
|
||||
let x = 10;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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")]
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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"))]
|
||||
|
@ -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>,
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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]
|
||||
|
247
src/optimizer.rs
247
src/optimizer.rs
@ -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);
|
||||
|
@ -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).
|
||||
|
@ -105,7 +105,7 @@ impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
|
||||
self.dir = 0;
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
unreachable!("`dir` != 0");
|
||||
}
|
||||
|
||||
Some(v)
|
||||
|
@ -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"))]
|
||||
|
@ -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.
|
||||
|
@ -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)?
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user