Open Source Repository

Home /spring/spring-expression-3.0.5 | Repository Home



org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.expression.spel.standard;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateAwareExpressionParser;
import org.springframework.expression.spel.InternalParseException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.Assign;
import org.springframework.expression.spel.ast.BeanReference;
import org.springframework.expression.spel.ast.BooleanLiteral;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.ConstructorReference;
import org.springframework.expression.spel.ast.Elvis;
import org.springframework.expression.spel.ast.FunctionReference;
import org.springframework.expression.spel.ast.Identifier;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.Literal;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.expression.spel.ast.OpDivide;
import org.springframework.expression.spel.ast.OpEQ;
import org.springframework.expression.spel.ast.OpGE;
import org.springframework.expression.spel.ast.OpGT;
import org.springframework.expression.spel.ast.OpLE;
import org.springframework.expression.spel.ast.OpLT;
import org.springframework.expression.spel.ast.OpMinus;
import org.springframework.expression.spel.ast.OpModulus;
import org.springframework.expression.spel.ast.OpMultiply;
import org.springframework.expression.spel.ast.OpNE;
import org.springframework.expression.spel.ast.OpOr;
import org.springframework.expression.spel.ast.OpPlus;
import org.springframework.expression.spel.ast.OperatorInstanceof;
import org.springframework.expression.spel.ast.OperatorMatches;
import org.springframework.expression.spel.ast.OperatorNot;
import org.springframework.expression.spel.ast.OperatorPower;
import org.springframework.expression.spel.ast.Projection;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.ast.QualifiedIdentifier;
import org.springframework.expression.spel.ast.Selection;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.expression.spel.ast.Ternary;
import org.springframework.expression.spel.ast.TypeReference;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.util.Assert;

/**
 * Hand written SpEL parser. Instances are reusable but are not thread safe.
 
 @author Andy Clement
 @since 3.0
 */
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {

  // The expression being parsed
  private String expressionString;  
  
  // The token stream constructed from that expression string
  private List<Token> tokenStream;
  
  // length of a populated token stream
  private int tokenStreamLength;
  
  // Current location in the token stream when processing tokens
  private int tokenStreamPointer;
  
  // For rules that build nodes, they are stacked here for return
  private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
  
  private SpelParserConfiguration configuration;


  /**
   * Create a parser with some configured behavior.
   @param configuration custom configuration options
   */
  public InternalSpelExpressionParser(SpelParserConfiguration configuration) {
    this.configuration = configuration;
  }


  @Override
  protected SpelExpression doParseExpression(String expressionString, ParserContext contextthrows ParseException {
    try {
      this.expressionString = expressionString;
      Tokenizer tokenizer = new Tokenizer(expressionString);
      tokenizer.process();
      tokenStream = tokenizer.getTokens();
      tokenStreamLength = tokenStream.size();
      tokenStreamPointer = 0;
      constructedNodes.clear();
      SpelNodeImpl ast = eatExpression();
      if (moreTokens()) {
        throw new SpelParseException(peekToken().startpos,SpelMessage.MORE_INPUT,toString(nextToken()));
      }
      Assert.isTrue(constructedNodes.isEmpty());
      return new SpelExpression(expressionString, ast, configuration);  
    }
    catch (InternalParseException ipe) {
      throw ipe.getCause();
    }
  }
  
  //  expression
  //    : logicalOrExpression
  //      ( (ASSIGN^ logicalOrExpression) 
  //      | (DEFAULT^ logicalOrExpression) 
  //      | (QMARK^ expression COLON! expression)
  //      | (ELVIS^ expression))?;
  private SpelNodeImpl eatExpression() {
    SpelNodeImpl expr = eatLogicalOrExpression();
    if (moreTokens()) {
      Token t = peekToken();
      if (t.kind==TokenKind.ASSIGN) { // a=b
        if (expr==null) {
        expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1));
      }
        nextToken();
        SpelNodeImpl assignedValue = eatLogicalOrExpression();
        return new Assign(toPos(t),expr,assignedValue);
      else if (t.kind==TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b)
        if (expr==null) {
          expr = new NullLiteral(toPos(t.startpos-1,t.endpos-2));
        }
        nextToken()// elvis has left the building
        SpelNodeImpl valueIfNull = eatExpression();
        if (valueIfNull==null) {
          valueIfNull = new NullLiteral(toPos(t.startpos+1,t.endpos+1));
        }
        return new Elvis(toPos(t),expr,valueIfNull);
      else if (t.kind==TokenKind.QMARK) { // a?b:c
        if (expr==null) {
          expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1));
        }
        nextToken();
        SpelNodeImpl ifTrueExprValue = eatExpression();  
        eatToken(TokenKind.COLON);
        SpelNodeImpl ifFalseExprValue = eatExpression();  
        return new Ternary(toPos(t),expr,ifTrueExprValue,ifFalseExprValue);
      }
    }
    return expr;
  }
                          
  //logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*;
  private SpelNodeImpl eatLogicalOrExpression() {
    SpelNodeImpl expr = eatLogicalAndExpression();
    while (peekIdentifierToken("or")) {
      Token t = nextToken()//consume OR
      SpelNodeImpl rhExpr = eatLogicalAndExpression();
      checkRightOperand(t,rhExpr);
      expr = new OpOr(toPos(t),expr,rhExpr);
    }
    return expr;
  }

  // logicalAndExpression : relationalExpression (AND^ relationalExpression)*;
  private SpelNodeImpl eatLogicalAndExpression() {
    SpelNodeImpl expr = eatRelationalExpression();
    while (peekIdentifierToken("and")) {
      Token t = nextToken();// consume 'AND'
      SpelNodeImpl rhExpr = eatRelationalExpression();
      checkRightOperand(t,rhExpr);
      expr = new OpAnd(toPos(t),expr,rhExpr);
    }
    return expr;
  }
  
  // relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
  private SpelNodeImpl eatRelationalExpression() {
    SpelNodeImpl expr = eatSumExpression();
    Token relationalOperatorToken = maybeEatRelationalOperator();
    if (relationalOperatorToken != null) {
      Token t = nextToken()//consume relational operator token
      SpelNodeImpl rhExpr = eatSumExpression();
      checkRightOperand(t,rhExpr);
      TokenKind tk = relationalOperatorToken.kind;
      if (relationalOperatorToken.isNumericRelationalOperator()) {
        int pos = toPos(t);
        if (tk==TokenKind.GT) {
          return new OpGT(pos,expr,rhExpr);
        else if (tk==TokenKind.LT) {
          return new OpLT(pos,expr,rhExpr);
        else if (tk==TokenKind.LE) {
          return new OpLE(pos,expr,rhExpr);
        else if (tk==TokenKind.GE) {
          return new OpGE(pos,expr,rhExpr);
        else if (tk == TokenKind.EQ) {
          return new OpEQ(pos,expr,rhExpr);
        else {
          Assert.isTrue(tk == TokenKind.NE);
          return new OpNE(pos,expr,rhExpr);
        }
      }
      if (tk==TokenKind.INSTANCEOF) {
        return new OperatorInstanceof(toPos(t),expr,rhExpr);
      else if (tk==TokenKind.MATCHES) {
        return new OperatorMatches(toPos(t),expr,rhExpr);
      else {
        Assert.isTrue(tk==TokenKind.BETWEEN);
        return new org.springframework.expression.spel.ast.OperatorBetween(toPos(t),expr,rhExpr);
      }
    }
    return expr;
  }
  
  //sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
  private SpelNodeImpl eatSumExpression() {
    SpelNodeImpl expr = eatProductExpression();    
    while (peekToken(TokenKind.PLUS,TokenKind.MINUS)) {
      Token t = nextToken();//consume PLUS or MINUS
      SpelNodeImpl rhExpr = eatProductExpression();
      checkRightOperand(t,rhExpr);
      if (t.kind==TokenKind.PLUS) {
        expr = new OpPlus(toPos(t),expr,rhExpr);
      else {
        Assert.isTrue(t.kind==TokenKind.MINUS);
        expr = new OpMinus(toPos(t),expr,rhExpr);
      }
    }
    return expr;
  }
  
  // productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
  private SpelNodeImpl eatProductExpression() {
    SpelNodeImpl expr = eatPowerExpression();
    while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) {
      Token t = nextToken()// consume STAR/DIV/MOD
      SpelNodeImpl rhExpr = eatPowerExpression();
      checkRightOperand(t,rhExpr);
      if (t.kind==TokenKind.STAR) {
        expr = new OpMultiply(toPos(t),expr,rhExpr);
      else if (t.kind==TokenKind.DIV) {
        expr = new OpDivide(toPos(t),expr,rhExpr);
      else {
        Assert.isTrue(t.kind==TokenKind.MOD);
        expr = new OpModulus(toPos(t),expr,rhExpr);
      }
    }
    return expr;
  }
  
  // powerExpr  : unaryExpression (POWER^ unaryExpression)? ;
  private SpelNodeImpl eatPowerExpression() {
    SpelNodeImpl expr = eatUnaryExpression();
    if (peekToken(TokenKind.POWER)) {
      Token t = nextToken();//consume POWER
      SpelNodeImpl rhExpr = eatUnaryExpression();
      checkRightOperand(t,rhExpr);
      return new OperatorPower(toPos(t),expr, rhExpr);
    }
    return expr;
  }

  // unaryExpression: (PLUS^ | MINUS^ | BANG^) unaryExpression | primaryExpression ;
  private SpelNodeImpl eatUnaryExpression() {
    if (peekToken(TokenKind.PLUS,TokenKind.MINUS,TokenKind.NOT)) {
      Token t = nextToken();
      SpelNodeImpl expr = eatUnaryExpression();
      if (t.kind==TokenKind.NOT) {
        return new OperatorNot(toPos(t),expr);
      else if (t.kind==TokenKind.PLUS) {
        return new OpPlus(toPos(t),expr);
      else {
        Assert.isTrue(t.kind==TokenKind.MINUS);
        return new OpMinus(toPos(t),expr);
      }
    else {
      return eatPrimaryExpression();
    }
  }
  
  // primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?);
  private SpelNodeImpl eatPrimaryExpression() {
    List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
    SpelNodeImpl start = eatStartNode()// always a start node
    nodes.add(start);
    while (maybeEatNode()) {
      nodes.add(pop());
    }
    if (nodes.size()==1) {
      return nodes.get(0);
    else {
      return new CompoundExpression(toPos(start.getStartPosition(),nodes.get(nodes.size()-1).getEndPosition()),nodes.toArray(new SpelNodeImpl[nodes.size()]));
    }
  }
  
  // node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+;
  private boolean maybeEatNode() {
    SpelNodeImpl expr = null;
    if (peekToken(TokenKind.DOT,TokenKind.SAFE_NAVI)) {
      expr = eatDottedNode();
    else {
      expr = maybeEatNonDottedNode();
    }
    if (expr==null) {
      return false;
    else {
      push(expr);
      return true;
    }
  }
  
  // nonDottedNode: indexer;
  private SpelNodeImpl maybeEatNonDottedNode() {
    if (peekToken(TokenKind.LSQUARE)) {
      if (maybeEatIndexer()) {
        return pop();
      }
    }
    return null;
  }
  
  //dottedNode
  // : ((methodOrProperty
  //    | functionOrVar
  //    | projection 
  //    | selection 
  //    | firstSelection 
  //    | lastSelection 
  //    ))
  //  ;
  private SpelNodeImpl eatDottedNode() {
    Token t = nextToken();// it was a '.' or a '?.'
    boolean nullSafeNavigation = t.kind==TokenKind.SAFE_NAVI;
    if (maybeEatMethodOrProperty(nullSafeNavigation|| maybeEatFunctionOrVar() || maybeEatProjection(nullSafeNavigation|| maybeEatSelection(nullSafeNavigation)) {
      return pop();
    }
    if (peekToken()==null) {
      // unexpectedly ran out of data 
      raiseInternalException(t.startpos,SpelMessage.OOD);
    else {
      raiseInternalException(t.startpos,SpelMessage.UNEXPECTED_DATA_AFTER_DOT,toString(peekToken()));
    }
    return null;
  }
  
  // functionOrVar 
  // : (POUND ID LPAREN) => function
  // | var
  //    
  // function : POUND id=ID methodArgs -> ^(FUNCTIONREF[$id] methodArgs);   
  // var : POUND id=ID -> ^(VARIABLEREF[$id]); 
  private boolean maybeEatFunctionOrVar() {
    if (!peekToken(TokenKind.HASH)) {
      return false;
    }
    Token t = nextToken();
    Token functionOrVariableName = eatToken(TokenKind.IDENTIFIER);
    SpelNodeImpl[] args = maybeEatMethodArgs();
    if (args==null) {
      push(new VariableReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos)));
      return true;
    else {
      push(new FunctionReference(functionOrVariableName.data,toPos(t.startpos,functionOrVariableName.endpos),args));
      return true;
    }
  }
  
  // methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
  private SpelNodeImpl[] maybeEatMethodArgs() {
    if (!peekToken(TokenKind.LPAREN)) {
      return null;
    }
    List<SpelNodeImpl> args = new ArrayList<SpelNodeImpl>();
    consumeArguments(args);
    eatToken(TokenKind.RPAREN);
    return args.toArray(new SpelNodeImpl[args.size()]);
  }
  
  private void eatConstructorArgs(List<SpelNodeImpl> accumulatedArguments) {
    if (!peekToken(TokenKind.LPAREN)) {
      throw new InternalParseException(new SpelParseException(expressionString,positionOf(peekToken()),SpelMessage.MISSING_CONSTRUCTOR_ARGS));
    }
    consumeArguments(accumulatedArguments);
    eatToken(TokenKind.RPAREN);
  }
  
  /**
   * Used for consuming arguments for either a method or a constructor call
   */
  private void consumeArguments(List<SpelNodeImpl> accumulatedArguments) {
    int pos = peekToken().startpos;
    Token next = null;
    do {
      nextToken();// consume ( (first time through) or comma (subsequent times)
      Token t = peekToken();
      if (t==null) {
        raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
      }
      if (t.kind!=TokenKind.RPAREN) {
        accumulatedArguments.add(eatExpression());
      }
      next = peekToken();
    while (next!=null && next.kind==TokenKind.COMMA);
    if (next==null) {
      raiseInternalException(pos,SpelMessage.RUN_OUT_OF_ARGUMENTS);
    }
  }
  
  private int positionOf(Token t) {
    if (t==null) {
      // if null assume the problem is because the right token was 
      // not found at the end of the expression
      return expressionString.length();
    else {
      return t.startpos;
    }
  }
    

  //startNode 
  // : parenExpr | literal
  //      | type
  //      | methodOrProperty 
  //      | functionOrVar
  //      | projection 
  //      | selection 
  //      | firstSelection
  //      | lastSelection
  //      | indexer
  //      | constructor
  private SpelNodeImpl eatStartNode() {
    if (maybeEatLiteral()) {
      return pop();
    else if (maybeEatParenExpression()) {
      return pop();
    else if (maybeEatTypeReference() || maybeEatNullReference() || maybeEatConstructorReference() || maybeEatMethodOrProperty(false|| maybeEatFunctionOrVar()) {
      return pop();
    else if (maybeEatBeanReference()) {
      return pop();
    else if (maybeEatProjection(false|| maybeEatSelection(false|| maybeEatIndexer()) {
      return pop();
    else if (maybeEatInlineList()) {
      return pop();
    else {
      return null;
    }
  }
  
  // parse: @beanname @'bean.name'
  // quoted if dotted
  private boolean maybeEatBeanReference() {
    if (peekToken(TokenKind.BEAN_REF)) {
      Token beanRefToken = nextToken();
      Token beanNameToken = null;
      String beanname = null;
      if (peekToken(TokenKind.IDENTIFIER)) {
        beanNameToken = eatToken(TokenKind.IDENTIFIER);
        beanname = beanNameToken.data;
      else if (peekToken(TokenKind.LITERAL_STRING)) {
        beanNameToken = eatToken(TokenKind.LITERAL_STRING);
        beanname = beanNameToken.stringValue();
        beanname = beanname.substring(1, beanname.length() 1);
      else {
        raiseInternalException(beanRefToken.startpos,SpelMessage.INVALID_BEAN_REFERENCE);
      }
      
      BeanReference beanReference = new BeanReference(toPos(beanNameToken),beanname);
      constructedNodes.push(beanReference);
      return true;
    }
    return false;
  }

  private boolean maybeEatTypeReference() {
    if (peekToken(TokenKind.IDENTIFIER)) {
      Token typeName = peekToken();
      if (!typeName.stringValue().equals("T")) {
        return false;
      }
      nextToken();
      eatToken(TokenKind.LPAREN);
      SpelNodeImpl node = eatPossiblyQualifiedId();
      // dotted qualified id
      eatToken(TokenKind.RPAREN);
      constructedNodes.push(new TypeReference(toPos(typeName),node));
      return true;
    }
    return false;
  }

  private boolean maybeEatNullReference() {
    if (peekToken(TokenKind.IDENTIFIER)) {
      Token nullToken = peekToken();
      if (!nullToken.stringValue().equals("null")) {
        return false;
      }
      nextToken();
      constructedNodes.push(new NullLiteral(toPos(nullToken)));
      return true;
    }
    return false;
  }

  //projection: PROJECT^ expression RCURLY!;
  private boolean maybeEatProjection(boolean nullSafeNavigation) {
    Token t = peekToken();
    if (!peekToken(TokenKind.PROJECT,true)) {
      return false;
    }
    SpelNodeImpl expr = eatExpression();
    eatToken(TokenKind.RSQUARE);
    constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr));
    return true;
  }
  
  // list = LCURLY (element (COMMA element)*) RCURLY
  private boolean maybeEatInlineList() {
    Token t = peekToken();
    if (!peekToken(TokenKind.LCURLY,true)) {
      return false;
    }
    SpelNodeImpl expr = null;
    Token closingCurly = peekToken();
    if (peekToken(TokenKind.RCURLY,true)) {
      // empty list '[]'
      expr = new InlineList(toPos(t.startpos,closingCurly.endpos));
    else {
      List<SpelNodeImpl> listElements = new ArrayList<SpelNodeImpl>();
      do {
        listElements.add(eatExpression());
      while (peekToken(TokenKind.COMMA,true));  
      closingCurly = eatToken(TokenKind.RCURLY);
      expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()]));
    }
    constructedNodes.push(expr);
    return true;
  }
  
  private boolean maybeEatIndexer() {
    Token t = peekToken();
    if (!peekToken(TokenKind.LSQUARE,true)) {
      return false;
    }
    SpelNodeImpl expr = eatExpression();
    eatToken(TokenKind.RSQUARE);
    constructedNodes.push(new Indexer(toPos(t),expr));
    return true;
  }
  
  private boolean maybeEatSelection(boolean nullSafeNavigation) {
    Token t = peekToken();
    if (!peekSelectToken()) {
      return false;
    }
    nextToken();
    SpelNodeImpl expr = eatExpression();
    eatToken(TokenKind.RSQUARE);
    if (t.kind==TokenKind.SELECT_FIRST) {      
      constructedNodes.push(new Selection(nullSafeNavigation,Selection.FIRST,toPos(t),expr));
    else if (t.kind==TokenKind.SELECT_LAST) {
      constructedNodes.push(new Selection(nullSafeNavigation,Selection.LAST,toPos(t),expr));
    else {
      constructedNodes.push(new Selection(nullSafeNavigation,Selection.ALL,toPos(t),expr));
    }
    return true;
  }

  /**
   * Eat an identifier, possibly qualified (meaning that it is dotted).
   * TODO AndyC Could create complete identifiers (a.b.c) here rather than a sequence of them? (a, b, c)
   */
  private SpelNodeImpl eatPossiblyQualifiedId() {
    List<SpelNodeImpl> qualifiedIdPieces = new ArrayList<SpelNodeImpl>();
    Token startnode = eatToken(TokenKind.IDENTIFIER);
    qualifiedIdPieces.add(new Identifier(startnode.stringValue(),toPos(startnode)));
    while (peekToken(TokenKind.DOT,true)) {
      Token node = eatToken(TokenKind.IDENTIFIER);
      qualifiedIdPieces.add(new Identifier(node.stringValue(),toPos(node)));              
    }
    return new QualifiedIdentifier(toPos(startnode.startpos,qualifiedIdPieces.get(qualifiedIdPieces.size()-1).getEndPosition()),qualifiedIdPieces.toArray(new SpelNodeImpl[qualifiedIdPieces.size()]));
  }
  
  // This is complicated due to the support for dollars in identifiers.  Dollars are normally separate tokens but
  // there we want to combine a series of identifiers and dollars into a single identifier
  private boolean maybeEatMethodOrProperty(boolean nullSafeNavigation) {
      if (peekToken(TokenKind.IDENTIFIER)) {
      Token methodOrPropertyName = nextToken();
      SpelNodeImpl[] args = maybeEatMethodArgs();
      if (args==null) {
        // property
        push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName)));
        return true;
      else {
        // methodreference
        push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,toPos(methodOrPropertyName),args));
        // TODO what is the end position for a method reference? the name or the last arg?
        return true;
      }
    }
    return false;

  }
  
  //constructor  
    //:  ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
  private boolean maybeEatConstructorReference() {
    if (peekIdentifierToken("new")) {
      Token newToken = nextToken();
      SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId();
      List<SpelNodeImpl> nodes = new ArrayList<SpelNodeImpl>();
      nodes.add(possiblyQualifiedConstructorName);
      if (peekToken(TokenKind.LSQUARE)) {
        // array initializer
        List<SpelNodeImpl> dimensions = new ArrayList<SpelNodeImpl>();
        while (peekToken(TokenKind.LSQUARE,true)) {
          if (!peekToken(TokenKind.RSQUARE)) {
            dimensions.add(eatExpression());
          else {
            dimensions.add(null);
          }
          eatToken(TokenKind.RSQUARE);
        }
        if (maybeEatInlineList()) {
          nodes.add(pop());
        }
        push(new ConstructorReference(toPos(newToken), dimensions.toArray(new SpelNodeImpl[dimensions.size()]),
            nodes.toArray(new SpelNodeImpl[nodes.size()])));
      else {
        // regular constructor invocation
        eatConstructorArgs(nodes);
        // TODO correct end position?
        push(new ConstructorReference(toPos(newToken), nodes.toArray(new SpelNodeImpl[nodes.size()])));
      }
      return true;
    }
    return false;
  }

  private void push(SpelNodeImpl newNode) {
    constructedNodes.push(newNode);
  }

  private SpelNodeImpl pop() {
    return constructedNodes.pop();
  }
  
  //  literal 
  //  : INTEGER_LITERAL 
  //  | boolLiteral
  //  | STRING_LITERAL    
  //  | HEXADECIMAL_INTEGER_LITERAL 
  //  | REAL_LITERAL
  //  | DQ_STRING_LITERAL
  //  | NULL_LITERAL
  private boolean maybeEatLiteral() {
    Token t = peekToken();
    if (t==null) {
      return false;
    }
    if (t.kind==TokenKind.LITERAL_INT) {
      push(Literal.getIntLiteral(t.data, toPos(t)10));
    else if (t.kind==TokenKind.LITERAL_LONG) {
      push(Literal.getLongLiteral(t.data, toPos(t)10));
    else if (t.kind==TokenKind.LITERAL_HEXINT) {
      push(Literal.getIntLiteral(t.data, toPos(t)16));
    else if (t.kind==TokenKind.LITERAL_HEXLONG) {
      push(Literal.getLongLiteral(t.data, toPos(t)16));
    else if (t.kind==TokenKind.LITERAL_REAL) {
      push(Literal.getRealLiteral(t.data, toPos(t),false));
    else if (t.kind==TokenKind.LITERAL_REAL_FLOAT) {
      push(Literal.getRealLiteral(t.data, toPos(t)true));
    else if (peekIdentifierToken("true")) { 
      push(new BooleanLiteral(t.data,toPos(t),true));
    else if (peekIdentifierToken("false")) {
      push(new BooleanLiteral(t.data,toPos(t),false));
    else if (t.kind==TokenKind.LITERAL_STRING) {
      push(new StringLiteral(t.data,toPos(t),t.data));
    else {
      return false;      
    }
    nextToken();
    return true;
  }

  //parenExpr : LPAREN! expression RPAREN!;
  private boolean maybeEatParenExpression() {
    if (peekToken(TokenKind.LPAREN)) {
      nextToken();
      SpelNodeImpl expr = eatExpression();
      eatToken(TokenKind.RPAREN);
      push(expr);
      return true;
    else {
      return false;
    }
  }

  // relationalOperator
  // : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN
  // | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES 
  private Token maybeEatRelationalOperator() {
    Token t = peekToken();
    if (t==null) {
      return null
    }
    if (t.isNumericRelationalOperator()) {
      return t;
    }
    if (t.isIdentifier()) {
      String idString = t.stringValue();
      if (idString.equalsIgnoreCase("instanceof")) {
        return t.asInstanceOfToken();
      else if (idString.equalsIgnoreCase("matches")) {
        return t.asMatchesToken();
      else if (idString.equalsIgnoreCase("between")) {
        return t.asBetweenToken();
      }      
    }
    return null;
  }

  private Token eatToken(TokenKind expectedKind) {
    Token t = nextToken();
    if (t==null) {
      raiseInternalExceptionexpressionString.length(), SpelMessage.OOD);
    }
    if (t.kind!=expectedKind) { 
      raiseInternalException(t.startpos,SpelMessage.NOT_EXPECTED_TOKEN, expectedKind.toString().toLowerCase(),t.getKind().toString().toLowerCase());
    }
    return t;
  }

  private boolean peekToken(TokenKind desiredTokenKind) {
    return peekToken(desiredTokenKind,false);
  }

  private boolean peekToken(TokenKind desiredTokenKind, boolean consumeIfMatched) {
    if (!moreTokens()) {
      return false;
    }
    Token t = peekToken();
    if (t.kind==desiredTokenKind) {
      if (consumeIfMatched) {
        tokenStreamPointer++;
      }
      return true;
    else {
      return false;
    }
  }

  private boolean peekToken(TokenKind possible1,TokenKind possible2) {
    if (!moreTokens()) return false;
    Token t = peekToken();
    return t.kind==possible1 || t.kind==possible2;
  }

  private boolean peekToken(TokenKind possible1,TokenKind possible2, TokenKind possible3) {
    if (!moreTokens()) return false;
    Token t = peekToken();
    return t.kind==possible1 || t.kind==possible2 || t.kind==possible3;
  }

  private boolean peekIdentifierToken(String identifierString) {
    if (!moreTokens()) {
      return false;
    }
    Token t = peekToken();
    return t.kind==TokenKind.IDENTIFIER && t.stringValue().equalsIgnoreCase(identifierString);
  }
  
  private boolean peekSelectToken() {
    if (!moreTokens()) return false;
    Token t = peekToken();
    return t.kind==TokenKind.SELECT || t.kind==TokenKind.SELECT_FIRST || t.kind==TokenKind.SELECT_LAST;
  }
  
  
  private boolean moreTokens() {
    return tokenStreamPointer<tokenStream.size();
  }
  
  private Token nextToken() {
    if (tokenStreamPointer>=tokenStreamLength) {
      return null;
    }
    return tokenStream.get(tokenStreamPointer++);
  }

  private Token peekToken() {
    if (tokenStreamPointer>=tokenStreamLength) {
      return null;
    }
    return tokenStream.get(tokenStreamPointer);
  }

  private void raiseInternalException(int pos, SpelMessage message,Object... inserts) {
    throw new InternalParseException(new SpelParseException(expressionString,pos,message,inserts));    
  }
  
  public String toString(Token t) {
    if (t.getKind().hasPayload()) {
      return t.stringValue();
    else {
      return t.kind.toString().toLowerCase();
    }
  }
  
  private void checkRightOperand(Token token, SpelNodeImpl operandExpression) {
    if (operandExpression==null) {
      raiseInternalException(token.startpos,SpelMessage.RIGHT_OPERAND_PROBLEM);
    }
  }

  /**
   * Compress the start and end of a token into a single int
   */
  private int toPos(Token t) {
    return (t.startpos<<16)+t.endpos;
  }

  private int toPos(int start,int end) {
    return (start<<16)+end;
  }
  
}