src/SpelExpressionParser.js
/*
* Copyright 2002-2015 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.
*/
/**
* @author Andy Clement
* @author Juergen Hoeller
* @author Ben March
* @since 0.2.0
*
*/
import {TokenKind} from './TokenKind';
import {Tokenizer} from './Tokenizer';
import {BooleanLiteral} from './ast/BooleanLiteral';
import {NumberLiteral} from './ast/NumberLiteral';
import {StringLiteral} from './ast/StringLiteral';
import {NullLiteral} from './ast/NullLiteral';
import {FunctionReference} from './ast/FunctionReference';
import {MethodReference} from './ast/MethodReference';
import {PropertyReference} from './ast/PropertyReference';
import {VariableReference} from './ast/VariableReference';
import {CompoundExpression} from './ast/CompoundExpression';
import {Indexer} from './ast/Indexer';
import {Assign} from './ast/Assign';
import {OpEQ} from './ast/OpEQ';
import {OpNE} from './ast/OpNE';
import {OpGE} from './ast/OpGE';
import {OpGT} from './ast/OpGT';
import {OpLE} from './ast/OpLE';
import {OpLT} from './ast/OpLT';
import {OpPlus} from './ast/OpPlus';
import {OpMinus} from './ast/OpMinus';
import {OpMultiply} from './ast/OpMultiply';
import {OpDivide} from './ast/OpDivide';
import {OpModulus} from './ast/OpModulus';
import {OpPower} from './ast/OpPower';
import {OpInc} from './ast/OpInc';
import {OpDec} from './ast/OpDec';
import {OpNot} from './ast/OpNot';
import {OpAnd} from './ast/OpAnd';
import {OpOr} from './ast/OpOr';
import {OpMatches} from "./ast/OpMatches";
import {Ternary} from './ast/Ternary';
import {Elvis} from './ast/Elvis';
import {InlineList} from './ast/InlineList';
import {InlineMap} from './ast/InlineMap';
import {Selection} from './ast/Selection';
import {Projection} from './ast/Projection';
//not yet implemented
import {OpInstanceof} from './ast/OpInstanceof';
import {OpBetween} from './ast/OpBetween';
import {TypeReference} from './ast/TypeReference';
import {BeanReference} from './ast/BeanReference';
import {Identifier} from './ast/Identifier';
import {QualifiedIdentifier} from './ast/QualifiedIdentifier';
import {ConstructorReference} from './ast/ConstructorReference';
export var SpelExpressionParser = function () {
var VALID_QUALIFIED_ID_PATTERN = new RegExp('[\\p{L}\\p{N}_$]+');
var configuration;
// For rules that build nodes, they are stacked here for return
var constructedNodes = [];
// The expression being parsed
var expressionString;
// The token stream constructed from that expression string
var tokenStream;
// length of a populated token stream
var tokenStreamLength;
// Current location in the token stream when processing tokens
var tokenStreamPointer;
/**
* Create a parser with some configured behavior.
* @param config custom configuration options
*/
function setConfiguration(config) {
configuration = config;
}
function parse(expression, context) {
try {
expressionString = expression;
tokenStream = Tokenizer.tokenize(expression);
tokenStreamLength = tokenStream.length;
tokenStreamPointer = 0;
constructedNodes = [];
var ast = eatExpression();
if (moreTokens()) {
raiseInternalException(peekToken().startPos, 'MORE_INPUT', nextToken().toString());
}
//Assert.isTrue(this.constructedNodes.isEmpty());
return ast;
}
catch (e) {
throw e.message;
}
}
// expression
// : logicalOrExpression
// ( (ASSIGN^ logicalOrExpression)
// | (DEFAULT^ logicalOrExpression)
// | (QMARK^ expression COLON! expression)
// | (ELVIS^ expression))?;
function eatExpression() {
var expr = eatLogicalOrExpression();
if (moreTokens()) {
var token = peekToken();
if (token.getKind() === TokenKind.ASSIGN) { // a=b
if (expr === null) {
expr = NullLiteral.create(toPosBounds(token.startPos - 1, token.endPos - 1));
}
nextToken();
var assignedValue = eatLogicalOrExpression();
return Assign.create(toPosToken(token), expr, assignedValue);
}
if (token.getKind() === TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b)
if (expr === null) {
expr = NullLiteral.create(toPosBounds(token.startPos - 1, token.endPos - 2));
}
nextToken(); // elvis has left the building
var valueIfNull = eatExpression();
if (valueIfNull === null) {
valueIfNull = NullLiteral.create(toPosBounds(token.startPos + 1, token.endPos + 1));
}
return Elvis.create(toPosToken(token), expr, valueIfNull);
}
if (token.getKind() === TokenKind.QMARK) { // a?b:c
if (expr === null) {
expr = NullLiteral.create(toPosBounds(token.startPos - 1, token.endPos - 1));
}
nextToken();
var ifTrueExprValue = eatExpression();
eatToken(TokenKind.COLON);
var ifFalseExprValue = eatExpression();
return Ternary.create(toPosToken(token), expr, ifTrueExprValue, ifFalseExprValue);
}
}
return expr;
}
//logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*;
function eatLogicalOrExpression() {
var expr = eatLogicalAndExpression();
while (peekIdentifierToken('or') || peekTokenOne(TokenKind.SYMBOLIC_OR)) {
var token = nextToken(); //consume OR
var rhExpr = eatLogicalAndExpression();
checkOperands(token, expr, rhExpr);
expr = OpOr.create(toPosToken(token), expr, rhExpr);
}
return expr;
}
// logicalAndExpression : relationalExpression (AND^ relationalExpression)*;
function eatLogicalAndExpression() {
var expr = eatRelationalExpression();
while (peekIdentifierToken('and') || peekTokenOne(TokenKind.SYMBOLIC_AND)) {
var token = nextToken(); // consume 'AND'
var rhExpr = eatRelationalExpression();
checkOperands(token, expr, rhExpr);
expr = OpAnd.create(toPosToken(token), expr, rhExpr);
}
return expr;
}
// relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
function eatRelationalExpression() {
var expr = eatSumExpression();
var relationalOperatorToken = maybeEatRelationalOperator();
if (relationalOperatorToken !== null) {
var token = nextToken(); // consume relational operator token
var rhExpr = eatSumExpression();
checkOperands(token, expr, rhExpr);
var tk = relationalOperatorToken.kind;
if (relationalOperatorToken.isNumericRelationalOperator()) {
var pos = toPosToken(token);
if (tk === TokenKind.GT) {
return OpGT.create(pos, expr, rhExpr);
}
if (tk === TokenKind.LT) {
return OpLT.create(pos, expr, rhExpr);
}
if (tk === TokenKind.LE) {
return OpLE.create(pos, expr, rhExpr);
}
if (tk === TokenKind.GE) {
return OpGE.create(pos, expr, rhExpr);
}
if (tk === TokenKind.EQ) {
return OpEQ.create(pos, expr, rhExpr);
}
//Assert.isTrue(tk === TokenKind.NE);
return OpNE.create(pos, expr, rhExpr);
}
if (tk === TokenKind.INSTANCEOF) {
return OpInstanceof.create(toPosToken(token), expr, rhExpr);
}
if (tk === TokenKind.MATCHES) {
return OpMatches.create(toPosToken(token), expr, rhExpr);
}
//Assert.isTrue(tk === TokenKind.BETWEEN);
return OpBetween.create(toPosToken(token), expr, rhExpr);
}
return expr;
}
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
function eatSumExpression() {
var expr = eatProductExpression();
while (peekTokenAny(TokenKind.PLUS, TokenKind.MINUS, TokenKind.INC)) {
var token = nextToken();//consume PLUS or MINUS or INC
var rhExpr = eatProductExpression();
checkRightOperand(token, rhExpr);
if (token.getKind() === TokenKind.PLUS) {
expr = OpPlus.create(toPosToken(token), expr, rhExpr);
}
else if (token.getKind() === TokenKind.MINUS) {
expr = OpMinus.create(toPosToken(token), expr, rhExpr);
}
}
return expr;
}
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
function eatProductExpression() {
var expr = eatPowerIncDecExpression();
while (peekTokenAny(TokenKind.STAR, TokenKind.DIV, TokenKind.MOD)) {
var token = nextToken(); // consume STAR/DIV/MOD
var rhExpr = eatPowerIncDecExpression();
checkOperands(token, expr, rhExpr);
if (token.getKind() === TokenKind.STAR) {
expr = OpMultiply.create(toPosToken(token), expr, rhExpr);
}
else if (token.getKind() === TokenKind.DIV) {
expr = OpDivide.create(toPosToken(token), expr, rhExpr);
}
else {
//Assert.isTrue(token.getKind() === TokenKind.MOD);
expr = OpModulus.create(toPosToken(token), expr, rhExpr);
}
}
return expr;
}
// powerExpr : unaryExpression (POWER^ unaryExpression)? (INC || DEC) ;
function eatPowerIncDecExpression() {
var expr = eatUnaryExpression(),
token;
if (peekTokenOne(TokenKind.POWER)) {
token = nextToken(); //consume POWER
var rhExpr = eatUnaryExpression();
checkRightOperand(token, rhExpr);
return OpPower.create(toPosToken(token), expr, rhExpr);
}
if (expr !== null && peekTokenAny(TokenKind.INC, TokenKind.DEC)) {
token = nextToken(); //consume INC/DEC
if (token.getKind() === TokenKind.INC) {
return OpInc.create(toPosToken(token), true, expr);
}
return OpDec.create(toPosToken(token), true, expr);
}
return expr;
}
// unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ;
function eatUnaryExpression() {
var token,
expr;
if (peekTokenAny(TokenKind.PLUS, TokenKind.MINUS, TokenKind.NOT)) {
token = nextToken();
expr = eatUnaryExpression();
if (token.getKind() === TokenKind.NOT) {
return OpNot.create(toPosToken(token), expr);
}
if (token.getKind() === TokenKind.PLUS) {
return OpPlus.create(toPosToken(token), expr);
}
//Assert.isTrue(token.getKind() === TokenKind.MINUS);
return OpMinus.create(toPosToken(token), expr);
}
if (peekTokenAny(TokenKind.INC, TokenKind.DEC)) {
token = nextToken();
expr = eatUnaryExpression();
if (token.getKind() === TokenKind.INC) {
return OpInc.create(toPosToken(token), false, expr);
}
return OpDec.create(toPosToken(token), false, expr);
}
return eatPrimaryExpression();
}
// primaryExpression : startNode (node)? -> ^(EXPRESSION startNode (node)?);
function eatPrimaryExpression() {
var nodes = [];
var start = eatStartNode(); // always a start node
nodes.push(start);
while (maybeEatNode()) {
nodes.push(pop());
}
if (nodes.length === 1) {
return nodes[0];
}
return CompoundExpression.create(toPosBounds(start.getStartPosition(), nodes[nodes.length - 1].getEndPosition()), nodes);
}
// node : ((DOT dottedNode) | (SAFE_NAVI dottedNode) | nonDottedNode)+;
function maybeEatNode() {
var expr = null;
if (peekTokenAny(TokenKind.DOT, TokenKind.SAFE_NAVI)) {
expr = eatDottedNode();
}
else {
expr = maybeEatNonDottedNode();
}
if (expr === null) {
return false;
}
else {
push(expr);
return true;
}
}
// nonDottedNode: indexer;
function maybeEatNonDottedNode() {
if (peekTokenOne(TokenKind.LSQUARE)) {
if (maybeEatIndexer()) {
return pop();
}
}
return null;
}
//dottedNode
// : ((methodOrProperty
// | functionOrVar
// | projection
// | selection
// | firstSelection
// | lastSelection
// ))
// ;
function eatDottedNode() {
var token = nextToken();// it was a '.' or a '?.'
var nullSafeNavigation = token.getKind() === TokenKind.SAFE_NAVI;
if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() || maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) {
return pop();
}
if (peekToken() === null) {
// unexpectedly ran out of data
raiseInternalException(token.startPos, 'OOD');
}
else {
raiseInternalException(token.startPos, '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]);
function maybeEatFunctionOrVar() {
if (!peekTokenOne(TokenKind.HASH)) {
return false;
}
var token = nextToken();
var functionOrVariableName = eatToken(TokenKind.IDENTIFIER);
var args = maybeEatMethodArgs();
if (args === null) {
push(VariableReference.create(functionOrVariableName.data, toPosBounds(token.startPos, functionOrVariableName.endPos)));
return true;
}
push(FunctionReference.create(functionOrVariableName.data, toPosBounds(token.startPos, functionOrVariableName.endPos), args));
return true;
}
// methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
function maybeEatMethodArgs() {
if (!peekTokenOne(TokenKind.LPAREN)) {
return null;
}
var args = [];
consumeArguments(args);
eatToken(TokenKind.RPAREN);
return args;
}
function eatConstructorArgs(accumulatedArguments) {
if (!peekTokenOne(TokenKind.LPAREN)) {
raiseInternalException(toPosToken(peekToken()), 'MISSING_CONSTRUCTOR_ARGS');
}
consumeArguments(accumulatedArguments);
eatToken(TokenKind.RPAREN);
}
/**
* Used for consuming arguments for either a method or a constructor call
*/
function consumeArguments(accumulatedArguments) {
var pos = peekToken().startPos;
var next;
do {
nextToken(); // consume ( (first time through) or comma (subsequent times)
var token = peekToken();
if (token === null) {
raiseInternalException(pos, 'RUN_OUT_OF_ARGUMENTS');
}
if (token.getKind() !== TokenKind.RPAREN) {
accumulatedArguments.push(eatExpression());
}
next = peekToken();
}
while (next !== null && next.kind === TokenKind.COMMA);
if (next === null) {
raiseInternalException(pos, 'RUN_OUT_OF_ARGUMENTS');
}
}
function positionOf(token) {
if (token === null) {
// if null assume the problem is because the right token was
// not found at the end of the expression
return expressionString.length;
}
return token.startPos;
}
//startNode
// : parenExpr | literal
// | type
// | methodOrProperty
// | functionOrVar
// | projection
// | selection
// | firstSelection
// | lastSelection
// | indexer
// | constructor
function 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 (maybeEatInlineListOrMap()) {
return pop();
}
else {
return null;
}
}
// parse: @beanname @'bean.name'
// quoted if dotted
function maybeEatBeanReference() {
if (peekTokenOne(TokenKind.BEAN_REF)) {
var beanRefToken = nextToken();
var beanNameToken = null;
var beanName = null;
if (peekTokenOne(TokenKind.IDENTIFIER)) {
beanNameToken = eatToken(TokenKind.IDENTIFIER);
beanName = beanNameToken.data;
}
else if (peekTokenOne(TokenKind.LITERAL_STRING)) {
beanNameToken = eatToken(TokenKind.LITERAL_STRING);
beanName = beanNameToken.stringValue();
beanName = beanName.substring(1, beanName.length() - 1);
}
else {
raiseInternalException(beanRefToken.startPos, 'INVALID_BEAN_REFERENCE');
}
var beanReference = BeanReference.create(toPosToken(beanNameToken), beanName);
push(beanReference);
return true;
}
return false;
}
function maybeEatTypeReference() {
if (peekTokenOne(TokenKind.IDENTIFIER)) {
var typeName = peekToken();
if (typeName.stringValue() !== 'T') {
return false;
}
// It looks like a type reference but is T being used as a map key?
var token = nextToken();
if (peekTokenOne(TokenKind.RSQUARE)) {
// looks like 'T]' (T is map key)
push(PropertyReference.create(token.stringValue(), toPosToken(token)));
return true;
}
eatToken(TokenKind.LPAREN);
var node = eatPossiblyQualifiedId();
// dotted qualified id
// Are there array dimensions?
var dims = 0;
while (peekTokenConsumeIfMatched(TokenKind.LSQUARE, true)) {
eatToken(TokenKind.RSQUARE);
dims++;
}
eatToken(TokenKind.RPAREN);
push(TypeReference.create(toPosToken(typeName), node, dims));
return true;
}
return false;
}
function maybeEatNullReference() {
if (peekTokenOne(TokenKind.IDENTIFIER)) {
var nullToken = peekToken();
if (nullToken.stringValue().toLowerCase() !== 'null') {
return false;
}
nextToken();
push(NullLiteral.create(toPosToken(nullToken)));
return true;
}
return false;
}
//projection: PROJECT^ expression RCURLY!;
function maybeEatProjection(nullSafeNavigation) {
var token = peekToken();
if (!peekTokenConsumeIfMatched(TokenKind.PROJECT, true)) {
return false;
}
var expr = eatExpression();
eatToken(TokenKind.RSQUARE);
push(Projection.create(nullSafeNavigation, toPosToken(token), expr));
return true;
}
// list = LCURLY (element (COMMA element)*) RCURLY
// map = LCURLY (key ':' value (COMMA key ':' value)*) RCURLY
function maybeEatInlineListOrMap() {
var token = peekToken(),
listElements = [];
if (!peekTokenConsumeIfMatched(TokenKind.LCURLY, true)) {
return false;
}
var expr = null;
var closingCurly = peekToken();
if (peekTokenConsumeIfMatched(TokenKind.RCURLY, true)) {
// empty list '{}'
expr = InlineList.create(toPosBounds(token.startPos, closingCurly.endPos));
}
else if (peekTokenConsumeIfMatched(TokenKind.COLON, true)) {
closingCurly = eatToken(TokenKind.RCURLY);
// empty map '{:}'
expr = InlineMap.create(toPosBounds(token.startPos, closingCurly.endPos));
}
else {
var firstExpression = eatExpression();
// Next is either:
// '}' - end of list
// ',' - more expressions in this list
// ':' - this is a map!
if (peekTokenOne(TokenKind.RCURLY)) { // list with one item in it
listElements.push(firstExpression);
closingCurly = eatToken(TokenKind.RCURLY);
expr = InlineList.create(toPosBounds(token.startPos, closingCurly.endPos), listElements);
}
else if (peekTokenConsumeIfMatched(TokenKind.COMMA, true)) { // multi item list
listElements.push(firstExpression);
do {
listElements.push(eatExpression());
}
while (peekTokenConsumeIfMatched(TokenKind.COMMA, true));
closingCurly = eatToken(TokenKind.RCURLY);
expr = InlineList.create(toPosToken(token.startPos, closingCurly.endPos), listElements);
}
else if (peekTokenConsumeIfMatched(TokenKind.COLON, true)) { // map!
var mapElements = [];
mapElements.push(firstExpression);
mapElements.push(eatExpression());
while (peekTokenConsumeIfMatched(TokenKind.COMMA, true)) {
mapElements.push(eatExpression());
eatToken(TokenKind.COLON);
mapElements.push(eatExpression());
}
closingCurly = eatToken(TokenKind.RCURLY);
expr = InlineMap.create(toPosBounds(token.startPos, closingCurly.endPos), mapElements);
}
else {
raiseInternalException(token.startPos, 'OOD');
}
}
push(expr);
return true;
}
function maybeEatIndexer() {
var token = peekToken();
if (!peekTokenConsumeIfMatched(TokenKind.LSQUARE, true)) {
return false;
}
var expr = eatExpression();
eatToken(TokenKind.RSQUARE);
push(Indexer.create(toPosToken(token), expr));
return true;
}
function maybeEatSelection(nullSafeNavigation) {
var token = peekToken();
if (!peekSelectToken()) {
return false;
}
nextToken();
var expr = eatExpression();
if (expr === null) {
raiseInternalException(toPosToken(token), 'MISSING_SELECTION_EXPRESSION');
}
eatToken(TokenKind.RSQUARE);
if (token.getKind() === TokenKind.SELECT_FIRST) {
push(Selection.create(nullSafeNavigation, Selection.FIRST, toPosToken(token), expr));
}
else if (token.getKind() === TokenKind.SELECT_LAST) {
push(Selection.create(nullSafeNavigation, Selection.LAST, toPosToken(token), expr));
}
else {
push(Selection.create(nullSafeNavigation, Selection.ALL, toPosToken(token), 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)
*/
function eatPossiblyQualifiedId() {
var qualifiedIdPieces = [];
var node = peekToken();
while (isValidQualifiedId(node)) {
nextToken();
if (node.kind !== TokenKind.DOT) {
qualifiedIdPieces.push(Identifier.create(node.stringValue(), toPosToken(node)));
}
node = peekToken();
}
if (!qualifiedIdPieces.length) {
if (node === null) {
raiseInternalException(expressionString.length(), 'OOD');
}
raiseInternalException(node.startPos, 'NOT_EXPECTED_TOKEN', 'qualified ID', node.getKind().toString().toLowerCase());
}
var pos = toPosBounds(qualifiedIdPieces[0].getStartPosition(), qualifiedIdPieces[qualifiedIdPieces.length - 1].getEndPosition());
return QualifiedIdentifier.create(pos, qualifiedIdPieces);
}
function isValidQualifiedId(node) {
if (node === null || node.kind === TokenKind.LITERAL_STRING) {
return false;
}
if (node.kind === TokenKind.DOT || node.kind === TokenKind.IDENTIFIER) {
return true;
}
var value = node.stringValue();
return (value && value.length && VALID_QUALIFIED_ID_PATTERN.test(value));
}
// 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
function maybeEatMethodOrProperty(nullSafeNavigation) {
if (peekTokenOne(TokenKind.IDENTIFIER)) {
var methodOrPropertyName = nextToken();
var args = maybeEatMethodArgs();
if (args === null) {
// property
push(PropertyReference.create(nullSafeNavigation, methodOrPropertyName.stringValue(), toPosToken(methodOrPropertyName)));
return true;
}
// methodreference
push(MethodReference.create(nullSafeNavigation, methodOrPropertyName.stringValue(), toPosToken(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)
function maybeEatConstructorReference() {
if (peekIdentifierToken('new')) {
var newToken = nextToken();
// It looks like a constructor reference but is NEW being used as a map key?
if (peekTokenOne(TokenKind.RSQUARE)) {
// looks like 'NEW]' (so NEW used as map key)
push(PropertyReference.create(newToken.stringValue(), toPosToken(newToken)));
return true;
}
var possiblyQualifiedConstructorName = eatPossiblyQualifiedId();
var nodes = [];
nodes.push(possiblyQualifiedConstructorName);
if (peekTokenOne(TokenKind.LSQUARE)) {
// array initializer
var dimensions = [];
while (peekTokenConsumeIfMatched(TokenKind.LSQUARE, true)) {
if (!peekTokenOne(TokenKind.RSQUARE)) {
dimensions.push(eatExpression());
}
else {
dimensions.push(null);
}
eatToken(TokenKind.RSQUARE);
}
if (maybeEatInlineListOrMap()) {
nodes.push(pop());
}
push(ConstructorReference.create(toPosToken(newToken), dimensions, nodes));
}
else {
// regular constructor invocation
eatConstructorArgs(nodes);
// TODO correct end position?
push(ConstructorReference.create(toPosToken(newToken), nodes));
}
return true;
}
return false;
}
function push(newNode) {
constructedNodes.push(newNode);
}
function pop() {
return constructedNodes.pop();
}
// literal
// : INTEGER_LITERAL
// | boolLiteral
// | STRING_LITERAL
// | HEXADECIMAL_INTEGER_LITERAL
// | REAL_LITERAL
// | DQ_STRING_LITERAL
// | NULL_LITERAL
function maybeEatLiteral() {
var token = peekToken();
if (token === null) {
return false;
}
if (token.getKind() === TokenKind.LITERAL_INT ||
token.getKind() === TokenKind.LITERAL_LONG) {
push(NumberLiteral.create(parseInt(token.stringValue(), 10), toPosToken(token)));
}
else if ( token.getKind() === TokenKind.LITERAL_REAL ||
token.getKind() === TokenKind.LITERAL_REAL_FLOAT) {
push(NumberLiteral.create(parseFloat(token.stringValue()), toPosToken(token)));
}
else if ( token.getKind() === TokenKind.LITERAL_HEXINT ||
token.getKind() === TokenKind.LITERAL_HEXLONG) {
push(NumberLiteral.create(parseInt(token.stringValue(), 16), toPosToken(token)));
}
else if (peekIdentifierToken('true')) {
push(BooleanLiteral.create(true, toPosToken(token)));
}
else if (peekIdentifierToken('false')) {
push(BooleanLiteral.create(false, toPosToken(token)));
}
else if (token.getKind() === TokenKind.LITERAL_STRING) {
push(StringLiteral.create(token.stringValue(), toPosToken(token)));
}
else {
return false;
}
nextToken();
return true;
}
//parenExpr : LPAREN! expression RPAREN!;
function maybeEatParenExpression() {
if (peekTokenOne(TokenKind.LPAREN)) {
nextToken();
var 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
function maybeEatRelationalOperator() {
var token = peekToken();
if (token === null) {
return null;
}
if (token.isNumericRelationalOperator()) {
return token;
}
if (token.isIdentifier()) {
var idString = token.stringValue();
if (idString.toLowerCase() === 'instanceof') {
return token.asInstanceOfToken();
}
if (idString.toLowerCase() === 'matches') {
return token.asMatchesToken();
}
if (idString.toLowerCase() === 'between') {
return token.asBetweenToken();
}
}
return null;
}
function eatToken(expectedKind) {
var token = nextToken();
if (token === null) {
raiseInternalException(expressionString.length, 'OOD');
}
if (token.getKind() !== expectedKind) {
raiseInternalException(token.startPos, 'NOT_EXPECTED_TOKEN',
expectedKind.toString().toLowerCase(), token.getKind().toString().toLowerCase());
}
return token;
}
function peekTokenOne(desiredTokenKind) {
return peekTokenConsumeIfMatched(desiredTokenKind, false);
}
function peekTokenConsumeIfMatched(desiredTokenKind, consumeIfMatched) {
if (!moreTokens()) {
return false;
}
var token = peekToken();
if (token.getKind() === desiredTokenKind) {
if (consumeIfMatched) {
tokenStreamPointer++;
}
return true;
}
if (desiredTokenKind === TokenKind.IDENTIFIER) {
// might be one of the textual forms of the operators (e.g. NE for !== ) - in which case we can treat it as an identifier
// The list is represented here: Tokenizer.alternativeOperatorNames and those ones are in order in the TokenKind enum
if (token.getKind().ordinal() >= TokenKind.DIV.ordinal() && token.getKind().ordinal() <= TokenKind.NOT.ordinal() && token.data !== null) {
// if token.data were null, we'd know it wasn'token the textual form, it was the symbol form
return true;
}
}
return false;
}
function peekTokenAny() {
if (!moreTokens()) {
return false;
}
var token = peekToken();
var args = Array.prototype.slice.call(arguments);
for (var i = 0, l = args.length; i < l; i += 1) {
if (token.getKind() === args[i]) {
return true;
}
}
return false;
}
function peekIdentifierToken(identifierString) {
if (!moreTokens()) {
return false;
}
var token = peekToken();
return token.getKind() === TokenKind.IDENTIFIER && token.stringValue().toLowerCase() === identifierString.toLowerCase();
}
function peekSelectToken() {
if (!moreTokens()) {
return false;
}
var token = peekToken();
return token.getKind() === TokenKind.SELECT || token.getKind() === TokenKind.SELECT_FIRST || token.getKind() === TokenKind.SELECT_LAST;
}
function moreTokens() {
return tokenStreamPointer < tokenStream.length;
}
function nextToken() {
if (tokenStreamPointer >= tokenStreamLength) {
return null;
}
return tokenStream[tokenStreamPointer++];
}
function peekToken() {
if (tokenStreamPointer >= tokenStreamLength) {
return null;
}
return tokenStream[tokenStreamPointer];
}
function raiseInternalException(pos, message, expected, actual) {
if (expected) {
message += '\nExpected: ' + expected;
}
if (actual) {
message += '\nActual: ' + actual;
}
throw {
name: 'InternalParseException',
message: 'Error occurred while attempting to parse expression \'' + expressionString + '\' at position ' + pos + '. Message: ' + message
};
}
function toString(token) {
if (token.getKind().hasPayload()) {
return token.stringValue();
}
return token.getKind().toString().toLowerCase();
}
function checkOperands(token, left, right) {
checkLeftOperand(token, left);
checkRightOperand(token, right);
}
function checkLeftOperand(token, operandExpression) {
if (operandExpression === null) {
raiseInternalException(token.startPos, 'LEFT_OPERAND_PROBLEM');
}
}
function checkRightOperand(token, operandExpression) {
if (operandExpression === null) {
raiseInternalException(token.startPos, 'RIGHT_OPERAND_PROBLEM');
}
}
/**
* Compress the start and end of a token into a single int.
*/
function toPosToken(token) {
return (token.startPos << 16) + token.endPos;
}
function toPosBounds(start, end) {
return (start << 16) + end;
}
return {
setConfiguration: setConfiguration,
parse: parse
};
};