var defaultOperator = '='; var validSymbols = ['>', '>=', '<', '<=', '=', '<>']; var TOKEN_TYPE_OPERATOR = 'operator'; var TOKEN_TYPE_LITERAL = 'literal'; var SUPPORTED_TOKENS = [TOKEN_TYPE_OPERATOR, TOKEN_TYPE_LITERAL]; exports.TOKEN_TYPE_OPERATOR = TOKEN_TYPE_OPERATOR; exports.TOKEN_TYPE_LITERAL = TOKEN_TYPE_LITERAL; /** * Create token which describe passed symbol/value. * * @param {String} value Value/Symbol to describe. * @param {String} type Type of the token 'operator' or 'literal'. * @return {Object} */ function createToken(value, type) { if (SUPPORTED_TOKENS.indexOf(type) === -1) { throw new Error('Unsupported token type: ' + type); } return { value: value, type: type, }; } /** * Tries to cast numeric values to their type passed as a string. * * @param {*} value * @return {*} */ function castValueToCorrectType(value) { if (typeof value !== 'string') { return value; } if (/^\d+(\.\d+)?$/.test(value)) { value = value.indexOf('.') === -1 ? parseInt(value, 10) : parseFloat(value); } return value; } /** * Generate stream of tokens from passed expression. * * @param {String} expression * @return {String[]} */ function tokenizeExpression(expression) { var expressionLength = expression.length; var tokens = []; var cursorIndex = 0; var processedValue = ''; var processedSymbol = ''; while (cursorIndex < expressionLength) { var char = expression.charAt(cursorIndex); switch (char) { case '>': case '<': case '=': processedSymbol = processedSymbol + char; if (processedValue.length > 0) { tokens.push(processedValue); processedValue = ''; } break; default: if (processedSymbol.length > 0) { tokens.push(processedSymbol); processedSymbol = ''; } processedValue = processedValue + char; break; } cursorIndex++; } if (processedValue.length > 0) { tokens.push(processedValue); } if (processedSymbol.length > 0) { tokens.push(processedSymbol); } return tokens; }; /** * Analyze and convert tokens to an object which describes their meaning. * * @param {String[]} tokens * @return {Object[]} */ function analyzeTokens(tokens) { var literalValue = ''; var analyzedTokens = []; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (i === 0 && validSymbols.indexOf(token) >= 0) { analyzedTokens.push(createToken(token, TOKEN_TYPE_OPERATOR)); } else { literalValue += token; } } if (literalValue.length > 0) { analyzedTokens.push(createToken(castValueToCorrectType(literalValue), TOKEN_TYPE_LITERAL)); } if (analyzedTokens.length > 0 && analyzedTokens[0].type !== TOKEN_TYPE_OPERATOR) { analyzedTokens.unshift(createToken(defaultOperator, TOKEN_TYPE_OPERATOR)); } return analyzedTokens; }; /** * Compute/Evaluate an expression passed as an array of tokens. * * @param {Object[]} tokens * @return {Boolean} */ function computeExpression(tokens) { var values = []; var operator; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; switch (token.type) { case TOKEN_TYPE_OPERATOR: operator = token.value; break; case TOKEN_TYPE_LITERAL: values.push(token.value); break; } } return evaluate(values, operator); }; /** * Evaluate values based on passed math operator. * * @param {*} values * @param {String} operator * @return {Boolean} */ function evaluate(values, operator) { var result = false; switch (operator) { case '>': result = values[0] > values[1]; break; case '>=': result = values[0] >= values[1]; break; case '<': result = values[0] < values[1]; break; case '<=': result = values[0] <= values[1]; break; case '=': result = values[0] == values[1]; break; case '<>': result = values[0] != values[1]; break; } return result; } exports.parse = function(expression) { return analyzeTokens(tokenizeExpression(expression)); }; exports.createToken = createToken; exports.compute = computeExpression;