All Downloads are FREE. Search and download functionalities are using the official Maven repository.

freemarker.core.FMParser.jj Maven / Gradle / Ivy

/*
 * Copyright (c) 2003 The Visigoth Software Society. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Visigoth Software Society (http://www.visigoths.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 
 *    project contributors may be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
 *    nor may "FreeMarker" or "Visigoth" appear in their names
 *    without prior written permission of the Visigoth Software Society.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Visigoth Software Society. For more
 * information on the Visigoth Software Society, please see
 * http://www.visigoths.org/
 */

options
{
   STATIC=false;
   UNICODE_INPUT=true;
//   DEBUG_TOKEN_MANAGER=true;
//   DEBUG_PARSER=true;
}

PARSER_BEGIN(FMParser)

package freemarker.core;

import freemarker.template.*;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.DeepUnwrap;
import java.io.*;
import java.util.*;

/**
 * This class is generated by JavaCC from a grammar file.
 */
public class FMParser {

// Necessary for adding macros and setting location info.
  Template template;
  private String templateName;

// variables that keep track of whether we are in a loop or a switch.
  private int loopNesting, switchNesting;
  private boolean inMacro, inFunction, stripWhitespace, stripText;
  private LinkedList escapes = new LinkedList();
  private int contentNesting; // for stripText
  
  /**
   * Create an FM expression parser using a string.
   */
  static public FMParser createExpressionParser(String s) {
      SimpleCharStream scs = new SimpleCharStream(new StringReader(s), 1, 1, s.length());
      FMParserTokenManager token_source = new FMParserTokenManager(scs);
      token_source.SwitchTo(FMParserConstants.FM_EXPRESSION);
      return new FMParser(token_source);
  }
  
  /**
   * Constructs a new parser object.
   * @param template The template associated with this parser.
   * @param reader The character stream to use as input
   * @param strictEscapeSyntax Whether FreeMarker directives must start with a #
   */
  public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace) {
      this(reader);
      this.template = template;
      token_source.strictEscapeSyntax = strictEscapeSyntax;
      this.templateName = template != null ? template.getName() : "";
      token_source.templateName = templateName;
      this.stripWhitespace = stripWhitespace;
  }
  
  public FMParser(Template template, Reader reader, boolean strictEscapeSyntax, boolean stripWhitespace, int tagSyntax) {
      this(template, reader, strictEscapeSyntax, stripWhitespace);
      switch (tagSyntax) {
          case Configuration.AUTO_DETECT_TAG_SYNTAX : 
	     token_source.autodetectTagSyntax = true;
	     break;
	  case Configuration.ANGLE_BRACKET_TAG_SYNTAX :
	     token_source.altDirectiveSyntax = false;
	     break;
	  case Configuration.SQUARE_BRACKET_TAG_SYNTAX :
	     token_source.altDirectiveSyntax = true;
	     break;
	  default : throw new IllegalArgumentException("Illegal argument for tagSyntax");
      }
  }
  
  public FMParser(String template) {
      this(null, new StringReader(template), true, true);
  }
  
  private String getErrorStart(Token t) {
      return "Error in template: " + template.getName()
            + "\non line " + t.beginLine + ", column " + t.beginColumn;
  }

  /**
   * Throw an exception if the expression passed in is a String
   * Literal
   */
  private void notStringLiteral(Expression exp, String expected) throws ParseException {
      if (exp instanceof StringLiteral) {
         String msg = "Error " + exp.getStartLocation()
                     + "\nFound string literal: " + exp
                     + "\nExpecting: " + expected;
         throw new ParseException(msg, exp);
      }
  }

  /**
   * Throw an exception if the expression passed in is a Number
   * Literal
   */
  private void notNumberLiteral(Expression exp, String expected) throws ParseException {
      if (exp instanceof NumberLiteral) {
         String msg = "Error " + exp.getStartLocation()
                     + "\nFound number literal: " + exp.getCanonicalForm()
                     + "\nExpecting " + expected;
         throw new ParseException(msg, exp);
      }
  }

  /**
   * Throw an exception if the expression passed in is a boolean
   * Literal
   */
  private void notBooleanLiteral(Expression exp, String expected) throws ParseException {
      if (exp instanceof BooleanLiteral) {
         String msg = "Error " + exp.getStartLocation()
                     + "\nFound: " + exp.getCanonicalForm()
                     + "\nExpecting " + expected;
         throw new ParseException(msg, exp);
      }
  }

  /**
   * Throw an exception if the expression passed in is a Hash
   * Literal
   */
  private void notHashLiteral(Expression exp, String expected) throws ParseException {
        if (exp instanceof HashLiteral) {
         String msg = "Error " + exp.getStartLocation()
                     + "\nFound hash literal: " + exp.getCanonicalForm()
                     + "\nExpecting " + expected;
         throw new ParseException(msg, exp);
      }
  }

  /**
   * Throw an exception if the expression passed in is a List
   * Literal
   */

  private void notListLiteral(Expression exp, String expected)
      throws ParseException
  {
        if (exp instanceof ListLiteral) {
         String msg = "Error " + exp.getStartLocation()
                     + "\nFound list literal: " + exp.getCanonicalForm()
                     + "\nExpecting " + expected;
         throw new ParseException(msg, exp);
      }
  }
  /**
   * Throw an exception if the expression passed in is a literal
   * other than of the numerical type
   */
  private void numberLiteralOnly(Expression exp) throws ParseException {
      notStringLiteral(exp, "number");
      notListLiteral(exp, "number");
      notHashLiteral(exp, "number");
      notBooleanLiteral(exp, "number");
  }

  /**
   * Throw an exception if the expression passed in is
   * not a string.
   */
  private void stringLiteralOnly(Expression exp) throws ParseException {
      notNumberLiteral(exp, "number");
      notListLiteral(exp, "number");
      notHashLiteral(exp, "number");
      notBooleanLiteral(exp, "number");
  }

  /**
   * Throw an exception if the expression passed in is a literal
   * other than of the boolean type
   */
  private void booleanLiteralOnly(Expression exp) throws ParseException {
      notStringLiteral(exp, "boolean (true/false)");
      notListLiteral(exp, "boolean (true/false)");
      notHashLiteral(exp, "boolean (true/false)");
      notNumberLiteral(exp, "boolean (true/false)");
  }

  private Expression escapedExpression(Expression exp) {
      if(!escapes.isEmpty()) {
          return ((EscapeBlock)escapes.getFirst()).doEscape(exp);
      }
      return exp;
  }
  
  private boolean getBoolean(Expression exp) throws ParseException {
      TemplateModel tm = null;
      try {
          tm = exp.getAsTemplateModel(null);
      } catch (Exception e) {
         throw new ParseException(e.getMessage() 
                                  + "\nCould not evaluate expression: "
                                  + exp.getCanonicalForm()
                                  + exp.getStartLocation(), exp);
      }
      if (tm instanceof TemplateBooleanModel) {
          try {
             return ((TemplateBooleanModel) tm).getAsBoolean();
          } catch (TemplateModelException tme) {
          }
      }
      if (tm instanceof TemplateScalarModel) {
          try {
              return StringUtil.getYesNo(((TemplateScalarModel) tm).getAsString());
          } catch (Exception e) {
              throw new ParseException(e.getMessage()
                                       + "\nExpecting yes/no, found: " + exp.getCanonicalForm()
                                       + exp.getStartLocation(), exp);
          }
      }
      throw new ParseException("Expecting boolean (yes/no) parameter" + exp.getStartLocation(), exp);
  }
}

PARSER_END(FMParser)

/**
 * The lexer portion defines 5 lexical states:
 * DEFAULT, FM_EXPRESSION, IN_PAREN, NO_PARSE, and EXPRESSION_COMMENT.
 * The DEFAULT state is when you are parsing
 * text but are not inside a FreeMarker expression.
 * FM_EXPRESSION is the state you are in
 * when the parser wants a FreeMarker expression.
 * IN_PAREN is almost identical really. The difference
 * is that you are in this state when you are within
 * FreeMarker expression and also within (...).
 * This is a necessary subtlety because the
 * ">" and ">=" symbols can only be used
 * within parentheses because otherwise, it would
 * be ambiguous with the end of a directive.
 * So, for example, you enter the FM_EXPRESSION state
 * right after a ${ and leave it after the matching }.
 * Or, you enter the FM_EXPRESSION state right after
 * an ""
 * that ends the if directive,
 * you go back to DEFAULT lexical state.
 * If, within the FM_EXPRESSION state, you enter a
 * parenthetical expression, you enter the IN_PAREN
 * state.
 * Note that whitespace is ignored in the
 * FM_EXPRESSION and IN_PAREN states
 * but is passed through to the parser as PCDATA in the DEFAULT state.
 * NO_PARSE and EXPRESSION_COMMENT are extremely simple
 * lexical states. NO_PARSE is when you are in a comment
 * block and EXPRESSION_COMMENT is when you are in a comment
 * that is within an FTL expression.
 */

TOKEN_MGR_DECLS :
{

/**
  The noparseTag is set when we enter
  a block of text that the parser more or less ignores.
  These are  and . This variable
  tells us what the closing tag should be, and when
  we hit that, we resume parsing. Note that with this
  scheme,  and  tags cannot nest
  recursively, but it is not clear how important that is.
*/
    String noparseTag;

/**
    Keeps track of how deeply nested
    we have the hash literals.
    This is necessary since we need to be
    able to distinguish the } used to close
    a hash literal and the one used to
    close a ${
 */
 private int hashLiteralNesting;
 private int parenthesisNesting;
 private int bracketNesting;
 private boolean inFTLHeader;
 boolean strictEscapeSyntax, 
         onlyTextOutput, 
         altDirectiveSyntax,
	 autodetectTagSyntax,
         directiveSyntaxEstablished,
	 inInvocation;
 String templateName;

 
 // This method checks if we are in a strict mode where all
 // FreeMarker directives must start with <#

 private void strictSyntaxCheck(Token tok, int newLexState) {
     if (onlyTextOutput) {
        tok.kind = PRINTABLE_CHARS;
        return;
     }
     char firstChar = tok.image.charAt(0);
     if (autodetectTagSyntax && !directiveSyntaxEstablished) {
         altDirectiveSyntax = (firstChar == '[');
     }
     if ((firstChar == '[' && !altDirectiveSyntax) || (firstChar == '<' && altDirectiveSyntax)) {
        tok.kind = PRINTABLE_CHARS;
        return;
     }
     if (!strictEscapeSyntax) {
        SwitchTo(newLexState);
        return;
     }
     if (!altDirectiveSyntax) {
         if (!tok.image.startsWith("<#") && !tok.image.startsWith("0) {
        --bracketNesting;
     } else {
        tok.kind=DIRECTIVE_END;
        if (inFTLHeader) {
           eatNewline(); 
           inFTLHeader = false; 
        }     
        SwitchTo(DEFAULT);     
     }
 }
 

 private void eatNewline() {
     int charsRead = 0;
     try {
        while (true) {
           char c = input_stream.readChar();
           ++charsRead;
           if (!Character.isWhitespace(c)) {
              input_stream.backup(charsRead);
	      return;
           } else if (c=='\r') {
              char next = input_stream.readChar();
              ++charsRead;
              if (next != '\n') {
                 input_stream.backup(1);
              }
              return;
           } else if (c=='\n') {
              return;
           }
        }
     } catch (IOException ioe) {
        input_stream.backup(charsRead);
     }
 }
 
 private void ftlHeader(Token matchedToken) {
       if (!directiveSyntaxEstablished) {
           altDirectiveSyntax = matchedToken.image.charAt(0) == '[';
	   directiveSyntaxEstablished = true;
	   autodetectTagSyntax = false;
       }
       String img = matchedToken.image;
       char firstChar = img.charAt(0);
       char lastChar = img.charAt(img.length() -1);
       if ((firstChar == '[' && !altDirectiveSyntax) || (firstChar == '<' && altDirectiveSyntax)) {
          matchedToken.kind = PRINTABLE_CHARS;
       } 
       if (matchedToken.kind != PRINTABLE_CHARS) {
          if (lastChar != '>' && lastChar != ']') {
              SwitchTo(FM_EXPRESSION);
              inFTLHeader = true;
          } else {
            eatNewline();
          }
       }
 }
}

TOKEN:
{
    <#BLANK : [" ", "\t", "\n", "\r"]>
    |
    <#START_TAG : "<" | "<#" | "[#">
    |
    <#END_TAG : "
    |
    <#CLOSE_TAG1 : ()* (">" | "]")>
    |
    <#CLOSE_TAG2 : ()* ("/")? (">" | "]")>
    |
     "attempt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "recover" > {strictSyntaxCheck(matchedToken, DEFAULT);} 
    |
     "if" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "elseif" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "list" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "foreach" >  {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "switch" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "case" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "assign" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "global" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "local" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "include" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "import" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "function" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "macro" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "transform" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "visit" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "stop" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "return" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "call" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "setting" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "compress" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "comment" > {strictSyntaxCheck(matchedToken, NO_PARSE); noparseTag="comment";}
    |
     {noparseTag = "-->"; strictSyntaxCheck(matchedToken, NO_PARSE); }
    |
     "noparse" > {strictSyntaxCheck(matchedToken, NO_PARSE); noparseTag="noparse";}
    |
     "if" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "list" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "recover" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "attempt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "foreach" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "local" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "global" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "assign" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "function" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "macro" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "compress" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "transform" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "switch" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "else" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "break" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "return" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "stop" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "flush" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "t" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "lt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "rt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "nt" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "default" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "nested" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "nested" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "recurse" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "recurse" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "fallback" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "escape" > {strictSyntaxCheck(matchedToken, FM_EXPRESSION);}
    |
     "escape" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "noescape" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
     "noescape" > {strictSyntaxCheck(matchedToken, DEFAULT);}
    |
      {unifiedCall(matchedToken);}
    |
    ) (".")*)? > {unifiedCallEnd(matchedToken);}
    |
    > {ftlHeader(matchedToken);}
    |
    " | "]")> {ftlHeader(matchedToken);}
    |
    
    {
       if (!directiveSyntaxEstablished) {
           matchedToken.kind = PRINTABLE_CHARS;
       } else {
           char firstChar = matchedToken.image.charAt(0);
           if (firstChar == '<' && altDirectiveSyntax) {
              matchedToken.kind = PRINTABLE_CHARS;
           } else if (firstChar == '[' && !altDirectiveSyntax) {
              matchedToken.kind = PRINTABLE_CHARS;
           }
           else if (strictEscapeSyntax) {
             String s = matchedToken.image;
             int index = s.indexOf('#');
             s = s.substring(index);
             String msg = "Unknown directive: " 
             + s
             + " on line: " + matchedToken.beginLine
             + ", column: " + matchedToken.beginColumn +1
             + ", in template: " + templateName;
             throw new TokenMgrError(msg, TokenMgrError.LEXICAL_ERROR);
           }
       } 
    }
}

 TOKEN :
{
    
    |
    
    |
     // to handle a lone dollar sign or "<" or "# or <@ with whitespace after"
    |
     : FM_EXPRESSION
    |
     : FM_EXPRESSION
}

 SKIP :
{
    < ( " " | "\t" | "\n" | "\r" )+ >
    |
    < ["<", "["] ["#", "!"] "--"> : EXPRESSION_COMMENT
}

 SKIP:
{
    < (~["-", ">", "]"])+ >
    |
    < ">">
    |
    < "]">
    |
    < "-">
    |
    < "-->" | "--]"> {if (parenthesisNesting >0) SwitchTo(IN_PAREN); else if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION); else SwitchTo(FM_EXPRESSION);}
}

 TOKEN :
{
  <#ESCAPED_CHAR :  "\\"
           (
             ["n","t","r","f","b","g","l","a","\\","'","\"","$","{"]
             |
             ("x" ["0"-"9","A"-"F","a"-"f"])
           )
  >
  | 
  )*
      "\"")
      |
      ("'"
        ((~["'","\\"]) | )*
      "'"
      )
  >
  |
  
  |
  
  |
  
  |
  
  |
   "." >
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
  
  |
   {++bracketNesting;}
  |
   {closeBracket(matchedToken);}
  |
  
  {
     ++parenthesisNesting;
     if (parenthesisNesting == 1)
        SwitchTo(IN_PAREN);
  }
  |
  
  {
     --parenthesisNesting;
     if (parenthesisNesting == 0) {
	 if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION);
         else SwitchTo(FM_EXPRESSION);
     }
  }
  |
   {++hashLiteralNesting;}
  |
  
  {
     if (hashLiteralNesting == 0)
        SwitchTo(DEFAULT);
     else
        --hashLiteralNesting;
  }
  |
  < IN : "in">
  |
  < AS : "as">
  |
  < USING : "using">
  |
  < ID:  (|)* >
  |
  < #LETTER:
      [
       "\u0024",
       "\u0040"-"\u005a",
       "\u005f",
       "\u0061"-"\u007a",
       "\u00c0"-"\u00d6",
       "\u00d8"-"\u00f6",
       "\u00f8"-"\u00ff",
       "\u0100"-"\u1fff",
       "\u3040"-"\u318f",
       "\u3300"-"\u337f",
       "\u3400"-"\u3d2d",
       "\u4e00"-"\u9fff",
       "\uf900"-"\ufaff"
      ]
  >
  |
  < #DIGIT:
      [
       "\u0030"-"\u0039",
       "\u0660"-"\u0669",
       "\u06f0"-"\u06f9",
       "\u0966"-"\u096f",
       "\u09e6"-"\u09ef",
       "\u0a66"-"\u0a6f",
       "\u0ae6"-"\u0aef",
       "\u0b66"-"\u0b6f",
       "\u0be7"-"\u0bef",
       "\u0c66"-"\u0c6f",
       "\u0ce6"-"\u0cef",
       "\u0d66"-"\u0d6f",
       "\u0e50"-"\u0e59",
       "\u0ed0"-"\u0ed9",
       "\u1040"-"\u1049"
      ]
  >
}

 TOKEN :
{
  ">
    {
        if (inFTLHeader) eatNewline();
        inFTLHeader = false;
        if (altDirectiveSyntax) {
            matchedToken.kind = NATURAL_GT;
        } else {
            SwitchTo(DEFAULT);
        }
    }
  |
  " | "/]"> {if (inFTLHeader) eatNewline(); inFTLHeader = false; SwitchTo(DEFAULT);}
}

 TOKEN :
{
  ">
  |
  =">
}

 TOKEN :
{
     : FM_EXPRESSION
}

 TOKEN :
{
     : FM_EXPRESSION
}


 TOKEN :
{
    " | "--]">
    {
        if (noparseTag.equals("-->")) {
	   boolean squareBracket = matchedToken.image.endsWith("]");
	   if ((altDirectiveSyntax && squareBracket) || (!altDirectiveSyntax && !squareBracket))
	      SwitchTo(DEFAULT);
	}
    }
    |
    " | "]")
    >
    {
         StringTokenizer st = new StringTokenizer(image.toString(),
                                                  " \t\n\r<>[]/#",
                                                  false);
         if (st.nextToken().equals(noparseTag)) {
            SwitchTo(DEFAULT);
         }
    }
    |
    
    |
    
}

// Now the actual parsing code, starting
// with the productions for FreeMarker's
// expression syntax.

/**
 * This is the same as OrExpression, since
 * the OR is the operator with the lowest
 * precedence.
 */

Expression Expression() :
{
   Expression exp;
}
{
   exp=OrExpression()
   {
      return exp;
   }
}

/**
 * Lowest level expression, a literal, a variable,
 * or a possibly more complex expression bounded
 * by parentheses.
 */
Expression PrimaryExpression() :
{
   Expression exp;
}
{
   (
      exp=NumberLiteral()
      |
      exp=HashLiteral()
      |
      exp=StringLiteral(true)
      |
      exp=BooleanLiteral()
      |
      exp=ListLiteral()
      |
      exp=Identifier()
      |
      exp=Parenthesis()
      |
      exp=BuiltinVariable()
  )
  (
      LOOKAHEAD(
                |
                |
                |
                |
		 |
                |)
      exp=AddSubExpression(exp)
  )*
  {
     return exp;
  }
}

Expression Parenthesis() :
{
   Expression exp, result;
   Token start, end;
}
{
    start=
    exp=Expression()
    end=
    {
       result = new ParentheticalExpression(exp);
       result.setLocation(template, start, end);
       return result;
    }
}

/**
 * A primary expression preceded by zero or
 * more unary operators. (The only unary operator we
 * currently have is the NOT.)
 */
Expression UnaryExpression() :
{
   Expression exp, result;
   boolean haveNot = false;
   Token t = null, start=null;
}
{
  (
   result=UnaryPlusMinusExpression()
   |
   result=NotExpression()
   |
   result=PrimaryExpression()
  )
  {
     return result;
  }
/*   
   (
     t=
     {
        haveNot = !haveNot;
        if (start == null)
            start = t;
     }
   )*
   exp=PrimaryExpression()
   {
      result = exp;
      if (haveNot) {
          booleanLiteralOnly(exp);
          result = new NotExpression(exp);
          result.setLocation(template, start, exp);
      }
      return result;
   }
*/   
}

Expression NotExpression() : 
{
   Token t;
   Expression exp, result=null;
   ArrayList nots = new ArrayList();
}
{
   (
      t= {nots.add(t);}
   )+
   exp=PrimaryExpression()
   {
      for (int i=0; i
      |
      t= {isMinus = true;}
   )
   exp=PrimaryExpression()
   {
      result = new UnaryPlusMinusExpression(exp, isMinus);  
      result.setLocation(template, t, exp);
      return result;
   }
}

Expression AdditiveExpression() :
{
   Expression lhs, rhs, result;
   boolean plus;
}
{
   lhs=MultiplicativeExpression() {result = lhs;}
   (
      LOOKAHEAD(|)
      (
        (
          {plus = true;}
         |
          {plus = false;}
        )
      )
      rhs=MultiplicativeExpression()
      {
         if (plus) {
           // plus is treated separately, since it is also
           // used for concatenation.
             result = new AddConcatExpression(lhs, rhs);
         }
         else {
             numberLiteralOnly(lhs);
             numberLiteralOnly(rhs);
             result = new ArithmeticExpression(lhs,
                                            rhs,
                                            ArithmeticExpression.SUBSTRACTION);
         }
         result.setLocation(template, lhs, rhs);
         lhs = result;
      }
   )*
   {
      return result;
   }
}

/**
 * A unary expression followed by zero or more
 * unary expressions with operators in between.
 */
Expression MultiplicativeExpression() :
{
   Expression lhs, rhs, result;
   int operation = ArithmeticExpression.MULTIPLICATION;
}
{
   lhs=UnaryExpression() {result = lhs;}
   (
      LOOKAHEAD(||)
      (
        (
          {operation = ArithmeticExpression.MULTIPLICATION;}
         |
          {operation = ArithmeticExpression.DIVISION;}
         |
         {operation = ArithmeticExpression.MODULUS;}
        )
      )
      rhs=UnaryExpression()
      {
         numberLiteralOnly(lhs);
         numberLiteralOnly(rhs);
         result = new ArithmeticExpression(lhs, rhs, operation);
         result.setLocation(template, lhs, rhs);
         lhs = result;
      }
   )*
   {
      return result;
   }
}


Expression EqualityExpression() :
{
   Expression lhs, rhs, result;
   Token t;
}
{
   lhs=RelationalExpression() {result = lhs;}
   [
   	 LOOKAHEAD(||)
     (
      t= 
      |
      t= 
      |
      t=
     )
     rhs=RelationalExpression()
     {
        notHashLiteral(lhs, "scalar");
        notHashLiteral(rhs, "scalar");
        notListLiteral(lhs, "scalar");
        notListLiteral(rhs, "scalar");
        result = new ComparisonExpression(lhs, rhs, t.image);
        result.setLocation(template, lhs, rhs);
     }
   ]
   {
      return result;
   }
}

Expression RelationalExpression() :
{
   Expression lhs, rhs, result;
   Token t;
}
{
   lhs=RangeExpression() {result = lhs;}
   [
     LOOKAHEAD(||||||)
     (
      t=
      |
      t=
      |
      t=
      |
      t=
      |
      t=
      |
      t=
     )
     rhs=RangeExpression()
     {
        notHashLiteral(lhs, "scalar");
        notHashLiteral(rhs, "scalar");
        notListLiteral(lhs, "scalar");
        notListLiteral(rhs, "scalar");
        notStringLiteral(lhs, "number");
        notStringLiteral(rhs, "number");
        result = new ComparisonExpression(lhs, rhs, t.image);
        result.setLocation(template, lhs, rhs);
     }
   ]
   {
      return result;
   }
}

Expression RangeExpression() :
{
   Expression lhs, rhs=null, result;
}
{
    lhs=AdditiveExpression() {result = lhs;}
    [
      LOOKAHEAD()
      
       [
        LOOKAHEAD(Expression())
        rhs=AdditiveExpression()
       ]
       {
           numberLiteralOnly(lhs);
           if (rhs != null) {
               numberLiteralOnly(rhs);
           }
           Range range = new Range(lhs, rhs);
           if (rhs != null) {
               range.setLocation(template, lhs, rhs);
           } else {
              range.setLocation(template, lhs, lhs);
           }
           result = range;
        }
    ]
    {
        return result;
    }
}




Expression AndExpression() :
{
   Expression lhs, rhs, result;
}
{
   lhs=EqualityExpression() {result = lhs;}
   (
      LOOKAHEAD()
      
      rhs=EqualityExpression()
      {
         booleanLiteralOnly(lhs);
         booleanLiteralOnly(rhs);
         result = new AndExpression(lhs, rhs);
         result.setLocation(template, lhs, rhs);
         lhs = result;
      }
   )*
   {
      return result;
   }
}

Expression OrExpression() :
{
   Expression lhs, rhs, result;
}
{
   lhs=AndExpression() {result = lhs;}
   (
     LOOKAHEAD()
      
      rhs=AndExpression()
      {
         booleanLiteralOnly(lhs);
         booleanLiteralOnly(rhs);
         result = new OrExpression(lhs, rhs);
         result.setLocation(template, lhs, rhs);
         lhs = result;
      }
   )*
   {
      return result;
   }
}

ListLiteral ListLiteral() :
{
   ArrayList values = new ArrayList();
   Token begin, end;
}
{
    begin=
    values=PositionalArgs()
    end=
    {
        ListLiteral result = new ListLiteral(values);
        result.setLocation(template, begin, end);
        return result;
    }
}

Expression NumberLiteral() :
{
   Token op = null, t;
}
{
   (
      t=
      |
      t=
   )
   {
       String s = t.image;
       Expression result = new NumberLiteral(template.getArithmeticEngine().toNumber(s));
       Token startToken = (op != null) ? op : t;
       result.setLocation(template, startToken, t);
       return result;
   }
}

Identifier Identifier() :
{
    Token t;
}
{
    t=
    {
        Identifier id = new Identifier(t.image);
        id.setLocation(template, t, t);
        return id;
    }
}

Expression IdentifierOrStringLiteral() :
{
   Expression exp;
}
{
   (
      exp=Identifier()
      |
      exp=StringLiteral(false)
   )
   {
      return exp;
   }   
}

BuiltinVariable BuiltinVariable() :
{
   Token dot, name;
}
{
   dot=
   name=
   {
      BuiltinVariable result = null;
      try {
          result = new BuiltinVariable(name.image);
      } catch (ParseException pe) {
          pe.lineNumber = dot.beginLine;
          pe.columnNumber = dot.beginColumn;
          throw pe;
      }
      result.setLocation(template, dot, name);
      return result;
   }
}

/**
 * Production that builds up an expression
 * using the dot or dynamic key name
 * or the args list if this is a method invocation.
 */
Expression AddSubExpression(Expression exp) :
{
   Expression result = null;
}
{
     (
       result=DotVariable(exp)
       |
       result=DynamicKey(exp)
       |
       result=MethodArgs(exp)
       |
       result=BuiltIn(exp)
       |
       result=DefaultTo(exp)
       |
       result=Exists(exp)
     )
     {
        return result;
     }
}

Expression DefaultTo(Expression exp) :
{
   Expression rhs = null;
   Token t;
}
{
   (
      t=
      |
      (
        t=
        [
          LOOKAHEAD(Expression())
            rhs=Expression()
        ]
      )
   )
   {
      DefaultToExpression result = new DefaultToExpression(exp, rhs);
      if (rhs ==null) {
          result.setLocation(template, exp, t);
      }
      else {
          result.setLocation(template, exp, rhs);
      }
      return result;
   }
}

Expression Exists(Expression exp) :
{
   Token t;
}
{
   t=
   {
       ExistsExpression result = new ExistsExpression(exp);
       result.setLocation(template, exp, t);
       return result;
   }
}

Expression BuiltIn(Expression exp) :
{
   Token t=null;
}
{
   
   t=
   {
       BuiltIn result = null;
       try {
           result = BuiltIn.newBuiltIn(exp, t.image, t, templateName);
       } catch (ParseException pe) {
           pe.lineNumber = t.beginLine;
           pe.columnNumber = t.beginColumn;
           throw pe;
       }
       result.setLocation(template, exp, t);
       return result;
   }
}


/**
 * production for when a key is specified by  + keyname
 */
Expression DotVariable(Expression exp) :
{
  Token t;
}
{
     
     (
        t= | t= | t= 
        |
        (
            t=
          | t=
          | t=
          | t=
          | t=
          | t=
          | t=
          | t=
          | t=
        )
        {
            if (!Character.isLetter(t.image.charAt(0))) {
                String msg = getErrorStart(t)
                            + "\n" + t.image + " is not a valid identifier.";
                throw new ParseException(msg, t.beginLine, t.beginColumn);
            }
        }
     )
     {
         notListLiteral(exp, "hash");
         notStringLiteral(exp, "hash");
         notBooleanLiteral(exp, "hash");
         Dot dot = new Dot(exp, t.image);
         dot.setLocation(template, exp, t);
         return dot;
     }
}

/**
 * production for when the key is specified
 * in brackets.
 */
Expression DynamicKey(Expression exp) :
{
   Expression arg;
   Token t;
}
{
   
   arg=Expression()
   t=
   {
       notBooleanLiteral(exp, "list or hash");
       notNumberLiteral(exp, "list or hash");
       DynamicKeyName dkn = new DynamicKeyName(exp, arg);
       dkn.setLocation(template, exp, t);
       return dkn;
   }
}

/**
 * production for an arglist part of a method invocation.
 */
MethodCall MethodArgs(Expression exp) :
{
     ArrayList args = new ArrayList();
     Token end;
}
{
     
     args=PositionalArgs()
     end=
     {
        args.trimToSize();
        MethodCall result = new MethodCall(exp, args);
        result.setLocation(template, exp, end);
        return result;
     }
}

StringLiteral StringLiteral(boolean interpolate) :
{
  Token t;
  boolean raw = false;
}
{
   (
     t=
     |
     t= {raw = true;}
   )
   {
       String s = t.image;
       // Get rid of the quotes.
       s = s.substring(1, s.length() -1);
       if (raw) {
           s=s.substring(1);
       }
       else try {
          s = StringUtil.FTLStringLiteralDec(s);
       } catch (ParseException pe) {
          pe.lineNumber = t.beginLine;
          pe.columnNumber = t.beginColumn;
          throw pe;
       }
       StringLiteral result = new StringLiteral(s);
       result.setLocation(template, t, t);
       if (interpolate && !raw) {
           if (t.image.indexOf("${") >=0 || t.image.indexOf("#{") >=0)
              result.checkInterpolation();
       }
       return result;
   }
}

Expression BooleanLiteral() :
{
   Token t;
   Expression result;
}
{
   (
       t= {result = new BooleanLiteral(false);}
       |
       t= {result = new BooleanLiteral(true);}
   )
   {
       result.setLocation(template, t, t);
       return result;
   }
}


HashLiteral HashLiteral() :
{
   Token begin, end;
   Expression key, value;
   ArrayList keys = new ArrayList();
   ArrayList values = new ArrayList();
}
{
   begin=
   [
       key=Expression()
       (|)
       value=Expression()
       {
          stringLiteralOnly(key);
          keys.add(key);
          values.add(value);
       }
       (
          
           key=Expression()
           (|)
           value=Expression()
           {
               stringLiteralOnly(key);
               keys.add(key);
               values.add(value);
           }
       )*
   ]
   end=
   {
      HashLiteral result = new HashLiteral(keys, values);
      result.setLocation(template, begin, end);
      return result;
   }
}

/**
 * A production representing the ${...}
 * that outputs a variable.
 */
DollarVariable StringOutput() :
{
   Expression exp;
   Token begin, end;
}
{
   begin=
   exp=Expression()
   {
      notHashLiteral(exp, "scalar");
      notListLiteral(exp, "scalar");
      notBooleanLiteral(exp, "scalar");
   }
   end=
   {
      DollarVariable result = new DollarVariable(exp, escapedExpression(exp));
      result.setLocation(template, begin, end);
      return result;
   }
}

NumericalOutput NumericalOutput() :
{
   Expression exp;
   Token fmt = null, begin, end;
}
{
   begin=
   exp=Expression() {numberLiteralOnly(exp);}
   [
      
      fmt=
   ]
   end=
   {
      NumericalOutput result;
      if (fmt != null) {
         int minFrac = -1;  // -1 indicates that the value has not been set
         int maxFrac = -1;

         StringTokenizer st = new StringTokenizer(fmt.image, "mM", true);
         char type = '-';
         while (st.hasMoreTokens()) {
             String token = st.nextToken();
             try {
                if (type != '-') {
                    switch (type) {
                    case 'm':
                       if (minFrac != -1) throw new ParseException("invalid formatting string", fmt.beginLine, fmt.beginColumn);
                       minFrac = Integer.parseInt(token);
                       break;
                    case 'M':
                       if (maxFrac != -1) throw new ParseException("invalid formatting string", fmt.beginLine, fmt.beginColumn);
                       maxFrac = Integer.parseInt(token);
                       break;
                    default:
                       throw new ParseException();
                    }
                    type = '-';
                } else if (token.equals("m")) {
                   type = 'm';
                } else if (token.equals("M")) {
                   type = 'M';
                } else {
                   throw new ParseException();
                }
             }
             catch (ParseException e) {
                String msg = getErrorStart(fmt)
                      + "\nInvalid format specifier "
                      + fmt.image;
                throw new ParseException(msg, fmt.beginLine, fmt.beginColumn);
             }
             catch (NumberFormatException e) {
                String msg = getErrorStart(fmt)
                      + "\nInvalid number in the format specifier "
                      + fmt.image;
                throw new ParseException(msg, fmt.beginLine, fmt.beginColumn);
             }
         }

         if (maxFrac == -1) {
            if (minFrac == -1) {
               String msg = getErrorStart(fmt)
                     + "\nInvalid format specification, at least one of m and M must be specified!";
               throw new ParseException(msg, fmt.beginLine, fmt.beginColumn);
            }
            maxFrac = minFrac;
         } else if (minFrac == -1) {
            minFrac = 0;
         }
         if (minFrac > maxFrac) {
            String msg = getErrorStart(fmt)
                  + "\nInvalid format specification, min cannot be greater than max!";
            throw new ParseException(msg, fmt.beginLine, fmt.beginColumn);
         }
         if (minFrac > 50 || maxFrac > 50) {// sanity check
               String msg = getErrorStart(fmt)
               + "\nCannot specify more than 50 fraction digits";
             throw new ParseException(msg, fmt.beginLine, fmt.beginColumn);
         }
         result = new NumericalOutput(exp, minFrac, maxFrac);
      } else {  // if format != null
         result = new NumericalOutput(exp);
      }
      result.setLocation(template, begin, end);
      return result;
   }
}

TemplateElement If() :
{
    Token start, end, t;
    Expression condition;
    TemplateElement block;
    IfBlock ifBlock;
    ConditionalBlock cblock;
}
{
   start=
   condition=Expression()
   
   block=OptionalBlock()
   {
      cblock = new ConditionalBlock(condition, block, true);
      cblock.setLocation(template, start, block);
      ifBlock = new IfBlock(cblock);
   }
   (
       t=
       condition=Expression()
       LooseDirectiveEnd()
       block=OptionalBlock()
       {
          cblock = new ConditionalBlock(condition, block, false);
          cblock.setLocation(template, t, block);
          ifBlock.addBlock(cblock);
       }
   )*
   [
         t=
         block=OptionalBlock()
         {
            cblock = new ConditionalBlock(null, block, false);
            cblock.setLocation(template, t, block);
            ifBlock.addBlock(cblock);
         }
   ]
   end=
   {
       ifBlock.setLocation(template, start, end);
       return ifBlock;
   }
}

AttemptBlock Attempt() :
{
   Token start, end;
   TemplateElement block, recoveryBlock;
}
{
   start=
   block=OptionalBlock()
   recoveryBlock=Recover()
   (
      end=
      |
      end=
   )
   {
      AttemptBlock result = new AttemptBlock(block, recoveryBlock);
      result.setLocation(template, start, end);
      return result;
   }
}

RecoveryBlock Recover() : 
{
   Token start;
   TemplateElement block;
}
{
   start=
   block=OptionalBlock()
   {
      RecoveryBlock result = new RecoveryBlock(block);
      result.setLocation(template, start, block);
      return result;
   }
}

IteratorBlock List() :
{
    Expression exp;
    Token index, start, end;
    TemplateElement block;
}
{
   start= {++loopNesting;}
   exp=Expression()
   
   index=
   
   block=OptionalBlock()
   end=
   {
     --loopNesting;
     IteratorBlock result = new IteratorBlock(exp,
                                              index.image,
                                              block,
                                              false);
     result.setLocation(template, start, end);
     return result;
   }
}

IteratorBlock ForEach() :
{
     Expression exp;
     Token index, start, end;
     TemplateElement block;
}
{
   start= {++loopNesting;}
   index=
   
   exp=Expression()
   
   block=OptionalBlock()
   end=
   {
     --loopNesting;
     IteratorBlock result = new IteratorBlock(exp,
                                              index.image,
                                              block,
                                              true);
     result.setLocation(template, start, end);
     return result;
   }
}

VisitNode Visit() :
{
   Token start, end;
   Expression targetNode, namespaces=null;
}
{
   start=
   targetNode=Expression()
   [
       
       namespaces=Expression()
   ]
   end=LooseDirectiveEnd()
   {
       VisitNode result = new VisitNode(targetNode, namespaces);
       result.setLocation(template, start, end);
       return result;
   }
}

RecurseNode Recurse() :
{
   Token start, end = null;
   Expression node=null, namespaces=null;
}
{
   (
      start=
      |
      (
         start=
         [
            node=Expression()
         ]
         [
            
            namespaces=Expression()
         ]
         end=LooseDirectiveEnd()
      )
   )
   {
       if (end == null) end = start;
       RecurseNode result = new RecurseNode(node, namespaces);
       result.setLocation(template, start, end);
       return result;
   }
}

FallbackInstruction FallBack() :
{
   Token tok;
}
{
   tok=
   {
      if (!inMacro) {
          throw new ParseException(getErrorStart(tok)
	                           + "\nCannot fall back "
				   + " outside a macro.",
                                   tok.beginLine, tok.beginColumn);
      
      }
      FallbackInstruction result = new FallbackInstruction();
      result.setLocation(template, tok, tok);
      return result;
   }
}

/**
 * Production used to break out of a loop or a switch block.
 */
BreakInstruction Break() :
{
   Token start;
}
{
    start=
    {
       if (loopNesting < 1 && switchNesting <1)
       {
          String msg = getErrorStart(start) + "\n"
                       + start.image
                       + " occurred outside a loop or a switch block.";
          throw new ParseException(msg, start.beginLine, start.beginColumn);
       }
       BreakInstruction result = new BreakInstruction();
       result.setLocation(template, start, start);
       return result;
    }
}

/**
 * Production used to jump out of a macro.
 * The stop instruction terminates the rendering of the template.
 */
ReturnInstruction Return() :
{
   Token start, end=null;
   Expression exp = null;
}
{
   (
      start={end = start;}
      |
      start= exp=Expression() end=LooseDirectiveEnd()
   )
   {
      if (inMacro) {
         if (exp != null) {
            String msg = getErrorStart(start) 
                         + "\nA macro cannot return a value";
            throw new ParseException(msg, start.beginLine, start.beginColumn);
         }
      }
      else if (inFunction) {
         if (exp == null) {
            String msg = getErrorStart(start) 
                         + "\nA function must return a value";
            throw new ParseException(msg, start.beginLine, start.beginColumn);
         }
      }
      else {
         if (exp == null) {
            String msg = getErrorStart(start) 
                         + "\nA return instruction can only occur inside a macro of function";
            throw new ParseException(msg, start.beginLine, start.beginColumn);
         }
      }
      ReturnInstruction result = new ReturnInstruction(exp);
      result.setLocation(template, start, end);
      return result;
   }
}

StopInstruction Stop() :
{
   Token start = null;
   Expression exp = null;
}
{
    (
      start=
      |
      start= exp=Expression() LooseDirectiveEnd()
    )
    {
       StopInstruction result = new StopInstruction(exp);
       result.setLocation(template, start, start);
       return result;
    }
}

TemplateElement Nested() :
{
  Token t, end;
  ArrayList bodyParameters;
  BodyInstruction result = null;
}
{
   (
      (
         t=
         {
            result = new BodyInstruction(null);
            result.setLocation(template, t, t);
         }
      )
      |
      (
          t=
          bodyParameters=PositionalArgs()
          end=LooseDirectiveEnd()
          {
             result = new BodyInstruction(bodyParameters);
             result.setLocation(template, t, end);
          }
      )
   )
   {
       if (!inMacro) {
          throw new ParseException(getErrorStart(t)
	                           + "\nCannot use a "
				   + t.image
				   + " instruction outside a macro.",
                                   t.beginLine, t.beginColumn);
       }
       return result;
   }
}

TemplateElement Flush() :
{
  Token t;
}
{
   t=
   {
       FlushInstruction result = new FlushInstruction();
       result.setLocation(template, t, t);
       return result;
   }
}

TemplateElement Trim() :
{
  Token t;
  TrimInstruction result=null;
}
{
   (
     t= {result = new TrimInstruction(true, true);}
     |
     t= {result = new TrimInstruction(true, false);}
     |
     t= {result = new TrimInstruction(false, true);}
     |
     t= {result = new TrimInstruction(false, false);}
   )
   {
       result.setLocation(template, t, t);
       return result;
   }
}


TemplateElement Assign() :
{
   Token start, end;
   int scope;
   Token id=null;
   Expression nameExp, exp, nsExp=null;
   String varName;
   ArrayList assignments = new ArrayList();
   Assignment ass;
   TemplateElement block;
}
{
    (
      start= {scope = Assignment.NAMESPACE;}
      |
      start={scope = Assignment.GLOBAL;}
      |
      start= {scope = Assignment.LOCAL;}
      {
          scope = Assignment.LOCAL;
          if (!inMacro && !inFunction) {
             String msg = getErrorStart(start)
                          + "\nLocal variable assigned outside a macro.";
             throw new ParseException(msg, start.beginLine, start.beginColumn);
          }
      }
    )
    nameExp=IdentifierOrStringLiteral()
    {
       varName = (nameExp instanceof StringLiteral) ? ((StringLiteral) nameExp).getAsString() : nameExp.toString();
    }
    ((
       
       exp=Expression()
       {
          ass = new Assignment(varName, exp, scope);
          ass.setLocation(template, nameExp, exp);
          assignments.add(ass);
       }
       (
         LOOKAHEAD([](|))
            []
            nameExp=IdentifierOrStringLiteral()
            {
               varName = (nameExp instanceof StringLiteral) ? ((StringLiteral) nameExp).getAsString() : nameExp.toString();
            }
            
            exp=Expression()
            {
               ass = new Assignment(varName, exp, scope);
               ass.setLocation(template, nameExp, exp);
               assignments.add(ass);
           } 
       )*
       [
          id=
          nsExp=Expression() {if (scope != Assignment.NAMESPACE) throw new ParseException(getErrorStart(id) + "\nCannot assign to namespace here.", id.beginLine, id.beginColumn);}
       ]
       end=LooseDirectiveEnd()
       {
           AssignmentInstruction ai = new AssignmentInstruction(scope);
           for (int i = 0; i< assignments.size(); i++) {
                ai.addAssignment((Assignment) assignments.get(i));
           }
           ai.setNamespaceExp(nsExp);
           ai.setLocation(template, start, end);
           return ai;
       }
    )
    |
    (
       [
          id=
          nsExp=Expression() {if (scope != Assignment.NAMESPACE) throw new ParseException(getErrorStart(id) + "\nCannot assign to namespace here.", id.beginLine, id.beginColumn);}
       ]
       
       block=OptionalBlock()
       (
          end= {if (scope != Assignment.LOCAL) throw new ParseException(getErrorStart(end) + "\nMismatched assignment tags.", end.beginLine, end.beginColumn);}
          |
          end= {if (scope != Assignment.NAMESPACE) throw new ParseException(getErrorStart(end) + "\nMismatched assignment tags.", end.beginLine, end.beginColumn);}
          |
          end= {if (scope != Assignment.GLOBAL) throw new ParseException(getErrorStart(end) + "\nMismatched assignment tags", end.beginLine, end.beginColumn);}
       )
       {
          BlockAssignment ba = new BlockAssignment(block, varName, scope, nsExp);
          ba.setLocation(template, start, end);
          return ba;
       }
    ))
}

Include Include() :
{
   Expression nameExp;
   Token att, start, end;
   Expression exp, parseExp = null, encodingExp = null;
}
{
    start=
    nameExp=Expression()
    []
    (
	  att=
	  
	  exp=Expression()
	  {
	     String attString = att.image;
	     if (attString.equalsIgnoreCase("parse")) {
	        parseExp = exp;
	     }
	     else if (attString.equalsIgnoreCase("encoding")) {
	        encodingExp = exp;
	     }
	     else {
	         String msg = getErrorStart(att)
	               + "\nexpecting parse= or encoding= to be specified.";
	         throw new ParseException(msg, att.beginLine, att.beginColumn);
	     }
	  }
	)*
    end=LooseDirectiveEnd()
    {
       Include result = new Include(template, nameExp, encodingExp, parseExp);
       result.setLocation(template, start, end);
       return result;
    }
}

LibraryLoad Import() :
{
   Token start, end, ns;
   Expression nameExp;
}
{
   start=
   nameExp=Expression()
   
   ns=
   end=LooseDirectiveEnd()
   {
       LibraryLoad result = new LibraryLoad(template, nameExp, ns.image);
       result.setLocation(template, start, end);
       template.addImport(result);
       return result;
   }
}

Macro Macro() :
{
   Token arg, start, end;
   Expression nameExp;
   String name;
   ArrayList argNames = new ArrayList();
   HashMap args = new HashMap();
   ArrayList defNames = new ArrayList();
   Expression defValue=null;
   TemplateElement block;
   boolean isFunction = false, hasDefaults=false;
   boolean isCatchAll = false;
   String catchAll = null;
}
{
    (
        start=
        |
        start= {isFunction = true;}
    )
    {
       if (inMacro || inFunction) {
         throw new ParseException(getErrorStart(start)
                     + "\nMacros cannot be nested.", start.beginLine, start.endLine);
       }
       if (isFunction) inFunction = true; else inMacro = true;
    }
    nameExp=IdentifierOrStringLiteral()
    {
       name = (nameExp instanceof StringLiteral) ? ((StringLiteral) nameExp).getAsString() : nameExp.toString();
    }
    []
    (
          arg= {defValue = null;}
          [ { isCatchAll = true; }]
          [
            
          	defValue=Expression()
            {
              defNames.add(arg.image);
	      hasDefaults = true;
            }
          ]
          []
	  {
          if (catchAll != null) {
              throw new ParseException(getErrorStart(arg)
                + "\nThere may only be one \"catch-all\" parameter in a macro declaration, "
                + "and it must be the last parameter.", arg.beginLine, arg.endLine);
          }
          if (isCatchAll) {
              if (defValue != null) {
                  throw new ParseException(getErrorStart(arg)
                    + "\n\"Catch-all\" macro parameter may not have a default value.",
                    arg.beginLine, arg.endLine);
              }
              catchAll = arg.image;
          } else {
              argNames.add(arg.image);
              if (hasDefaults && defValue == null) {
                  throw new ParseException(getErrorStart(arg)
                    + "\nIn a macro declaration, parameters without a default value "
                    + "must all occur before the parameters with default values.",
                    arg.beginLine, arg.endLine);
              }
              args.put(arg.image, defValue);
          }
	  }
    )*
    []
    
    block=OptionalBlock()
    (
        end= { if(isFunction) throw new ParseException(getErrorStart(start) + "\nExpected function end tag here.", start.beginLine, start.endLine); }
        |
        end= { if(!isFunction) throw new ParseException(getErrorStart(start) + "\nExpected macro end tag here.", start.beginLine, start.endLine); }
    )
    {
       inMacro = inFunction = false;
       Macro result = new Macro(name, argNames, args, block);
       result.setCatchAll(catchAll);
       result.isFunction = isFunction;
       result.setLocation(template, start, end);
       template.addMacro(result);
       return result;
    }
}


CompressedBlock Compress() :
{
   TemplateElement block;
   Token start, end;
}
{
    start=
    block=OptionalBlock()
    end=
    {
       CompressedBlock cb = new CompressedBlock(block);
       cb.setLocation(template, start, end);
       return cb;
    }
}

TemplateElement UnifiedMacroTransform() :
{
   Token start=null, end, t;
   HashMap namedArgs = null;
   ArrayList positionalArgs = null, bodyParameters = null;
   String directiveName = null;
   TemplateElement nestedBlock = null;
   Expression exp;
}
{
    start=
    exp=Expression()
    {
        if (exp instanceof Identifier || (exp instanceof Dot && ((Dot) exp).onlyHasIdentifiers())) {
	   directiveName = exp.getCanonicalForm();
	}
    }
    []
    (
       LOOKAHEAD()
       namedArgs = NamedArgs()
       |
       positionalArgs=PositionalArgs()
    )
    [
       {bodyParameters = new ArrayList();}
       [
           []t= {bodyParameters.add(t.image);}
           (
               []
               []t = {bodyParameters.add(t.image);}
           )*
       ]
    ]
    (
      end=
      |
      (
        
	nestedBlock=OptionalBlock()
	end=
	{
	   String s = end.image.substring(3);
	   s = s.substring(0, s.length() -1).trim();
	   if (s.length() >0 && !s.equals(directiveName)) {
              String msg = getErrorStart(end);
	      if (directiveName == null) {
                    throw new ParseException(msg + "\nExpecting ", end.beginLine, end.beginColumn);
	      }
	      else {
	          throw new ParseException(msg + "\nExpecting  or ", end.beginLine, end.beginColumn);
	      }
	   }
	}
      )
    )
    {
       TemplateElement result = (positionalArgs != null) ? new UnifiedCall(exp, positionalArgs, nestedBlock, bodyParameters)
                                         : new UnifiedCall(exp, namedArgs, nestedBlock, bodyParameters);
       result.setLocation(template, start, end);
       return result;
    }
}

TemplateElement Call() :
{
   Token start, end, id;
   HashMap namedArgs = null;
   ArrayList positionalArgs = null;
   String macroName= null;
}
{
    start=
    id= {macroName = id.image;}
    (
       LOOKAHEAD()
       namedArgs=NamedArgs()
       |
       (
          [
	    LOOKAHEAD()
	       
	  ]
	  positionalArgs=PositionalArgs()
	  []
       )
    )
    end=LooseDirectiveEnd()
    {
       UnifiedCall result = null;
       if (positionalArgs != null) {
          result = new UnifiedCall(new Identifier(macroName), positionalArgs, null, null);
       }
       else {
          result = new UnifiedCall(new Identifier(macroName), namedArgs, null, null);
       }
       result.legacySyntax = true;
       result.setLocation(template, start, end);
       return result;
    }
}

HashMap NamedArgs() :
{
    HashMap result = new HashMap();
    Token t;
    Expression exp;
}
{
  (
     t=
     
     {
         token_source.SwitchTo(token_source.NAMED_PARAMETER_EXPRESSION);
	 token_source.inInvocation = true;
     }	     
     exp=Expression()
     {
        result.put(t.image, exp);
     }
  )+
  {
      token_source.inInvocation = false;
     return result;
  }
}

ArrayList PositionalArgs() :
{
  ArrayList result = new ArrayList();
  Expression arg;
}
{
  [
      arg=Expression() {result.add(arg);}
      (
         []
         arg=Expression() {result.add(arg);}
      )*
  ]
  {
     return result;
  }
}


Comment Comment() :
{
   Token start, end;
   StringBuffer buf = new StringBuffer();
}
{
    (
       start=
       |
       start=
    )
    end=UnparsedContent(buf)
    {
       Comment result = new Comment(buf.toString());
       result.setLocation(template, start, end);
       return result;
    }
}

TextBlock NoParse() :
{
   Token start, end;
   StringBuffer buf = new StringBuffer();
}
{
    start=
    end=UnparsedContent(buf)
    {
        TextBlock result = new TextBlock(buf.toString(), true);
        result.setLocation(template, start, end);
        return result;
    }
}


TransformBlock Transform() :
{
   Token start, end, argName;
   Expression exp, argExp;
   TemplateElement content = null;
   HashMap args = null;
}
{
    start=
    exp=Expression()
    []
    (
       argName=
       
       argExp = Expression()
       {
           if (args == null) args = new HashMap();
           args.put(argName.image, argExp);
       }
    )*
    (
       end=
       |
       (content=OptionalBlock()end=)
    )
    {
       TransformBlock result = new TransformBlock(exp, args, content);
       result.setLocation(template, start, end);
       return result;
    }
}

SwitchBlock Switch() :
{
    SwitchBlock switchBlock;
    Case caseIns;
    Expression switchExp;
    Token start, end;
    boolean defaultFound = false;
}
{
   start=
   switchExp=Expression()
   
   {
      ++switchNesting;
      switchBlock = new SwitchBlock(switchExp);
   }
   (
     LOOKAHEAD(2)
     caseIns=Case()
     {
        if (caseIns.isDefault) {
	   if (defaultFound) {
                String msg = getErrorStart(start)
                            + "\nYou can only have one default case in a switch statement";
                throw new ParseException(msg, start.beginLine, start.beginColumn);
	   }
	   defaultFound = true;
	}
        switchBlock.addCase(caseIns);
     }
   )*
   []
   end=
   {
      --switchNesting;
      switchBlock.setLocation(template, start, end);
      return switchBlock;
   }
}

Case Case() :
{
    Expression exp = null;
    TemplateElement block;
    boolean isDefault = false;
    Token start;
}
{
   []
   (
      start=exp=Expression()
      |
      start={isDefault = true;}
   )
   block=OptionalBlock()
   {
       Case result = new Case(exp, block, isDefault);
       result.setLocation(template, start, block);
       return result;
   }
}

EscapeBlock Escape() :
{
    Token variable, start, end;
    Expression escapeExpr;
    TemplateElement content;
}
{
    start=
    variable=
    
    escapeExpr=Expression()
    
    {
        EscapeBlock result = new EscapeBlock(variable.image, escapeExpr, escapedExpression(escapeExpr));
        escapes.addFirst(result);
    }
    content=OptionalBlock()
    {
        result.setContent(content);
        escapes.removeFirst();
    }
    end=
    {
       result.setLocation(template, start, end);
       return result;
    }
}

NoEscapeBlock NoEscape() :
{
    Token start, end;
    TemplateElement content;
}
{
    start=
    {
        if(escapes.isEmpty()) {
                String msg = getErrorStart(start)
                                  + "\nnoescape with no matching escape encountered.";
            throw new ParseException(msg, start.beginLine, start.beginColumn);
        }
        Object escape = escapes.removeFirst();
    }
    content=OptionalBlock()
    end=
    {
       escapes.addFirst(escape);
       NoEscapeBlock result = new NoEscapeBlock(content);
       result.setLocation(template, start, end);
       return result;
    }
}

/**
 * Production to terminate potentially empty elements. Either a ">" or "/>"
 */

Token LooseDirectiveEnd() :
{
    Token t;
}
{
   (
      t=
      |
      t=
   )
   {
      return t;
   }
}

PropertySetting Setting() :
{
   Token start, end, key;
   Expression value;
}
{
   start=
   key=
   
   value=Expression()
   end=LooseDirectiveEnd()
   {
      PropertySetting result = new PropertySetting(key.image, value);
      result.setLocation(template, start, end);
      return result;
   }
}

/**
 * A production for FreeMarker directives.
 */
TemplateElement FreemarkerDirective() :
{
   TemplateElement tp;
}
{
   (
     tp=If()
     |
     tp=List()
     |
     tp=ForEach()
     |
     tp=Assign()
     |
     tp=Include()
     |
     tp=Import()
     |
     tp=Macro()
     |
     tp=Compress()
     |
     tp=UnifiedMacroTransform()
     |
     tp=Call()
     |
     tp=Comment()
     |
     tp=NoParse()
     |
     tp=Transform()
     |
     tp=Switch()
     |
     tp=Setting()
     |
     tp=Break()
     |
     tp=Return()
     |
     tp=Stop()
     |
     tp=Flush()
     |
     tp=Trim()
     |
     tp=Nested()
     |
     tp=Escape()
     |
     tp=NoEscape()
     |
     tp=Visit()
     |
     tp=Recurse()
     |
     tp=FallBack()
     |
     tp=Attempt()
   )
   {
      return tp;
   }
}

/**
 * Production for a block of raw text
 * i.e. text that contains no
 * FreeMarker directives.
 */

TextBlock PCData() :
{
    StringBuffer buf = new StringBuffer();
    Token t=null, start=null, prevToken = null;
}
{
    (
      LOOKAHEAD(||)
      (
         {prevToken = t;}         
         t=
	 |
         t=
         |
         t=
      )
      {
         buf.append(t.image);
         if (start == null) start = t;
         {if (prevToken != null) prevToken.next = null;}
      }
    )+
    {
         if (stripText && contentNesting == 1)
             return TextBlock.EMPTY_BLOCK;

         TextBlock result = new TextBlock(buf.toString(), false);
         result.setLocation(template, start, t);
         return result;
    }
}

/**
 * Production for dealing with unparsed content,
 * i.e. what is inside a comment or noparse tag.
 * It returns the ending token. The content
 * of the tag is put in buf.
 */

Token UnparsedContent(StringBuffer buf) :
{
   Token t;
}
{
   ((t= | t= | t= | t=)
   {
       buf.append(t.image);
   })+
   {
      buf.setLength(buf.length() - t.image.length());
      return t;
   }
}

TemplateElement Content() :
{
    MixedContent nodes = new MixedContent();
    TemplateElement elem, begin=null;
    contentNesting++;
}
{
    (
      LOOKAHEAD(1) // Just tells javacc that we know what we're doing.
      (
         elem=PCData()
         |
         elem=StringOutput()
         |
         elem=NumericalOutput()
         |
         elem=FreemarkerDirective()
      )
      {
            if (begin == null) {
               begin = elem;
            }
            nodes.addElement(elem);
      }
    )+
    {
        contentNesting--;
        nodes.setLocation(template, begin, elem);
	return nodes;
    }
}

/**
 * A production freemarker text that may contain
 * ${...} and #{...} but no directives.
 */

TemplateElement FreeMarkerText() :
{
     MixedContent nodes = new MixedContent();
     TemplateElement elem, begin = null;
}
{
    (
      (
        elem=PCData()
        |
        elem=StringOutput()
        |
        elem=NumericalOutput()
      )
      {
         if (begin == null) {
            begin = elem;
         }
         nodes.addElement(elem);
      }
    )+
    {
       nodes.setLocation(template, begin, elem);
       return nodes;
    }
}

/**
 * A production for a block of optional content.
 * Returns an empty Text block if there is no
 * content.
 */

TemplateElement OptionalBlock() :
{
   TemplateElement tp = TextBlock.EMPTY_BLOCK;
}
{
   [
      LOOKAHEAD(1) // has no effect but to get rid of a spurious warning.
         tp=Content()
   ]
   {
      return tp;
   }
}

void HeaderElement() :
{
   Token key;
   Expression exp = null;
}
{
   []
   (
     
     |
     (
       
       (
          key=
          
          exp=Expression()
          {
             String ks = key.image;
             TemplateModel value = null;
             try {
                value = exp.getAsTemplateModel(null);
             } catch (Exception e) {
                throw new ParseException("Could not evaluate expression: " 
                                         + exp.getCanonicalForm() + " " 
                                         + exp.getStartLocation()
                                         + "\nUnderlying cause: " + 
                                         e.getMessage(), exp);
             }
	     String vs = null;
             if (value instanceof TemplateScalarModel) {
                try {
                   vs = ((TemplateScalarModel) exp).getAsString();
                } catch (TemplateModelException tme) {}
             }
             if (template != null) {
                 if (ks.equalsIgnoreCase("encoding")) {
                     if (vs == null) {
                        throw new ParseException("expecting encoding string here: " 
                                                 + exp.getStartLocation(), exp);
                     }
                     String encoding = template.getEncoding();
                     if (encoding != null && !encoding.equals(vs)) {
                         throw new Template.WrongEncodingException(vs);
                     }
                 }
                 else if (ks.equalsIgnoreCase("STRIP_WHITESPACE")) {
                     this.stripWhitespace = getBoolean(exp);
                 }
                 else if (ks.equalsIgnoreCase("STRIP_TEXT")) {
                     this.stripText = getBoolean(exp);
                 }
                 else if (ks.equalsIgnoreCase("STRICT_SYNTAX")) {
                     this.token_source.strictEscapeSyntax = getBoolean(exp);
                 }
                 else if (ks.equalsIgnoreCase("ns_prefixes")) {
                     if (!(value instanceof TemplateHashModelEx)) {
                         throw new ParseException("Expecting a hash of prefixes to namespace URI's here. " + exp.getStartLocation(), exp);
                     }
                     TemplateHashModelEx prefixMap = (TemplateHashModelEx) value;
                     try {
                         TemplateCollectionModel keys = prefixMap.keys();
                         for (TemplateModelIterator it = keys.iterator(); it.hasNext();) {
                             String prefix = ((TemplateScalarModel) it.next()).getAsString();
                             TemplateModel valueModel = prefixMap.get(prefix);
                             if (!(valueModel instanceof TemplateScalarModel)) {
                                 throw new ParseException("Non-string value in prefix to namespace hash. " + exp.getStartLocation(), exp);
                             }
                             String nsURI = ((TemplateScalarModel) valueModel).getAsString();
                             try {
                                 template.addPrefixNSMapping(prefix, nsURI);
                             } catch (IllegalArgumentException iae) {
                                 throw new ParseException(iae.getMessage() + " " + exp.getStartLocation(), exp);
                             }
                         }
                     } catch (TemplateModelException tme) {
                     }
                 }
                 else if (ks.equalsIgnoreCase("attributes")) {
                     if (!(value instanceof TemplateHashModelEx)) {
                         throw new ParseException("Expecting a hash of attribute names to values here. " + exp.getStartLocation(), exp);
                     }
                     TemplateHashModelEx attributeMap = (TemplateHashModelEx) value;
                     try {
                         TemplateCollectionModel keys = attributeMap.keys();
                         for (TemplateModelIterator it = keys.iterator(); it.hasNext();) {
                             String attName = ((TemplateScalarModel) it.next()).getAsString();
                             Object attValue = DeepUnwrap.unwrap(attributeMap.get(attName));
                             template.setCustomAttribute(attName, attValue);
                         }
                     } catch (TemplateModelException tme) {
                     }
                 }
                 else {
                     throw new ParseException("Unknown FTL header parameter: " + key.image,
                                               key.beginLine, key.beginColumn);
                 }
             }
          }
       )*
     )
     LooseDirectiveEnd()
   )
}

Map ParamList() :
{
   Identifier id;
   Expression exp;
   Map result = new HashMap();
}
{
   (
      id=Identifier()
      
      exp=Expression() {result.put(id.toString(), exp);}
      []
   )+
   {
       return result;
   }
}


/**
 * Root production to be used when parsing
 * an entire file.
 */
TemplateElement Root() :
{
   TemplateElement doc;
}
{
   [
      LOOKAHEAD([](|))
       HeaderElement()
   ]
   doc=OptionalBlock()
   
   {
       doc.setParentRecursively(null);
       return doc.postParseCleanup(stripWhitespace);
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy