From ef8d428f42164c537122cb6f7630d95132d31f05 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 18 Mar 2020 10:36:50 +0800 Subject: [PATCH] Add code comments. --- src/any.rs | 10 +-- src/builtin.rs | 74 +++++++++++---- src/optimize.rs | 235 ++++++++++++++++++++++++++++-------------------- src/parser.rs | 218 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 385 insertions(+), 152 deletions(-) diff --git a/src/any.rs b/src/any.rs index 96fe099b..5f22231f 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,7 +1,7 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. use std::{ - any::{type_name, TypeId}, + any::{type_name, Any as StdAny, TypeId}, fmt, }; @@ -12,7 +12,7 @@ pub type Variant = dyn Any; pub type Dynamic = Box; /// A trait covering any type. -pub trait Any: std::any::Any { +pub trait Any: StdAny { /// Get the `TypeId` of this type. fn type_id(&self) -> TypeId; @@ -22,12 +22,12 @@ pub trait Any: std::any::Any { /// Convert into `Dynamic`. fn into_dynamic(&self) -> Dynamic; - /// This type may only be implemented by `rhai`. + /// This trait may only be implemented by `rhai`. #[doc(hidden)] fn _closed(&self) -> _Private; } -impl Any for T { +impl Any for T { fn type_id(&self) -> TypeId { TypeId::of::() } @@ -90,7 +90,7 @@ pub trait AnyExt: Sized { /// Get a copy of a `Dynamic` value as a specific type. fn downcast(self) -> Result, Self>; - /// This type may only be implemented by `rhai`. + /// This trait may only be implemented by `rhai`. #[doc(hidden)] fn _closed(&self) -> _Private; } diff --git a/src/builtin.rs b/src/builtin.rs index b85cf306..9679ddf5 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -5,7 +5,7 @@ use crate::any::Any; #[cfg(not(feature = "no_index"))] use crate::engine::Array; use crate::engine::Engine; -use crate::fn_register::{RegisterFn, RegisterResultFn}; +use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; use crate::parser::{Position, INT}; use crate::result::EvalAltResult; @@ -52,6 +52,7 @@ macro_rules! reg_op_result1 { impl Engine<'_> { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { + /// Checked add #[cfg(not(feature = "unchecked"))] fn add(x: T, y: T) -> Result { x.checked_add(&y).ok_or_else(|| { @@ -61,6 +62,7 @@ impl Engine<'_> { ) }) } + /// Checked subtract #[cfg(not(feature = "unchecked"))] fn sub(x: T, y: T) -> Result { x.checked_sub(&y).ok_or_else(|| { @@ -70,6 +72,7 @@ impl Engine<'_> { ) }) } + /// Checked multiply #[cfg(not(feature = "unchecked"))] fn mul(x: T, y: T) -> Result { x.checked_mul(&y).ok_or_else(|| { @@ -79,11 +82,13 @@ impl Engine<'_> { ) }) } + /// Checked divide #[cfg(not(feature = "unchecked"))] fn div(x: T, y: T) -> Result where T: Display + CheckedDiv + PartialEq + Zero, { + // Detect division by zero if y == T::zero() { return Err(EvalAltResult::ErrorArithmetic( format!("Division by zero: {} / {}", x, y), @@ -98,6 +103,7 @@ impl Engine<'_> { ) }) } + /// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX #[cfg(not(feature = "unchecked"))] fn neg(x: T) -> Result { x.checked_neg().ok_or_else(|| { @@ -107,6 +113,7 @@ impl Engine<'_> { ) }) } + /// Checked absolute #[cfg(not(feature = "unchecked"))] fn abs(x: T) -> Result { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics @@ -122,26 +129,32 @@ impl Engine<'_> { }) } } + /// Unchecked add - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> ::Output { x + y } + /// Unchecked subtract - may panic on underflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> ::Output { x - y } + /// Unchecked multiply - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> ::Output { x * y } + /// Unchecked divide - may panic when dividing by zero #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> ::Output { x / y } + /// Unchecked negative - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> ::Output { -x } + /// Unchecked absolute - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> ::Output where @@ -154,6 +167,9 @@ impl Engine<'_> { x.into() } } + + // Comparison operators + fn lt(x: T, y: T) -> bool { x < y } @@ -172,6 +188,9 @@ impl Engine<'_> { fn ne(x: T, y: T) -> bool { x != y } + + // Logic operators + fn and(x: bool, y: bool) -> bool { x && y } @@ -181,6 +200,9 @@ impl Engine<'_> { fn not(x: bool) -> bool { !x } + + // Bit operators + fn binary_and(x: T, y: T) -> ::Output { x & y } @@ -190,8 +212,11 @@ impl Engine<'_> { fn binary_xor(x: T, y: T) -> ::Output { x ^ y } + + /// Checked left-shift #[cfg(not(feature = "unchecked"))] fn shl(x: T, y: INT) -> Result { + // Cannot shift by a negative number of bits if y < 0 { return Err(EvalAltResult::ErrorArithmetic( format!("Left-shift by a negative number: {} << {}", x, y), @@ -201,13 +226,15 @@ impl Engine<'_> { CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Left-shift overflow: {} << {}", x, y), + format!("Left-shift by too many bits: {} << {}", x, y), Position::none(), ) }) } + /// Checked right-shift #[cfg(not(feature = "unchecked"))] fn shr(x: T, y: INT) -> Result { + // Cannot shift by a negative number of bits if y < 0 { return Err(EvalAltResult::ErrorArithmetic( format!("Right-shift by a negative number: {} >> {}", x, y), @@ -217,44 +244,49 @@ impl Engine<'_> { CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Right-shift overflow: {} % {}", x, y), + format!("Right-shift by too many bits: {} % {}", x, y), Position::none(), ) }) } + /// Unchecked left-shift - may panic if shifting by a negative number of bits #[cfg(feature = "unchecked")] fn shl_u>(x: T, y: T) -> >::Output { x.shl(y) } + /// Unchecked right-shift - may panic if shifting by a negative number of bits #[cfg(feature = "unchecked")] fn shr_u>(x: T, y: T) -> >::Output { x.shr(y) } + /// Checked modulo #[cfg(not(feature = "unchecked"))] fn modulo(x: T, y: T) -> Result { x.checked_rem(&y).ok_or_else(|| { EvalAltResult::ErrorArithmetic( - format!("Modulo division overflow: {} % {}", x, y), + format!("Modulo division by zero or overflow: {} % {}", x, y), Position::none(), ) }) } + /// Unchecked modulo - may panic if dividing by zero #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> ::Output { x % y } + /// Checked power #[cfg(not(feature = "unchecked"))] - fn pow_i_i_u(x: INT, y: INT) -> Result { + fn pow_i_i(x: INT, y: INT) -> Result { #[cfg(not(feature = "only_i32"))] { if y > (u32::MAX as INT) { Err(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + format!("Integer raised to too large an index: {} ~ {}", x, y), Position::none(), )) } else if y < 0 { Err(EvalAltResult::ErrorArithmetic( - format!("Power underflow: {} ~ {}", x, y), + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), )) } else { @@ -271,7 +303,7 @@ impl Engine<'_> { { if y < 0 { Err(EvalAltResult::ErrorArithmetic( - format!("Power underflow: {} ~ {}", x, y), + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), )) } else { @@ -284,29 +316,34 @@ impl Engine<'_> { } } } + /// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) #[cfg(feature = "unchecked")] - fn pow_i_i(x: INT, y: INT) -> INT { + fn pow_i_i_u(x: INT, y: INT) -> INT { x.pow(y as u32) } + /// Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { x.powf(y) } + /// Checked power #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_float"))] - fn pow_f_i_u(x: FLOAT, y: INT) -> Result { + fn pow_f_i(x: FLOAT, y: INT) -> Result { + // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { return Err(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + format!("Number raised to too large an index: {} ~ {}", x, y), Position::none(), )); } Ok(x.powi(y as i32)) } + /// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) #[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] - fn pow_f_i(x: FLOAT, y: INT) -> FLOAT { + fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT { x.powi(y as i32) } @@ -390,8 +427,10 @@ impl Engine<'_> { } } + // `&&` and `||` are treated specially as they short-circuit //reg_op!(self, "||", or, bool); //reg_op!(self, "&&", and, bool); + reg_op!(self, "|", or, bool); reg_op!(self, "&", and, bool); @@ -445,18 +484,18 @@ impl Engine<'_> { #[cfg(not(feature = "unchecked"))] { - self.register_result_fn("~", pow_i_i_u); + self.register_result_fn("~", pow_i_i); #[cfg(not(feature = "no_float"))] - self.register_result_fn("~", pow_f_i_u); + self.register_result_fn("~", pow_f_i); } #[cfg(feature = "unchecked")] { - self.register_fn("~", pow_i_i); + self.register_fn("~", pow_i_i_u); #[cfg(not(feature = "no_float"))] - self.register_fn("~", pow_f_i); + self.register_fn("~", pow_f_i_u); } { @@ -625,9 +664,6 @@ macro_rules! reg_fn2y { impl Engine<'_> { #[cfg(not(feature = "no_stdlib"))] pub(crate) fn register_stdlib(&mut self) { - #[cfg(not(feature = "no_index"))] - use crate::fn_register::RegisterDynamicFn; - #[cfg(not(feature = "no_float"))] { // Advanced math functions diff --git a/src/optimize.rs b/src/optimize.rs index 7f201df5..30d89abf 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -9,50 +9,54 @@ use crate::scope::{Scope, ScopeEntry, VariableType}; use std::sync::Arc; -/// Level of optimization performed +/// Level of optimization performed. #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum OptimizationLevel { - /// No optimization performed + /// No optimization performed. None, - /// Only perform simple optimizations without evaluating functions + /// Only perform simple optimizations without evaluating functions. Simple, /// Full optimizations performed, including evaluating functions. - /// Take care that this may cause side effects. + /// Take care that this may cause side effects as it essentially assumes that all functions are pure. Full, } +/// Mutable state throughout an optimization pass. struct State<'a> { + /// Has the AST been changed during this pass? changed: bool, + /// Collection of constants to use for eager function evaluations. constants: Vec<(String, Expr)>, - engine: Option<&'a Engine<'a>>, + /// An `Engine` instance for eager function evaluation. + engine: &'a Engine<'a>, } impl State<'_> { - pub fn new() -> Self { - State { - changed: false, - constants: vec![], - engine: None, - } - } + /// Reset the state from dirty to clean. pub fn reset(&mut self) { self.changed = false; } + /// Set the AST state to be dirty (i.e. changed). pub fn set_dirty(&mut self) { self.changed = true; } + /// Is the AST dirty (i.e. changed)? pub fn is_dirty(&self) -> bool { self.changed } + /// Does a constant exist? pub fn contains_constant(&self, name: &str) -> bool { self.constants.iter().any(|(n, _)| n == name) } + /// Prune the list of constants back to a specified size. pub fn restore_constants(&mut self, len: usize) { self.constants.truncate(len) } + /// Add a new constant to the list. pub fn push_constant(&mut self, name: &str, value: Expr) { self.constants.push((name.to_string(), value)) } + /// Look up a constant from the list. pub fn find_constant(&self, name: &str) -> Option<&Expr> { for (n, expr) in self.constants.iter().rev() { if n == name { @@ -64,60 +68,68 @@ impl State<'_> { } } +/// Optimize a statement. fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { match stmt { - Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => { + // if expr { Noop } + Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => { state.set_dirty(); let pos = expr.position(); let expr = optimize_expr(*expr, state); - if matches!(expr, Expr::False(_) | Expr::True(_)) { - Stmt::Noop(stmt1.position()) + if preserve_result { + // -> { expr, Noop } + Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos) } else { - let stmt = Stmt::Expr(Box::new(expr)); - - if preserve_result { - Stmt::Block(vec![stmt, *stmt1], pos) - } else { - stmt - } + // -> expr + Stmt::Expr(Box::new(expr)) } } - - Stmt::IfElse(expr, stmt1, None) => match *expr { + // if expr { if_block } + Stmt::IfElse(expr, if_block, None) => match *expr { + // if false { if_block } -> Noop Expr::False(pos) => { state.set_dirty(); Stmt::Noop(pos) } - Expr::True(_) => optimize_stmt(*stmt1, state, true), + // if true { if_block } -> if_block + Expr::True(_) => optimize_stmt(*if_block, state, true), + // if expr { if_block } expr => Stmt::IfElse( Box::new(optimize_expr(expr, state)), - Box::new(optimize_stmt(*stmt1, state, true)), + Box::new(optimize_stmt(*if_block, state, true)), None, ), }, - - Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr { - Expr::False(_) => optimize_stmt(*stmt2, state, true), - Expr::True(_) => optimize_stmt(*stmt1, state, true), + // if expr { if_block } else { else_block } + Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr { + // if false { if_block } else { else_block } -> else_block + Expr::False(_) => optimize_stmt(*else_block, state, true), + // if true { if_block } else { else_block } -> if_block + Expr::True(_) => optimize_stmt(*if_block, state, true), + // if expr { if_block } else { else_block } expr => Stmt::IfElse( Box::new(optimize_expr(expr, state)), - Box::new(optimize_stmt(*stmt1, state, true)), - match optimize_stmt(*stmt2, state, true) { - stmt if stmt.is_noop() => None, + Box::new(optimize_stmt(*if_block, state, true)), + match optimize_stmt(*else_block, state, true) { + stmt if matches!(stmt, Stmt::Noop(_)) => None, // Noop -> no else block stmt => Some(Box::new(stmt)), }, ), }, - - Stmt::While(expr, stmt) => match *expr { + // while expr { block } + Stmt::While(expr, block) => match *expr { + // while false { block } -> Noop Expr::False(pos) => { state.set_dirty(); Stmt::Noop(pos) } - Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), - expr => match optimize_stmt(*stmt, state, false) { + // while true { block } -> loop { block } + Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*block, state, false))), + // while expr { block } + expr => match optimize_stmt(*block, state, false) { + // while expr { break; } -> { expr; } Stmt::Break(pos) => { // Only a single break statement - turn into running the guard expression once state.set_dirty(); @@ -127,48 +139,50 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - } Stmt::Block(statements, pos) } + // while expr { block } stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)), }, }, - Stmt::Loop(stmt) => match optimize_stmt(*stmt, state, false) { + // loop { block } + Stmt::Loop(block) => match optimize_stmt(*block, state, false) { + // loop { break; } -> Noop Stmt::Break(pos) => { // Only a single break statement state.set_dirty(); Stmt::Noop(pos) } + // loop { block } stmt => Stmt::Loop(Box::new(stmt)), }, - Stmt::For(id, expr, stmt) => Stmt::For( + // for id in expr { block } + Stmt::For(id, expr, block) => Stmt::For( id, Box::new(optimize_expr(*expr, state)), - Box::new(match optimize_stmt(*stmt, state, false) { - Stmt::Break(pos) => { - // Only a single break statement - state.set_dirty(); - Stmt::Noop(pos) - } - stmt => stmt, - }), + Box::new(optimize_stmt(*block, state, false)), ), - + // let id = expr; Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos) } + // let id; Stmt::Let(_, None, _) => stmt, + // { block } + Stmt::Block(block, pos) => { + let orig_len = block.len(); // Original number of statements in the block, for change detection + let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later - Stmt::Block(statements, pos) => { - let orig_len = statements.len(); - let orig_constants_len = state.constants.len(); - - let mut result: Vec<_> = statements - .into_iter() // For each statement + // Optimize each statement in the block + let mut result: Vec<_> = block + .into_iter() .map(|stmt| { if let Stmt::Const(name, value, pos) = stmt { + // Add constant into the state state.push_constant(&name, *value); state.set_dirty(); Stmt::Noop(pos) // No need to keep constants } else { - optimize_stmt(stmt, state, preserve_result) // Optimize the statement + // Optimize the statement + optimize_stmt(stmt, state, preserve_result) } }) .collect(); @@ -203,11 +217,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - result.push(Stmt::Noop(pos)) } + // Optimize all the statements again result = result .into_iter() .rev() .enumerate() - .map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again + .map(|(i, s)| optimize_stmt(s, state, i == 0)) .rev() .collect(); } @@ -230,10 +245,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - true }); + // Change detection if orig_len != result.len() { state.set_dirty(); } + // Pop the stack and remove all the local constants state.restore_constants(orig_constants_len); match result[..] { @@ -250,44 +267,54 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - _ => Stmt::Block(result, pos), } } - + // expr; Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), - + // return expr; Stmt::ReturnWithVal(Some(expr), is_return, pos) => { Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos) } - + // All other statements - skip stmt => stmt, } } +/// Optimize an expression. fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { + // These keywords are handled specially const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST]; match expr { + // ( stmt ) Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) { + // ( Noop ) -> () Stmt::Noop(_) => { state.set_dirty(); Expr::Unit(pos) } + // ( expr ) -> expr Stmt::Expr(expr) => { state.set_dirty(); *expr } + // ( stmt ) stmt => Expr::Stmt(Box::new(stmt), pos), }, - Expr::Assignment(id1, expr1, pos1) => match *expr1 { - Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) { - (Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => { + // id = expr + Expr::Assignment(id, expr, pos) => match *expr { + //id = id2 = expr2 + Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) { + // var = var = expr2 -> var = expr2 + (Expr::Variable(var, _), Expr::Variable(var2, _)) if var == var2 => { // Assignment to the same variable - fold state.set_dirty(); Expr::Assignment( - Box::new(Expr::Variable(var1, pos1)), + Box::new(Expr::Variable(var, pos)), Box::new(optimize_expr(*expr2, state)), - pos1, + pos, ) } + // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( Box::new(id1), Box::new(Expr::Assignment( @@ -295,19 +322,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Box::new(optimize_expr(*expr2, state)), pos2, )), - pos1, + pos, ), }, - expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1), + // id = expr + expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos), }, + // lhs.rhs Expr::Dot(lhs, rhs, pos) => Expr::Dot( Box::new(optimize_expr(*lhs, state)), Box::new(optimize_expr(*rhs, state)), pos, ), + // lhs[rhs] #[cfg(not(feature = "no_index"))] Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { + // array[int] (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => { @@ -316,6 +347,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { state.set_dirty(); items.remove(i as usize) } + // string[int] (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if i >= 0 && (i as usize) < s.chars().count() => { @@ -323,14 +355,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { state.set_dirty(); Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos) } - + // lhs[rhs] (lhs, rhs) => Expr::Index( Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(rhs, state)), pos, ), }, - + // [ items .. ] #[cfg(not(feature = "no_index"))] Expr::Array(items, pos) => { let orig_len = items.len(); @@ -346,38 +378,47 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Expr::Array(items, pos) } - + // lhs && rhs Expr::And(lhs, rhs) => match (*lhs, *rhs) { + // true && rhs -> rhs (Expr::True(_), rhs) => { state.set_dirty(); rhs } + // false && rhs -> false (Expr::False(pos), _) => { state.set_dirty(); Expr::False(pos) } + // lhs && true -> lhs (lhs, Expr::True(_)) => { state.set_dirty(); - lhs + optimize_expr(lhs, state) } + // lhs && rhs (lhs, rhs) => Expr::And( Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(rhs, state)), ), }, + // lhs || rhs Expr::Or(lhs, rhs) => match (*lhs, *rhs) { + // false || rhs -> rhs (Expr::False(_), rhs) => { state.set_dirty(); rhs } + // true || rhs -> true (Expr::True(pos), _) => { state.set_dirty(); Expr::True(pos) } + // lhs || false (lhs, Expr::False(_)) => { state.set_dirty(); - lhs + optimize_expr(lhs, state) } + // lhs || rhs (lhs, rhs) => Expr::Or( Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(rhs, state)), @@ -388,24 +429,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=> Expr::FunctionCall(id, args, def_value, pos), - // Actually call function to optimize it + // Eagerly call functions Expr::FunctionCall(id, args, def_value, pos) - if state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations + if state.engine.optimization_level == OptimizationLevel::Full // full optimizations && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { - let engine = state.engine.expect("engine should be Some"); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 { - engine.map_type_name(call_args[0].type_name()) + state.engine.map_type_name(call_args[0].type_name()) } else { "" }; - engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r| + state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r| r.or_else(|| { if !arg_for_type_of.is_empty() { // Handle `type_of()` @@ -421,10 +461,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) } - // Optimize the function call arguments + // id(args ..) -> optimize function call arguments Expr::FunctionCall(id, args, def_value, pos) => Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), - + // constant-name Expr::Variable(ref name, _) if state.contains_constant(name) => { state.set_dirty(); @@ -434,28 +474,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { .expect("should find constant in scope!") .clone() } - + // All other expressions - skip expr => expr, } } -pub(crate) fn optimize<'a>( - statements: Vec, - engine: Option<&Engine<'a>>, - scope: &Scope, -) -> Vec { +pub(crate) fn optimize<'a>(statements: Vec, engine: &Engine<'a>, scope: &Scope) -> Vec { // If optimization level is None then skip optimizing - if engine - .map(|eng| eng.optimization_level == OptimizationLevel::None) - .unwrap_or(false) - { + if engine.optimization_level == OptimizationLevel::None { return statements; } // Set up the state - let mut state = State::new(); - state.engine = engine; + let mut state = State { + changed: false, + constants: vec![], + engine, + }; + // Add constants from the scope into the state scope .iter() .filter(|ScopeEntry { var_type, expr, .. }| { @@ -472,9 +509,9 @@ pub(crate) fn optimize<'a>( let orig_constants_len = state.constants.len(); - // Optimization loop let mut result = statements; + // Optimization loop loop { state.reset(); state.restore_constants(orig_constants_len); @@ -492,7 +529,7 @@ pub(crate) fn optimize<'a>( } else { // Keep all variable declarations at this level // and always keep the last return value - let keep = stmt.is_var() || i == num_statements - 1; + let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; optimize_stmt(stmt, &mut state, keep) } @@ -517,6 +554,7 @@ pub(crate) fn optimize<'a>( result } +/// Optimize an AST. pub fn optimize_ast( engine: &Engine, scope: &Scope, @@ -526,8 +564,8 @@ pub fn optimize_ast( AST( match engine.optimization_level { OptimizationLevel::None => statements, - OptimizationLevel::Simple => optimize(statements, None, &scope), - OptimizationLevel::Full => optimize(statements, Some(engine), &scope), + OptimizationLevel::Simple => optimize(statements, engine, &scope), + OptimizationLevel::Full => optimize(statements, engine, &scope), }, functions .into_iter() @@ -536,14 +574,21 @@ pub fn optimize_ast( OptimizationLevel::None => (), OptimizationLevel::Simple | OptimizationLevel::Full => { let pos = fn_def.body.position(); - let mut body = optimize(vec![fn_def.body], None, &Scope::new()); + + // Optimize the function body + let mut body = optimize(vec![fn_def.body], engine, &Scope::new()); + + // {} -> Noop fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { + // { return val; } -> val Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => { Stmt::Expr(val) } + // { return; } -> () Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::Expr(Box::new(Expr::Unit(pos))) } + // All others stmt => stmt, }; } diff --git a/src/parser.rs b/src/parser.rs index a96b509f..5f7da436 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -19,13 +19,13 @@ use std::{ #[cfg(not(feature = "only_i32"))] pub type INT = i64; -/// The system integer type +/// The system integer type. /// /// If the `only_i32` feature is not enabled, this will be `i64` instead. #[cfg(feature = "only_i32")] pub type INT = i32; -/// The system floating-point type +/// The system floating-point type. #[cfg(not(feature = "no_float"))] pub type FLOAT = f64; @@ -35,7 +35,9 @@ type PERR = ParseErrorType; /// A location (line number + character position) in the input script. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { + /// Line number - 0 = none, MAX = EOF line: usize, + /// Character position - 0 = BOL, MAX = EOF pos: usize, } @@ -152,45 +154,69 @@ impl fmt::Debug for Position { #[derive(Debug, Clone)] pub struct AST(pub(crate) Vec, pub(crate) Vec>); +/// A script-function definition. #[derive(Debug, Clone)] pub struct FnDef { + /// Function name. pub name: String, + /// Names of function parameters. pub params: Vec, + /// Function body. pub body: Stmt, + /// Position of the function definition. pub pos: Position, } impl FnDef { + /// Function to order two FnDef records, for binary search. pub fn compare(&self, name: &str, params_len: usize) -> Ordering { + // First order by name match self.name.as_str().cmp(name) { + // Then by number of parameters Ordering::Equal => self.params.len().cmp(¶ms_len), order => order, } } } +/// `return`/`throw` statement. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ReturnType { + /// `return` statement. Return, + /// `throw` statement. Exception, } +/// A statement. #[derive(Debug, Clone)] pub enum Stmt { + /// No-op. Noop(Position), + /// if expr { stmt } else { stmt } IfElse(Box, Box, Option>), + /// while expr { stmt } While(Box, Box), + /// loop { stmt } Loop(Box), + /// for id in expr { stmt } For(String, Box, Box), + /// let id = expr Let(String, Option>, Position), + /// const id = expr Const(String, Box, Position), + /// { stmt; ... } Block(Vec, Position), + /// { stmt } Expr(Box), + /// break Break(Position), + /// `return`/`throw` ReturnWithVal(Option>, ReturnType, Position), } impl Stmt { + /// Get the `Position` of this statement. pub fn position(&self) -> Position { match self { Stmt::Noop(pos) @@ -204,18 +230,7 @@ impl Stmt { } } - pub fn is_noop(&self) -> bool { - matches!(self, Stmt::Noop(_)) - } - - pub fn is_op(&self) -> bool { - !matches!(self, Stmt::Noop(_)) - } - - pub fn is_var(&self) -> bool { - matches!(self, Stmt::Let(_, _, _)) - } - + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? pub fn is_self_terminated(&self) -> bool { match self { Stmt::Noop(_) @@ -233,6 +248,7 @@ impl Stmt { } } + /// Is this statement _pure_? pub fn is_pure(&self) -> bool { match self { Stmt::Noop(_) => true, @@ -252,31 +268,54 @@ impl Stmt { } } +/// An expression. #[derive(Debug, Clone)] pub enum Expr { + /// Integer constant. IntegerConstant(INT, Position), + /// Floating-point constant. #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT, Position), - Variable(String, Position), - Property(String, Position), + /// Character constant. CharConstant(char, Position), + /// String constant. StringConstant(String, Position), + /// Variable access. + Variable(String, Position), + /// Property access. + Property(String, Position), + /// { stmt } Stmt(Box, Position), + /// func(expr, ... ) FunctionCall(String, Vec, Option, Position), + /// expr = expr Assignment(Box, Box, Position), + /// lhs.rhs Dot(Box, Box, Position), + /// expr[expr] #[cfg(not(feature = "no_index"))] Index(Box, Box, Position), #[cfg(not(feature = "no_index"))] + /// [ expr, ... ] Array(Vec, Position), + /// lhs && rhs And(Box, Box), + /// lhs || rhs Or(Box, Box), + /// true True(Position), + /// false False(Position), + /// () Unit(Position), } impl Expr { + /// Get the `Dynamic` value of a constant expression. + /// + /// # Panics + /// + /// Panics when the expression is not constant. pub fn get_constant_value(&self) -> Dynamic { match self { Expr::IntegerConstant(i, _) => i.into_dynamic(), @@ -300,6 +339,11 @@ impl Expr { } } + /// Get the display value of a constant expression. + /// + /// # Panics + /// + /// Panics when the expression is not constant. pub fn get_constant_str(&self) -> String { match self { Expr::IntegerConstant(i, _) => i.to_string(), @@ -319,6 +363,7 @@ impl Expr { } } + /// Get the `Position` of the expression. pub fn position(&self) -> Position { match self { Expr::IntegerConstant(_, pos) @@ -348,7 +393,7 @@ impl Expr { } } - /// Is this expression pure? + /// Is the expression pure? /// /// A pure expression has no side effects. pub fn is_pure(&self) -> bool { @@ -367,6 +412,7 @@ impl Expr { } } + /// Is the expression a constant? pub fn is_constant(&self) -> bool { match self { Expr::IntegerConstant(_, _) @@ -379,6 +425,7 @@ impl Expr { #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, _) => true, + // An array literal is constant if all items are constant #[cfg(not(feature = "no_index"))] Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant), @@ -387,6 +434,7 @@ impl Expr { } } +/// Tokens. #[derive(Debug, PartialEq, Clone)] pub enum Token { IntegerConstant(INT), @@ -460,6 +508,7 @@ pub enum Token { } impl Token { + /// Get the syntax of the token. pub fn syntax<'a>(&'a self) -> Cow<'a, str> { use self::Token::*; @@ -540,8 +589,8 @@ impl Token { } } - // if another operator is after these, it's probably an unary operator - // not sure about fn's name + // If another operator is after these, it's probably an unary operator + // (not sure about fn name). pub fn is_next_unary(&self) -> bool { use self::Token::*; @@ -602,6 +651,7 @@ impl Token { } } + /// Get the precedence number of the token. pub fn precedence(&self) -> u8 { match self { Self::Equals @@ -642,8 +692,10 @@ impl Token { } } + /// Does an expression bind to the right (instead of left)? pub fn is_bind_right(&self) -> bool { match self { + // Assignments bind to the right Self::Equals | Self::PlusAssign | Self::MinusAssign @@ -657,6 +709,7 @@ impl Token { | Self::ModuloAssign | Self::PowerOfAssign => true, + // Property access binds to the right Self::Period => true, _ => false, @@ -664,24 +717,36 @@ impl Token { } } +/// An iterator on a `Token` stream. pub struct TokenIterator<'a> { + /// The last token seen. last: Token, + /// Current position. pos: Position, + /// The input characters stream. char_stream: Peekable>, } impl<'a> TokenIterator<'a> { + /// Move the current position one character ahead. fn advance(&mut self) { self.pos.advance(); } + /// Move the current position back one character. + /// + /// # Panics + /// + /// Panics if already at the beginning of a line - cannot rewind to the previous line. fn rewind(&mut self) { self.pos.rewind(); } + /// Move the current position to the next line. fn new_line(&mut self) { self.pos.new_line() } - pub fn parse_string_const( + /// Parse a string literal wrapped by `enclosing_char`. + pub fn parse_string_literal( &mut self, enclosing_char: char, ) -> Result { @@ -693,25 +758,31 @@ impl<'a> TokenIterator<'a> { self.advance(); match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { + // \... '\\' if escape.is_empty() => { escape.push('\\'); } + // \\ '\\' if !escape.is_empty() => { escape.clear(); result.push('\\'); } + // \t 't' if !escape.is_empty() => { escape.clear(); result.push('\t'); } + // \n 'n' if !escape.is_empty() => { escape.clear(); result.push('\n'); } + // \r 'r' if !escape.is_empty() => { escape.clear(); result.push('\r'); } + // \x??, \u????, \U???????? ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { let mut seq = escape.clone(); seq.push(ch); @@ -744,15 +815,25 @@ impl<'a> TokenIterator<'a> { .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, ); } + + // \{enclosing_char} - escaped ch if enclosing_char == ch && !escape.is_empty() => result.push(ch), + + // Close wrapper ch if enclosing_char == ch && escape.is_empty() => break, + + // Unknown escape sequence _ if !escape.is_empty() => { return Err((LERR::MalformedEscapeSequence(escape), self.pos)) } + + // Cannot have new-lines inside string literals '\n' => { self.rewind(); return Err((LERR::UnterminatedString, self.pos)); } + + // All other characters ch => { escape.clear(); result.push(ch); @@ -763,6 +844,7 @@ impl<'a> TokenIterator<'a> { Ok(result.iter().collect()) } + /// Get the next token. fn inner_next(&mut self) -> Option<(Token, Position)> { let mut negated = false; @@ -772,7 +854,9 @@ impl<'a> TokenIterator<'a> { let pos = self.pos; match c { + // \n '\n' => self.new_line(), + // digit ... '0'..='9' => { let mut result = Vec::new(); let mut radix_base: Option = None; @@ -801,6 +885,7 @@ impl<'a> TokenIterator<'a> { } } } + // 0x????, 0o????, 0b???? ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' if c == '0' => { @@ -850,6 +935,7 @@ impl<'a> TokenIterator<'a> { result.insert(0, '-'); } + // Parse number if let Some(radix) = radix_base { let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); @@ -865,6 +951,7 @@ impl<'a> TokenIterator<'a> { let out: String = result.iter().filter(|&&c| c != '_').collect(); let num = INT::from_str(&out).map(Token::IntegerConstant); + // If integer parsing is unnecessary, try float instead #[cfg(not(feature = "no_float"))] let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); @@ -876,6 +963,7 @@ impl<'a> TokenIterator<'a> { )); } } + // letter ... 'A'..='Z' | 'a'..='z' | '_' => { let mut result = Vec::new(); result.push(c); @@ -920,13 +1008,15 @@ impl<'a> TokenIterator<'a> { pos, )); } + // " - string literal '"' => { - return match self.parse_string_const('"') { + return match self.parse_string_literal('"') { Ok(out) => Some((Token::StringConst(out), pos)), Err(e) => Some((Token::LexError(e.0), e.1)), } } - '\'' => match self.parse_string_const('\'') { + // ' - character literal + '\'' => match self.parse_string_literal('\'') { Ok(result) => { let mut chars = result.chars(); @@ -945,16 +1035,22 @@ impl<'a> TokenIterator<'a> { } Err(e) => return Some((Token::LexError(e.0), e.1)), }, + + // Braces '{' => return Some((Token::LeftBrace, pos)), '}' => return Some((Token::RightBrace, pos)), + + // Parentheses '(' => return Some((Token::LeftParen, pos)), ')' => return Some((Token::RightParen, pos)), + // Indexing #[cfg(not(feature = "no_index"))] '[' => return Some((Token::LeftBracket, pos)), #[cfg(not(feature = "no_index"))] ']' => return Some((Token::RightBracket, pos)), + // Operators '+' => { return Some(( match self.char_stream.peek() { @@ -1218,6 +1314,7 @@ impl<'a> Iterator for TokenIterator<'a> { } } +/// Tokenize an input text stream. pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { last: Token::LexError(LERR::InputError("".into())), @@ -1226,6 +1323,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> { } } +/// Parse ( expr ) fn parse_paren_expr<'a>( input: &mut Peekable>, begin: Position, @@ -1242,13 +1340,16 @@ fn parse_paren_expr<'a>( let expr = parse_expr(input)?; match input.next() { + // ( xxx ) Some((Token::RightParen, _)) => Ok(expr), + // ( xxx ??? Some((_, pos)) => { return Err(ParseError::new( PERR::MissingRightParen("a matching ( in the expression".into()), pos, )) } + // ( xxx None => Err(ParseError::new( PERR::MissingRightParen("a matching ( in the expression".into()), Position::eof(), @@ -1256,6 +1357,7 @@ fn parse_paren_expr<'a>( } } +/// Parse a function call. fn parse_call_expr<'a>( id: String, input: &mut Peekable>, @@ -1263,6 +1365,7 @@ fn parse_call_expr<'a>( ) -> Result { let mut args_expr_list = Vec::new(); + // id() if let (Token::RightParen, _) = input.peek().ok_or_else(|| { ParseError::new( PERR::MissingRightParen(format!( @@ -1308,6 +1411,7 @@ fn parse_call_expr<'a>( } } +/// Parse an indexing expression.s #[cfg(not(feature = "no_index"))] fn parse_index_expr<'a>( lhs: Box, @@ -1318,6 +1422,7 @@ fn parse_index_expr<'a>( // Check type of indexing - must be integer match &idx_expr { + // lhs[int] Expr::IntegerConstant(i, pos) if *i < 0 => { return Err(ParseError::new( PERR::MalformedIndexExpr(format!( @@ -1327,6 +1432,7 @@ fn parse_index_expr<'a>( *pos, )) } + // lhs[float] #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => { return Err(ParseError::new( @@ -1334,6 +1440,7 @@ fn parse_index_expr<'a>( *pos, )) } + // lhs[char] Expr::CharConstant(_, pos) => { return Err(ParseError::new( PERR::MalformedIndexExpr( @@ -1342,18 +1449,21 @@ fn parse_index_expr<'a>( *pos, )) } + // lhs[string] Expr::StringConstant(_, pos) => { return Err(ParseError::new( PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()), *pos, )) } + // lhs[??? = ??? ], lhs[()] Expr::Assignment(_, _, pos) | Expr::Unit(pos) => { return Err(ParseError::new( PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()), *pos, )) } + // lhs[??? && ???], lhs[??? || ???] Expr::And(lhs, _) | Expr::Or(lhs, _) => { return Err(ParseError::new( PERR::MalformedIndexExpr( @@ -1362,6 +1472,7 @@ fn parse_index_expr<'a>( lhs.position(), )) } + // lhs[true], lhs[false] Expr::True(pos) | Expr::False(pos) => { return Err(ParseError::new( PERR::MalformedIndexExpr( @@ -1370,6 +1481,7 @@ fn parse_index_expr<'a>( *pos, )) } + // All other expressions _ => (), } @@ -1393,29 +1505,35 @@ fn parse_index_expr<'a>( } } +/// Parse an expression that begins with an identifier. fn parse_ident_expr<'a>( id: String, input: &mut Peekable>, begin: Position, ) -> Result { match input.peek() { + // id(...) - function call Some((Token::LeftParen, _)) => { input.next(); parse_call_expr(id, input, begin) } + // id[...] - indexing #[cfg(not(feature = "no_index"))] Some((Token::LeftBracket, pos)) => { let pos = *pos; input.next(); parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos) } + // id - variable Some(_) => Ok(Expr::Variable(id, begin)), + // EOF None => Ok(Expr::Variable(id, Position::eof())), } } +/// Parse an array literal. #[cfg(not(feature = "no_index"))] -fn parse_array_expr<'a>( +fn parse_array_literal<'a>( input: &mut Peekable>, begin: Position, ) -> Result { @@ -1462,8 +1580,9 @@ fn parse_array_expr<'a>( } } +/// Parse a primary expression. fn parse_primary<'a>(input: &mut Peekable>) -> Result { - // Block statement as expression + // { - block statement as expression match input.peek() { Some((Token::LeftBrace, pos)) => { let pos = *pos; @@ -1500,7 +1619,7 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result { can_be_indexed = true; - parse_array_expr(input, pos) + parse_array_literal(input, pos) } (Token::True, pos) => Ok(Expr::True(pos)), (Token::False, pos) => Ok(Expr::False(pos)), @@ -1524,11 +1643,13 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { match input .peek() .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? { + // -expr (Token::UnaryMinus, pos) => { let pos = *pos; @@ -1561,10 +1682,12 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)), } } + // +expr (Token::UnaryPlus, _) => { input.next(); parse_unary(input) } + // !expr (Token::Bang, pos) => { let pos = *pos; @@ -1577,34 +1700,41 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result parse_primary(input), } } +/// Parse an assignment. fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result { + // Is the LHS in a valid format? fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option { match expr { + // var Expr::Variable(_, _) => { assert!(is_top, "property expected but gets variable"); None } + // property Expr::Property(_, _) => { assert!(!is_top, "variable expected but gets property"); None } + // var[...] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => { assert!(is_top, "property expected but gets variable"); None } - + // property[...] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => { assert!(!is_top, "variable expected but gets property"); None } + // idx_lhs[...] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, pos) => Some(ParseError::new( match idx_lhs.as_ref() { @@ -1614,22 +1744,27 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result match dot_lhs.as_ref() { + // var.dot_rhs Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), + // property.dot_rhs Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false), - + // var[...] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top => { valid_assignment_chain(dot_rhs, false) } + // property[...] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top => { valid_assignment_chain(dot_rhs, false) } + // idx_lhs[...] #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) => Some(ParseError::new( ParseErrorType::AssignmentToCopy, @@ -1652,6 +1787,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result( input: &mut Peekable>, parent_precedence: u8, @@ -1820,11 +1957,13 @@ fn parse_binary_op<'a>( } } +/// Parse an expression. fn parse_expr<'a>(input: &mut Peekable>) -> Result { let lhs = parse_unary(input)?; parse_binary_op(input, 1, lhs) } +/// Parse an if statement. fn parse_if<'a>( input: &mut Peekable>, breakable: bool, @@ -1849,6 +1988,7 @@ fn parse_if<'a>( Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body)) } +/// Parse a while loop. fn parse_while<'a>(input: &mut Peekable>) -> Result { input.next(); @@ -1858,6 +1998,7 @@ fn parse_while<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { input.next(); @@ -1866,6 +2007,7 @@ fn parse_loop<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { input.next(); @@ -1894,7 +2036,8 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result( +/// Parse a variable definition statement. +fn parse_let<'a>( input: &mut Peekable>, var_type: VariableType, ) -> Result { @@ -1935,6 +2078,7 @@ fn parse_var<'a>( } } +/// Parse a statement block. fn parse_block<'a>( input: &mut Peekable>, breakable: bool, @@ -1995,10 +2139,12 @@ fn parse_block<'a>( } } +/// Parse an expression as a statement. fn parse_expr_stmt<'a>(input: &mut Peekable>) -> Result { Ok(Stmt::Expr(Box::new(parse_expr(input)?))) } +/// Parse a single statement. fn parse_stmt<'a>( input: &mut Peekable>, breakable: bool, @@ -2030,14 +2176,14 @@ fn parse_stmt<'a>( input.next(); match input.peek() { - // return/throw at EOF + // `return`/`throw` at EOF None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), - // return; or throw; + // `return;` or `throw;` Some((Token::SemiColon, pos)) => { let pos = *pos; Ok(Stmt::ReturnWithVal(None, return_type, pos)) } - // return or throw with expression + // `return` or `throw` with expression Some((_, pos)) => { let pos = *pos; Ok(Stmt::ReturnWithVal( @@ -2049,12 +2195,13 @@ fn parse_stmt<'a>( } } (Token::LeftBrace, _) => parse_block(input, breakable), - (Token::Let, _) => parse_var(input, VariableType::Normal), - (Token::Const, _) => parse_var(input, VariableType::Constant), + (Token::Let, _) => parse_let(input, VariableType::Normal), + (Token::Const, _) => parse_let(input, VariableType::Constant), _ => parse_expr_stmt(input), } } +/// Parse a function definition. #[cfg(not(feature = "no_function"))] fn parse_fn<'a>(input: &mut Peekable>) -> Result { let pos = input @@ -2156,6 +2303,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result( input: &mut Peekable>, ) -> Result<(Vec, Vec), ParseError> { @@ -2204,6 +2352,7 @@ fn parse_global_level<'a, 'e>( Ok((statements, functions)) } +/// Run the parser on an input stream, returning an AST. pub fn parse<'a, 'e>( input: &mut Peekable>, engine: &Engine<'e>, @@ -2219,6 +2368,9 @@ pub fn parse<'a, 'e>( ) } +/// Map a `Dynamic` value to an expression. +/// +/// Returns Some(expression) if conversion is successful. Otherwise None. pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dynamic) { if value.is::() { let value2 = value.clone();