java.lang.Object
com.google.auto.value.processor.escapevelocity.Parser

class Parser extends Object
A parser that reads input from the given Reader and parses it to produce a Template.
  • Field Details

    • EOF

      private static final int EOF
      See Also:
    • reader

      private final LineNumberReader reader
    • resourceName

      private final String resourceName
    • resourceOpener

      private final Template.ResourceOpener resourceOpener
    • c

      private int c
      The invariant of this parser is that c is always the next character of interest. This means that we almost 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.

      Sometimes we need to read two characters ahead, and in that case we use pushback.

    • pushback

      private int pushback
      A single character of pushback. If this is not negative, the next() method will return it instead of reading a character.
    • CODE_POINT_TO_OPERATORS

      private static final com.google.common.collect.ImmutableListMultimap<Integer,Parser.Operator> CODE_POINT_TO_OPERATORS
      Maps a code point to the operators that begin with that code point. For example, maps < to LESS and LESS_OR_EQUAL.
    • ASCII_LETTER

      private static final com.google.common.base.CharMatcher ASCII_LETTER
    • ASCII_DIGIT

      private static final com.google.common.base.CharMatcher ASCII_DIGIT
    • ID_CHAR

      private static final com.google.common.base.CharMatcher ID_CHAR
  • Constructor Details

  • Method Details

    • parse

      Template parse() throws IOException
      Parse the input completely to produce a 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 #end
       
      is 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.
      Throws:
      IOException
    • parseTokens

      private com.google.common.collect.ImmutableList<Node> parseTokens() throws IOException
      Throws:
      IOException
    • lineNumber

      private int lineNumber()
    • next

      private void next() throws IOException
      Gets the next character from the reader and assigns it to c. If there are no more characters, sets c to EOF if it is not already.
      Throws:
      IOException
    • pushback

      private void pushback(int c1)
      Saves the current character c to be read again, and sets c to the given c1. Suppose the text contains xy and we have just read y. So c == 'y'. Now if we execute pushback('x'), we will have c == 'x' and the next call to next() will set c == 'y'. Subsequent calls to next() will continue reading from reader. So the pushback essentially puts us back in the state we were in before we read y.
    • skipSpace

      private void skipSpace() throws IOException
      If c is a space character, keeps reading until c is a non-space character or there are no more characters.
      Throws:
      IOException
    • nextNonSpace

      private void nextNonSpace() throws IOException
      Gets the next character from the reader, and if it is a space character, keeps reading until a non-space character is found.
      Throws:
      IOException
    • expect

      private void expect(char expected) throws IOException
      Skips any space in the reader, and then throws an exception if the first non-space character found is not the expected one. Sets c to the first character after that expected one.
      Throws:
      IOException
    • parseNode

      private Node parseNode() throws IOException
      Parses a single node from the reader, as part of the first parsing phase.
      
       <template> -> <empty> |
                     <directive> <template> |
                     <non-directive> <template>
       
      Throws:
      IOException
    • parseHashSquare

      private Node parseHashSquare() throws IOException
      Throws:
      IOException
    • parseNonDirective

      private Node parseNonDirective() throws IOException
      Parses a single non-directive node from the reader.
      
       <non-directive> -> <reference> |
                          <text containing neither $ nor #>
       
      Throws:
      IOException
    • parseDirective

      private Node parseDirective() throws IOException
      Parses a single directive token from the reader. Directives can be spelled with or without braces, for example #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> |
                      <parse-token> |
                      <macro-token> |
                      <macro-call> |
                      <comment>
       
      Throws:
      IOException
    • parseIfOrElseIf

      private Node parseIfOrElseIf(String directive) throws IOException
      Parses the condition following #if or #elseif.
      
       <if-token> -> #if ( <condition> )
       <elseif-token> -> #elseif ( <condition> )
       
      Parameters:
      directive - either "if" or "elseif".
      Throws:
      IOException
    • parseForEach

      private Node parseForEach() throws IOException
      Parses a #foreach token from the reader.
      
       <foreach-token> -> #foreach ( $<id> in <expression> )
       
      Throws:
      IOException
    • parseSet

      private Node parseSet() throws IOException
      Parses a #set token from the reader.
      
       <set-token> -> #set ( $<id> = <expression>)
       
      Throws:
      IOException
    • parseParse

      private Node parseParse() throws IOException
      Parses a #parse token from the reader.
      
       <parse-token> -> #parse ( <string-literal> )
       

      The way this works is inconsistent with Velocity. In Velocity, the #parse directive is evaluated when it is encountered during template evaluation. That means that the argument can be a variable, and it also means that you can use #if to choose whether or not to do the #parse. Neither of those is true in EscapeVelocity. The contents of the #parse are integrated into the containing template pretty much as if they had been written inline. That also means that EscapeVelocity allows forward references to macros inside #parse directives, which Velocity does not.

      Throws:
      IOException
    • parseMacroDefinition

      private Node parseMacroDefinition() throws IOException
      Parses a #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.

      Throws:
      IOException
    • parsePossibleMacroCall

      private Node parsePossibleMacroCall(String directive) throws IOException
      Parses an identifier after # 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> | ,
       
      Throws:
      IOException
    • parseLineComment

      private Node parseLineComment() throws IOException
      Parses and discards a line comment, which is ## followed by any number of characters up to and including the next newline.
      Throws:
      IOException
    • parseBlockComment

      private Node parseBlockComment() throws IOException
      Parses and discards a block comment, which is #* followed by everything up to and including the next *#.
      Throws:
      IOException
    • parsePlainText

      private Node parsePlainText(int firstChar) throws IOException
      Parses plain text, which is text that contains neither $ 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).
      Throws:
      IOException
    • parsePlainText

      private Node parsePlainText(StringBuilder sb) throws IOException
      Throws:
      IOException
    • parseReference

      private Node parseReference() throws IOException
      Parses a reference, which is everything that can start with a $. 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 $.

      Throws:
      IOException
    • parseRequiredReference

      private ReferenceNode parseRequiredReference() throws IOException
      Same as parseReference(), except it really must be a reference. A $ in normal text doesn't start a reference if it is not followed by an identifier. But in an expression, for example in #if ($x == 23), $ must be followed by an identifier.
      Throws:
      IOException
    • parseReferenceNoBrace

      private ReferenceNode parseReferenceNoBrace() throws IOException
      Parses a reference, in the simple form without braces.
      
       <reference-no-brace> -> <id><reference-suffix>
       
      Throws:
      IOException
    • parseReferenceSuffix

      private ReferenceNode parseReferenceSuffix(ReferenceNode lhs) throws IOException
      Parses the modifiers that can appear at the tail of a reference.
      
       <reference-suffix> -> <empty> |
                             <reference-member> |
                             <reference-index>
       
      Parameters:
      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.
      Throws:
      IOException
    • parseReferenceMember

      private ReferenceNode parseReferenceMember(ReferenceNode lhs) throws IOException
      Parses a reference member, which is either a property reference like $x.y or a method call like $x.y($z).
      
       <reference-member> -> .<id><reference-property-or-method><reference-suffix>
       <reference-property-or-method> -> <id> |
                                         <id> ( <method-parameter-list> )
       
      Parameters:
      lhs - the reference node representing what appears to the left of the dot, like the $x in $x.foo or $x.foo().
      Throws:
      IOException
    • parseReferenceMethodParams

      private ReferenceNode parseReferenceMethodParams(ReferenceNode lhs, String id) throws IOException
      Parses the parameters to a method reference, like $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>
       
      Parameters:
      lhs - the reference node representing what appears to the left of the dot, like the $x in $x.foo().
      Throws:
      IOException
    • parseReferenceIndex

      private ReferenceNode parseReferenceIndex(ReferenceNode lhs) throws IOException
      Parses an index suffix to a method, like $x[$i].
      
       <reference-index> -> [ <expression> ]
       
      Parameters:
      lhs - the reference node representing what appears to the left of the dot, like the $x in $x[$i].
      Throws:
      IOException
    • parseExpression

      private ExpressionNode parseExpression() throws IOException
      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).
      
       <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> -> * | / | %
       
      Throws:
      IOException
    • parseUnaryExpression

      private ExpressionNode parseUnaryExpression() throws IOException
      Parses an expression not containing any operators (except inside parentheses).
      
       <unary-expression> -> <primary> |
                             ( <expression> ) |
                             ! <unary-expression>
       
      Throws:
      IOException
    • parsePrimary

      private ExpressionNode parsePrimary() throws IOException
      Parses an expression containing only literals or references.
      
       <primary> -> <reference> |
                    <string-literal> |
                    <integer-literal> |
                    <boolean-literal>
       
      Throws:
      IOException
    • parseStringLiteral

      private ExpressionNode parseStringLiteral() throws IOException
      Throws:
      IOException
    • readStringLiteral

      private String readStringLiteral() throws IOException
      Throws:
      IOException
    • parseIntLiteral

      private ExpressionNode parseIntLiteral(String prefix) throws IOException
      Throws:
      IOException
    • parseBooleanLiteral

      private ExpressionNode parseBooleanLiteral() throws IOException
      Parses a boolean literal, either true or false. <boolean-literal> -> true | false
      Throws:
      IOException
    • isAsciiLetter

      private static boolean isAsciiLetter(int c)
    • isAsciiDigit

      private static boolean isAsciiDigit(int c)
    • isIdChar

      private static boolean isIdChar(int c)
    • parseId

      private String parseId(String what) throws IOException
      Parse an identifier as specified by the VTL . Identifiers are ASCII: starts with a letter, then letters, digits, - and _.
      Throws:
      IOException
    • parseException

      private ParseException parseException(String message) throws IOException
      Returns an exception to be thrown describing a parse error with the given message, and including information about where it occurred.
      Throws:
      IOException