UNPKG

@pawel-up/jexl

Version:

Javascript Expression Language: Powerful context-based expression parser and evaluator

168 lines 5.82 kB
const numericRegex = /^-?(?:(?:[0-9]*\.[0-9]+)|[0-9]+)$/; const identRegex = /^[a-zA-Zа-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$][a-zA-Zа-яА-Я0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$]*$/; const escEscRegex = /\\\\/; const whitespaceRegex = /^\s*$/; const preOpRegexElements = [ "'(?:(?:\\\\')|[^'])*'", '"(?:(?:\\\\")|[^"])*"', '\\s+', '\\btrue\\b', '\\bfalse\\b', '\\bnull\\b', '\\bundefined\\b', '(?:[0-9]+(?:\\.[0-9]+)?|\\.[0-9]+)', ]; const postOpRegexElements = [ '[a-zA-Zа-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\\$][a-zA-Z0-9а-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\\$]*', ]; const unaryOpsAfter = ['binaryOp', 'unaryOp', 'openParen', 'openBracket', 'question', 'colon', 'comma']; export default class Lexer { _grammar; _splitRegex; constructor(grammar) { this._grammar = grammar; } getElements(str) { const regex = this._getSplitRegex(); return str.split(regex).filter((elem) => { return elem; }); } getTokens(elements) { const tokens = []; let negate = false; for (let i = 0; i < elements.length; i++) { const element = elements[i]; if (!element) continue; if (this._isWhitespace(element)) { if (tokens.length > 0) { tokens[tokens.length - 1].raw += element; } } else if ((element === '+' || element === '-') && this._isUnary(tokens)) { const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] : null; if (lastToken && lastToken.type === 'binaryOp' && (lastToken.value === '+' || lastToken.value === '-') && !lastToken.raw.match(/\s$/)) { throw new Error(`Unexpected token '${element}' after operator '${lastToken.value}'`); } let nextElement = ''; for (let j = i + 1; j < elements.length; j++) { if (!this._isWhitespace(elements[j])) { nextElement = elements[j]; break; } } if (element === '-') { if (nextElement.match(numericRegex)) { negate = true; } else { const token = this._createToken(element); token.type = 'unaryOp'; tokens.push(token); } } else { if (!nextElement.match(numericRegex)) { const token = this._createToken(element); token.type = 'unaryOp'; tokens.push(token); } } } else { if (negate) { elements[i] = '-' + element; negate = false; } tokens.push(this._createToken(elements[i])); } } if (negate) { tokens.push(this._createToken('-')); } return tokens; } tokenize(str) { const elements = this.getElements(str); return this.getTokens(elements); } _createToken(element) { const token = { type: 'literal', value: element, raw: element, }; if (element[0] === '"' || element[0] === "'") { token.value = this._unquote(element); } else if (element.match(numericRegex)) { token.value = parseFloat(element); } else if (element === 'true' || element === 'false') { token.value = element === 'true'; } else if (element === 'null') { token.value = null; } else if (element === 'undefined') { token.value = undefined; } else if (this._grammar.elements[element]) { token.type = this._grammar.elements[element].type; } else if (element.match(identRegex)) { token.type = 'identifier'; } else { throw new Error(`Invalid expression token: ${element}`); } return token; } _escapeRegExp(str) { str = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); if (str.match(identRegex)) { str = '\\b' + str + '\\b'; } return str; } _getSplitRegex() { if (!this._splitRegex) { const elemArray = Object.keys(this._grammar.elements) .sort((a, b) => { return b.length - a.length; }) .map((elem) => { return this._escapeRegExp(elem); }, this); this._splitRegex = new RegExp('(' + [preOpRegexElements.join('|'), elemArray.join('|'), postOpRegexElements.join('|')].join('|') + ')'); } return this._splitRegex; } _isUnary(tokens) { if (!tokens.length) return true; const lastToken = tokens[tokens.length - 1]; if (!lastToken) return true; return unaryOpsAfter.some((type) => type === lastToken.type); } _isWhitespace(str) { return !!str.match(whitespaceRegex); } _unquote(str) { const quote = str[0]; if (!quote) { throw new Error('Cannot unquote empty string'); } const escQuoteRegex = new RegExp('\\\\' + quote, 'g'); return str .substr(1, str.length - 2) .replace(escQuoteRegex, quote) .replace(escEscRegex, '\\'); } } //# sourceMappingURL=Lexer.js.map