commit
0744884bf3
@ -11,7 +11,7 @@ edition = "2018"
|
|||||||
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://rhaiscript.github.io/book"
|
homepage = "https://rhaiscript.github.io/book"
|
||||||
repository = "https://github.com/rhaiscript/rhai"
|
repository = "https://github.com/rhaiscript"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
include = [
|
include = [
|
||||||
|
@ -70,15 +70,13 @@ Documentation
|
|||||||
|
|
||||||
See [The Rhai Book](https://rhaiscript.github.io/book) for details on the Rhai scripting engine and language.
|
See [The Rhai Book](https://rhaiscript.github.io/book) for details on the Rhai scripting engine and language.
|
||||||
|
|
||||||
To build _The Book_, first install [`mdbook`](https://github.com/rust-lang/mdBook)
|
|
||||||
and [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating).
|
|
||||||
Running `mdbook build` builds it.
|
|
||||||
|
|
||||||
|
|
||||||
Playground
|
Playground
|
||||||
----------
|
----------
|
||||||
|
|
||||||
An [Online Playground](https://rhaiscript.github.io/playground) is available with syntax-highlighting editor.
|
An [Online Playground](https://rhaiscript.github.io/playground) is available with
|
||||||
|
syntax-highlighting editor, powered by WebAssembly.
|
||||||
|
|
||||||
Scripts can be evaluated directly from the editor.
|
Scripts can be evaluated directly from the editor.
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,9 @@ Version 0.19.10
|
|||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
* `no_std` feature now compiles correctly (bug introduced in `0.19.9`).
|
||||||
* Bug in `FileModuleResolver::clear_cache_for_path` path mapping fixed.
|
* Bug in `FileModuleResolver::clear_cache_for_path` path mapping fixed.
|
||||||
|
* Some optimizer fringe cases are fixed - related to constants propagation when the evil `eval` is present.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
17
src/ast.rs
17
src/ast.rs
@ -1058,29 +1058,20 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(INTERNALS)_ A custom syntax definition.
|
/// _(INTERNALS)_ A custom syntax expression.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// # WARNING
|
/// # WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct CustomExpr {
|
pub struct CustomExpr {
|
||||||
/// List of keywords.
|
/// List of keywords.
|
||||||
pub keywords: StaticVec<Expr>,
|
pub keywords: StaticVec<Expr>,
|
||||||
/// List of tokens actually parsed.
|
/// List of tokens actually parsed.
|
||||||
pub tokens: Vec<ImmutableString>,
|
pub tokens: Vec<ImmutableString>,
|
||||||
}
|
/// Delta number of variables in the scope.
|
||||||
|
pub scope_delta: isize,
|
||||||
impl fmt::Debug for CustomExpr {
|
|
||||||
#[inline(always)]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("CustomExpr { keywords:")?;
|
|
||||||
fmt::Debug::fmt(&self.keywords, f)?;
|
|
||||||
f.write_str(", tokens:")?;
|
|
||||||
fmt::Debug::fmt(&self.tokens, f)?;
|
|
||||||
f.write_str("}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(INTERNALS)_ A binary expression.
|
/// _(INTERNALS)_ A binary expression.
|
||||||
|
@ -58,6 +58,8 @@ struct State<'a> {
|
|||||||
changed: bool,
|
changed: bool,
|
||||||
/// Collection of constants to use for eager function evaluations.
|
/// Collection of constants to use for eager function evaluations.
|
||||||
variables: Vec<(String, AccessMode, Expr)>,
|
variables: Vec<(String, AccessMode, Expr)>,
|
||||||
|
/// Activate constants propagation?
|
||||||
|
propagate_constants: bool,
|
||||||
/// An [`Engine`] instance for eager function evaluation.
|
/// An [`Engine`] instance for eager function evaluation.
|
||||||
engine: &'a Engine,
|
engine: &'a Engine,
|
||||||
/// Collection of sub-modules.
|
/// Collection of sub-modules.
|
||||||
@ -75,6 +77,7 @@ impl<'a> State<'a> {
|
|||||||
Self {
|
Self {
|
||||||
changed: false,
|
changed: false,
|
||||||
variables: vec![],
|
variables: vec![],
|
||||||
|
propagate_constants: true,
|
||||||
engine,
|
engine,
|
||||||
mods: (&engine.global_sub_modules).into(),
|
mods: (&engine.global_sub_modules).into(),
|
||||||
lib,
|
lib,
|
||||||
@ -109,6 +112,10 @@ impl<'a> State<'a> {
|
|||||||
/// Look up a constant from the list.
|
/// Look up a constant from the list.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||||
|
if !self.propagate_constants {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
for (n, access, expr) in self.variables.iter().rev() {
|
for (n, access, expr) in self.variables.iter().rev() {
|
||||||
if n == name {
|
if n == name {
|
||||||
return if access.is_read_only() {
|
return if access.is_read_only() {
|
||||||
@ -160,18 +167,28 @@ fn optimize_stmt_block(
|
|||||||
) -> Stmt {
|
) -> Stmt {
|
||||||
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
||||||
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
|
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
|
||||||
|
let orig_propagate_constants = state.propagate_constants;
|
||||||
|
|
||||||
// Optimize each statement in the block
|
// Optimize each statement in the block
|
||||||
statements.iter_mut().for_each(|stmt| match stmt {
|
statements.iter_mut().for_each(|stmt| {
|
||||||
|
match stmt {
|
||||||
// Add constant literals into the state
|
// Add constant literals into the state
|
||||||
Stmt::Const(var_def, Some(expr), _, _) if expr.is_constant() => {
|
Stmt::Const(var_def, Some(value_expr), _, _) => {
|
||||||
state.push_var(&var_def.name, AccessMode::ReadOnly, mem::take(expr));
|
optimize_expr(value_expr, state);
|
||||||
|
|
||||||
|
if value_expr.is_constant() {
|
||||||
|
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Const(var_def, None, _, _) => {
|
Stmt::Const(var_def, None, _, _) => {
|
||||||
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
|
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
|
||||||
}
|
}
|
||||||
// Add variables into the state
|
// Add variables into the state
|
||||||
Stmt::Let(var_def, _, _, _) => {
|
Stmt::Let(var_def, expr, _, _) => {
|
||||||
|
if let Some(value_expr) = expr {
|
||||||
|
optimize_expr(value_expr, state);
|
||||||
|
}
|
||||||
|
|
||||||
state.push_var(
|
state.push_var(
|
||||||
&var_def.name,
|
&var_def.name,
|
||||||
AccessMode::ReadWrite,
|
AccessMode::ReadWrite,
|
||||||
@ -180,6 +197,7 @@ fn optimize_stmt_block(
|
|||||||
}
|
}
|
||||||
// Optimize the statement
|
// Optimize the statement
|
||||||
_ => optimize_stmt(stmt, state, preserve_result),
|
_ => optimize_stmt(stmt, state, preserve_result),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove all raw expression statements that are pure except for the very last statement
|
// Remove all raw expression statements that are pure except for the very last statement
|
||||||
@ -251,6 +269,8 @@ fn optimize_stmt_block(
|
|||||||
// Pop the stack and remove all the local constants
|
// Pop the stack and remove all the local constants
|
||||||
state.restore_var(orig_constants_len);
|
state.restore_var(orig_constants_len);
|
||||||
|
|
||||||
|
state.propagate_constants = orig_propagate_constants;
|
||||||
|
|
||||||
match &statements[..] {
|
match &statements[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
@ -637,6 +657,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// eval!
|
||||||
|
Expr::FnCall(x, _) if x.name == KEYWORD_EVAL => {
|
||||||
|
state.propagate_constants = false;
|
||||||
|
}
|
||||||
// Do not call some special keywords
|
// Do not call some special keywords
|
||||||
Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
|
Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
|
||||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
||||||
@ -726,7 +750,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom syntax
|
// Custom syntax
|
||||||
Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)),
|
Expr::Custom(x, _) => {
|
||||||
|
if x.scope_delta != 0 {
|
||||||
|
state.propagate_constants = false;
|
||||||
|
}
|
||||||
|
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state));
|
||||||
|
}
|
||||||
|
|
||||||
// All other expressions - skip
|
// All other expressions - skip
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -790,7 +819,11 @@ fn optimize_top_level(
|
|||||||
Stmt::Const(var_def, None, _, _) => {
|
Stmt::Const(var_def, None, _, _) => {
|
||||||
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
|
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
|
||||||
}
|
}
|
||||||
Stmt::Let(var_def, _, _, _) => {
|
Stmt::Let(var_def, expr, _, _) => {
|
||||||
|
if let Some(value_expr) = expr {
|
||||||
|
optimize_expr(value_expr, &mut state);
|
||||||
|
}
|
||||||
|
|
||||||
state.push_var(
|
state.push_var(
|
||||||
&var_def.name,
|
&var_def.name,
|
||||||
AccessMode::ReadWrite,
|
AccessMode::ReadWrite,
|
||||||
|
@ -1995,7 +1995,14 @@ fn parse_custom_syntax(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Expr::Custom(Box::new(CustomExpr { keywords, tokens }), pos))
|
Ok(Expr::Custom(
|
||||||
|
Box::new(CustomExpr {
|
||||||
|
keywords,
|
||||||
|
tokens,
|
||||||
|
scope_delta: syntax.scope_delta,
|
||||||
|
}),
|
||||||
|
pos,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression.
|
/// Parse an expression.
|
||||||
|
@ -101,6 +101,14 @@ impl Engine {
|
|||||||
/// * `keywords` holds a slice of strings that define the custom syntax.
|
/// * `keywords` holds a slice of strings that define the custom syntax.
|
||||||
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
|
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
|
||||||
/// * `func` is the implementation function.
|
/// * `func` is the implementation function.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// If `new_vars` is positive, then a number of new variables are expected to be pushed into the
|
||||||
|
/// current [`Scope`][crate::Scope].
|
||||||
|
///
|
||||||
|
/// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the
|
||||||
|
/// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables.
|
||||||
pub fn register_custom_syntax<S: AsRef<str> + Into<ImmutableString>>(
|
pub fn register_custom_syntax<S: AsRef<str> + Into<ImmutableString>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keywords: impl AsRef<[S]>,
|
keywords: impl AsRef<[S]>,
|
||||||
|
@ -55,7 +55,7 @@ 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!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident("DECISION" @ 1:9), Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
|
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident("DECISION" @ 1:9), Some(BoolConstant(false, 1:20)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user