nscomponentsreact
Version:
React Wrapper Components for NSComponents
455 lines (429 loc) • 14.8 kB
JavaScript
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);