156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/* 256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Copyright (C) 2010 Google Inc. 356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Licensed under the Apache License, Version 2.0 (the "License"); 556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * you may not use this file except in compliance with the License. 656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * You may obtain a copy of the License at 756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * http://www.apache.org/licenses/LICENSE-2.0 956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 1056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Unless required by applicable law or agreed to in writing, software 1156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * distributed under the License is distributed on an "AS IS" BASIS, 1256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * See the License for the specific language governing permissions and 1456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * limitations under the License. 1556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 1656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 1756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpackage com.google.clearsilver.jsilver.syntax; 1856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 1956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; 2056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.AAddExpression; 2156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; 2256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.AFunctionExpression; 2356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.AMultipleCommand; 2456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.ANameVariable; 2556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.AStringExpression; 2656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.AVarCommand; 2756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.Node; 2856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.PCommand; 2956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.PExpression; 3056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.PPosition; 3156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.PVariable; 3256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.node.TString; 3356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 3456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Collection; 3556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.LinkedList; 3656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 3756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/** 3856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Recursively optimizes the syntax tree with a set of simple operations. This class currently 3956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * optimizes: 4056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <ul> 4156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <li>String concatenation in var commands 4256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <li>Function calls to escaping functions 4356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </ul> 4456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <p> 4556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * String add expressions in var commands are optimized by replacing something like: 4656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 4756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 4856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:a + b ?> 4956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 5056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * with: 5156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 5256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 5356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:a ?><cs? var:b ?> 5456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 5556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 5656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * This avoids having to construct the intermediate result {@code a + b} at runtime and reduces 5756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * runtime heap allocations. 5856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <p> 5956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Functions call to escaping functions are optimized by replacing them with the equivalent escaping 6056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * construct. This is faster because escapers are called with the strings themselves whereas general 6156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * function calls require value objects to be created. 6256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <p> 6356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Expressions such as: 6456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 6556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 6656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:html_escape(foo) ?> 6756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 6856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * are turned into: 6956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 7056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 7156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"html" ?> 7256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:foo ?> 7356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs /escape ?> 7456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 7556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 7656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * It also optimizes sequences of escaped expressions into a single escaped sequence. 7756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <p> 7856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * It is important to note that these optimizations cannot be done in isolation if we want to 7956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * optimize compound expressions such as: 8056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 8156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 8256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? html_escape(foo + bar) + baz ?> 8356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 8456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * which is turned into: 8556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 8656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 8756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"html" ?> 8856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:foo ?> 8956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:bar ?> 9056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs /escape ?> 9156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs var:baz ?> 9256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 9356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 9456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * WARNING: This class isn't strictly just an optimization and its modification of the syntax tree 9556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * actually improves JSilver's behavior, bringing it more in line with ClearSilver. Consider the 9656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * sequence: 9756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 9856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 9956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"html" ?> 10056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:url_escape(foo) ?> 10156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs /escape ?> 10256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 10356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 10456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * In JSilver (without this optimizer being run) this would result in {@code foo} being escaped by 10556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * both the html escaper and the url escaping function. However ClearSilver treats top-level escaper 10656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * functions specially and {@code foo} is only escaped once by the url escaping function. 10756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 10856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * The good news is that this optimization rewrites the above example to: 10956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 11056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 11156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"html" ?> 11256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"url" ?> 11356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:foo ?> 11456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs /escape ?> 11556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs /escape ?> 11656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 11756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * which fixes the problem because the new url escaper replaces the existing html escaper (rather 11856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * than combining with it). 11956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 12056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * The only fly in the ointment here is the {@code url_validate} function which is treated like an 12156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * escaper by ClearSilver but which does not (currently) have an escaper associated with it. This 12256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * means that: 12356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 12456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 12556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"html" ?> 12656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:url_validate(foo) ?> 12756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <?cs /escape ?> 12856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 12956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * will not be rewritten by this class and will result in {@code foo} being escaped twice. 13056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 13156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 13256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpublic class VarOptimizer extends DepthFirstAdapter { 13356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 13456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 13556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * A list of escaper names that are also exposed as escaping functions (eg, if the "foo" escaper 13656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * is also exposed as "foo_escape" function then this collection should contain the string "foo"). 13756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 13856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private final Collection<String> escaperNames; 13956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 14056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public VarOptimizer(Collection<String> escaperNames) { 14156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.escaperNames = escaperNames; 14256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 14356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 14456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 14556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void caseAMultipleCommand(AMultipleCommand multiCommand) { 14656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson super.caseAMultipleCommand(multiCommand); 14756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson multiCommand.replaceBy(optimizeEscapeSequences(multiCommand)); 14856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 14956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 15056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 15156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void caseAVarCommand(AVarCommand varCommand) { 15256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson super.caseAVarCommand(varCommand); 15356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson varCommand.replaceBy(optimizeVarCommands(varCommand)); 15456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 15556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 15656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 15756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Optimizes a complex var command by recursively expanding its expression into a sequence of 15856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * simpler var commands. Currently two expressions are targetted for expansion: string 15956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * concatenation and escaping functions. 16056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 16156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private PCommand optimizeVarCommands(AVarCommand varCommand) { 16256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson PExpression expression = varCommand.getExpression(); 16356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson PPosition position = varCommand.getPosition(); 16456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 16556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // This test relies on the type optimizer having replaced add commands 16656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // with numeric add commands. 16756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (expression instanceof AAddExpression) { 16856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Replace: <?cs var:a + b ?> 16956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // with: <?cs var:a ?><?cs var:b ?> 17056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AAddExpression addExpression = (AAddExpression) expression; 17156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AMultipleCommand multiCommand = new AMultipleCommand(); 17256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson addToContents(multiCommand, optimizedVarCommandOf(position, addExpression.getLeft())); 17356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson addToContents(multiCommand, optimizedVarCommandOf(position, addExpression.getRight())); 17456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return optimizeEscapeSequences(multiCommand); 17556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 17656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 17756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // This test relies on the sequence optimizer removing single element 17856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // sequence commands. 17956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (expression instanceof AFunctionExpression) { 18056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Replace: <?cs var:foo_escape(x) ?> 18156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // with: <?cs escape:"foo" ?><?cs var:x ?><?cs /escape ?> 18256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AFunctionExpression functionExpression = (AFunctionExpression) expression; 18356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String name = escapeNameOf(functionExpression); 18456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (escaperNames.contains(name)) { 18556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson LinkedList<PExpression> args = functionExpression.getArgs(); 18656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (args.size() == 1) { 18756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return new AEscapeCommand(position, quotedStringExpressionOf(name), 18856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson optimizedVarCommandOf(position, args.getFirst())); 18956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 19056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 19156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 19256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return varCommand; 19356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 19456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 19556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 19656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Create a var command from the given expression and recursively optimize it, returning the 19756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * result. 19856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 19956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private PCommand optimizedVarCommandOf(PPosition position, PExpression expression) { 20056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return optimizeVarCommands(new AVarCommand(cloneOf(position), cloneOf(expression))); 20156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 20256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 20356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** Simple helper to clone nodes in a typesafe way */ 20456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @SuppressWarnings("unchecked") 20556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static <T extends Node> T cloneOf(T t) { 20656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return (T) t.clone(); 20756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 20856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 20956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 21056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Helper to efficiently add commands to a multiple command (if the command to be added is a 21156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * multiple command, we add its contents). This is used to implement a tail recursion optimization 21256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * to flatten multiple commands. 21356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 21456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static void addToContents(AMultipleCommand multi, PCommand command) { 21556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (command instanceof AMultipleCommand) { 21656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson multi.getCommand().addAll(((AMultipleCommand) command).getCommand()); 21756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 21856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson multi.getCommand().add(command); 21956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 22056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 22156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 22256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** When used as functions, escapers have the name 'foo_escape' */ 22356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static final String ESCAPE_SUFFIX = "_escape"; 22456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 22556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 22656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the name of the escaper which could replace this function (or null if this function 22756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * cannot be replaced). 22856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 22956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static String escapeNameOf(AFunctionExpression function) { 23056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson PVariable nvar = function.getName(); 23156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (!(nvar instanceof ANameVariable)) { 23256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // We are not interested in dynamic function calls (such as "a.b(x)") 23356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return null; 23456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 23556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String name = ((ANameVariable) nvar).getWord().getText(); 23656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (!name.endsWith(ESCAPE_SUFFIX)) { 23756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return null; 23856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 23956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return name.substring(0, name.length() - ESCAPE_SUFFIX.length()); 24056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 24156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 24256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 24356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns a quoted string expression of the given text. 24456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <p> 24556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * This is used because when an escaper is called as a function we need to replace: 24656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 24756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 24856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? var:foo_escape(bar) ?> 24956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 25056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * with: 25156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 25256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre> 25356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <cs? escape:"foo" ?><cs? var:bar ?><?cs /escape ?> 25456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre> 25556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Using the quoted escaper name. 25656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 25756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static AStringExpression quotedStringExpressionOf(String text) { 25856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson assert text.indexOf('"') == -1; 25956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return new AStringExpression(new TString('"' + text + '"')); 26056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 26156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 26256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 26356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns a new command containing the contents of the given multiple command but with with 26456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * multiple successive (matching) escape commands folded into one. 26556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 26656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static PCommand optimizeEscapeSequences(AMultipleCommand multiCommand) { 26756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AEscapeCommand lastEscapeCommand = null; 26856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson LinkedList<PCommand> commands = new LinkedList<PCommand>(); 26956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (PCommand command : multiCommand.getCommand()) { 27056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AEscapeCommand escapeCommand = asSimpleEscapeCommand(command); 27156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (isSameEscaper(escapeCommand, lastEscapeCommand)) { 27256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson addToContents(contentsOf(lastEscapeCommand), escapeCommand.getCommand()); 27356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 27456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Add the original command and set the escaper (possibly null) 27556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson commands.add(command); 27656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson lastEscapeCommand = escapeCommand; 27756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 27856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 27956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson assert !commands.isEmpty(); 28056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return (commands.size() > 1) ? new AMultipleCommand(commands) : commands.getFirst(); 28156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 28256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 28356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 28456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the escaped command associated with the given escape function as a multiple command. If 28556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * the command was already a multiple command, it is returned, otherwise a new multiple command is 28656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * created to wrap the original escaped command. This helper facilitates merging multiple 28756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * sequences of escapers. 28856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 28956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static AMultipleCommand contentsOf(AEscapeCommand escapeCommand) { 29056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson PCommand escapedCommand = escapeCommand.getCommand(); 29156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (escapedCommand instanceof AMultipleCommand) { 29256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return (AMultipleCommand) escapedCommand; 29356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 29456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AMultipleCommand multiCommand = new AMultipleCommand(); 29556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson multiCommand.getCommand().add(escapedCommand); 29656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson escapeCommand.setCommand(multiCommand); 29756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return multiCommand; 29856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 29956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 30056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 30156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the given command only if it is an escape command with a simple, string literal, name; 30256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * otherwise returns {@code null}. 30356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 30456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static AEscapeCommand asSimpleEscapeCommand(PCommand command) { 30556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (!(command instanceof AEscapeCommand)) { 30656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return null; 30756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 30856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson AEscapeCommand escapeCommand = (AEscapeCommand) command; 30956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (!(escapeCommand.getExpression() instanceof AStringExpression)) { 31056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return null; 31156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 31256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return escapeCommand; 31356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 31456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 31556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 31656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Compares two simple escape commands and returns true if they perform the same escaping 31756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * function. 31856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 31956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static boolean isSameEscaper(AEscapeCommand newCommand, AEscapeCommand oldCommand) { 32056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (newCommand == null || oldCommand == null) { 32156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return false; 32256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 32356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return simpleNameOf(newCommand).equals(simpleNameOf(oldCommand)); 32456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 32556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 32656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 32756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the name of the given simple escape command (as returned by 32856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * {@link #asSimpleEscapeCommand(PCommand)}). 32956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 33056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static String simpleNameOf(AEscapeCommand escapeCommand) { 33156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return ((AStringExpression) escapeCommand.getExpression()).getValue().getText(); 33256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 33356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson} 334