UNPKG

spel2js

Version:

Parse Spring Expression Language in JavaScript

1,505 lines (1,269 loc) 168 kB
/* * 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. */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["spel2js"] = factory(); else root["spel2js"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 3); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /* * 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. */ /** * The common supertype of all AST nodes in a parsed Spring Expression Language * format expression. * * @author Andy Clement * @author Ben March * @since 0.2.0 */ function createSpelNode(nodeType, position) { var node = {}, type = nodeType || 'Abstract', children = [], parent = null, activeContext; node._type = type; node.getType = function () { return type; }; node.setType = function (nodeType) { type = nodeType; }; node.getChildren = function () { return children; }; node.addChild = function (childNode) { if (!childNode) { // See OpMinus and OpPlus: right node can be null for unary mode return; } if (!childNode.setParent) { throw { name: 'Error', message: 'Trying to add a child which is not a node: ' + JSON.stringify(childNode) }; } childNode.setParent(node); children.push(childNode); }; node.getParent = function () { return parent; }; node.setParent = function (parentNode) { parent = parentNode; }; node.getContext = function (state) { return activeContext || state.activeContext.peek(); }; node.setContext = function (nodeContext) { activeContext = nodeContext; }; node.getStartPosition = function () { return position >> 16; }; node.getEndPosition = function () { return position & 0xffff; }; //must override node.getValue = function () { throw { name: 'MethodNotImplementedException', message: 'SpelNode#getValue() must be overridden.' }; }; node.toString = function () { var s = 'Kind: ' + node.getType(); //s += ', Value: ' + node.getValue(); s += ', Children: ['; for (var i = 0, l = node.getChildren().length; i < l; i += 1) { s += '{' + node.getChildren()[i] + '}, '; } s += ']'; return s; }; //constructor if (position === 0) { throw { name: 'Error', message: 'Position cannot be 0' }; } for (var _len = arguments.length, operands = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { operands[_key - 2] = arguments[_key]; } if (operands) { operands.forEach(function (operand) { node.addChild(operand); }); } return node; } var SpelNode = exports.SpelNode = { create: createSpelNode }; /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Stack = Stack; /* * 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 Ben March * @since 0.2.0 */ function Stack(startingElements) { this.elements = startingElements || []; } Stack.prototype.push = function (el) { this.elements.push(el); return el; }; Stack.prototype.pop = function () { return this.elements.pop(); }; Stack.prototype.peek = function () { return this.elements[this.elements.length - 1]; }; Stack.prototype.empty = function () { return this.elements.length > 0; }; Stack.prototype.search = function (el) { return this.elements.length - this.elements.indexOf(el); }; /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /* * 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 Ben March * @since 0.2.0 */ var types = { LITERAL_INT: 1, //tested LITERAL_LONG: 2, //tested LITERAL_HEXINT: 3, //tested LITERAL_HEXLONG: 4, //tested LITERAL_STRING: 5, //tested LITERAL_REAL: 6, //tested LITERAL_REAL_FLOAT: 7, //tested LPAREN: '(', //tested RPAREN: ')', //tested COMMA: ',', //tested IDENTIFIER: 0, //tested COLON: ':', //tested HASH: '#', //tested RSQUARE: ']', //tested LSQUARE: '[', //tested LCURLY: '{', //tested RCURLY: '}', //tested DOT: '.', //tested PLUS: '+', //tested STAR: '*', //tested MINUS: '-', //tested SELECT_FIRST: '^[', //tested SELECT_LAST: '$[', //tested QMARK: '?', //tested PROJECT: '![', //tested DIV: '/', //tested GE: '>=', //tested GT: '>', //tested LE: '<=', //tested LT: '<', //tested EQ: '==', //tested NE: '!=', //tested MOD: '%', //tested NOT: '!', //tested ASSIGN: '=', //tested INSTANCEOF: 'instanceof', //test fails MATCHES: 'matches', //test fails BETWEEN: 'between', //test fails SELECT: '?[', //tested POWER: '^', //tested ELVIS: '?:', //tested SAFE_NAVI: '?.', //tested BEAN_REF: '@', //tested SYMBOLIC_OR: '||', //tested SYMBOLIC_AND: '&&', //tested INC: '++', //tested DEC: '--' //tested }; function TokenKind(type) { this.type = type; this.tokenChars = types[type]; this._hasPayload = typeof types[type] !== 'string'; if (typeof types[type] === 'number') { this._ordinal = types[type]; } } //create enum for (var t in types) { if (types.hasOwnProperty(t)) { TokenKind[t] = new TokenKind(t); } } TokenKind.prototype.toString = function () { return this.type + (this.tokenChars.length !== 0 ? '(' + this.tokenChars + ')' : ''); }; TokenKind.prototype.getLength = function () { return this.tokenChars.length; }; TokenKind.prototype.hasPayload = function () { return this._hasPayload; }; TokenKind.prototype.valueOf = function (id) { for (var t in types) { if (types.hasOwnProperty(t) && types[t] === id) { return TokenKind[t]; } } }; TokenKind.prototype.ordinal = function () { return this._ordinal; }; exports.TokenKind = TokenKind; /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StandardContext = exports.SpelExpressionEvaluator = undefined; var _SpelExpressionEvaluator = __webpack_require__(4); var _StandardContext = __webpack_require__(50); /* * 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 Ben March * @since 0.2.0 */ exports.SpelExpressionEvaluator = _SpelExpressionEvaluator.SpelExpressionEvaluator; exports.StandardContext = _StandardContext.StandardContext; /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SpelExpressionEvaluator = undefined; var _SpelExpressionParser = __webpack_require__(5); var _Stack = __webpack_require__(1); /* * 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 Ben March * @since 0.2.0 */ var spelExpressionEvaluator = {}; function evalCompiled(compiledExpression, context, locals) { var activeContext = new _Stack.Stack(), state; if (!context) { context = {}; } activeContext.push(context); state = { rootContext: context, activeContext: activeContext, locals: locals }; return compiledExpression.getValue(state); } spelExpressionEvaluator.compile = function (expression) { var compiledExpression = (0, _SpelExpressionParser.SpelExpressionParser)().parse(expression); return { eval: function _eval(context, locals) { return evalCompiled(compiledExpression, context, locals); }, _compiledExpression: compiledExpression }; }; spelExpressionEvaluator.eval = function (expression, context, locals) { return spelExpressionEvaluator.compile(expression).eval(context, locals); }; exports.SpelExpressionEvaluator = spelExpressionEvaluator; /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SpelExpressionParser = undefined; var _TokenKind = __webpack_require__(2); var _Tokenizer = __webpack_require__(6); var _BooleanLiteral = __webpack_require__(8); var _NumberLiteral = __webpack_require__(9); var _StringLiteral = __webpack_require__(10); var _NullLiteral = __webpack_require__(11); var _FunctionReference = __webpack_require__(12); var _MethodReference = __webpack_require__(13); var _PropertyReference = __webpack_require__(14); var _VariableReference = __webpack_require__(15); var _CompoundExpression = __webpack_require__(16); var _Indexer = __webpack_require__(17); var _Assign = __webpack_require__(18); var _OpEQ = __webpack_require__(19); var _OpNE = __webpack_require__(20); var _OpGE = __webpack_require__(21); var _OpGT = __webpack_require__(22); var _OpLE = __webpack_require__(23); var _OpLT = __webpack_require__(24); var _OpPlus = __webpack_require__(25); var _OpMinus = __webpack_require__(26); var _OpMultiply = __webpack_require__(27); var _OpDivide = __webpack_require__(28); var _OpModulus = __webpack_require__(29); var _OpPower = __webpack_require__(30); var _OpInc = __webpack_require__(31); var _OpDec = __webpack_require__(32); var _OpNot = __webpack_require__(33); var _OpAnd = __webpack_require__(34); var _OpOr = __webpack_require__(35); var _OpMatches = __webpack_require__(36); var _Ternary = __webpack_require__(37); var _Elvis = __webpack_require__(38); var _InlineList = __webpack_require__(39); var _InlineMap = __webpack_require__(40); var _Selection = __webpack_require__(41); var _Projection = __webpack_require__(42); var _OpInstanceof = __webpack_require__(43); var _OpBetween = __webpack_require__(44); var _TypeReference = __webpack_require__(45); var _BeanReference = __webpack_require__(46); var _Identifier = __webpack_require__(47); var _QualifiedIdentifier = __webpack_require__(48); var _ConstructorReference = __webpack_require__(49); /* * 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 * */ var SpelExpressionParser = exports.SpelExpressionParser = function SpelExpressionParser() { 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.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.TokenKind.ASSIGN) { // a=b if (expr === null) { expr = _NullLiteral.NullLiteral.create(toPosBounds(token.startPos - 1, token.endPos - 1)); } nextToken(); var assignedValue = eatLogicalOrExpression(); return _Assign.Assign.create(toPosToken(token), expr, assignedValue); } if (token.getKind() === _TokenKind.TokenKind.ELVIS) { // a?:b (a if it isn't null, otherwise b) if (expr === null) { expr = _NullLiteral.NullLiteral.create(toPosBounds(token.startPos - 1, token.endPos - 2)); } nextToken(); // elvis has left the building var valueIfNull = eatExpression(); if (valueIfNull === null) { valueIfNull = _NullLiteral.NullLiteral.create(toPosBounds(token.startPos + 1, token.endPos + 1)); } return _Elvis.Elvis.create(toPosToken(token), expr, valueIfNull); } if (token.getKind() === _TokenKind.TokenKind.QMARK) { // a?b:c if (expr === null) { expr = _NullLiteral.NullLiteral.create(toPosBounds(token.startPos - 1, token.endPos - 1)); } nextToken(); var ifTrueExprValue = eatExpression(); eatToken(_TokenKind.TokenKind.COLON); var ifFalseExprValue = eatExpression(); return _Ternary.Ternary.create(toPosToken(token), expr, ifTrueExprValue, ifFalseExprValue); } } return expr; } //logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*; function eatLogicalOrExpression() { var expr = eatLogicalAndExpression(); while (peekIdentifierToken('or') || peekTokenOne(_TokenKind.TokenKind.SYMBOLIC_OR)) { var token = nextToken(); //consume OR var rhExpr = eatLogicalAndExpression(); checkOperands(token, expr, rhExpr); expr = _OpOr.OpOr.create(toPosToken(token), expr, rhExpr); } return expr; } // logicalAndExpression : relationalExpression (AND^ relationalExpression)*; function eatLogicalAndExpression() { var expr = eatRelationalExpression(); while (peekIdentifierToken('and') || peekTokenOne(_TokenKind.TokenKind.SYMBOLIC_AND)) { var token = nextToken(); // consume 'AND' var rhExpr = eatRelationalExpression(); checkOperands(token, expr, rhExpr); expr = _OpAnd.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.TokenKind.GT) { return _OpGT.OpGT.create(pos, expr, rhExpr); } if (tk === _TokenKind.TokenKind.LT) { return _OpLT.OpLT.create(pos, expr, rhExpr); } if (tk === _TokenKind.TokenKind.LE) { return _OpLE.OpLE.create(pos, expr, rhExpr); } if (tk === _TokenKind.TokenKind.GE) { return _OpGE.OpGE.create(pos, expr, rhExpr); } if (tk === _TokenKind.TokenKind.EQ) { return _OpEQ.OpEQ.create(pos, expr, rhExpr); } //Assert.isTrue(tk === TokenKind.NE); return _OpNE.OpNE.create(pos, expr, rhExpr); } if (tk === _TokenKind.TokenKind.INSTANCEOF) { return _OpInstanceof.OpInstanceof.create(toPosToken(token), expr, rhExpr); } if (tk === _TokenKind.TokenKind.MATCHES) { return _OpMatches.OpMatches.create(toPosToken(token), expr, rhExpr); } //Assert.isTrue(tk === TokenKind.BETWEEN); return _OpBetween.OpBetween.create(toPosToken(token), expr, rhExpr); } return expr; } //sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*; function eatSumExpression() { var expr = eatProductExpression(); while (peekTokenAny(_TokenKind.TokenKind.PLUS, _TokenKind.TokenKind.MINUS, _TokenKind.TokenKind.INC)) { var token = nextToken(); //consume PLUS or MINUS or INC var rhExpr = eatProductExpression(); checkRightOperand(token, rhExpr); if (token.getKind() === _TokenKind.TokenKind.PLUS) { expr = _OpPlus.OpPlus.create(toPosToken(token), expr, rhExpr); } else if (token.getKind() === _TokenKind.TokenKind.MINUS) { expr = _OpMinus.OpMinus.create(toPosToken(token), expr, rhExpr); } } return expr; } // productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ; function eatProductExpression() { var expr = eatPowerIncDecExpression(); while (peekTokenAny(_TokenKind.TokenKind.STAR, _TokenKind.TokenKind.DIV, _TokenKind.TokenKind.MOD)) { var token = nextToken(); // consume STAR/DIV/MOD var rhExpr = eatPowerIncDecExpression(); checkOperands(token, expr, rhExpr); if (token.getKind() === _TokenKind.TokenKind.STAR) { expr = _OpMultiply.OpMultiply.create(toPosToken(token), expr, rhExpr); } else if (token.getKind() === _TokenKind.TokenKind.DIV) { expr = _OpDivide.OpDivide.create(toPosToken(token), expr, rhExpr); } else { //Assert.isTrue(token.getKind() === TokenKind.MOD); expr = _OpModulus.OpModulus.create(toPosToken(token), expr, rhExpr); } } return expr; } // powerExpr : unaryExpression (POWER^ unaryExpression)? (INC || DEC) ; function eatPowerIncDecExpression() { var expr = eatUnaryExpression(), token; if (peekTokenOne(_TokenKind.TokenKind.POWER)) { token = nextToken(); //consume POWER var rhExpr = eatUnaryExpression(); checkRightOperand(token, rhExpr); return _OpPower.OpPower.create(toPosToken(token), expr, rhExpr); } if (expr !== null && peekTokenAny(_TokenKind.TokenKind.INC, _TokenKind.TokenKind.DEC)) { token = nextToken(); //consume INC/DEC if (token.getKind() === _TokenKind.TokenKind.INC) { return _OpInc.OpInc.create(toPosToken(token), true, expr); } return _OpDec.OpDec.create(toPosToken(token), true, expr); } return expr; } // unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ; function eatUnaryExpression() { var token, expr; if (peekTokenAny(_TokenKind.TokenKind.PLUS, _TokenKind.TokenKind.MINUS, _TokenKind.TokenKind.NOT)) { token = nextToken(); expr = eatUnaryExpression(); if (token.getKind() === _TokenKind.TokenKind.NOT) { return _OpNot.OpNot.create(toPosToken(token), expr); } if (token.getKind() === _TokenKind.TokenKind.PLUS) { return _OpPlus.OpPlus.create(toPosToken(token), expr); } //Assert.isTrue(token.getKind() === TokenKind.MINUS); return _OpMinus.OpMinus.create(toPosToken(token), expr); } if (peekTokenAny(_TokenKind.TokenKind.INC, _TokenKind.TokenKind.DEC)) { token = nextToken(); expr = eatUnaryExpression(); if (token.getKind() === _TokenKind.TokenKind.INC) { return _OpInc.OpInc.create(toPosToken(token), false, expr); } return _OpDec.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.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.TokenKind.DOT, _TokenKind.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.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.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.TokenKind.HASH)) { return false; } var token = nextToken(); var functionOrVariableName = eatToken(_TokenKind.TokenKind.IDENTIFIER); var args = maybeEatMethodArgs(); if (args === null) { push(_VariableReference.VariableReference.create(functionOrVariableName.data, toPosBounds(token.startPos, functionOrVariableName.endPos))); return true; } push(_FunctionReference.FunctionReference.create(functionOrVariableName.data, toPosBounds(token.startPos, functionOrVariableName.endPos), args)); return true; } // methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!; function maybeEatMethodArgs() { if (!peekTokenOne(_TokenKind.TokenKind.LPAREN)) { return null; } var args = []; consumeArguments(args); eatToken(_TokenKind.TokenKind.RPAREN); return args; } function eatConstructorArgs(accumulatedArguments) { if (!peekTokenOne(_TokenKind.TokenKind.LPAREN)) { raiseInternalException(toPosToken(peekToken()), 'MISSING_CONSTRUCTOR_ARGS'); } consumeArguments(accumulatedArguments); eatToken(_TokenKind.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.TokenKind.RPAREN) { accumulatedArguments.push(eatExpression()); } next = peekToken(); } while (next !== null && next.kind === _TokenKind.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.TokenKind.BEAN_REF)) { var beanRefToken = nextToken(); var beanNameToken = null; var beanName = null; if (peekTokenOne(_TokenKind.TokenKind.IDENTIFIER)) { beanNameToken = eatToken(_TokenKind.TokenKind.IDENTIFIER); beanName = beanNameToken.data; } else if (peekTokenOne(_TokenKind.TokenKind.LITERAL_STRING)) { beanNameToken = eatToken(_TokenKind.TokenKind.LITERAL_STRING); beanName = beanNameToken.stringValue(); beanName = beanName.substring(1, beanName.length() - 1); } else { raiseInternalException(beanRefToken.startPos, 'INVALID_BEAN_REFERENCE'); } var beanReference = _BeanReference.BeanReference.create(toPosToken(beanNameToken), beanName); push(beanReference); return true; } return false; } function maybeEatTypeReference() { if (peekTokenOne(_TokenKind.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.TokenKind.RSQUARE)) { // looks like 'T]' (T is map key) push(_PropertyReference.PropertyReference.create(token.stringValue(), toPosToken(token))); return true; } eatToken(_TokenKind.TokenKind.LPAREN); var node = eatPossiblyQualifiedId(); // dotted qualified id // Are there array dimensions? var dims = 0; while (peekTokenConsumeIfMatched(_TokenKind.TokenKind.LSQUARE, true)) { eatToken(_TokenKind.TokenKind.RSQUARE); dims++; } eatToken(_TokenKind.TokenKind.RPAREN); push(_TypeReference.TypeReference.create(toPosToken(typeName), node, dims)); return true; } return false; } function maybeEatNullReference() { if (peekTokenOne(_TokenKind.TokenKind.IDENTIFIER)) { var nullToken = peekToken(); if (nullToken.stringValue().toLowerCase() !== 'null') { return false; } nextToken(); push(_NullLiteral.NullLiteral.create(toPosToken(nullToken))); return true; } return false; } //projection: PROJECT^ expression RCURLY!; function maybeEatProjection(nullSafeNavigation) { var token = peekToken(); if (!peekTokenConsumeIfMatched(_TokenKind.TokenKind.PROJECT, true)) { return false; } var expr = eatExpression(); eatToken(_TokenKind.TokenKind.RSQUARE); push(_Projection.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.TokenKind.LCURLY, true)) { return false; } var expr = null; var closingCurly = peekToken(); if (peekTokenConsumeIfMatched(_TokenKind.TokenKind.RCURLY, true)) { // empty list '{}' expr = _InlineList.InlineList.create(toPosBounds(token.startPos, closingCurly.endPos)); } else if (peekTokenConsumeIfMatched(_TokenKind.TokenKind.COLON, true)) { closingCurly = eatToken(_TokenKind.TokenKind.RCURLY); // empty map '{:}' expr = _InlineMap.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.TokenKind.RCURLY)) { // list with one item in it listElements.push(firstExpression); closingCurly = eatToken(_TokenKind.TokenKind.RCURLY); expr = _InlineList.InlineList.create(toPosBounds(token.startPos, closingCurly.endPos), listElements); } else if (peekTokenConsumeIfMatched(_TokenKind.TokenKind.COMMA, true)) { // multi item list listElements.push(firstExpression); do { listElements.push(eatExpression()); } while (peekTokenConsumeIfMatched(_TokenKind.TokenKind.COMMA, true)); closingCurly = eatToken(_TokenKind.TokenKind.RCURLY); expr = _InlineList.InlineList.create(toPosToken(token.startPos, closingCurly.endPos), listElements); } else if (peekTokenConsumeIfMatched(_TokenKind.TokenKind.COLON, true)) { // map! var mapElements = []; mapElements.push(firstExpression); mapElements.push(eatExpression()); while (peekTokenConsumeIfMatched(_TokenKind.TokenKind.COMMA, true)) { mapElements.push(eatExpression()); eatToken(_TokenKind.TokenKind.COLON); mapElements.push(eatExpression()); } closingCurly = eatToken(_TokenKind.TokenKind.RCURLY); expr = _InlineMap.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.TokenKind.LSQUARE, true)) { return false; } var expr = eatExpression(); eatToken(_TokenKind.TokenKind.RSQUARE); push(_Indexer.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.TokenKind.RSQUARE); if (token.getKind() === _TokenKind.TokenKind.SELECT_FIRST) { push(_Selection.Selection.create(nullSafeNavigation, _Selection.Selection.FIRST, toPosToken(token), expr)); } else if (token.getKind() === _TokenKind.TokenKind.SELECT_LAST) { push(_Selection.Selection.create(nullSafeNavigation, _Selection.Selection.LAST, toPosToken(token), expr)); } else { push(_Selection.Selection.create(nullSafeNavigation, _Selection.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.TokenKind.DOT) { qualifiedIdPieces.push(_Identifier.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.QualifiedIdentifier.create(pos, qualifiedIdPieces); } function isValidQualifiedId(node) { if (node === null || node.kind === _TokenKind.TokenKind.LITERAL_STRING) { return false; } if (node.kind === _TokenKind.TokenKind.DOT || node.kind === _TokenKind.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.TokenKind.IDENTIFIER)) { var methodOrPropertyName = nextToken(); var args = maybeEatMethodArgs(); if (args === null) { // property push(_PropertyReference.PropertyReference.create(nullSafeNavigation, methodOrPropertyName.stringValue(), toPosToken(methodOrPropertyName))); return true; } // methodreference push(_MethodReference.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.TokenKind.RSQUARE)) { // looks like 'NEW]' (so NEW used as map key) push(_PropertyReference.PropertyReference.create(newToken.stringValue(), toPosToken(newToken))); return true; } var possiblyQualifiedConstructorName = eatPossiblyQualifiedId(); var nodes = []; nodes.push(possiblyQualifiedConstructorName); if (peekTokenOne(_TokenKind.TokenKind.LSQUARE)) { // array initializer var dimensions = []; while (peekTokenConsumeIfMatched(_TokenKind.TokenKind.LSQUARE, true)) { if (!peekTokenOne(_TokenKind.TokenKind.RSQUARE)) { dimensions.push(eatExpression()); } else { dimensions.push(null); } eatToken(_TokenKind.TokenKind.RSQUARE); } if (maybeEatInlineListOrMap()) { nodes.push(pop()); } push(_ConstructorReference.ConstructorReference.create(toPosToken(newToken), dimensions, nodes)); } else { // regular constructor invocation eatConstructorArgs(nodes); // TODO correct end position? push(_ConstructorReference.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.TokenKind.LITERAL_INT || token.getKind() === _TokenKind.TokenKind.LITERAL_LONG) { push(_NumberLiteral.NumberLiteral.create(parseInt(token.stringValue(), 10), toPosToken(token))); } else if (token.getKind() === _TokenKind.TokenKind.LITERAL_REAL || token.getKind() === _TokenKind.TokenKind.LITERAL_REAL_FLOAT) { push(_NumberLiteral.NumberLiteral.create(parseFloat(token.stringValue()), toPosToken(token))); } else if (token.getKind() === _TokenKind.TokenKind.LITERAL_HEXINT || token.getKind() === _TokenKind.TokenKind.LITERAL_HEXLONG) { push(_NumberLiteral.NumberLiteral.create(parseInt(token.stringValue(), 16), toPosToken(token))); } else if (peekIdentifierToken('true')) { push(_BooleanLiteral.BooleanLiteral.create(true, toPosToken(token))); } else if (peekIdentifierToken('false')) { push(_BooleanLiteral.BooleanLiteral.create(false, toPosToken(token))); } else if (token.getKind() === _TokenKind.TokenKind.LITERAL_STRING) { push(_StringLiteral.StringLiteral.create(token.stringValue(), toPosToken(token))); } else { return false; } nextToken(); return true; } //parenExpr : LPAREN! expression RPAREN!; function maybeEatParenExpression() { if (peekTokenOne(_TokenKind.TokenKind.LPAREN)) { nextToken(); var expr = eatExpression(); eatToken(_TokenKind.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.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.TokenKind.DIV.ordinal() && token.getKind().ordinal() <= _TokenKind.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) {