aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--value/src/main/java/com/google/auto/value/processor/escapevelocity/Parser.java63
-rw-r--r--value/src/test/java/com/google/auto/value/processor/escapevelocity/TemplateTest.java13
2 files changed, 70 insertions, 6 deletions
diff --git a/value/src/main/java/com/google/auto/value/processor/escapevelocity/Parser.java b/value/src/main/java/com/google/auto/value/processor/escapevelocity/Parser.java
index 53915161..73e360b4 100644
--- a/value/src/main/java/com/google/auto/value/processor/escapevelocity/Parser.java
+++ b/value/src/main/java/com/google/auto/value/processor/escapevelocity/Parser.java
@@ -74,12 +74,20 @@ class Parser {
/**
* The invariant of this parser is that {@code c} is always the next character of interest.
- * This means that we never have to "unget" a character by reading too far. For example, after
- * we parse an integer, {@code c} will be the first character after the integer, which is exactly
- * the state we will be in when there are no more digits.
+ * This means that we almost never have to "unget" a character by reading too far. For example,
+ * after we parse an integer, {@code c} will be the first character after the integer, which is
+ * exactly the state we will be in when there are no more digits.
+ *
+ * <p>Sometimes we need to read two characters ahead, and in that case we use {@link #pushback}.
*/
private int c;
+ /**
+ * A single character of pushback. If this is not negative, the {@link #next()} method will
+ * return it instead of reading a character.
+ */
+ private int pushback = -1;
+
Parser(Reader reader, String resourceName, Template.ResourceOpener resourceOpener)
throws IOException {
this.reader = new LineNumberReader(reader);
@@ -148,11 +156,29 @@ class Parser {
*/
private void next() throws IOException {
if (c != EOF) {
- c = reader.read();
+ if (pushback < 0) {
+ c = reader.read();
+ } else {
+ c = pushback;
+ pushback = -1;
+ }
}
}
/**
+ * Saves the current character {@code c} to be read again, and sets {@code c} to the given
+ * {@code c1}. Suppose the text contains {@code xy} and we have just read {@code y}.
+ * So {@code c == 'y'}. Now if we execute {@code pushback('x')}, we will have
+ * {@code c == 'x'} and the next call to {@link #next()} will set {@code c == 'y'}. Subsequent
+ * calls to {@code next()} will continue reading from {@link #reader}. So the pushback
+ * essentially puts us back in the state we were in before we read {@code y}.
+ */
+ private void pushback(int c1) {
+ pushback = c;
+ c = c1;
+ }
+
+ /**
* If {@code c} is a space character, keeps reading until {@code c} is a non-space character or
* there are no more characters.
*/
@@ -562,7 +588,27 @@ class Parser {
*
* <p>On entry to this method, {@link #c} is the character immediately after the {@code $}.
*/
- private ReferenceNode parseReference() throws IOException {
+ private Node parseReference() throws IOException {
+ if (c == '{') {
+ next();
+ if (!isAsciiLetter(c)) {
+ return parsePlainText(new StringBuilder("${"));
+ }
+ ReferenceNode node = parseReferenceNoBrace();
+ expect('}');
+ return node;
+ } else {
+ return parseReferenceNoBrace();
+ }
+ }
+
+ /**
+ * Same as {@link #parseReference()}, except it really must be a reference. A {@code $} in
+ * normal text doesn't start a reference if it is not followed by an identifier. But in an
+ * expression, for example in {@code #if ($x == 23)}, {@code $} must be followed by an
+ * identifier.
+ */
+ private ReferenceNode parseRequiredReference() throws IOException {
if (c == '{') {
next();
ReferenceNode node = parseReferenceNoBrace();
@@ -622,6 +668,11 @@ class Parser {
private ReferenceNode parseReferenceMember(ReferenceNode lhs) throws IOException {
assert c == '.';
next();
+ if (!isAsciiLetter(c)) {
+ // We've seen something like `$foo.!`, so it turns out it's not a member after all.
+ pushback('.');
+ return lhs;
+ }
String id = parseId("Member");
ReferenceNode reference;
if (c == '(') {
@@ -868,7 +919,7 @@ class Parser {
ExpressionNode node;
if (c == '$') {
next();
- node = parseReference();
+ node = parseRequiredReference();
} else if (c == '"') {
node = parseStringLiteral();
} else if (c == '-') {
diff --git a/value/src/test/java/com/google/auto/value/processor/escapevelocity/TemplateTest.java b/value/src/test/java/com/google/auto/value/processor/escapevelocity/TemplateTest.java
index 16b06b83..32bd0103 100644
--- a/value/src/test/java/com/google/auto/value/processor/escapevelocity/TemplateTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/escapevelocity/TemplateTest.java
@@ -203,6 +203,11 @@ public class TemplateTest {
compare("=${t.name}=", ImmutableMap.of("t", Thread.currentThread()));
}
+ @Test
+ public void substituteNotPropertyId() {
+ compare("$foo.!", ImmutableMap.of("foo", false));
+ }
+
/* TODO(emcmanus): make this work.
@Test
public void substituteNotPropertyId() {
@@ -699,6 +704,14 @@ public class TemplateTest {
}
@Test
+ public void badBraceReference() throws IOException {
+ String template = "line 1\nline 2\nbar${foo.!}baz";
+ thrown.expect(ParseException.class);
+ thrown.expectMessage("Expected }, on line 3, at text starting: .!}baz");
+ Template.parseFrom(new StringReader(template));
+ }
+
+ @Test
public void undefinedMacro() throws IOException {
String template = "#oops()";
thrown.expect(ParseException.class);