Merge pull request #331 from schungx/master

Ready 0.19.10.
This commit is contained in:
Stephen Chung 2021-01-14 19:26:41 +08:00 committed by GitHub
commit 0744884bf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 40 deletions

View File

@ -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 = [

View File

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

View File

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

View File

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

View File

@ -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,26 +167,37 @@ 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| {
// Add constant literals into the state match stmt {
Stmt::Const(var_def, Some(expr), _, _) if expr.is_constant() => { // Add constant literals into the state
state.push_var(&var_def.name, AccessMode::ReadOnly, mem::take(expr)); Stmt::Const(var_def, Some(value_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, _, _) => {
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
}
// Add variables into the state
Stmt::Let(var_def, expr, _, _) => {
if let Some(value_expr) = expr {
optimize_expr(value_expr, state);
}
state.push_var(
&var_def.name,
AccessMode::ReadWrite,
Expr::Unit(var_def.pos),
);
}
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
} }
Stmt::Const(var_def, None, _, _) => {
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
}
// Add variables into the state
Stmt::Let(var_def, _, _, _) => {
state.push_var(
&var_def.name,
AccessMode::ReadWrite,
Expr::Unit(var_def.pos),
);
}
// Optimize the statement
_ => 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,

View File

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

View File

@ -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]>,

View File

@ -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 }")?;