UNPKG

expression-language

Version:

Javascript implementation of symfony/expression-language

165 lines (164 loc) 5.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.tokenize = tokenize; var _SyntaxError = _interopRequireDefault(require("./SyntaxError")); var _TokenStream = require("./TokenStream"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function tokenize(expression) { expression = expression.replace(/\r|\n|\t|\v|\f/g, ' '); let cursor = 0, tokens = [], brackets = [], end = expression.length; while (cursor < end) { if (' ' === expression[cursor]) { ++cursor; continue; } let number = extractNumber(expression.substr(cursor)); if (number !== null) { // numbers const numberLength = number.length; if (number.indexOf(".") === -1) { number = parseInt(number); } else { number = parseFloat(number); } tokens.push(new _TokenStream.Token(_TokenStream.Token.NUMBER_TYPE, number, cursor + 1)); cursor += numberLength; } else { if ('([{'.indexOf(expression[cursor]) >= 0) { // opening bracket brackets.push([expression[cursor], cursor]); tokens.push(new _TokenStream.Token(_TokenStream.Token.PUNCTUATION_TYPE, expression[cursor], cursor + 1)); ++cursor; } else { if (')]}'.indexOf(expression[cursor]) >= 0) { if (brackets.length === 0) { throw new _SyntaxError.default(`Unexpected "${expression[cursor]}"`, cursor, expression); } let [expect, cur] = brackets.pop(), matchExpect = expect.replace("(", ")").replace("{", "}").replace("[", "]"); if (expression[cursor] !== matchExpect) { throw new _SyntaxError.default(`Unclosed "${expect}"`, cur, expression); } tokens.push(new _TokenStream.Token(_TokenStream.Token.PUNCTUATION_TYPE, expression[cursor], cursor + 1)); ++cursor; } else { let str = extractString(expression.substr(cursor)); if (str !== null) { //console.log("adding string: " + str); tokens.push(new _TokenStream.Token(_TokenStream.Token.STRING_TYPE, str.captured, cursor + 1)); cursor += str.length; //console.log(`Extracted string: ${str.captured}; Remaining: ${expression.substr(cursor)}`, cursor, expression); } else { let operator = extractOperator(expression.substr(cursor)); if (operator) { tokens.push(new _TokenStream.Token(_TokenStream.Token.OPERATOR_TYPE, operator, cursor + 1)); cursor += operator.length; } else { if (".,?:".indexOf(expression[cursor]) >= 0) { tokens.push(new _TokenStream.Token(_TokenStream.Token.PUNCTUATION_TYPE, expression[cursor], cursor + 1)); ++cursor; } else { let name = extractName(expression.substr(cursor)); if (name) { tokens.push(new _TokenStream.Token(_TokenStream.Token.NAME_TYPE, name, cursor + 1)); cursor += name.length; //console.log(`Extracted name: ${name}; Remaining: ${expression.substr(cursor)}`, cursor, expression) } else { throw new _SyntaxError.default(`Unexpected character "${expression[cursor]}"`, cursor, expression); } } } } } } } } tokens.push(new _TokenStream.Token(_TokenStream.Token.EOF_TYPE, null, cursor + 1)); if (brackets.length > 0) { let [expect, cur] = brackets.pop(); throw new _SyntaxError.default(`Unclosed "${expect}"`, cur, expression); } return new _TokenStream.TokenStream(expression, tokens); } function extractNumber(str) { let extracted = null; let matches = str.match(/^[0-9]+(?:.[0-9]+)?/); if (matches && matches.length > 0) { extracted = matches[0]; } return extracted; } const strRegex = /^"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/s; /** * * @param str * @returns {null|string} */ function extractString(str) { let extracted = null; if (["'", '"'].indexOf(str.substr(0, 1)) === -1) { return extracted; } let m = strRegex.exec(str); if (m !== null && m.length > 0) { if (m[1]) { extracted = { captured: m[1] }; } else { extracted = { captured: m[2] }; } extracted.length = m[0].length; } return extracted; } const operators = ["&&", "and", "||", "or", // Binary "+", "-", "**", "*", "/", "%", // Arithmetic "&", "|", "^", // Bitwise "===", "!==", "!=", "==", "<=", ">=", "<", ">", // Comparison "contains", "matches", "starts with", "ends with", "not in", "in", "not", "!", "~", // String concatenation, '..' // Range function ]; const wordBasedOperators = ["and", "or", "matches", "contains", "starts with", "ends with", "not in", "in", "not"]; /** * * @param str * @returns {null|string} */ function extractOperator(str) { let extracted = null; for (let operator of operators) { if (str.substr(0, operator.length) === operator) { // If it is one of the word based operators, make sure there is a space after it if (wordBasedOperators.indexOf(operator) >= 0) { if (str.substr(0, operator.length + 1) === operator + " ") { extracted = operator; } } else { extracted = operator; } break; } } return extracted; } function extractName(str) { let extracted = null; let matches = str.match(/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/); if (matches && matches.length > 0) { extracted = matches[0]; } return extracted; }