aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/google/escapevelocity/Parser.java
diff options
context:
space:
mode:
authoremcmanus <emcmanus@google.com>2019-04-30 16:51:41 -0400
committerRon Shapiro <ronshapiro@google.com>2019-04-30 16:56:50 -0400
commitb515e83e6e713ff7ae6035c181b968ed67cb59db (patch)
tree7dee6967f17c226b0fa2a14980628e86a79dd7d1 /src/main/java/com/google/escapevelocity/Parser.java
parent4bb1d7445fc2f99dbf4371e6f3177b5b1e4c5d60 (diff)
downloadescapevelocity-b515e83e6e713ff7ae6035c181b968ed67cb59db.tar.gz
Sync from internal
--- Cache Method objects per template rather than per template evaluation. In a somewhat artificial benchmark, this sped up evaluation by 35%. The benchmark compiles AutoValueTest.java 100 times, and measures how much time was spent by AutoValueProcessor in template evaluation. AutoValueTest.java has 40 @AutoValue classes, and each of those triggers a separate template evaluation. Previously every one of those created a new Method cache (MethodFinder object). Now only the first one (on each iteration of the benchmark) does. Compilation runs will rarely have as many as 40 @AutoValue classes, but they have often have several, so there is still some benefit. According to this benchmark, EscapeVelocity and Apache Velocity now have indistinguishable performance. Internal change: 245835876 --- Avoid excessive reflection overhead by caching the results of method lookups. On an ad-hoc benchmark this improved template evaluation time by 38%. That means that code generators such as AutoValue that use EscapeVelocity should see a substantial speedup. Internal change: 244671738 --- If $foo is a Map then Velocity interprets $foo.bar the same as $foo["bar"]. Previously EscapeVelocity interpreted it the same as for other objects, by looking for a getBar() method (or boolean isBar()). It turns out that autoannotation.vm was depending on the old behaviour, so fix that. Internal change: 244364373
Diffstat (limited to 'src/main/java/com/google/escapevelocity/Parser.java')
-rw-r--r--src/main/java/com/google/escapevelocity/Parser.java122
1 files changed, 84 insertions, 38 deletions
diff --git a/src/main/java/com/google/escapevelocity/Parser.java b/src/main/java/com/google/escapevelocity/Parser.java
index 17c7fba..4416c48 100644
--- a/src/main/java/com/google/escapevelocity/Parser.java
+++ b/src/main/java/com/google/escapevelocity/Parser.java
@@ -15,6 +15,13 @@
*/
package com.google.escapevelocity;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.primitives.Chars;
+import com.google.common.primitives.Ints;
import com.google.escapevelocity.DirectiveNode.SetNode;
import com.google.escapevelocity.ExpressionNode.BinaryExpressionNode;
import com.google.escapevelocity.ExpressionNode.NotExpressionNode;
@@ -31,13 +38,6 @@ import com.google.escapevelocity.TokenNode.ForEachTokenNode;
import com.google.escapevelocity.TokenNode.IfTokenNode;
import com.google.escapevelocity.TokenNode.MacroDefinitionTokenNode;
import com.google.escapevelocity.TokenNode.NestedTokenNode;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Verify;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Iterables;
-import com.google.common.primitives.Chars;
-import com.google.common.primitives.Ints;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
@@ -419,10 +419,11 @@ class Parser {
private Node parseParse() throws IOException {
expect('(');
skipSpace();
- if (c != '"') {
+ if (c != '"' && c != '\'') {
throw parseException("#parse only supported with string literal argument");
}
- String nestedResourceName = readStringLiteral();
+ ExpressionNode nestedResourceNameExpression = parseStringLiteral(c, false);
+ String nestedResourceName = nestedResourceNameExpression.evaluate(null).toString();
expect(')');
try (Reader nestedReader = resourceOpener.openResource(nestedResourceName)) {
Parser nestedParser = new Parser(nestedReader, nestedResourceName, resourceOpener);
@@ -438,7 +439,7 @@ class Parser {
* $<id> <macro-parameter-list>
* }</pre>
*
- * <p>Macro parameters are not separated by commas, though method-reference parameters are.
+ * <p>Macro parameters are optionally separated by commas.
*/
private Node parseMacroDefinition() throws IOException {
expect('(');
@@ -451,6 +452,10 @@ class Parser {
next();
break;
}
+ if (c == ',') {
+ next();
+ skipSpace();
+ }
if (c != '$') {
throw parseException("Macro parameters should look like $name");
}
@@ -518,15 +523,12 @@ class Parser {
int startLine = lineNumber();
int lastC = '\0';
next();
- while (!(lastC == '*' && c == '#')) {
- if (c == EOF) {
- throw new ParseException(
- "Unterminated #* - did not see matching *#", resourceName, startLine);
- }
+ // Consistently with Velocity, we do not make it an error if a #* comment is not closed.
+ while (!(lastC == '*' && c == '#') && c != EOF) {
lastC = c;
next();
}
- next();
+ next(); // this may read EOF twice, which works
return new CommentTokenNode(resourceName, startLine);
}
@@ -889,7 +891,6 @@ class Parser {
}
}
-
/**
* Parses an expression containing only literals or references.
* <pre>{@code
@@ -905,7 +906,9 @@ class Parser {
next();
node = parseRequiredReference();
} else if (c == '"') {
- node = parseStringLiteral();
+ node = parseStringLiteral(c, true);
+ } else if (c == '\'') {
+ node = parseStringLiteral(c, false);
} else if (c == '-') {
// Velocity does not have a negation operator. If we see '-' it must be the start of a
// negative integer literal.
@@ -922,30 +925,73 @@ class Parser {
return node;
}
- private ExpressionNode parseStringLiteral() throws IOException {
- return new ConstantExpressionNode(resourceName, lineNumber(), readStringLiteral());
- }
-
- private String readStringLiteral() throws IOException {
- assert c == '"';
- StringBuilder sb = new StringBuilder();
+ /**
+ * Parses a string literal, which may contain references to be expanded. Examples are
+ * {@code "foo"} or {@code "foo${bar}baz"}.
+ * <pre>{@code
+ * <string-literal> -> <double-quote-literal> | <single-quote-literal>
+ * <double-quote-literal> -> " <double-quote-string-contents> "
+ * <double-quote-string-contents> -> <empty> |
+ * <reference> <double-quote-string-contents> |
+ * <character-other-than-"> <double-quote-string-contents>
+ * <single-quote-literal> -> ' <single-quote-string-contents> '
+ * <single-quote-string-contents> -> <empty> |
+ * <character-other-than-'> <single-quote-string-contents>
+ * }</pre>
+ */
+ private ExpressionNode parseStringLiteral(int quote, boolean allowReferences)
+ throws IOException {
+ assert c == quote;
next();
- while (c != '"') {
- if (c == '\n' || c == EOF) {
- throw parseException("Unterminated string constant");
- }
- if (c == '$' || c == '\\') {
- // In real Velocity, you can have a $ reference expanded inside a "" string literal.
- // There are also '' string literals where that is not so. We haven't needed that yet
- // so it's not supported.
- throw parseException(
- "Escapes or references in string constants are not currently supported");
+ ImmutableList.Builder<Node> nodes = ImmutableList.builder();
+ StringBuilder sb = new StringBuilder();
+ while (c != quote) {
+ switch (c) {
+ case '\n':
+ case EOF:
+ throw parseException("Unterminated string constant");
+ case '\\':
+ throw parseException(
+ "Escapes in string constants are not currently supported");
+ case '$':
+ if (allowReferences) {
+ if (sb.length() > 0) {
+ nodes.add(new ConstantExpressionNode(resourceName, lineNumber(), sb.toString()));
+ sb.setLength(0);
+ }
+ next();
+ nodes.add(parseReference());
+ break;
+ }
+ // fall through
+ default:
+ sb.appendCodePoint(c);
+ next();
}
- sb.appendCodePoint(c);
- next();
}
next();
- return sb.toString();
+ if (sb.length() > 0) {
+ nodes.add(new ConstantExpressionNode(resourceName, lineNumber(), sb.toString()));
+ }
+ return new StringLiteralNode(resourceName, lineNumber(), nodes.build());
+ }
+
+ private static class StringLiteralNode extends ExpressionNode {
+ private final ImmutableList<Node> nodes;
+
+ StringLiteralNode(String resourceName, int lineNumber, ImmutableList<Node> nodes) {
+ super(resourceName, lineNumber);
+ this.nodes = nodes;
+ }
+
+ @Override
+ Object evaluate(EvaluationContext context) {
+ StringBuilder sb = new StringBuilder();
+ for (Node node : nodes) {
+ sb.append(node.evaluate(context));
+ }
+ return sb.toString();
+ }
}
private ExpressionNode parseIntLiteral(String prefix) throws IOException {