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 * &lt;cs? var:a + b ?&gt;
4956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre>
5056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * with:
5156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
5256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre>
5356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:a ?&gt;&lt;cs? var:b ?&gt;
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 * &lt;cs? var:html_escape(foo) ?&gt;
6756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre>
6856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * are turned into:
6956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
7056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre>
7156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? escape:&quot;html&quot; ?&gt;
7256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:foo ?&gt;
7356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs /escape ?&gt;
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 * &lt;cs? html_escape(foo + bar) + baz ?&gt;
8356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </pre>
8456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * which is turned into:
8556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
8656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <pre>
8756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? escape:&quot;html&quot; ?&gt;
8856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:foo ?&gt;
8956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:bar ?&gt;
9056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs /escape ?&gt;
9156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs var:baz ?&gt;
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 * &lt;cs? escape:&quot;html&quot; ?&gt;
10056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:url_escape(foo) ?&gt;
10156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs /escape ?&gt;
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 * &lt;cs? escape:&quot;html&quot; ?&gt;
11256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? escape:&quot;url&quot; ?&gt;
11356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:foo ?&gt;
11456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs /escape ?&gt;
11556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs /escape ?&gt;
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 * &lt;cs? escape:&quot;html&quot; ?&gt;
12656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;cs? var:url_validate(foo) ?&gt;
12756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * &lt;?cs /escape ?&gt;
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   * &lt;cs? var:foo_escape(bar) ?&gt;
24956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * </pre>
25056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * with:
25156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
25256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * <pre>
25356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * &lt;cs? escape:&quot;foo&quot; ?&gt;&lt;cs? var:bar ?&gt;&lt;?cs /escape ?&gt;
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