diff --git a/Cargo.toml b/Cargo.toml index 8d3234c3..cb68a5e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" homepage = "https://rhaiscript.github.io/book" -repository = "https://github.com/rhaiscript/rhai" +repository = "https://github.com/rhaiscript" readme = "README.md" license = "MIT OR Apache-2.0" include = [ diff --git a/README.md b/README.md index caf5be6c..82aa7dec 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,13 @@ Documentation 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 ---------- -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. diff --git a/RELEASES.md b/RELEASES.md index 582f7273..1240a3cf 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,7 +7,9 @@ Version 0.19.10 Bug fixes --------- +* `no_std` feature now compiles correctly (bug introduced in `0.19.9`). * 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 ---------------- diff --git a/src/ast.rs b/src/ast.rs index e374c83f..12bf72ec 100644 --- a/src/ast.rs +++ b/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. /// /// # WARNING /// /// This type is volatile and may change. -#[derive(Clone, Hash)] +#[derive(Debug, Clone, Hash)] pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, /// List of tokens actually parsed. pub tokens: Vec, -} - -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("}") - } + /// Delta number of variables in the scope. + pub scope_delta: isize, } /// _(INTERNALS)_ A binary expression. diff --git a/src/optimize.rs b/src/optimize.rs index 729d43e8..2cc27819 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -58,6 +58,8 @@ struct State<'a> { changed: bool, /// Collection of constants to use for eager function evaluations. variables: Vec<(String, AccessMode, Expr)>, + /// Activate constants propagation? + propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, /// Collection of sub-modules. @@ -75,6 +77,7 @@ impl<'a> State<'a> { Self { changed: false, variables: vec![], + propagate_constants: true, engine, mods: (&engine.global_sub_modules).into(), lib, @@ -109,6 +112,10 @@ impl<'a> State<'a> { /// Look up a constant from the list. #[inline] pub fn find_constant(&self, name: &str) -> Option<&Expr> { + if !self.propagate_constants { + return None; + } + for (n, access, expr) in self.variables.iter().rev() { if n == name { return if access.is_read_only() { @@ -160,26 +167,37 @@ fn optimize_stmt_block( ) -> Stmt { 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_propagate_constants = state.propagate_constants; // Optimize each statement in the block - statements.iter_mut().for_each(|stmt| match stmt { - // Add constant literals into the state - Stmt::Const(var_def, Some(expr), _, _) if expr.is_constant() => { - state.push_var(&var_def.name, AccessMode::ReadOnly, mem::take(expr)); + statements.iter_mut().for_each(|stmt| { + match stmt { + // Add constant literals into the state + 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 @@ -251,6 +269,8 @@ fn optimize_stmt_block( // Pop the stack and remove all the local constants state.restore_var(orig_constants_len); + state.propagate_constants = orig_propagate_constants; + match &statements[..] { // 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); } }, + // eval! + Expr::FnCall(x, _) if x.name == KEYWORD_EVAL => { + state.propagate_constants = false; + } // Do not call some special keywords Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => { 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 - 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 _ => (), @@ -790,7 +819,11 @@ fn optimize_top_level( Stmt::Const(var_def, None, _, _) => { 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( &var_def.name, AccessMode::ReadWrite, diff --git a/src/parser.rs b/src/parser.rs index 4c462f66..fbf17165 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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. diff --git a/src/syntax.rs b/src/syntax.rs index 65b6513c..78ce64f7 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -101,6 +101,14 @@ impl Engine { /// * `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). /// * `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 + Into>( &mut self, keywords: impl AsRef<[S]>, diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 94d77d3c..d062ec5f 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -55,7 +55,7 @@ fn test_optimizer_parse() -> Result<(), Box> { 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 }")?;