From e28bdd5b172924150e4d36ecdeca14b729d2cce7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 23 Apr 2023 00:00:55 +0800 Subject: [PATCH] Rewrite combo chains in parser. --- src/optimizer.rs | 67 -------------------------------------- src/parser.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/optimizer.rs b/src/optimizer.rs index add394dd..b9546eb2 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -857,63 +857,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } -/// Optimize the structure of a chained expression. -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -fn optimize_chain_structure(expr: &mut Expr, state: &mut OptimizerState) { - let (mut x, options, pos, mut lhs_x, mut lhs_options, lhs_pos, sub, root): ( - _, - _, - _, - _, - _, - _, - fn(_, _, _) -> Expr, - fn(_, _, _) -> Expr, - ) = match expr.take() { - #[cfg(not(feature = "no_index"))] - Expr::Index(mut x, opt, pos) => match x.lhs.take() { - Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Index), - #[cfg(not(feature = "no_object"))] - Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Dot), - _ => return, - }, - #[cfg(not(feature = "no_object"))] - Expr::Dot(mut x, opt, pos) => match x.lhs.take() { - #[cfg(not(feature = "no_index"))] - Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index), - Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index), - _ => return, - }, - _ => return, - }; - - // Find the end of the chain - let mut last_node = lhs_x.as_mut(); - let mut last_options = &mut lhs_options; - - while !last_options.contains(ASTFlags::BREAK) { - match last_node.rhs { - Expr::Index(ref mut x, ref mut options2, ..) => { - last_node = x.as_mut(); - last_options = options2; - } - #[cfg(not(feature = "no_object"))] - Expr::Dot(ref mut x, ref mut options2, ..) => { - last_node = x.as_mut(); - last_options = options2; - } - _ => break, - } - } - - last_options.remove(ASTFlags::BREAK); - - x.lhs = last_node.rhs.take(); - last_node.rhs = sub(x, options, pos); - *expr = root(lhs_x, lhs_options, lhs_pos); - state.set_dirty(); -} - /// Optimize an [expression][Expr]. fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // These keywords are handled specially @@ -979,11 +922,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { *expr = Expr::from_dynamic(Dynamic::FALSE, *pos); } } - #[cfg(not(feature = "no_index"))] - // (lhs_lhs[lhs_rhs])[rhs] - (Expr::Index(..), ..) => optimize_chain_structure(expr, state), - // (lhs_lhs[lhs_rhs]).rhs - (Expr::Dot(..), ..) => optimize_chain_structure(expr, state), // lhs.rhs (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); } } @@ -1054,11 +992,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // var[rhs] (Expr::Variable(..), rhs) => optimize_expr(rhs, state, true), - // (lhs_lhs[lhs_rhs])[rhs] - (Expr::Index(..), ..) => optimize_chain_structure(expr, state), - // (lhs_lhs[lhs_rhs]).rhs - #[cfg(not(feature = "no_object"))] - (Expr::Dot(..), ..) => optimize_chain_structure(expr, state), // lhs[rhs] (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); } }, diff --git a/src/parser.rs b/src/parser.rs index 4def8a60..27ae8301 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -539,6 +539,82 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position } } +/// Optimize the structure of a chained expression where the root expression is another chained expression. +/// +/// # Panics +/// +/// Panics if the expression is not a combo chain. +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +fn optimize_combo_chain(expr: &mut Expr) { + let (mut x, x_options, x_pos, mut root, mut root_options, root_pos, make_sub, make_root): ( + _, + _, + _, + _, + _, + _, + fn(_, _, _) -> Expr, + fn(_, _, _) -> Expr, + ) = match expr.take() { + #[cfg(not(feature = "no_index"))] + Expr::Index(mut x, opt, pos) => match x.lhs.take() { + Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Index), + #[cfg(not(feature = "no_object"))] + Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Dot), + _ => panic!("combo chain expected"), + }, + #[cfg(not(feature = "no_object"))] + Expr::Dot(mut x, opt, pos) => match x.lhs.take() { + #[cfg(not(feature = "no_index"))] + Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index), + Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index), + _ => panic!("combo chain expected"), + }, + _ => panic!("combo chain expected"), + }; + + // Rewrite the chains like this: + // + // Source: ( x[y].prop_a )[z].prop_b + // ^ ^ + // parentheses that generated the combo chain + // + // From: Index( Index( x, Dot(y, prop_a) ), Dot(z, prop_b) ) + // ^ ^ ^ + // x root tail + // + // To: Index( x, Dot(y, Index(prop_a, Dot(z, prop_b) ) ) ) + // + // Equivalent to: x[y].prop_a[z].prop_b + + // Find the end of the root chain. + let mut tail = root.as_mut(); + let mut tail_options = &mut root_options; + + while !tail_options.contains(ASTFlags::BREAK) { + match tail.rhs { + Expr::Index(ref mut x, ref mut options2, ..) => { + tail = x.as_mut(); + tail_options = options2; + } + #[cfg(not(feature = "no_object"))] + Expr::Dot(ref mut x, ref mut options2, ..) => { + tail = x.as_mut(); + tail_options = options2; + } + _ => break, + } + } + + // Since we attach the outer chain to the root chain, we no longer terminate at the end of the + // root chain, so remove the ASTFlags::BREAK flag. + tail_options.remove(ASTFlags::BREAK); + + x.lhs = tail.rhs.take(); // remove tail and insert it into head of outer chain + tail.rhs = make_sub(x, x_options, x_pos); // attach outer chain to tail + *expr = make_root(root, root_options, root_pos); +} + impl Engine { /// Parse a function call. fn parse_fn_call( @@ -1857,6 +1933,13 @@ impl Engine { parent_options = ASTFlags::empty(); } + // Optimize chain where the root expression is another chain + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + if matches!(lhs, Expr::Index(ref x, ..) | Expr::Dot(ref x, ..) if matches!(x.lhs, Expr::Index(..) | Expr::Dot(..))) + { + optimize_combo_chain(&mut lhs) + } + // Cache the hash key for namespace-qualified variables #[cfg(not(feature = "no_module"))] let namespaced_variable = match lhs {