class Parser
extends java.lang.Object
Reader
and parses it to produce a
Template
.Modifier and Type | Class and Description |
---|---|
(package private) static class |
Parser.Operator |
private class |
Parser.OperatorParser
An operator-precedence parser for the binary operations we understand.
|
Modifier and Type | Field and Description |
---|---|
private static com.google.common.base.CharMatcher |
ASCII_DIGIT |
private static com.google.common.base.CharMatcher |
ASCII_LETTER |
private int |
c
The invariant of this parser is that
c is always the next character of interest. |
private static com.google.common.collect.ImmutableListMultimap<java.lang.Integer,Parser.Operator> |
CODE_POINT_TO_OPERATORS
Maps a code point to the operators that begin with that code point.
|
private static int |
EOF |
private static com.google.common.base.CharMatcher |
ID_CHAR |
private java.io.LineNumberReader |
reader |
Constructor and Description |
---|
Parser(java.io.Reader reader) |
Modifier and Type | Method and Description |
---|---|
private void |
expect(char expected)
Skips any space in the reader, and then throws an exception if the first non-space character
found is not the expected one.
|
private static boolean |
isAsciiDigit(int c) |
private static boolean |
isAsciiLetter(int c) |
private static boolean |
isIdChar(int c) |
private int |
lineNumber() |
private void |
next()
Gets the next character from the reader and assigns it to
c . |
private void |
nextNonSpace()
Gets the next character from the reader, and if it is a space character, keeps reading until
a non-space character is found.
|
(package private) Template |
parse()
Parse the input completely to produce a
Template . |
private ExpressionNode |
parseBooleanLiteral()
Parses a boolean literal, either
true or false . |
private Node |
parseComment()
Parses and discards a comment, which is
## followed by any number of characters up to
and including the next newline. |
private Node |
parseDirective()
Parses a single directive token from the reader.
|
private ParseException |
parseException(java.lang.String message)
Returns an exception to be thrown describing a parse error with the given message, and
including information about where it occurred.
|
private ExpressionNode |
parseExpression()
Parses an expression, which can occur within a directive like
#if or #set ,
or within a reference like $x[$a + $b] or $x.m($a + $b) . |
private Node |
parseForEach()
Parses a
#foreach token from the reader. |
private java.lang.String |
parseId(java.lang.String what)
Parse an identifier as specified by the
VTL
.
|
private Node |
parseIfOrElseIf(java.lang.String directive)
Parses the condition following
#if or #elseif . |
private ExpressionNode |
parseIntLiteral(java.lang.String prefix) |
private Node |
parseMacroDefinition()
Parses a
#macro token from the reader. |
private Node |
parseNode()
Parses a single node from the reader, as part of the first parsing phase.
|
private Node |
parseNonDirective()
Parses a single non-directive node from the reader.
|
private Node |
parsePlainText(int firstChar)
Parses plain text, which is text that contains neither
$ nor # . |
private Node |
parsePossibleMacroCall(java.lang.String directive)
Parses an identifier after
# that is not one of the standard directives. |
private ExpressionNode |
parsePrimary()
Parses an expression containing only literals or references.
|
private ReferenceNode |
parseReference()
Parses a reference, which is everything that can start with a
$ . |
private ReferenceNode |
parseReferenceIndex(ReferenceNode lhs)
Parses an index suffix to a method, like
$x[$i] . |
private ReferenceNode |
parseReferenceMember(ReferenceNode lhs)
Parses a reference member, which is either a property reference like
$x.y or a method
call like $x.y($z) . |
private ReferenceNode |
parseReferenceMethodParams(ReferenceNode lhs,
java.lang.String id)
Parses the parameters to a method reference, like
$foo.bar($a, $b) . |
private ReferenceNode |
parseReferenceNoBrace()
Parses a reference, in the simple form without braces.
|
private ReferenceNode |
parseReferenceSuffix(ReferenceNode lhs)
Parses the modifiers that can appear at the tail of a reference.
|
private Node |
parseSet()
Parses a
#set token from the reader. |
private ExpressionNode |
parseStringLiteral() |
private ExpressionNode |
parseUnaryExpression()
Parses an expression not containing any operators (except inside parentheses).
|
private void |
skipSpace()
If
c is a space character, keeps reading until c is a non-space character or
there are no more characters. |
private static final int EOF
private final java.io.LineNumberReader reader
private int c
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, c
will be the first character after the integer, which is exactly
the state we will be in when there are no more digits.private static final com.google.common.collect.ImmutableListMultimap<java.lang.Integer,Parser.Operator> CODE_POINT_TO_OPERATORS
<
to LESS
and LESS_OR_EQUAL
.private static final com.google.common.base.CharMatcher ASCII_LETTER
private static final com.google.common.base.CharMatcher ASCII_DIGIT
private static final com.google.common.base.CharMatcher ID_CHAR
Parser(java.io.Reader reader) throws java.io.IOException
java.io.IOException
Template parse() throws java.io.IOException
Template
.
Parsing happens in two phases. First, we parse a sequence of "tokens", where tokens include entire references such as
${x.foo()[23]}or entire directives such as
#set ($x = $y + $z)But tokens do not span complex constructs. For example,
#if ($x == $y) something #endis three tokens:
#if ($x == $y) (literal text " something ") #end
The second phase then takes the sequence of tokens and constructs a parse tree out of it. Some nodes in the parse tree will be unchanged from the token sequence, such as the
${x.foo()[23]} #set ($x = $y + $z)examples above. But a construct such as the
#if ... #end
mentioned above will
become a single IfNode in the parse tree in the second phase.
The main reason for this approach is that Velocity has two kinds of lexical contexts. At the
top level, there can be arbitrary literal text; references like ${x.foo()}
; and
directives like #if
or #set
. Inside the parentheses of a directive, however,
neither arbitrary text nor directives can appear, but expressions can, so we need to tokenize
the inside of
#if ($x == $a + $b)as the five tokens "$x", "==", "$a", "+", "$b". Rather than having a classical parser/lexer combination, where the lexer would need to switch between these two modes, we replace the lexer with an ad-hoc parser that is the first phase described above, and we define a simple parser over the resultant tokens that is the second phase.
java.io.IOException
private int lineNumber()
private void next() throws java.io.IOException
c
. If there are no more
characters, sets c
to EOF
if it is not already.java.io.IOException
private void skipSpace() throws java.io.IOException
c
is a space character, keeps reading until c
is a non-space character or
there are no more characters.java.io.IOException
private void nextNonSpace() throws java.io.IOException
java.io.IOException
private void expect(char expected) throws java.io.IOException
c
to the first character after that expected one.java.io.IOException
private Node parseNode() throws java.io.IOException
<template> -> <empty> |
<directive> <template> |
<non-directive> <template>
java.io.IOException
private Node parseNonDirective() throws java.io.IOException
<non-directive> -> <reference> |
<text containing neither $ nor #>
java.io.IOException
private Node parseDirective() throws java.io.IOException
#if
or #{if}
. We omit the brace spelling in the productions
here:
<directive> -> <if-token> |
<else-token> |
<elseif-token> |
<end-token> |
<foreach-token> |
<set-token> |
<macro-token> |
<macro-call> |
<comment>
java.io.IOException
private Node parseIfOrElseIf(java.lang.String directive) throws java.io.IOException
#if
or #elseif
.
<if-token> -> #if ( <condition> )
<elseif-token> -> #elseif ( <condition> )
directive
- either "if"
or "elseif"
.java.io.IOException
private Node parseForEach() throws java.io.IOException
#foreach
token from the reader.
<foreach-token> -> #foreach ( $<id> in <expression> )
java.io.IOException
private Node parseSet() throws java.io.IOException
#set
token from the reader.
<set-token> -> #set ( $<id> = <expression>)
java.io.IOException
private Node parseMacroDefinition() throws java.io.IOException
#macro
token from the reader.
<macro-token> -> #macro ( <id> <macro-parameter-list> )
<macro-parameter-list> -> <empty> |
$<id> <macro-parameter-list>
Macro parameters are not separated by commas, though method-reference parameters are.
java.io.IOException
private Node parsePossibleMacroCall(java.lang.String directive) throws java.io.IOException
#
that is not one of the standard directives. The assumption
is that it is a call of a macro that is defined in the template. Macro definitions are
extracted from the template during the second parsing phase (and not during evaluation of the
template as you might expect). This means that a macro can be called before it is defined.
<macro-call> -> # <id> ( <expression-list> )
<expression-list> -> <empty> |
<expression> <optional-comma> <expression-list>
<optional-comma> -> <empty> | ,
java.io.IOException
private Node parseComment() throws java.io.IOException
##
followed by any number of characters up to
and including the next newline.java.io.IOException
private Node parsePlainText(int firstChar) throws java.io.IOException
$
nor #
. The given
firstChar
is the first character of the plain text, and c
is the second
(if the plain text is more than one character).java.io.IOException
private ReferenceNode parseReference() throws java.io.IOException
$
. References can
optionally be enclosed in braces, so $x
and ${x}
are the same. Braces are
useful when text after the reference would otherwise be parsed as part of it. For example,
${x}y
is a reference to the variable $x
, followed by the plain text y
.
Of course $xy
would be a reference to the variable $xy
.
<reference> -> $<reference-no-brace> |
${<reference-no-brace>}
On entry to this method, c
is the character immediately after the $
.
java.io.IOException
private ReferenceNode parseReferenceNoBrace() throws java.io.IOException
<reference-no-brace> -> <id><reference-suffix>
java.io.IOException
private ReferenceNode parseReferenceSuffix(ReferenceNode lhs) throws java.io.IOException
<reference-suffix> -> <empty> |
<reference-member> |
<reference-index>
lhs
- the reference node representing the first part of the reference
$x
in $x.foo
or $x.foo()
, or later $x.y
in $x.y.z
.java.io.IOException
private ReferenceNode parseReferenceMember(ReferenceNode lhs) throws java.io.IOException
$x.y
or a method
call like $x.y($z)
.
<reference-member> -> .<id><reference-method-or-property><reference-suffix>
<reference-method-or-property> -> <id> |
<id> ( <method-parameter-list> )
lhs
- the reference node representing what appears to the left of the dot, like the
$x
in $x.foo
or $x.foo()
.java.io.IOException
private ReferenceNode parseReferenceMethodParams(ReferenceNode lhs, java.lang.String id) throws java.io.IOException
$foo.bar($a, $b)
.
<method-parameter-list> -> <empty> |
<non-empty-method-parameter-list>
<non-empty-method-parameter-list> -> <expression> |
<expression> , <non-empty-method-parameter-list>
lhs
- the reference node representing what appears to the left of the dot, like the
$x
in $x.foo()
.java.io.IOException
private ReferenceNode parseReferenceIndex(ReferenceNode lhs) throws java.io.IOException
$x[$i]
.
<reference-index> -> [ <expression> ]
lhs
- the reference node representing what appears to the left of the dot, like the
$x
in $x[$i]
.java.io.IOException
private ExpressionNode parseExpression() throws java.io.IOException
#if
or #set
,
or within a reference like $x[$a + $b]
or $x.m($a + $b)
.
<expression> -> <and-expression> |
<expression> || <and-expression>
<and-expression> -> <relational-expression> |
<and-expression> && <relational-expression>
<equality-exression> -> <relational-expression> |
<equality-expression> <equality-op> <relational-expression>
<equality-op> -> == | !=
<relational-expression> -> <additive-expression> |
<relational-expression> <relation> <additive-expression>
<relation> -> < | <= | > | >=
<additive-expression> -> <multiplicative-expression> |
<additive-expression> <add-op> <multiplicative-expression>
<add-op> -> + | -
<multiplicative-expression> -> <unary-expression> |
<multiplicative-expression> <mult-op> <unary-expression>
<mult-op> -> * | / | %
java.io.IOException
private ExpressionNode parseUnaryExpression() throws java.io.IOException
<unary-expression> -> <primary> |
( <expression> ) |
! <unary-expression>
java.io.IOException
private ExpressionNode parsePrimary() throws java.io.IOException
<primary> -> <reference> |
<string-literal> |
<integer-literal> |
<boolean-literal>
java.io.IOException
private ExpressionNode parseStringLiteral() throws java.io.IOException
java.io.IOException
private ExpressionNode parseIntLiteral(java.lang.String prefix) throws java.io.IOException
java.io.IOException
private ExpressionNode parseBooleanLiteral() throws java.io.IOException
true
or false
.
java.io.IOException
private static boolean isAsciiLetter(int c)
private static boolean isAsciiDigit(int c)
private static boolean isIdChar(int c)
private java.lang.String parseId(java.lang.String what) throws java.io.IOException
-
and
_
.java.io.IOException
private ParseException parseException(java.lang.String message) throws java.io.IOException
java.io.IOException