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.
|
The minimum Rust version is now `1.60.0` in order to use the `dep:` syntax for dependencies.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* `switch` cases with conditions that evaluate to constant `()` no longer optimize to `false` (should raise a type error during runtime).
|
||||||
|
* Fixes concatenation of BLOB's and strings, where the BLOB's should be interpreted as UTF-8 encoded strings.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most).
|
* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most).
|
||||||
|
* Comment lines beginning with `//!` (requires the `metadata` feature) are now collected as the script file's _module documentation_.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
@ -25,6 +32,8 @@ Enhancements
|
|||||||
|
|
||||||
* `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block.
|
* `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block.
|
||||||
* A new `range` function variant that takes an exclusive range with a step.
|
* A new `range` function variant that takes an exclusive range with a step.
|
||||||
|
* `as_string` is added to BLOB's to convert it into a string by interpreting it as a UTF-8 byte stream.
|
||||||
|
* `FnAccess::is_private`, `FnAccess::is_public`, `FnNamespace::is_module_namespace` and `FnNameSpace::is_global_namespace` are added for convenience.
|
||||||
|
|
||||||
|
|
||||||
Version 1.8.0
|
Version 1.8.0
|
||||||
|
@ -6,6 +6,7 @@ name = "rhai"
|
|||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
rust-version = "1.60.0"
|
rust-version = "1.60.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
resolver = "2"
|
||||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||||
description = "Embedded scripting for Rust"
|
description = "Embedded scripting for Rust"
|
||||||
homepage = "https://rhai.rs"
|
homepage = "https://rhai.rs"
|
||||||
|
@ -200,7 +200,7 @@ pub fn generate_body(
|
|||||||
|
|
||||||
let ns_str = syn::Ident::new(
|
let ns_str = syn::Ident::new(
|
||||||
match namespace {
|
match namespace {
|
||||||
FnNamespaceAccess::Unset => unreachable!(),
|
FnNamespaceAccess::Unset => unreachable!("`namespace` should be set"),
|
||||||
FnNamespaceAccess::Global => "Global",
|
FnNamespaceAccess::Global => "Global",
|
||||||
FnNamespaceAccess::Internal => "Internal",
|
FnNamespaceAccess::Internal => "Internal",
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Implementation of the Event Handler With State Pattern - JS Style
|
//! Implementation of the Event Handler With State Pattern - JS Style
|
||||||
|
|
||||||
/// Initialize user-provided state.
|
/// Initialize user-provided state.
|
||||||
fn init() {
|
fn init() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Implementation of the Event Handler With State Pattern - Main Style
|
//! Implementation of the Event Handler With State Pattern - Main Style
|
||||||
|
|
||||||
/// Initialize user-provided state (shadows system-provided state, if any).
|
/// Initialize user-provided state (shadows system-provided state, if any).
|
||||||
fn init() {
|
fn init() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Implementation of the Event Handler With State Pattern - Map Style
|
//! Implementation of the Event Handler With State Pattern - Map Style
|
||||||
|
|
||||||
/// Initialize user-provided state.
|
/// Initialize user-provided state.
|
||||||
/// State is stored inside an object map bound to 'state'.
|
/// State is stored inside an object map bound to 'state'.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script contains a single assignment statement.
|
//! This script contains a single assignment statement.
|
||||||
|
|
||||||
let x = 78;
|
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,
|
/// The function `foo`, which prints `hello, world!` and a magic number,
|
||||||
/// accepts three parameters.
|
/// accepts three parameters.
|
||||||
///
|
///
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// This script calculates the n-th Fibonacci number using a really dumb algorithm
|
//! This script calculates the n-th Fibonacci number using a really dumb algorithm
|
||||||
// to test the speed of the scripting engine.
|
//! to test the speed of the scripting engine.
|
||||||
|
|
||||||
const TARGET = 28;
|
const TARGET = 28;
|
||||||
const REPEAT = 5;
|
const REPEAT = 5;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script runs for-loops.
|
//! This script runs for-loops.
|
||||||
|
|
||||||
let arr = [1, true, 123.456, "hello", 3, 42];
|
let arr = [1, true, 123.456, "hello", 3, 42];
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script runs for-loops
|
//! This script runs for-loops
|
||||||
|
|
||||||
const MAX = 1_000_000;
|
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 MAX = 100;
|
||||||
const CHECK = ((MAX - 1) ** 2) * MAX;
|
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() {
|
fn call_me() {
|
||||||
return 3;
|
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;
|
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;
|
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
|
// Use 'this' to refer to the object of a method call
|
||||||
fn action(x, y) {
|
fn action(x, y) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script runs if statements.
|
//! This script runs if statements.
|
||||||
|
|
||||||
let a = 42;
|
let a = 42;
|
||||||
let b = 123;
|
let b = 123;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script runs an if expression.
|
//! This script runs an if expression.
|
||||||
|
|
||||||
let a = 42;
|
let a = 42;
|
||||||
let b = 123;
|
let b = 123;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script runs an infinite loop, ending it with a break statement.
|
//! This script runs an infinite loop, ending it with a break statement.
|
||||||
|
|
||||||
let x = 10;
|
let x = 10;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// This script simulates multi-dimensional matrix calculations.
|
//! This script simulates multi-dimensional matrix calculations.
|
||||||
|
|
||||||
const SIZE = 50;
|
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;
|
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.
|
// External variable that will be captured.
|
||||||
let last_value = ();
|
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:");
|
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:");
|
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:");
|
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();
|
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 now = timestamp();
|
||||||
let x = 1_000_000;
|
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("hello");
|
||||||
print("this\nis \\ nice"); // escape sequences
|
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!");
|
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];
|
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;
|
let x = 10;
|
||||||
|
|
||||||
|
@ -222,7 +222,10 @@ impl Engine {
|
|||||||
self.token_mapper.as_ref().map(<_>::as_ref),
|
self.token_mapper.as_ref().map(<_>::as_ref),
|
||||||
);
|
);
|
||||||
let mut state = ParseState::new(self, scope, tokenizer_control);
|
let mut state = ParseState::new(self, scope, tokenizer_control);
|
||||||
self.parse(&mut stream.peekable(), &mut state, optimization_level)
|
let mut ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?;
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n"));
|
||||||
|
Ok(ast)
|
||||||
}
|
}
|
||||||
/// Compile a string containing an expression into an [`AST`],
|
/// Compile a string containing an expression into an [`AST`],
|
||||||
/// which can be used later for evaluation.
|
/// which can be used later for evaluation.
|
||||||
|
@ -78,7 +78,7 @@ impl Expression<'_> {
|
|||||||
///
|
///
|
||||||
/// The following option is available:
|
/// The following option is available:
|
||||||
///
|
///
|
||||||
/// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock]
|
/// * whether to rewind the [`Scope`][crate::Scope] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock]
|
||||||
///
|
///
|
||||||
/// # WARNING - Unstable API
|
/// # WARNING - Unstable API
|
||||||
///
|
///
|
||||||
|
@ -318,7 +318,7 @@ impl crate::Expression<'_> {
|
|||||||
///
|
///
|
||||||
/// # Deprecated
|
/// # Deprecated
|
||||||
///
|
///
|
||||||
/// This method is deprecated. Use [`get_string_value`][Expression::get_string_value] instead.
|
/// This method is deprecated. Use [`get_string_value`][crate::Expression::get_string_value] instead.
|
||||||
///
|
///
|
||||||
/// This method will be removed in the next major version.
|
/// This method will be removed in the next major version.
|
||||||
#[deprecated(since = "1.4.0", note = "use `get_string_value` instead")]
|
#[deprecated(since = "1.4.0", note = "use `get_string_value` instead")]
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#![cfg(not(feature = "no_optimize"))]
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
|
|
||||||
use crate::{Engine, OptimizationLevel, Scope, AST};
|
use crate::{Engine, OptimizationLevel, Scope, AST};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Control whether and how the [`Engine`] will optimize an [`AST`] after compilation.
|
/// Control whether and how the [`Engine`] will optimize an [`AST`] after compilation.
|
||||||
@ -59,13 +60,18 @@ impl Engine {
|
|||||||
.map(|f| f.func.get_script_fn_def().unwrap().clone())
|
.map(|f| f.func.get_script_fn_def().unwrap().clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
crate::optimizer::optimize_into_ast(
|
let mut new_ast = crate::optimizer::optimize_into_ast(
|
||||||
self,
|
self,
|
||||||
scope,
|
scope,
|
||||||
ast.take_statements(),
|
ast.take_statements(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib,
|
lib,
|
||||||
optimization_level,
|
optimization_level,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
new_ast.set_doc(mem::take(ast.doc_mut()));
|
||||||
|
|
||||||
|
new_ast
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,7 @@ impl Engine {
|
|||||||
let (stream, tokenizer_control) =
|
let (stream, tokenizer_control) =
|
||||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
|
self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref));
|
||||||
let mut state = ParseState::new(self, scope, tokenizer_control);
|
let mut state = ParseState::new(self, scope, tokenizer_control);
|
||||||
|
|
||||||
let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?;
|
let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?;
|
||||||
|
|
||||||
self.run_ast_with_scope(scope, &ast)
|
self.run_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
/// Evaluate an [`AST`], returning any error (if any).
|
/// Evaluate an [`AST`], returning any error (if any).
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Module defining the AST (abstract syntax tree).
|
//! Module defining the AST (abstract syntax tree).
|
||||||
|
|
||||||
use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer};
|
use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer};
|
||||||
use crate::{Dynamic, FnNamespace, Identifier, Position};
|
use crate::{Dynamic, FnNamespace, Identifier, Position, SmartString};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -21,6 +21,9 @@ pub struct AST {
|
|||||||
/// Source of the [`AST`].
|
/// Source of the [`AST`].
|
||||||
/// No source if string is empty.
|
/// No source if string is empty.
|
||||||
source: Identifier,
|
source: Identifier,
|
||||||
|
/// [`AST`] documentation.
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
doc: SmartString,
|
||||||
/// Global statements.
|
/// Global statements.
|
||||||
body: StmtBlock,
|
body: StmtBlock,
|
||||||
/// Script-defined functions.
|
/// Script-defined functions.
|
||||||
@ -42,13 +45,11 @@ impl fmt::Debug for AST {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut fp = f.debug_struct("AST");
|
let mut fp = f.debug_struct("AST");
|
||||||
|
|
||||||
if !self.source.is_empty() {
|
fp.field("source", &self.source);
|
||||||
fp.field("source: ", &self.source);
|
#[cfg(feature = "metadata")]
|
||||||
}
|
fp.field("doc", &self.doc);
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if let Some(ref resolver) = self.resolver {
|
fp.field("resolver", &self.resolver);
|
||||||
fp.field("resolver: ", resolver);
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.field("body", &self.body.as_slice());
|
fp.field("body", &self.body.as_slice());
|
||||||
|
|
||||||
@ -74,6 +75,8 @@ impl AST {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Identifier::new_const(),
|
source: Identifier::new_const(),
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
doc: SmartString::new_const(),
|
||||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: functions.into(),
|
lib: functions.into(),
|
||||||
@ -92,6 +95,8 @@ impl AST {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Identifier::new_const(),
|
source: Identifier::new_const(),
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
doc: SmartString::new_const(),
|
||||||
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
body: StmtBlock::new(statements, Position::NONE, Position::NONE),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: functions.into(),
|
lib: functions.into(),
|
||||||
@ -140,6 +145,8 @@ impl AST {
|
|||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Identifier::new_const(),
|
source: Identifier::new_const(),
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
doc: SmartString::new_const(),
|
||||||
body: StmtBlock::NONE,
|
body: StmtBlock::NONE,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: crate::Module::new().into(),
|
lib: crate::Module::new().into(),
|
||||||
@ -180,6 +187,41 @@ impl AST {
|
|||||||
self.source.clear();
|
self.source.clear();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Get the documentation (if any).
|
||||||
|
/// Exported under the `metadata` feature only.
|
||||||
|
///
|
||||||
|
/// Documentation is a collection of all comment lines beginning with `//!`.
|
||||||
|
///
|
||||||
|
/// Leading white-spaces are stripped, and each line always starts with `//!`.
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn doc(&self) -> &str {
|
||||||
|
&self.doc
|
||||||
|
}
|
||||||
|
/// Clear the documentation.
|
||||||
|
/// Exported under the `metadata` feature only.
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn clear_doc(&mut self) -> &mut Self {
|
||||||
|
self.doc.clear();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Get a mutable reference to the documentation.
|
||||||
|
///
|
||||||
|
/// Only available under `metadata`.
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn doc_mut(&mut self) -> &mut SmartString {
|
||||||
|
&mut self.doc
|
||||||
|
}
|
||||||
|
/// Set the documentation.
|
||||||
|
///
|
||||||
|
/// Only available under `metadata`.
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn set_doc(&mut self, doc: impl Into<SmartString>) {
|
||||||
|
self.doc = doc.into();
|
||||||
|
}
|
||||||
/// Get the statements.
|
/// Get the statements.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -292,6 +334,8 @@ impl AST {
|
|||||||
lib.merge_filtered(&self.lib, &filter);
|
lib.merge_filtered(&self.lib, &filter);
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
doc: self.doc.clone(),
|
||||||
body: StmtBlock::NONE,
|
body: StmtBlock::NONE,
|
||||||
lib: lib.into(),
|
lib: lib.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -305,6 +349,8 @@ impl AST {
|
|||||||
pub fn clone_statements_only(&self) -> Self {
|
pub fn clone_statements_only(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
doc: self.doc.clone(),
|
||||||
body: self.body.clone(),
|
body: self.body.clone(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
lib: crate::Module::new().into(),
|
lib: crate::Module::new().into(),
|
||||||
@ -543,6 +589,14 @@ impl AST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
if !other.doc.is_empty() {
|
||||||
|
if !_ast.doc.is_empty() {
|
||||||
|
_ast.doc.push('\n');
|
||||||
|
}
|
||||||
|
_ast.doc.push_str(other.doc());
|
||||||
|
}
|
||||||
|
|
||||||
_ast
|
_ast
|
||||||
}
|
}
|
||||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||||
@ -636,6 +690,14 @@ impl AST {
|
|||||||
crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter);
|
crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
if !other.doc.is_empty() {
|
||||||
|
if !self.doc.is_empty() {
|
||||||
|
self.doc.push('\n');
|
||||||
|
}
|
||||||
|
self.doc.push_str(&other.doc);
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Filter out the functions, retaining only some based on a filter predicate.
|
/// Filter out the functions, retaining only some based on a filter predicate.
|
||||||
|
@ -14,6 +14,27 @@ pub enum FnAccess {
|
|||||||
Public,
|
Public,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FnAccess {
|
||||||
|
/// Is this function private?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_private(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Private => true,
|
||||||
|
Self::Public => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is this function public?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_public(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Private => false,
|
||||||
|
Self::Public => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options.
|
/// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
|
@ -22,8 +22,8 @@ pub use script_fn::EncapsulatedEnviron;
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||||
pub use stmt::{
|
pub use stmt::{
|
||||||
CaseBlocksList, ConditionalStmtBlock, OpAssignment, RangeCase, Stmt, StmtBlock,
|
CaseBlocksList, ConditionalExpr, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer,
|
||||||
StmtBlockContainer, SwitchCasesCollection, TryCatchBlock,
|
SwitchCasesCollection, TryCatchBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
@ -45,6 +45,16 @@ pub struct ScriptFnDef {
|
|||||||
pub params: StaticVec<Identifier>,
|
pub params: StaticVec<Identifier>,
|
||||||
/// _(metadata)_ Function doc-comments (if any).
|
/// _(metadata)_ Function doc-comments (if any).
|
||||||
/// Exported under the `metadata` feature only.
|
/// Exported under the `metadata` feature only.
|
||||||
|
///
|
||||||
|
/// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`,
|
||||||
|
/// placed immediately before a function definition.
|
||||||
|
///
|
||||||
|
/// Block doc-comments are kept in a single string slice with line-breaks within.
|
||||||
|
///
|
||||||
|
/// Line doc-comments are kept in one string slice per line without the termination line-break.
|
||||||
|
///
|
||||||
|
/// Leading white-spaces are stripped, and each string slice always starts with the
|
||||||
|
/// corresponding doc-comment leader: `///` or `/**`.
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
pub comments: Box<[Box<str>]>,
|
pub comments: Box<[Box<str>]>,
|
||||||
}
|
}
|
||||||
@ -85,13 +95,15 @@ pub struct ScriptFnMetadata<'a> {
|
|||||||
/// _(metadata)_ Function doc-comments (if any).
|
/// _(metadata)_ Function doc-comments (if any).
|
||||||
/// Exported under the `metadata` feature only.
|
/// Exported under the `metadata` feature only.
|
||||||
///
|
///
|
||||||
|
/// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`,
|
||||||
|
/// placed immediately before a function definition.
|
||||||
|
///
|
||||||
/// Block doc-comments are kept in a single string slice with line-breaks within.
|
/// Block doc-comments are kept in a single string slice with line-breaks within.
|
||||||
///
|
///
|
||||||
/// Line doc-comments are kept in one string slice per line without the termination line-break.
|
/// Line doc-comments are kept in one string slice per line without the termination line-break.
|
||||||
///
|
///
|
||||||
/// Leading white-spaces are stripped, and each string slice always starts with the
|
/// Leading white-spaces are stripped, and each string slice always starts with the
|
||||||
/// corresponding doc-comment leader: `///` or `/**`.
|
/// corresponding doc-comment leader: `///` or `/**`.
|
||||||
/// Function access mode.
|
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
pub comments: Vec<&'a str>,
|
pub comments: Vec<&'a str>,
|
||||||
}
|
}
|
||||||
|
@ -122,33 +122,54 @@ impl fmt::Debug for OpAssignment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A statements block with a condition.
|
/// An expression with a condition.
|
||||||
///
|
///
|
||||||
/// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition.
|
/// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition.
|
||||||
#[derive(Debug, Clone, Default, Hash)]
|
#[derive(Debug, Clone, Default, Hash)]
|
||||||
pub struct ConditionalStmtBlock {
|
pub struct ConditionalExpr {
|
||||||
/// Condition.
|
/// Condition.
|
||||||
pub condition: Expr,
|
pub condition: Expr,
|
||||||
/// Statements block.
|
/// Expression.
|
||||||
pub statements: StmtBlock,
|
pub expr: Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Into<StmtBlock>> From<B> for ConditionalStmtBlock {
|
impl<E: Into<Expr>> From<E> for ConditionalExpr {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: B) -> Self {
|
fn from(value: E) -> Self {
|
||||||
Self {
|
Self {
|
||||||
condition: Expr::BoolConstant(true, Position::NONE),
|
condition: Expr::BoolConstant(true, Position::NONE),
|
||||||
statements: value.into(),
|
expr: value.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Into<StmtBlock>> From<(Expr, B)> for ConditionalStmtBlock {
|
impl<E: Into<Expr>> From<(Expr, E)> for ConditionalExpr {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: (Expr, B)) -> Self {
|
fn from(value: (Expr, E)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
condition: value.0,
|
condition: value.0,
|
||||||
statements: value.1.into(),
|
expr: value.1.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalExpr {
|
||||||
|
/// Is the condition always `true`?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_always_true(&self) -> bool {
|
||||||
|
match self.condition {
|
||||||
|
Expr::BoolConstant(true, ..) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is the condition always `false`?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_always_false(&self) -> bool {
|
||||||
|
match self.condition {
|
||||||
|
Expr::BoolConstant(false, ..) => true,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,14 +283,14 @@ pub type CaseBlocksList = smallvec::SmallVec<[usize; 1]>;
|
|||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct SwitchCasesCollection {
|
pub struct SwitchCasesCollection {
|
||||||
/// List of [`ConditionalStmtBlock`]'s.
|
/// List of [`ConditionalExpr`]'s.
|
||||||
pub case_blocks: StaticVec<ConditionalStmtBlock>,
|
pub expressions: StaticVec<ConditionalExpr>,
|
||||||
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
/// Dictionary mapping value hashes to [`ConditionalExpr`]'s.
|
||||||
pub cases: BTreeMap<u64, CaseBlocksList>,
|
pub cases: BTreeMap<u64, CaseBlocksList>,
|
||||||
/// Statements block for the default case (there can be no condition for the default case).
|
|
||||||
pub def_case: usize,
|
|
||||||
/// List of range cases.
|
/// List of range cases.
|
||||||
pub ranges: StaticVec<RangeCase>,
|
pub ranges: StaticVec<RangeCase>,
|
||||||
|
/// Statements block for the default case (there can be no condition for the default case).
|
||||||
|
pub def_case: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ A `try-catch` block.
|
/// _(internals)_ A `try-catch` block.
|
||||||
@ -757,17 +778,15 @@ impl Stmt {
|
|||||||
let (expr, sw) = &**x;
|
let (expr, sw) = &**x;
|
||||||
expr.is_pure()
|
expr.is_pure()
|
||||||
&& sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| {
|
&& sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| {
|
||||||
let block = &sw.case_blocks[c];
|
let block = &sw.expressions[c];
|
||||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
block.condition.is_pure() && block.expr.is_pure()
|
||||||
})
|
})
|
||||||
&& sw.ranges.iter().all(|r| {
|
&& sw.ranges.iter().all(|r| {
|
||||||
let block = &sw.case_blocks[r.index()];
|
let block = &sw.expressions[r.index()];
|
||||||
block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
block.condition.is_pure() && block.expr.is_pure()
|
||||||
})
|
})
|
||||||
&& sw.case_blocks[sw.def_case]
|
&& sw.def_case.is_some()
|
||||||
.statements
|
&& sw.expressions[sw.def_case.unwrap()].expr.is_pure()
|
||||||
.iter()
|
|
||||||
.all(Stmt::is_pure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loops that exit can be pure because it can never be infinite.
|
// Loops that exit can be pure because it can never be infinite.
|
||||||
@ -910,32 +929,28 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
for (.., blocks) in &sw.cases {
|
for (.., blocks) in &sw.cases {
|
||||||
for &b in blocks {
|
for &b in blocks {
|
||||||
let block = &sw.case_blocks[b];
|
let block = &sw.expressions[b];
|
||||||
|
|
||||||
if !block.condition.walk(path, on_node) {
|
if !block.condition.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &block.statements {
|
if !block.expr.walk(path, on_node) {
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for r in &sw.ranges {
|
|
||||||
let block = &sw.case_blocks[r.index()];
|
|
||||||
|
|
||||||
if !block.condition.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for s in &block.statements {
|
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for s in &sw.case_blocks[sw.def_case].statements {
|
for r in &sw.ranges {
|
||||||
if !s.walk(path, on_node) {
|
let block = &sw.expressions[r.index()];
|
||||||
|
|
||||||
|
if !block.condition.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !block.expr.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(index) = sw.def_case {
|
||||||
|
if !sw.expressions[index].expr.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,13 +156,11 @@ impl GlobalRuntimeState<'_> {
|
|||||||
pub fn find_import(&self, name: &str) -> Option<usize> {
|
pub fn find_import(&self, name: &str) -> Option<usize> {
|
||||||
let len = self.keys.len();
|
let len = self.keys.len();
|
||||||
|
|
||||||
self.keys.iter().rev().enumerate().find_map(|(i, key)| {
|
self.keys
|
||||||
if key == name {
|
.iter()
|
||||||
Some(len - 1 - i)
|
.rev()
|
||||||
} else {
|
.position(|key| key == name)
|
||||||
None
|
.map(|i| len - 1 - i)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
/// Push an imported [module][crate::Module] onto the stack.
|
/// Push an imported [module][crate::Module] onto the stack.
|
||||||
///
|
///
|
||||||
|
@ -394,7 +394,7 @@ impl Engine {
|
|||||||
let (
|
let (
|
||||||
expr,
|
expr,
|
||||||
SwitchCasesCollection {
|
SwitchCasesCollection {
|
||||||
case_blocks,
|
expressions,
|
||||||
cases,
|
cases,
|
||||||
def_case,
|
def_case,
|
||||||
ranges,
|
ranges,
|
||||||
@ -405,7 +405,7 @@ impl Engine {
|
|||||||
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level);
|
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level);
|
||||||
|
|
||||||
if let Ok(value) = value_result {
|
if let Ok(value) = value_result {
|
||||||
let stmt_block_result = if value.is_hashable() {
|
let expr_result = if value.is_hashable() {
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
@ -417,7 +417,7 @@ impl Engine {
|
|||||||
let mut result = Ok(None);
|
let mut result = Ok(None);
|
||||||
|
|
||||||
for &index in case_blocks_list {
|
for &index in case_blocks_list {
|
||||||
let block = &case_blocks[index];
|
let block = &expressions[index];
|
||||||
|
|
||||||
let cond_result = match block.condition {
|
let cond_result = match block.condition {
|
||||||
Expr::BoolConstant(b, ..) => Ok(b),
|
Expr::BoolConstant(b, ..) => Ok(b),
|
||||||
@ -435,7 +435,7 @@ impl Engine {
|
|||||||
|
|
||||||
match cond_result {
|
match cond_result {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
result = Ok(Some(&block.statements));
|
result = Ok(Some(&block.expr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ok(false) => (),
|
Ok(false) => (),
|
||||||
@ -453,7 +453,7 @@ impl Engine {
|
|||||||
let mut result = Ok(None);
|
let mut result = Ok(None);
|
||||||
|
|
||||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||||
let block = &case_blocks[r.index()];
|
let block = &expressions[r.index()];
|
||||||
|
|
||||||
let cond_result = match block.condition {
|
let cond_result = match block.condition {
|
||||||
Expr::BoolConstant(b, ..) => Ok(b),
|
Expr::BoolConstant(b, ..) => Ok(b),
|
||||||
@ -470,7 +470,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match cond_result {
|
match cond_result {
|
||||||
Ok(true) => result = Ok(Some(&block.statements)),
|
Ok(true) => result = Ok(Some(&block.expr)),
|
||||||
Ok(false) => continue,
|
Ok(false) => continue,
|
||||||
_ => result = cond_result.map(|_| None),
|
_ => result = cond_result.map(|_| None),
|
||||||
}
|
}
|
||||||
@ -488,27 +488,18 @@ impl Engine {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(Some(statements)) = stmt_block_result {
|
if let Ok(Some(expr)) = expr_result {
|
||||||
if !statements.is_empty() {
|
self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)
|
||||||
self.eval_stmt_block(
|
} else if let Ok(None) = expr_result {
|
||||||
scope, global, caches, lib, this_ptr, statements, true, level,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
}
|
|
||||||
} else if let Ok(None) = stmt_block_result {
|
|
||||||
// Default match clause
|
// Default match clause
|
||||||
let def_case = &case_blocks[*def_case].statements;
|
if let Some(index) = def_case {
|
||||||
|
let def_expr = &expressions[*index].expr;
|
||||||
if !def_case.is_empty() {
|
self.eval_expr(scope, global, caches, lib, this_ptr, def_expr, level)
|
||||||
self.eval_stmt_block(
|
|
||||||
scope, global, caches, lib, this_ptr, def_case, true, level,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stmt_block_result.map(|_| Dynamic::UNIT)
|
expr_result.map(|_| Dynamic::UNIT)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value_result
|
value_result
|
||||||
|
@ -293,10 +293,15 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if type1 == type2 {
|
if type2 == TypeId::of::<char>() {
|
||||||
return match op {
|
return match op {
|
||||||
"==" => Some(impl_op!(Blob == Blob)),
|
"+" => Some(|_, args| {
|
||||||
"!=" => Some(impl_op!(Blob != Blob)),
|
let mut buf = [0_u8; 4];
|
||||||
|
let mut blob = args[0].read_lock::<Blob>().expect(BUILTIN).clone();
|
||||||
|
let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf);
|
||||||
|
blob.extend(x.as_bytes());
|
||||||
|
Ok(Dynamic::from_blob(blob))
|
||||||
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -503,6 +508,33 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
if type1 == TypeId::of::<crate::Blob>() {
|
||||||
|
use crate::Blob;
|
||||||
|
|
||||||
|
return match op {
|
||||||
|
"+" => Some(|_, args| {
|
||||||
|
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
|
||||||
|
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN);
|
||||||
|
|
||||||
|
Ok(Dynamic::from_blob(if !blob2.is_empty() {
|
||||||
|
if blob1.is_empty() {
|
||||||
|
blob2.clone()
|
||||||
|
} else {
|
||||||
|
let mut blob = blob1.clone();
|
||||||
|
blob.extend(blob2);
|
||||||
|
blob
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blob1.clone()
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
"==" => Some(impl_op!(Blob == Blob)),
|
||||||
|
"!=" => Some(impl_op!(Blob != Blob)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if type1 == TypeId::of::<()>() {
|
if type1 == TypeId::of::<()>() {
|
||||||
return match op {
|
return match op {
|
||||||
"==" => Some(|_, _| Ok(Dynamic::TRUE)),
|
"==" => Some(|_, _| Ok(Dynamic::TRUE)),
|
||||||
@ -684,71 +716,39 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
{
|
{
|
||||||
// string op= blob
|
use crate::Blob;
|
||||||
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<crate::Blob>()) {
|
|
||||||
return match op {
|
|
||||||
"+=" => Some(|_, args| {
|
|
||||||
let buf = {
|
|
||||||
let x = args[1].read_lock::<crate::Blob>().expect(BUILTIN);
|
|
||||||
if x.is_empty() {
|
|
||||||
return Ok(Dynamic::UNIT);
|
|
||||||
}
|
|
||||||
let s = args[0].read_lock::<ImmutableString>().expect(BUILTIN);
|
|
||||||
let mut buf = crate::SmartString::from(s.as_str());
|
|
||||||
buf.push_str(&String::from_utf8_lossy(&x));
|
|
||||||
buf
|
|
||||||
};
|
|
||||||
let mut s = args[0].write_lock::<ImmutableString>().expect(BUILTIN);
|
|
||||||
*s = buf.into();
|
|
||||||
Ok(Dynamic::UNIT)
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// blob op= int
|
|
||||||
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<INT>()) {
|
|
||||||
use crate::Blob;
|
|
||||||
|
|
||||||
|
// blob op= int
|
||||||
|
if types_pair == (TypeId::of::<Blob>(), TypeId::of::<INT>()) {
|
||||||
return match op {
|
return match op {
|
||||||
"+=" => Some(|_, args| {
|
"+=" => Some(|_, args| {
|
||||||
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8;
|
let x = args[1].as_int().expect("`INT`");
|
||||||
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
|
let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||||
Ok(blob.push(x).into())
|
Ok(crate::packages::blob_basic::blob_functions::push(blob, x).into())
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// blob op= char
|
// blob op= char
|
||||||
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<char>()) {
|
if types_pair == (TypeId::of::<Blob>(), TypeId::of::<char>()) {
|
||||||
use crate::Blob;
|
|
||||||
|
|
||||||
return match op {
|
return match op {
|
||||||
"+=" => Some(|_, args| {
|
"+=" => Some(|_, args| {
|
||||||
let mut buf = [0_u8; 4];
|
let x = args[1].as_char().expect("`char`");
|
||||||
let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf);
|
let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||||
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
|
Ok(crate::packages::blob_basic::blob_functions::append_char(blob, x).into())
|
||||||
Ok(blob.extend(x.as_bytes()).into())
|
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// blob op= string
|
// blob op= string
|
||||||
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<ImmutableString>()) {
|
if types_pair == (TypeId::of::<Blob>(), TypeId::of::<ImmutableString>()) {
|
||||||
use crate::Blob;
|
|
||||||
|
|
||||||
return match op {
|
return match op {
|
||||||
"+=" => Some(|_, args| {
|
"+=" => Some(|_, args| {
|
||||||
let s: crate::Blob = {
|
let s = std::mem::take(args[1]).cast::<ImmutableString>();
|
||||||
let s = args[1].read_lock::<ImmutableString>().expect(BUILTIN);
|
let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||||
if s.is_empty() {
|
Ok(crate::packages::blob_basic::blob_functions::append_str(blob, &s).into())
|
||||||
return Ok(Dynamic::UNIT);
|
|
||||||
}
|
|
||||||
s.as_bytes().into()
|
|
||||||
};
|
|
||||||
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
|
|
||||||
Ok(blob.extend(s).into())
|
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
@ -838,14 +838,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
if type1 == TypeId::of::<crate::Blob>() {
|
if type1 == TypeId::of::<crate::Blob>() {
|
||||||
use crate::packages::blob_basic::blob_functions::*;
|
|
||||||
use crate::Blob;
|
use crate::Blob;
|
||||||
|
|
||||||
return match op {
|
return match op {
|
||||||
"+=" => Some(|_, args| {
|
"+=" => Some(|_, args| {
|
||||||
let blob2 = std::mem::take(args[1]).cast::<Blob>();
|
let blob2 = std::mem::take(args[1]).cast::<Blob>();
|
||||||
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||||
Ok(append(blob1, blob2).into())
|
Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
@ -284,7 +284,7 @@ pub use parser::ParseState;
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
pub use ast::{
|
pub use ast::{
|
||||||
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
|
ASTFlags, ASTNode, BinaryExpr, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock,
|
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,6 +32,27 @@ pub enum FnNamespace {
|
|||||||
Global,
|
Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FnNamespace {
|
||||||
|
/// Is this a module namespace?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_module_namespace(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Internal => true,
|
||||||
|
Self::Global => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is this a global namespace?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_global_namespace(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Internal => false,
|
||||||
|
Self::Global => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A type containing all metadata for a registered function.
|
/// A type containing all metadata for a registered function.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
279
src/optimizer.rs
279
src/optimizer.rs
@ -213,9 +213,9 @@ fn optimize_stmt_block(
|
|||||||
|
|
||||||
// Flatten blocks
|
// Flatten blocks
|
||||||
loop {
|
loop {
|
||||||
if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s {
|
if let Some(n) = statements.iter().position(|s| match s {
|
||||||
Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => Some(i),
|
Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => true,
|
||||||
_ => None,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
let (first, second) = statements.split_at_mut(n);
|
let (first, second) = statements.split_at_mut(n);
|
||||||
let stmt = mem::take(&mut second[0]);
|
let stmt = mem::take(&mut second[0]);
|
||||||
@ -527,7 +527,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
let (
|
let (
|
||||||
match_expr,
|
match_expr,
|
||||||
SwitchCasesCollection {
|
SwitchCasesCollection {
|
||||||
case_blocks,
|
expressions,
|
||||||
cases,
|
cases,
|
||||||
ranges,
|
ranges,
|
||||||
def_case,
|
def_case,
|
||||||
@ -544,36 +544,36 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match &case_blocks_list[..] {
|
match &case_blocks_list[..] {
|
||||||
[] => (),
|
[] => (),
|
||||||
[index] => {
|
[index] => {
|
||||||
let mut b = mem::take(&mut case_blocks[*index]);
|
let mut b = mem::take(&mut expressions[*index]);
|
||||||
cases.clear();
|
cases.clear();
|
||||||
|
|
||||||
match b.condition {
|
if b.is_always_true() {
|
||||||
Expr::BoolConstant(true, ..) => {
|
// Promote the matched case
|
||||||
// Promote the matched case
|
let mut statements = Stmt::Expr(mem::take(&mut b.expr).into());
|
||||||
let statements: StmtBlockContainer = mem::take(&mut b.statements);
|
optimize_stmt(&mut statements, state, true);
|
||||||
let statements =
|
*stmt = statements;
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
} else {
|
||||||
*stmt = (statements, b.statements.span()).into();
|
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
}
|
optimize_expr(&mut b.condition, state, false);
|
||||||
ref mut condition => {
|
|
||||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
|
||||||
optimize_expr(condition, state, false);
|
|
||||||
|
|
||||||
let def_case = &mut case_blocks[*def_case].statements;
|
let else_stmt = if let Some(index) = def_case {
|
||||||
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
let mut def_stmt =
|
||||||
let def_case: StmtBlockContainer = mem::take(def_case);
|
Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
|
||||||
let def_stmt =
|
optimize_stmt(&mut def_stmt, state, true);
|
||||||
optimize_stmt_block(def_case, state, true, true, false);
|
def_stmt.into()
|
||||||
*stmt = Stmt::If(
|
} else {
|
||||||
(
|
StmtBlock::NONE
|
||||||
mem::take(condition),
|
};
|
||||||
mem::take(&mut b.statements),
|
|
||||||
StmtBlock::new_with_span(def_stmt, def_span),
|
*stmt = Stmt::If(
|
||||||
)
|
(
|
||||||
.into(),
|
mem::take(&mut b.condition),
|
||||||
match_expr.start_position(),
|
Stmt::Expr(mem::take(&mut b.expr).into()).into(),
|
||||||
);
|
else_stmt,
|
||||||
}
|
)
|
||||||
|
.into(),
|
||||||
|
match_expr.start_position(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -581,20 +581,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
for &index in case_blocks_list {
|
for &index in case_blocks_list {
|
||||||
let mut b = mem::take(&mut case_blocks[index]);
|
let mut b = mem::take(&mut expressions[index]);
|
||||||
|
|
||||||
match b.condition {
|
if b.is_always_true() {
|
||||||
Expr::BoolConstant(true, ..) => {
|
// Promote the matched case
|
||||||
// Promote the matched case
|
let mut statements = Stmt::Expr(mem::take(&mut b.expr).into());
|
||||||
let statements: StmtBlockContainer =
|
optimize_stmt(&mut statements, state, true);
|
||||||
mem::take(&mut b.statements);
|
*stmt = statements;
|
||||||
let statements =
|
state.set_dirty();
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
return;
|
||||||
*stmt = (statements, b.statements.span()).into();
|
|
||||||
state.set_dirty();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,47 +602,42 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
// Only one range or all ranges without conditions
|
// Only one range or all ranges without conditions
|
||||||
if ranges.len() == 1
|
if ranges.len() == 1
|
||||||
|| ranges.iter().all(|r| {
|
|| ranges
|
||||||
matches!(
|
.iter()
|
||||||
case_blocks[r.index()].condition,
|
.all(|r| expressions[r.index()].is_always_true())
|
||||||
Expr::BoolConstant(true, ..)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||||
let condition = mem::take(&mut case_blocks[r.index()].condition);
|
let range_block = &mut expressions[r.index()];
|
||||||
|
|
||||||
match condition {
|
if range_block.is_always_true() {
|
||||||
Expr::BoolConstant(true, ..) => {
|
// Promote the matched case
|
||||||
// Promote the matched case
|
let block = &mut expressions[r.index()];
|
||||||
let block = &mut case_blocks[r.index()];
|
let mut statements = Stmt::Expr(mem::take(&mut block.expr).into());
|
||||||
let statements = mem::take(&mut *block.statements);
|
optimize_stmt(&mut statements, state, true);
|
||||||
let statements =
|
*stmt = statements;
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
} else {
|
||||||
*stmt = (statements, block.statements.span()).into();
|
let mut condition = mem::take(&mut range_block.condition);
|
||||||
}
|
|
||||||
mut condition => {
|
|
||||||
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
|
||||||
optimize_expr(&mut condition, state, false);
|
|
||||||
|
|
||||||
let def_case = &mut case_blocks[*def_case].statements;
|
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
optimize_expr(&mut condition, state, false);
|
||||||
let def_case: StmtBlockContainer = mem::take(def_case);
|
|
||||||
let def_stmt =
|
|
||||||
optimize_stmt_block(def_case, state, true, true, false);
|
|
||||||
|
|
||||||
let statements = mem::take(&mut case_blocks[r.index()].statements);
|
let else_stmt = if let Some(index) = def_case {
|
||||||
|
let mut def_stmt =
|
||||||
|
Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
|
||||||
|
optimize_stmt(&mut def_stmt, state, true);
|
||||||
|
def_stmt.into()
|
||||||
|
} else {
|
||||||
|
StmtBlock::NONE
|
||||||
|
};
|
||||||
|
|
||||||
*stmt = Stmt::If(
|
let if_stmt =
|
||||||
(
|
Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into())
|
||||||
condition,
|
.into();
|
||||||
statements,
|
|
||||||
StmtBlock::new_with_span(def_stmt, def_span),
|
*stmt = Stmt::If(
|
||||||
)
|
(condition, if_stmt, else_stmt).into(),
|
||||||
.into(),
|
match_expr.start_position(),
|
||||||
match_expr.start_position(),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -669,20 +659,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
for r in &*ranges {
|
for r in &*ranges {
|
||||||
let b = &mut case_blocks[r.index()];
|
let b = &mut expressions[r.index()];
|
||||||
let statements = mem::take(&mut *b.statements);
|
|
||||||
*b.statements =
|
|
||||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
|
||||||
|
|
||||||
optimize_expr(&mut b.condition, state, false);
|
optimize_expr(&mut b.condition, state, false);
|
||||||
|
optimize_expr(&mut b.expr, state, false);
|
||||||
match b.condition {
|
|
||||||
Expr::Unit(pos) => {
|
|
||||||
b.condition = Expr::BoolConstant(true, pos);
|
|
||||||
state.set_dirty()
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -690,18 +669,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
// Promote the default case
|
// Promote the default case
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let def_case = &mut case_blocks[*def_case].statements;
|
|
||||||
let def_span = def_case.span_or_else(*pos, Position::NONE);
|
if let Some(index) = def_case {
|
||||||
let def_case: StmtBlockContainer = mem::take(def_case);
|
let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into());
|
||||||
let def_stmt = optimize_stmt_block(def_case, state, true, true, false);
|
optimize_stmt(&mut def_stmt, state, true);
|
||||||
*stmt = (def_stmt, def_span).into();
|
*stmt = def_stmt;
|
||||||
|
} else {
|
||||||
|
*stmt = StmtBlock::empty(*pos).into();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// switch
|
// switch
|
||||||
Stmt::Switch(x, ..) => {
|
Stmt::Switch(x, ..) => {
|
||||||
let (
|
let (
|
||||||
match_expr,
|
match_expr,
|
||||||
SwitchCasesCollection {
|
SwitchCasesCollection {
|
||||||
case_blocks,
|
expressions,
|
||||||
cases,
|
cases,
|
||||||
ranges,
|
ranges,
|
||||||
def_case,
|
def_case,
|
||||||
@ -712,78 +694,76 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
optimize_expr(match_expr, state, false);
|
optimize_expr(match_expr, state, false);
|
||||||
|
|
||||||
// Optimize blocks
|
// Optimize blocks
|
||||||
for b in case_blocks.iter_mut() {
|
for b in expressions.iter_mut() {
|
||||||
let statements = mem::take(&mut *b.statements);
|
|
||||||
*b.statements =
|
|
||||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
|
||||||
|
|
||||||
optimize_expr(&mut b.condition, state, false);
|
optimize_expr(&mut b.condition, state, false);
|
||||||
|
optimize_expr(&mut b.expr, state, false);
|
||||||
|
|
||||||
match b.condition {
|
if b.is_always_false() {
|
||||||
Expr::Unit(pos) => {
|
if !b.expr.is_unit() {
|
||||||
b.condition = Expr::BoolConstant(true, pos);
|
b.expr = Expr::Unit(b.expr.position());
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
Expr::BoolConstant(false, ..) => {
|
|
||||||
if !b.statements.is_empty() {
|
|
||||||
b.statements = StmtBlock::NONE;
|
|
||||||
state.set_dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove false cases
|
// Remove false cases
|
||||||
let cases_len = cases.len();
|
|
||||||
cases.retain(|_, list| {
|
cases.retain(|_, list| {
|
||||||
// Remove all entries that have false conditions
|
// Remove all entries that have false conditions
|
||||||
list.retain(|index| match case_blocks[*index].condition {
|
list.retain(|index| {
|
||||||
Expr::BoolConstant(false, ..) => false,
|
if expressions[*index].is_always_false() {
|
||||||
_ => true,
|
state.set_dirty();
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Remove all entries after a `true` condition
|
// Remove all entries after a `true` condition
|
||||||
if let Some(n) = list
|
if let Some(n) = list
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&index| match case_blocks[index].condition {
|
.position(|&index| expressions[index].is_always_true())
|
||||||
Expr::BoolConstant(true, ..) => true,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
list.truncate(n + 1);
|
if n + 1 < list.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
list.truncate(n + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Remove if no entry left
|
// Remove if no entry left
|
||||||
!list.is_empty()
|
match list.is_empty() {
|
||||||
|
true => {
|
||||||
|
state.set_dirty();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
false => true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if cases.len() != cases_len {
|
|
||||||
state.set_dirty();
|
|
||||||
}
|
|
||||||
// Remove false ranges
|
// Remove false ranges
|
||||||
ranges.retain(|r| match case_blocks[r.index()].condition {
|
ranges.retain(|r| {
|
||||||
Expr::BoolConstant(false, ..) => {
|
if expressions[r.index()].is_always_false() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
false
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
_ => true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let def_stmt_block = &mut case_blocks[*def_case].statements;
|
if let Some(index) = def_case {
|
||||||
let def_block = mem::take(&mut **def_stmt_block);
|
optimize_expr(&mut expressions[*index].expr, state, false);
|
||||||
**def_stmt_block = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
}
|
||||||
|
|
||||||
// Remove unused block statements
|
// Remove unused block statements
|
||||||
for index in 0..case_blocks.len() {
|
for index in 0..expressions.len() {
|
||||||
if *def_case == index
|
if *def_case == Some(index)
|
||||||
|| cases.values().flat_map(|c| c.iter()).any(|&n| n == index)
|
|| cases.values().flat_map(|c| c.iter()).any(|&n| n == index)
|
||||||
|| ranges.iter().any(|r| r.index() == index)
|
|| ranges.iter().any(|r| r.index() == index)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = &mut case_blocks[index];
|
let b = &mut expressions[index];
|
||||||
|
|
||||||
if !b.statements.is_empty() {
|
if !b.expr.is_unit() {
|
||||||
b.statements = StmtBlock::NONE;
|
b.expr = Expr::Unit(b.expr.position());
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -904,6 +884,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
|
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expr(stmt)
|
||||||
|
Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
match expr.as_mut() {
|
||||||
|
Expr::Stmt(block) if !block.is_empty() => {
|
||||||
|
let mut stmt_block = *mem::take(block);
|
||||||
|
*stmt_block =
|
||||||
|
optimize_stmt_block(mem::take(&mut *stmt_block), state, true, true, false);
|
||||||
|
*stmt = stmt_block.into();
|
||||||
|
}
|
||||||
|
Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
|
||||||
|
_ => unreachable!("`Expr::Stmt`"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Stmt::Expr(expr) => {
|
Stmt::Expr(expr) => {
|
||||||
optimize_expr(expr, state, false);
|
optimize_expr(expr, state, false);
|
||||||
|
|
||||||
@ -948,6 +943,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
match expr {
|
match expr {
|
||||||
// {}
|
// {}
|
||||||
Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
|
Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
|
||||||
|
Expr::Stmt(x) if x.len() == 1 && matches!(x.statements()[0], Stmt::Expr(..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
match x.take_statements().remove(0) {
|
||||||
|
Stmt::Expr(mut e) => {
|
||||||
|
optimize_expr(&mut e, state, false);
|
||||||
|
*expr = *e;
|
||||||
|
}
|
||||||
|
_ => unreachable!("`Expr::Stmt`")
|
||||||
|
}
|
||||||
|
}
|
||||||
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
||||||
Expr::Stmt(x) => {
|
Expr::Stmt(x) => {
|
||||||
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false);
|
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false);
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{any::TypeId, mem};
|
use std::{any::TypeId, borrow::Cow, mem};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::{FLOAT, FLOAT_BYTES};
|
use crate::{FLOAT, FLOAT_BYTES};
|
||||||
@ -104,6 +104,27 @@ pub mod blob_functions {
|
|||||||
pub fn to_array(blob: &mut Blob) -> Array {
|
pub fn to_array(blob: &mut Blob) -> Array {
|
||||||
blob.iter().map(|&ch| (ch as INT).into()).collect()
|
blob.iter().map(|&ch| (ch as INT).into()).collect()
|
||||||
}
|
}
|
||||||
|
/// Convert the BLOB into a string.
|
||||||
|
///
|
||||||
|
/// The byte stream must be valid UTF-8, otherwise an error is raised.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let b = blob(5, 0x42);
|
||||||
|
///
|
||||||
|
/// let x = b.as_string();
|
||||||
|
///
|
||||||
|
/// print(x); // prints "FFFFF"
|
||||||
|
/// ```
|
||||||
|
pub fn as_string(blob: Blob) -> String {
|
||||||
|
let s = String::from_utf8_lossy(&blob);
|
||||||
|
|
||||||
|
match s {
|
||||||
|
Cow::Borrowed(_) => String::from_utf8(blob).unwrap(),
|
||||||
|
Cow::Owned(_) => s.into_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Return the length of the BLOB.
|
/// Return the length of the BLOB.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -200,6 +221,7 @@ pub mod blob_functions {
|
|||||||
///
|
///
|
||||||
/// print(b); // prints "[42]"
|
/// print(b); // prints "[42]"
|
||||||
/// ```
|
/// ```
|
||||||
|
#[rhai_fn(name = "push", name = "append")]
|
||||||
pub fn push(blob: &mut Blob, value: INT) {
|
pub fn push(blob: &mut Blob, value: INT) {
|
||||||
blob.push((value & 0x000000ff) as u8);
|
blob.push((value & 0x000000ff) as u8);
|
||||||
}
|
}
|
||||||
@ -235,13 +257,13 @@ pub mod blob_functions {
|
|||||||
///
|
///
|
||||||
/// print(b); // prints "[424242424268656c 6c6f]"
|
/// print(b); // prints "[424242424268656c 6c6f]"
|
||||||
/// ```
|
/// ```
|
||||||
#[rhai_fn(name = "+=", name = "append")]
|
#[rhai_fn(name = "append")]
|
||||||
pub fn append_str(blob: &mut Blob, string: &str) {
|
pub fn append_str(blob: &mut Blob, string: &str) {
|
||||||
if !string.is_empty() {
|
if !string.is_empty() {
|
||||||
blob.extend(string.as_bytes());
|
blob.extend(string.as_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Add a string (as UTF-8 encoded byte-stream) to the end of the BLOB
|
/// Add a character (as UTF-8 encoded byte-stream) to the end of the BLOB
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -252,38 +274,12 @@ pub mod blob_functions {
|
|||||||
///
|
///
|
||||||
/// print(b); // prints "[424242424221]"
|
/// print(b); // prints "[424242424221]"
|
||||||
/// ```
|
/// ```
|
||||||
#[rhai_fn(name = "+=", name = "append")]
|
#[rhai_fn(name = "append")]
|
||||||
pub fn append_char(blob: &mut Blob, character: char) {
|
pub fn append_char(blob: &mut Blob, character: char) {
|
||||||
let mut buf = [0_u8; 4];
|
let mut buf = [0_u8; 4];
|
||||||
let x = character.encode_utf8(&mut buf);
|
let x = character.encode_utf8(&mut buf);
|
||||||
blob.extend(x.as_bytes());
|
blob.extend(x.as_bytes());
|
||||||
}
|
}
|
||||||
/// Add another BLOB to the end of the BLOB, returning it as a new BLOB.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rhai
|
|
||||||
/// let b1 = blob(5, 0x42);
|
|
||||||
/// let b2 = blob(3, 0x11);
|
|
||||||
///
|
|
||||||
/// print(b1 + b2); // prints "[4242424242111111]"
|
|
||||||
///
|
|
||||||
/// print(b1); // prints "[4242424242]"
|
|
||||||
/// ```
|
|
||||||
#[rhai_fn(name = "+")]
|
|
||||||
pub fn concat(blob1: Blob, blob2: Blob) -> Blob {
|
|
||||||
if !blob2.is_empty() {
|
|
||||||
if blob1.is_empty() {
|
|
||||||
blob2
|
|
||||||
} else {
|
|
||||||
let mut blob = blob1;
|
|
||||||
blob.extend(blob2);
|
|
||||||
blob
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
blob1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Add a byte `value` to the BLOB at a particular `index` position.
|
/// Add a byte `value` to the BLOB at a particular `index` position.
|
||||||
///
|
///
|
||||||
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte).
|
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte).
|
||||||
|
@ -105,7 +105,7 @@ impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
|
|||||||
self.dir = 0;
|
self.dir = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!("`dir` != 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(v)
|
Some(v)
|
||||||
|
@ -4,20 +4,20 @@ use crate::{Module, Shared};
|
|||||||
|
|
||||||
pub(crate) mod arithmetic;
|
pub(crate) mod arithmetic;
|
||||||
pub(crate) mod array_basic;
|
pub(crate) mod array_basic;
|
||||||
mod bit_field;
|
pub(crate) mod bit_field;
|
||||||
pub(crate) mod blob_basic;
|
pub(crate) mod blob_basic;
|
||||||
mod debugging;
|
pub(crate) mod debugging;
|
||||||
mod fn_basic;
|
pub(crate) mod fn_basic;
|
||||||
pub(crate) mod iter_basic;
|
pub(crate) mod iter_basic;
|
||||||
mod lang_core;
|
pub(crate) mod lang_core;
|
||||||
mod logic;
|
pub(crate) mod logic;
|
||||||
mod map_basic;
|
pub(crate) mod map_basic;
|
||||||
mod math_basic;
|
pub(crate) mod math_basic;
|
||||||
mod pkg_core;
|
pub(crate) mod pkg_core;
|
||||||
mod pkg_std;
|
pub(crate) mod pkg_std;
|
||||||
mod string_basic;
|
pub(crate) mod string_basic;
|
||||||
mod string_more;
|
pub(crate) mod string_more;
|
||||||
mod time_basic;
|
pub(crate) mod time_basic;
|
||||||
|
|
||||||
pub use arithmetic::ArithmeticPackage;
|
pub use arithmetic::ArithmeticPackage;
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
@ -6,9 +6,6 @@ use std::{any::TypeId, mem};
|
|||||||
|
|
||||||
use super::string_basic::{print_with_func, FUNC_TO_STRING};
|
use super::string_basic::{print_with_func, FUNC_TO_STRING};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
use crate::Blob;
|
|
||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
|
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
|
||||||
pub MoreStringPackage(lib) {
|
pub MoreStringPackage(lib) {
|
||||||
@ -86,26 +83,50 @@ mod string_functions {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub mod blob_functions {
|
pub mod blob_functions {
|
||||||
|
use crate::Blob;
|
||||||
|
|
||||||
#[rhai_fn(name = "+", pure)]
|
#[rhai_fn(name = "+", pure)]
|
||||||
pub fn add_append_blob(string: &mut ImmutableString, utf8: Blob) -> ImmutableString {
|
pub fn add_append(string: &mut ImmutableString, utf8: Blob) -> ImmutableString {
|
||||||
if utf8.is_empty() {
|
if utf8.is_empty() {
|
||||||
string.clone()
|
return string.clone();
|
||||||
} else if string.is_empty() {
|
}
|
||||||
String::from_utf8_lossy(&utf8).into_owned().into()
|
|
||||||
|
let s = String::from_utf8_lossy(&utf8);
|
||||||
|
|
||||||
|
if string.is_empty() {
|
||||||
|
match s {
|
||||||
|
std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(),
|
||||||
|
std::borrow::Cow::Owned(_) => s.into_owned(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
let mut s = crate::SmartString::from(string.as_str());
|
let mut x = SmartString::from(string.as_str());
|
||||||
s.push_str(&String::from_utf8_lossy(&utf8));
|
x.push_str(s.as_ref());
|
||||||
s.into()
|
x.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "append")]
|
#[rhai_fn(name = "+=", name = "append")]
|
||||||
pub fn add_blob(string: &mut ImmutableString, utf8: Blob) {
|
pub fn add(string: &mut ImmutableString, utf8: Blob) {
|
||||||
let mut s = crate::SmartString::from(string.as_str());
|
let mut s = crate::SmartString::from(string.as_str());
|
||||||
if !utf8.is_empty() {
|
if !utf8.is_empty() {
|
||||||
s.push_str(&String::from_utf8_lossy(&utf8));
|
s.push_str(&String::from_utf8_lossy(&utf8));
|
||||||
*string = s.into();
|
*string = s.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn add_prepend(utf8: Blob, string: ImmutableString) -> ImmutableString {
|
||||||
|
let s = String::from_utf8_lossy(&utf8);
|
||||||
|
let mut s = match s {
|
||||||
|
std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(),
|
||||||
|
std::borrow::Cow::Owned(_) => s.into_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !string.is_empty() {
|
||||||
|
s.push_str(&string);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the length of the string, in number of characters.
|
/// Return the length of the string, in number of characters.
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
use crate::api::events::VarDefInfo;
|
use crate::api::events::VarDefInfo;
|
||||||
use crate::api::options::LangOptions;
|
use crate::api::options::LangOptions;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes,
|
ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCasesCollection,
|
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer,
|
||||||
TryCatchBlock,
|
SwitchCasesCollection, TryCatchBlock,
|
||||||
};
|
};
|
||||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||||
use crate::eval::GlobalRuntimeState;
|
use crate::eval::GlobalRuntimeState;
|
||||||
@ -798,7 +798,7 @@ impl Engine {
|
|||||||
match token {
|
match token {
|
||||||
Token::LeftBracket => ASTFlags::NONE,
|
Token::LeftBracket => ASTFlags::NONE,
|
||||||
Token::QuestionBracket => ASTFlags::NEGATED,
|
Token::QuestionBracket => ASTFlags::NEGATED,
|
||||||
_ => unreachable!(),
|
_ => unreachable!("`[` or `?[`"),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
settings.level_up(),
|
settings.level_up(),
|
||||||
@ -1055,11 +1055,11 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut case_blocks = StaticVec::<ConditionalStmtBlock>::new();
|
let mut expressions = StaticVec::<ConditionalExpr>::new();
|
||||||
let mut cases = BTreeMap::<u64, CaseBlocksList>::new();
|
let mut cases = BTreeMap::<u64, CaseBlocksList>::new();
|
||||||
let mut ranges = StaticVec::<RangeCase>::new();
|
let mut ranges = StaticVec::<RangeCase>::new();
|
||||||
let mut def_stmt_pos = Position::NONE;
|
let mut def_case = None;
|
||||||
let mut def_stmt_index = None;
|
let mut def_case_pos = Position::NONE;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
const MISSING_RBRACE: &str = "to end this switch block";
|
const MISSING_RBRACE: &str = "to end this switch block";
|
||||||
@ -1075,8 +1075,8 @@ impl Engine {
|
|||||||
.into_err(*pos),
|
.into_err(*pos),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(Token::Underscore, pos) if def_stmt_index.is_none() => {
|
(Token::Underscore, pos) if def_case.is_none() => {
|
||||||
def_stmt_pos = *pos;
|
def_case_pos = *pos;
|
||||||
eat_token(input, Token::Underscore);
|
eat_token(input, Token::Underscore);
|
||||||
|
|
||||||
let (if_clause, if_pos) = match_token(input, Token::If);
|
let (if_clause, if_pos) = match_token(input, Token::If);
|
||||||
@ -1087,8 +1087,8 @@ impl Engine {
|
|||||||
|
|
||||||
(Default::default(), Expr::BoolConstant(true, Position::NONE))
|
(Default::default(), Expr::BoolConstant(true, Position::NONE))
|
||||||
}
|
}
|
||||||
_ if def_stmt_index.is_some() => {
|
_ if def_case.is_some() => {
|
||||||
return Err(PERR::WrongSwitchDefaultCase.into_err(def_stmt_pos))
|
return Err(PERR::WrongSwitchDefaultCase.into_err(def_case_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
@ -1142,8 +1142,9 @@ impl Engine {
|
|||||||
let need_comma = !stmt.is_self_terminated();
|
let need_comma = !stmt.is_self_terminated();
|
||||||
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
||||||
|
|
||||||
case_blocks.push((condition, stmt).into());
|
let stmt_block: StmtBlock = stmt.into();
|
||||||
let index = case_blocks.len() - 1;
|
expressions.push((condition, Expr::Stmt(stmt_block.into())).into());
|
||||||
|
let index = expressions.len() - 1;
|
||||||
|
|
||||||
if !case_expr_list.is_empty() {
|
if !case_expr_list.is_empty() {
|
||||||
for expr in case_expr_list {
|
for expr in case_expr_list {
|
||||||
@ -1197,7 +1198,7 @@ impl Engine {
|
|||||||
.or_insert_with(|| [index].into());
|
.or_insert_with(|| [index].into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
def_stmt_index = Some(index);
|
def_case = Some(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
@ -1223,13 +1224,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let def_case = def_stmt_index.unwrap_or_else(|| {
|
|
||||||
case_blocks.push(Default::default());
|
|
||||||
case_blocks.len() - 1
|
|
||||||
});
|
|
||||||
|
|
||||||
let cases = SwitchCasesCollection {
|
let cases = SwitchCasesCollection {
|
||||||
case_blocks,
|
expressions,
|
||||||
cases,
|
cases,
|
||||||
def_case,
|
def_case,
|
||||||
ranges,
|
ranges,
|
||||||
@ -1408,9 +1404,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to parse the following as text
|
// Make sure to parse the following as text
|
||||||
let mut control = state.tokenizer_control.get();
|
state.tokenizer_control.borrow_mut().is_within_text = true;
|
||||||
control.is_within_text = true;
|
|
||||||
state.tokenizer_control.set(control);
|
|
||||||
|
|
||||||
match input.next().expect(NEVER_ENDS) {
|
match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::StringConstant(s), pos) => {
|
(Token::StringConstant(s), pos) => {
|
||||||
@ -1697,7 +1691,7 @@ impl Engine {
|
|||||||
let opt = match token {
|
let opt = match token {
|
||||||
Token::LeftBracket => ASTFlags::NONE,
|
Token::LeftBracket => ASTFlags::NONE,
|
||||||
Token::QuestionBracket => ASTFlags::NEGATED,
|
Token::QuestionBracket => ASTFlags::NEGATED,
|
||||||
_ => unreachable!(),
|
_ => unreachable!("`[` or `?[`"),
|
||||||
};
|
};
|
||||||
self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())?
|
self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())?
|
||||||
}
|
}
|
||||||
@ -1721,7 +1715,7 @@ impl Engine {
|
|||||||
let op_flags = match op {
|
let op_flags = match op {
|
||||||
Token::Period => ASTFlags::NONE,
|
Token::Period => ASTFlags::NONE,
|
||||||
Token::Elvis => ASTFlags::NEGATED,
|
Token::Elvis => ASTFlags::NEGATED,
|
||||||
_ => unreachable!(),
|
_ => unreachable!("`.` or `?.`"),
|
||||||
};
|
};
|
||||||
Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)?
|
Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)?
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use crate::{Engine, Identifier, LexError, SmartString, StaticVec, INT, UNSIGNED_
|
|||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::Cell,
|
cell::RefCell,
|
||||||
char, fmt,
|
char, fmt,
|
||||||
iter::{FusedIterator, Peekable},
|
iter::{FusedIterator, Peekable},
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
@ -20,11 +20,14 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// _(internals)_ A type containing commands to control the tokenizer.
|
/// _(internals)_ A type containing commands to control the tokenizer.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct TokenizerControlBlock {
|
pub struct TokenizerControlBlock {
|
||||||
/// Is the current tokenizer position within an interpolated text string?
|
/// Is the current tokenizer position within an interpolated text string?
|
||||||
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
|
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
|
||||||
pub is_within_text: bool,
|
pub is_within_text: bool,
|
||||||
|
/// Collection of global comments.
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
pub global_comments: Vec<SmartString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenizerControlBlock {
|
impl TokenizerControlBlock {
|
||||||
@ -34,12 +37,14 @@ impl TokenizerControlBlock {
|
|||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
is_within_text: false,
|
is_within_text: false,
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
global_comments: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ A shared object that allows control of the tokenizer from outside.
|
/// _(internals)_ A shared object that allows control of the tokenizer from outside.
|
||||||
pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
|
pub type TokenizerControl = Rc<RefCell<TokenizerControlBlock>>;
|
||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
|
|
||||||
@ -248,7 +253,7 @@ impl fmt::Debug for Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_position")]
|
#[cfg(feature = "no_position")]
|
||||||
unreachable!();
|
unreachable!("no position");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1098,12 +1103,14 @@ impl From<Token> for String {
|
|||||||
|
|
||||||
/// _(internals)_ State of the tokenizer.
|
/// _(internals)_ State of the tokenizer.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct TokenizeState {
|
pub struct TokenizeState {
|
||||||
/// Maximum length of a string.
|
/// Maximum length of a string.
|
||||||
pub max_string_size: Option<NonZeroUsize>,
|
pub max_string_size: Option<NonZeroUsize>,
|
||||||
/// Can the next token be a unary operator?
|
/// Can the next token be a unary operator?
|
||||||
pub next_token_cannot_be_unary: bool,
|
pub next_token_cannot_be_unary: bool,
|
||||||
|
/// Shared object to allow controlling the tokenizer externally.
|
||||||
|
pub tokenizer_control: TokenizerControl,
|
||||||
/// Is the tokenizer currently inside a block comment?
|
/// Is the tokenizer currently inside a block comment?
|
||||||
pub comment_level: usize,
|
pub comment_level: usize,
|
||||||
/// Include comments?
|
/// Include comments?
|
||||||
@ -1866,6 +1873,11 @@ fn get_next_token_inner(
|
|||||||
_ => Some("///".into()),
|
_ => Some("///".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
Some('!') => {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
Some("//!".into())
|
||||||
|
}
|
||||||
_ if state.include_comments => Some("//".into()),
|
_ if state.include_comments => Some("//".into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
@ -1890,7 +1902,15 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
return Some((Token::Comment(comment), start_pos));
|
match comment {
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
_ if comment.starts_with("//!") => state
|
||||||
|
.tokenizer_control
|
||||||
|
.borrow_mut()
|
||||||
|
.global_comments
|
||||||
|
.push(comment),
|
||||||
|
_ => return Some((Token::Comment(comment), start_pos)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
('/', '*') => {
|
('/', '*') => {
|
||||||
@ -2300,8 +2320,6 @@ pub struct TokenIterator<'a> {
|
|||||||
pub state: TokenizeState,
|
pub state: TokenizeState,
|
||||||
/// Current position.
|
/// Current position.
|
||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
/// Shared object to allow controlling the tokenizer externally.
|
|
||||||
pub tokenizer_control: TokenizerControl,
|
|
||||||
/// Input character stream.
|
/// Input character stream.
|
||||||
pub stream: MultiInputsStream<'a>,
|
pub stream: MultiInputsStream<'a>,
|
||||||
/// A processor function that maps a token to another.
|
/// A processor function that maps a token to another.
|
||||||
@ -2312,14 +2330,15 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||||||
type Item = (Token, Position);
|
type Item = (Token, Position);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut control = self.tokenizer_control.get();
|
{
|
||||||
|
let control = &mut *self.state.tokenizer_control.borrow_mut();
|
||||||
|
|
||||||
if control.is_within_text {
|
if control.is_within_text {
|
||||||
// Switch to text mode terminated by back-tick
|
// Switch to text mode terminated by back-tick
|
||||||
self.state.is_within_text_terminated_by = Some('`');
|
self.state.is_within_text_terminated_by = Some('`');
|
||||||
// Reset it
|
// Reset it
|
||||||
control.is_within_text = false;
|
control.is_within_text = false;
|
||||||
self.tokenizer_control.set(control);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
|
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
|
||||||
@ -2450,7 +2469,7 @@ impl Engine {
|
|||||||
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
|
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
|
||||||
token_mapper: Option<&'a OnParseTokenCallback>,
|
token_mapper: Option<&'a OnParseTokenCallback>,
|
||||||
) -> (TokenIterator<'a>, TokenizerControl) {
|
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||||
let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into();
|
let buffer: TokenizerControl = RefCell::new(TokenizerControlBlock::new()).into();
|
||||||
let buffer2 = buffer.clone();
|
let buffer2 = buffer.clone();
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -2462,12 +2481,12 @@ impl Engine {
|
|||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
max_string_size: None,
|
max_string_size: None,
|
||||||
next_token_cannot_be_unary: false,
|
next_token_cannot_be_unary: false,
|
||||||
|
tokenizer_control: buffer,
|
||||||
comment_level: 0,
|
comment_level: 0,
|
||||||
include_comments: false,
|
include_comments: false,
|
||||||
is_within_text_terminated_by: None,
|
is_within_text_terminated_by: None,
|
||||||
},
|
},
|
||||||
pos: Position::new(1, 0),
|
pos: Position::new(1, 0),
|
||||||
tokenizer_control: buffer,
|
|
||||||
stream: MultiInputsStream {
|
stream: MultiInputsStream {
|
||||||
buf: None,
|
buf: None,
|
||||||
streams: input
|
streams: input
|
||||||
|
@ -68,6 +68,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
@ -79,30 +80,39 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
|
|
||||||
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(123 @ 1:53)] }");
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
r#"AST { source: "", doc: "", resolver: None, body: [Expr(123 @ 1:53)] }"#
|
||||||
|
);
|
||||||
|
|
||||||
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", ast),
|
format!("{:?}", ast),
|
||||||
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
|
r#"AST { source: "", doc: "", resolver: None, body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert_eq!(format!("{:?}", ast), "AST { body: [] }");
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
r#"AST { source: "", doc: "", resolver: None, body: [] }"#
|
||||||
|
);
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let ast = engine.compile("abs(-42)")?;
|
let ast = engine.compile("abs(-42)")?;
|
||||||
|
|
||||||
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
|
||||||
|
);
|
||||||
|
|
||||||
let ast = engine.compile("NUMBER")?;
|
let ast = engine.compile("NUMBER")?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", ast),
|
format!("{:?}", ast),
|
||||||
"AST { body: [Expr(Variable(NUMBER) @ 1:1)] }"
|
r#"AST { source: "", doc: "", resolver: None, body: [Expr(Variable(NUMBER) @ 1:1)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
@ -112,7 +122,10 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile("NUMBER")?;
|
let ast = engine.compile("NUMBER")?;
|
||||||
|
|
||||||
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
r#"AST { source: "", doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user