UNPKG

nscomponentsreact

Version:

React Wrapper Components for NSComponents

455 lines (429 loc) 14.8 kB
var nsModuleExport = function(root,name,prototype) { if(typeof exports === 'object' && typeof module === 'object') { module.exports[name] = prototype; } else if (typeof define === "function" && define.amd) { define(name,[], function () {return prototype;}); } else if(typeof exports === 'object') { exports[name] = prototype; } else { root[name] = prototype; } };var nsIsWeb = function(root) { if(typeof exports === 'object' && typeof module === 'object') { return false; } else if (typeof define === "function" && define.amd) { return false; } else if(typeof exports === 'object') { return false; } else { return true; } };if(!nsIsWeb()) { var nsutilRef = require('./nsUtil.min.js'); var NSUtil = nsutilRef.NSUtil; } var NSExpressionEvaluator = (function() { function NSExpressionEvaluator(setting) { var self = this; var oldExpression = null; var nodes = []; if(!setting) { setting = {}; } var config = { throwErrorForUndefined: setting.throwErrorForUndefined | false }; var isCustomException = false; var operatorFunctions = { '+': function(left, right) { return left + right; }, '-': function(left, right) { return left - right; }, '*': function(left, right) { return left * right; }, '/': function(left, right) { return left / right; }, '%': function(left, right) { return left % right; }, '**': function(left, right) { return Math.pow(left, right); }, '^': function(left, right) { return left ^ right; }, '&': function(left, right) { return left & right; }, '|': function(left, right) { return left | right; }, '<<': function(left, right) { return left << right; }, '>>': function(left, right) { return right >> right; }, '>': function(left, right) { return left > right; }, '<': function(left, right) { return left < right; }, '>=': function(left, right) { return left >= right; }, '<=': function(left, right) { return left <= right; }, '===': function(left, right) { return left === right; }, '!==': function(left, right) { return left !== right; }, '&&': function(left, right) { return left && right; }, '||': function(left, right) { return left || right; } }; var operatorConfig = { '.': { precedence: 6, associativity: 'L' }, '[': { precedence: 6, associativity: 'L' }, ']': { precedence: 6, associativity: 'L' }, '+': { precedence: 3, associativity: 'L' }, '-': { precedence: 3, associativity: 'L' }, '*': { precedence: 4, associativity: 'L' }, '/': { precedence: 4, associativity: 'L' }, '%': { precedence: 4, associativity: 'L' }, '**': { precedence: 5, associativity: 'R' }, '^': { precedence: 2, associativity: 'L' }, '&': { precedence: 1, associativity: 'L' }, '|': { precedence: 1, associativity: 'L' }, '<<': { precedence: 2, associativity: 'L' }, '>>': { precedence: 2, associativity: 'L' }, '>': { precedence: 2, associativity: 'L' }, '<': { precedence: 2, associativity: 'L' }, '>=': { precedence: 2, associativity: 'L' }, '<=': { precedence: 2, associativity: 'L' }, '===': { precedence: 1, associativity: 'L' }, '!==': { precedence: 1, associativity: 'L' }, '&&': { precedence: 0, associativity: 'L' }, '||': { precedence: 0, associativity: 'L' } }; var evaluate = function(expression,model) { isCustomException = false; try { if(expression !== oldExpression || !nodes || nodes.length === 0) { setExpression(expression); } var val = getValue(model,nodes); return val; } catch(error) { if(!isCustomException) { //this logic if worst case some scenario is not covered by the logic of NSExpressionEvaluator but for most of the basic scenario the below code is not required try { var func = new Function('model', `with (model) { return ${expression}; }`); var val = func(model); return val; } catch(innerError) { throw new Error(innerError); } } else { isCustomException = true; throw error; } } }; var setExpression = function(expression) { var tokens = getTokens(expression); //console.log(tokens); nodes = parseTokens(tokens); //console.log(nodes); oldExpression = expression; }; var getTokens = function(expression) { var tokens = []; var current = ''; var addToken = function() { if (current.length > 0) { tokens.push(current); current = ''; } }; var isWithinBrackets = false; for (var i = 0; i < expression.length; i++) { var char = expression[i]; if (char === ' ' || char === '\t' || char === '\n') { continue; } if([','].includes(char) && isWithinBrackets) { addToken(); //for now not adding , in tokens /*current = char; addToken();*/ } else if (isDigit(char)) { current += char; } else if (isLetter(char) || (current.length > 0 && isLetter(current[0]) && char === '_')) { current += char; } else if (char === '(' || char === ')') { if(char === '(') { isWithinBrackets = true; } else { isWithinBrackets = false; } if (current.length > 0) { tokens.push(current); } tokens.push(char); current = ''; } else if (isOperator(expression, i)) { addToken(); var multiCharOp = ''; // Check for multi-character operators if (i + 2 < expression.length) { multiCharOp = expression.substr(i, 3); if (multiCharOp === '===' || multiCharOp === '!==') { tokens.push(multiCharOp); i += 2; continue; } } if (i + 1 < expression.length) { multiCharOp = expression.substr(i, 2); if (['==', '!=', '<=', '>=', '&&', '||', '**', '<<', '>>'].includes(multiCharOp)) { tokens.push(multiCharOp); i += 1; continue; } } tokens.push(char); } else if (char === '"' || char === '\'') { var quoteType = char; current = quoteType; i++; while (i < expression.length && expression[i] !== quoteType) { current += expression[i++]; } current += quoteType; tokens.push(current); current = ''; } else if (char === '[' || char === ']' || char === '(' || char === ')' || char === '.') { addToken(); tokens.push(char); } } addToken(); return tokens; }; var parseTokens = function(tokens) { var outputQueue = []; var operatorStack = []; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (!isNaN(parseFloat(token))) { outputQueue.push(getNode('Number', parseFloat(token))); } else if (isQuotedString(token)) { outputQueue.push(getNode('String', token)); } else if (isValidStringIdentifier(token)) { if (tokens[i+1] === '(') { // Handle function call var funcName = token; var args = []; i += 2; // skip function name and '(' while (tokens[i] !== ')') { args.push(tokens[i]); i++; } var paramQueue = []; if(args.length > 0) { paramQueue = parseTokens(args); console.log(args, paramQueue); } outputQueue.push(getNode('FunctionCall', funcName, null, null, paramQueue)); } else { i = handleVariableAndAccess(i, tokens, outputQueue); } } else if (operatorConfig[token]) { handleOperators(token, operatorStack, outputQueue); } else if (token === '(') { operatorStack.push(getNode('Operator', token)); } else if (token === ')') { i = handleClosingParenthesis(operatorStack, outputQueue, i); } } while (operatorStack.length > 0) { outputQueue.push(operatorStack.pop()); } return outputQueue; }; var handleVariableAndAccess = function(i, tokens, outputQueue) { var variable = tokens[i]; while (i + 1 < tokens.length && (tokens[i + 1] === '.' || tokens[i + 1] === '[')) { //skip the '.' and move to the property name if (tokens[i + 1] === '.') { i += 2; variable += '.' + tokens[i]; //skip the '[' and move to the index } else if (tokens[i + 1] === '[') { i += 2; //skip past the ']' variable += '[' + tokens[i] + ']'; i++; } } outputQueue.push(getNode('Variable', variable)); return i; }; var handleOperators = function(token, operatorStack, outputQueue) { var precedence = operatorConfig[token].precedence; var associativity = operatorConfig[token].associativity; while (operatorStack.length > 0 && operatorConfig[operatorStack[operatorStack.length - 1].value].precedence >= precedence && associativity === 'L') { outputQueue.push(operatorStack.pop()); } operatorStack.push(getNode('Operator', token)); }; var handleClosingParenthesis = function(operatorStack, outputQueue, i) { while (operatorStack.length > 0 && operatorStack[operatorStack.length - 1].value !== '(') { outputQueue.push(operatorStack.pop()); } operatorStack.pop(); return i; }; var getValue = function(model,paramNodes) { var stack = []; for(var count = 0;count < paramNodes.length;count++) { var node = paramNodes[count]; if (node.type === 'Number') { stack.push(node.value); } else if (node.type === 'String') { //removing quotes stack.push(node.value.slice(1, -1)); } else if (node.type === 'Variable') { var value = getVariableValue(model,node); //console.log(value,val); stack.push(value); } else if (node.type === 'FunctionCall') { var func = node.value; if (!isFunction(func)) { func = model[node.value]; } if (isFunction(func)) { var args = []; if(node.params && node.params.length > 0) { for(var paramCount = 0;paramCount < node.params.length;paramCount++) { var param = node.params[paramCount]; var paramVal = getValue(model,[param]); args.push(paramVal); } } stack.push(func.apply(null, args)); } else { throwException(`Function ${node.value} is not defined`); stack.push(undefined); } } else if (node.type === 'Operator') { var right = stack.pop(); var left = stack.pop(); if(operatorFunctions[node.value]) { stack.push(operatorFunctions[node.value](left,right)); } } }; return stack.pop(); }; var getVariableValue = function(model,node) { var value = model; var prevValue = null; var parts = node.value.split(/\.|\[|\]/).filter(Boolean); for(var count = 0;count < parts.length;count++) { var part = parts[count]; prevValue = value; if (!isNaN(part)) { value = value[parseInt(part)]; } else { value = value[part]; } if(isUndefined(value)) { throwException(`Value is undefined for ${part} in ${serializableToJson(prevValue)}`); break; } }; return value; }; var getNode = function(type, value, left, right, params) { return {type, value, left, right, params}; }; var throwException = function(message) { if(config.throwErrorForUndefined) { isCustomException = true; throw new Error(message); } return null; } //utility methods var isDigit = function(val) { return val >= '0' && val <= '9'; }; var isLetter = function(val) { return ((val >= 'a' && val <= 'z') || (val >= 'A' && val <= 'Z') || val === '_'); }; var isOperator = function(expr, index) { var nextChar = expr[index + 1]; var currentChar = expr[index]; var twoCharOp = currentChar + (nextChar || ''); // create the two-character string // Check for two-character operators if (['**', '<<', '>>', '&&', '||', '==', '!=', '<=', '>='].includes(twoCharOp)) { return true; } // Check for single-character operators if ("+-*/%<>=!&|^".includes(currentChar)) { return true; } return false; }; var isQuotedString = function(val) { if (val.length >= 2) { var startChar = val[0]; var endChar = val[val.length - 1]; if ((startChar === '"' || startChar === "'") && startChar === endChar) { // Check for the presence of the quote character in the middle of the string for (let i = 1; i < val.length - 1; i++) { if (val[i] === startChar) { //found the starting quote character in the middle return false; } } return true; } } return false; }; var isValidStringIdentifier = function(val) { if (val.length > 0) { var firstChar = val[0]; if (!(firstChar >= 'a' && firstChar <= 'z') && !(firstChar >= 'A' && firstChar <= 'Z') && firstChar !== '_') { return false; } for (let i = 1; i < val.length; i++) { var char = val[i]; if (!(char >= 'a' && char <= 'z') && !(char >= 'A' && char <= 'Z') && !(char >= '0' && char <= '9') && char !== '_') { return false; } } return true; } return false; }; var isUndefined = function(val) { return (typeof val === 'undefined'); }; var isFunction = function(val) { return (typeof val === 'function'); }; var serializableToJson = function(val) { try { var res = JSON.stringify(val); return res; } catch (e) { console.debug(e); } return val; }; self.evaluate = evaluate; self.setExpression = setExpression; }; return NSExpressionEvaluator; })(); nsModuleExport(this,"NSExpressionEvaluator",NSExpressionEvaluator);