UNPKG

xacro-parser

Version:

Utility for parsing and converting ROS Xacro files in Javascript.

1 lines 176 kB
{"version":3,"file":"index.cjs","sources":["../src/utils.js","../node_modules/expr-eval-fork/dist/index.mjs","../src/ExpressionParser.js","../src/XacroParser.js","../src/XacroLoader.js"],"sourcesContent":["\nexport function getUrlBase(url) {\n const tokens = url.split(/[\\\\/]/g);\n tokens.pop();\n if (tokens.length === 0) {\n return './';\n } else {\n return tokens.join('/') + '/';\n }\n}\n\n// XML Helpers\n// QuerySelectorAll that respects tag prefixes like 'xacro:'\nexport function getElementsWithName(node, name, res = []) {\n if (node.tagName === name) {\n res.push(node);\n }\n for (let i = 0, l = node.children.length; i < l; i++) {\n const child = node.children[i];\n getElementsWithName(child, name, res);\n }\n return res;\n}\n\n// Deep clone an xml node without the macro or property tags.\nexport function deepClone(node, stripPropsMacros) {\n const cloned = node.cloneNode();\n const childNodes = node.childNodes;\n for (let i = 0, l = childNodes.length; i < l; i++) {\n const child = childNodes[i];\n const tagName = child.tagName;\n if (!stripPropsMacros || (tagName !== 'xacro:property' && tagName !== 'xacro:macro')) {\n cloned.appendChild(deepClone(child, stripPropsMacros));\n }\n }\n return cloned;\n}\n\n// Takes an array of xml elements and removes the last elements that\n// are comments or newlines.\nexport function removeEndCommentsFromArray(arr) {\n while (arr.length > 0) {\n const el = arr[arr.length - 1];\n if (el.nodeType !== el.ELEMENT_NODE) {\n arr.pop();\n } else {\n break;\n }\n }\n}\n\n// Expression helpers\nexport function isOperator(str) {\n const regexp = /^[()/*+\\-%|&=[\\]]+$/;\n return regexp.test(str);\n}\n\nexport function isString(str) {\n const regexp = /^(('[^']*?')|(\"[^\"]*?\")|(`[^`]*?`))$/;\n return regexp.test(str);\n}\n\n// https://stackoverflow.com/a/175787\nexport function isNumber(str) {\n return !isNaN(Number(str)) && !isNaN(parseFloat(str));\n}\n\n// TODO: this needs to tokenize numbers together\nexport function tokenize(str) {\n // split text within quotes (', \", `) or operators\n const regexp = /(('[^']*?')|(\"[^\"]*?\")|(`[^`]*?`)|([()/*+\\-%|!&=]+?))/g;\n return str\n .replace(regexp, m => ` ${ m } `)\n .trim()\n .split(/\\s+/g);\n}\n\nexport function normalizeExpression(str) {\n // Remove any instances of \"--\" or \"++\" that might occur from negating a negative number\n // by adding a space that are not in a string.\n return str.replace(/[-+]{2,}/, val => {\n let positive = true;\n for (let i = 0, l = val.length; i < l; i++) {\n const operator = val[i];\n if (operator === '-') {\n positive = !positive;\n }\n }\n\n return positive ? '+' : '-';\n });\n}\n\n// Property Set Helpers\nexport const PARENT_SCOPE = Symbol('parent');\n\n// merges a set of properties together into a single set retaining\n// the parent scope link as well.\nexport function mergePropertySets(...args) {\n const res = {};\n for (let i = 0, l = args.length; i < l; i++) {\n const obj = args[i];\n for (const key in obj) {\n res[key] = obj[key];\n }\n if (PARENT_SCOPE in obj) {\n res[PARENT_SCOPE] = obj[PARENT_SCOPE];\n }\n }\n return res;\n}\n\n// Copies a property set and creates a link to the original set as a parent scope\nexport function createNewPropertyScope(properties) {\n const res = mergePropertySets(properties);\n res[PARENT_SCOPE] = properties;\n return res;\n}\n","var INUMBER = 'INUMBER';\nvar IOP1 = 'IOP1';\nvar IOP2 = 'IOP2';\nvar IOP3 = 'IOP3';\nvar IVAR = 'IVAR';\nvar IVARNAME = 'IVARNAME';\nvar IFUNCALL = 'IFUNCALL';\nvar IFUNDEF = 'IFUNDEF';\nvar IEXPR = 'IEXPR';\nvar IEXPREVAL = 'IEXPREVAL';\nvar IMEMBER = 'IMEMBER';\nvar IENDSTATEMENT = 'IENDSTATEMENT';\nvar IARRAY = 'IARRAY';\n\nfunction Instruction(type, value) {\n this.type = type;\n this.value = (value !== undefined && value !== null) ? value : 0;\n}\n\nInstruction.prototype.toString = function () {\n switch (this.type) {\n case INUMBER:\n case IOP1:\n case IOP2:\n case IOP3:\n case IVAR:\n case IVARNAME:\n case IENDSTATEMENT:\n return this.value;\n case IFUNCALL:\n return 'CALL ' + this.value;\n case IFUNDEF:\n return 'DEF ' + this.value;\n case IARRAY:\n return 'ARRAY ' + this.value;\n case IMEMBER:\n return '.' + this.value;\n default:\n return 'Invalid Instruction';\n }\n};\n\nfunction unaryInstruction(value) {\n return new Instruction(IOP1, value);\n}\n\nfunction binaryInstruction(value) {\n return new Instruction(IOP2, value);\n}\n\nfunction ternaryInstruction(value) {\n return new Instruction(IOP3, value);\n}\n\nfunction simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {\n var nstack = [];\n var newexpression = [];\n var n1, n2, n3;\n var f;\n for (var i = 0; i < tokens.length; i++) {\n var item = tokens[i];\n var type = item.type;\n if (type === INUMBER || type === IVARNAME) {\n if (Array.isArray(item.value)) {\n nstack.push.apply(nstack, simplify(item.value.map(function (x) {\n return new Instruction(INUMBER, x);\n }).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values));\n } else {\n nstack.push(item);\n }\n } else if (type === IVAR && Object.hasOwn(values, item.value)) {\n item = new Instruction(INUMBER, values[item.value]);\n nstack.push(item);\n } else if (type === IOP2 && nstack.length > 1) {\n n2 = nstack.pop();\n n1 = nstack.pop();\n f = binaryOps[item.value];\n item = new Instruction(INUMBER, f(n1.value, n2.value));\n nstack.push(item);\n } else if (type === IOP3 && nstack.length > 2) {\n n3 = nstack.pop();\n n2 = nstack.pop();\n n1 = nstack.pop();\n if (item.value === '?') {\n nstack.push(n1.value ? n2.value : n3.value);\n } else {\n f = ternaryOps[item.value];\n item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value));\n nstack.push(item);\n }\n } else if (type === IOP1 && nstack.length > 0) {\n n1 = nstack.pop();\n f = unaryOps[item.value];\n item = new Instruction(INUMBER, f(n1.value));\n nstack.push(item);\n } else if (type === IEXPR) {\n while (nstack.length > 0) {\n newexpression.push(nstack.shift());\n }\n newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)));\n } else if (type === IMEMBER && nstack.length > 0) {\n n1 = nstack.pop();\n nstack.push(new Instruction(INUMBER, n1.value[item.value]));\n /* } else if (type === IARRAY && nstack.length >= item.value) {\n var length = item.value;\n while (length-- > 0) {\n newexpression.push(nstack.pop());\n }\n newexpression.push(new Instruction(IARRAY, item.value));\n } */\n } else {\n while (nstack.length > 0) {\n newexpression.push(nstack.shift());\n }\n newexpression.push(item);\n }\n }\n while (nstack.length > 0) {\n newexpression.push(nstack.shift());\n }\n return newexpression;\n}\n\nfunction substitute(tokens, variable, expr) {\n var newexpression = [];\n for (var i = 0; i < tokens.length; i++) {\n var item = tokens[i];\n var type = item.type;\n if (type === IVAR && item.value === variable) {\n for (var j = 0; j < expr.tokens.length; j++) {\n var expritem = expr.tokens[j];\n var replitem;\n if (expritem.type === IOP1) {\n replitem = unaryInstruction(expritem.value);\n } else if (expritem.type === IOP2) {\n replitem = binaryInstruction(expritem.value);\n } else if (expritem.type === IOP3) {\n replitem = ternaryInstruction(expritem.value);\n } else {\n replitem = new Instruction(expritem.type, expritem.value);\n }\n newexpression.push(replitem);\n }\n } else if (type === IEXPR) {\n newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr)));\n } else {\n newexpression.push(item);\n }\n }\n return newexpression;\n}\n\n/**\n * Checks if a function reference 'f' is explicitly allowed to be executed.\n * This logic is the core security allowance gate.\n */\nfunction isAllowedFunc(f, expr, values) {\n for (var key in expr.functions) {\n if (expr.functions[key] === f) return true;\n }\n\n if (f.__expr_eval_safe_def) return true;\n\n for (var vKey in values) {\n if (typeof values[vKey] === 'object' && values[vKey] !== null) {\n for (var subKey in values[vKey]) {\n if (values[vKey][subKey] === f) return true;\n }\n }\n }\n return false;\n}\n\nfunction evaluate(tokens, expr, values) {\n var nstack = [];\n var n1, n2, n3;\n var f, args, argCount;\n\n if (isExpressionEvaluator(tokens)) {\n return resolveExpression(tokens, values);\n }\n\n var numTokens = tokens.length;\n\n for (var i = 0; i < numTokens; i++) {\n var item = tokens[i];\n var type = item.type;\n if (type === INUMBER || type === IVARNAME) {\n nstack.push(item.value);\n } else if (type === IOP2) {\n n2 = nstack.pop();\n n1 = nstack.pop();\n if (item.value === 'and') {\n nstack.push(n1 ? !!evaluate(n2, expr, values) : false);\n } else if (item.value === 'or') {\n nstack.push(n1 ? true : !!evaluate(n2, expr, values));\n } else if (item.value === '=') {\n f = expr.binaryOps[item.value];\n nstack.push(f(n1, evaluate(n2, expr, values), values));\n } else {\n f = expr.binaryOps[item.value];\n nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)));\n }\n } else if (type === IOP3) {\n n3 = nstack.pop();\n n2 = nstack.pop();\n n1 = nstack.pop();\n if (item.value === '?') {\n nstack.push(evaluate(n1 ? n2 : n3, expr, values));\n } else {\n f = expr.ternaryOps[item.value];\n nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values)));\n }\n } else if (type === IVAR) {\n if (/^__proto__|prototype|constructor$/.test(item.value)) {\n throw new Error('prototype access detected');\n }\n if (item.value in expr.functions) {\n nstack.push(expr.functions[item.value]);\n } else if (item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) {\n nstack.push(expr.unaryOps[item.value]);\n } else {\n var v = values[item.value];\n\n if (v !== undefined) {\n if (typeof v === 'function' && !isAllowedFunc(v, expr, values)) {\n /* function is not registered, not marked safe, and not a member function. BLOCKED. */\n throw new Error('Variable references an unallowed function: ' + item.value);\n }\n nstack.push(v);\n } else {\n throw new Error('undefined variable: ' + item.value);\n }\n }\n } else if (type === IOP1) {\n n1 = nstack.pop();\n f = expr.unaryOps[item.value];\n nstack.push(f(resolveExpression(n1, values)));\n } else if (type === IFUNCALL) {\n argCount = item.value;\n args = [];\n while (argCount-- > 0) {\n args.unshift(resolveExpression(nstack.pop(), values));\n }\n f = nstack.pop();\n if (!isAllowedFunc(f, expr, values)) {\n throw new Error('Is not an allowed function.');\n }\n if (f.apply && f.call) {\n nstack.push(f.apply(undefined, args));\n } else {\n throw new Error(f + ' is not a function');\n }\n } else if (type === IFUNDEF) {\n // Create closure to keep references to arguments and expression\n nstack.push((function () {\n var n2 = nstack.pop();\n var args = [];\n var argCount = item.value;\n while (argCount-- > 0) {\n args.unshift(nstack.pop());\n }\n var n1 = nstack.pop();\n var f = function () {\n var scope = Object.assign({}, values);\n for (var i = 0, len = args.length; i < len; i++) {\n scope[args[i]] = arguments[i];\n }\n return evaluate(n2, expr, scope);\n };\n // f.name = n1\n Object.defineProperty(f, 'name', {\n value: n1,\n writable: false\n });\n Object.defineProperty(f, '__expr_eval_safe_def', {\n value: true,\n writable: false\n });\n values[n1] = f;\n return f;\n })());\n } else if (type === IEXPR) {\n nstack.push(createExpressionEvaluator(item, expr));\n } else if (type === IEXPREVAL) {\n nstack.push(item);\n } else if (type === IMEMBER) {\n n1 = nstack.pop();\n nstack.push(n1[item.value]);\n } else if (type === IENDSTATEMENT) {\n nstack.pop();\n } else if (type === IARRAY) {\n argCount = item.value;\n args = [];\n while (argCount-- > 0) {\n args.unshift(nstack.pop());\n }\n nstack.push(args);\n } else {\n throw new Error('invalid Expression');\n }\n }\n if (nstack.length > 1) {\n throw new Error('invalid Expression (parity)');\n }\n // Explicitly return zero to avoid test issues caused by -0\n return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values);\n}\n\nfunction createExpressionEvaluator(token, expr, values) {\n if (isExpressionEvaluator(token)) return token;\n return {\n type: IEXPREVAL,\n value: function (scope) {\n return evaluate(token.value, expr, scope);\n }\n };\n}\n\nfunction isExpressionEvaluator(n) {\n return n && n.type === IEXPREVAL;\n}\n\nfunction resolveExpression(n, values) {\n return isExpressionEvaluator(n) ? n.value(values) : n;\n}\n\nfunction expressionToString(tokens, toJS) {\n var nstack = [];\n var n1, n2, n3;\n var f, args, argCount;\n for (var i = 0; i < tokens.length; i++) {\n var item = tokens[i];\n var type = item.type;\n if (type === INUMBER) {\n if (typeof item.value === 'number' && item.value < 0) {\n nstack.push('(' + item.value + ')');\n } else if (Array.isArray(item.value)) {\n nstack.push('[' + item.value.map(escapeValue).join(', ') + ']');\n } else {\n nstack.push(escapeValue(item.value));\n }\n } else if (type === IOP2) {\n n2 = nstack.pop();\n n1 = nstack.pop();\n f = item.value;\n if (toJS) {\n if (f === '^') {\n nstack.push('Math.pow(' + n1 + ', ' + n2 + ')');\n } else if (f === 'and') {\n nstack.push('(!!' + n1 + ' && !!' + n2 + ')');\n } else if (f === 'or') {\n nstack.push('(!!' + n1 + ' || !!' + n2 + ')');\n } else if (f === '||') {\n nstack.push('(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((' + n1 + '),(' + n2 + ')))');\n } else if (f === '==') {\n nstack.push('(' + n1 + ' === ' + n2 + ')');\n } else if (f === '!=') {\n nstack.push('(' + n1 + ' !== ' + n2 + ')');\n } else if (f === '[') {\n nstack.push(n1 + '[(' + n2 + ') | 0]');\n } else {\n nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')');\n }\n } else {\n if (f === '[') {\n nstack.push(n1 + '[' + n2 + ']');\n } else {\n nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')');\n }\n }\n } else if (type === IOP3) {\n n3 = nstack.pop();\n n2 = nstack.pop();\n n1 = nstack.pop();\n f = item.value;\n if (f === '?') {\n nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');\n } else {\n throw new Error('invalid Expression');\n }\n } else if (type === IVAR || type === IVARNAME) {\n nstack.push(item.value);\n } else if (type === IOP1) {\n n1 = nstack.pop();\n f = item.value;\n if (f === '-' || f === '+') {\n nstack.push('(' + f + n1 + ')');\n } else if (toJS) {\n if (f === 'not') {\n nstack.push('(' + '!' + n1 + ')');\n } else if (f === '!') {\n nstack.push('fac(' + n1 + ')');\n } else {\n nstack.push(f + '(' + n1 + ')');\n }\n } else if (f === '!') {\n nstack.push('(' + n1 + '!)');\n } else {\n nstack.push('(' + f + ' ' + n1 + ')');\n }\n } else if (type === IFUNCALL) {\n argCount = item.value;\n args = [];\n while (argCount-- > 0) {\n args.unshift(nstack.pop());\n }\n f = nstack.pop();\n nstack.push(f + '(' + args.join(', ') + ')');\n } else if (type === IFUNDEF) {\n n2 = nstack.pop();\n argCount = item.value;\n args = [];\n while (argCount-- > 0) {\n args.unshift(nstack.pop());\n }\n n1 = nstack.pop();\n if (toJS) {\n nstack.push('(' + n1 + ' = function(' + args.join(', ') + ') { return ' + n2 + ' })');\n } else {\n nstack.push('(' + n1 + '(' + args.join(', ') + ') = ' + n2 + ')');\n }\n } else if (type === IMEMBER) {\n n1 = nstack.pop();\n nstack.push(n1 + '.' + item.value);\n } else if (type === IARRAY) {\n argCount = item.value;\n args = [];\n while (argCount-- > 0) {\n args.unshift(nstack.pop());\n }\n nstack.push('[' + args.join(', ') + ']');\n } else if (type === IEXPR) {\n nstack.push('(' + expressionToString(item.value, toJS) + ')');\n } else if (type === IENDSTATEMENT) ; else {\n throw new Error('invalid Expression');\n }\n }\n if (nstack.length > 1) {\n if (toJS) {\n nstack = [nstack.join(',')];\n } else {\n nstack = [nstack.join(';')];\n }\n }\n return String(nstack[0]);\n}\n\nfunction escapeValue(v) {\n if (typeof v === 'string') {\n return JSON.stringify(v).replace(/\\u2028/g, '\\\\u2028').replace(/\\u2029/g, '\\\\u2029');\n }\n return v;\n}\n\nfunction contains(array, obj) {\n for (var i = 0; i < array.length; i++) {\n if (array[i] === obj) {\n return true;\n }\n }\n return false;\n}\n\nfunction getSymbols(tokens, symbols, options) {\n options = options || {};\n var withMembers = !!options.withMembers;\n var prevVar = null;\n\n for (var i = 0; i < tokens.length; i++) {\n var item = tokens[i];\n if (item.type === IVAR || item.type === IVARNAME) {\n if (!withMembers && !contains(symbols, item.value)) {\n symbols.push(item.value);\n } else if (prevVar !== null) {\n if (!contains(symbols, prevVar)) {\n symbols.push(prevVar);\n }\n prevVar = item.value;\n } else {\n prevVar = item.value;\n }\n } else if (item.type === IMEMBER && withMembers && prevVar !== null) {\n prevVar += '.' + item.value;\n } else if (item.type === IEXPR) {\n getSymbols(item.value, symbols, options);\n } else if (prevVar !== null) {\n if (!contains(symbols, prevVar)) {\n symbols.push(prevVar);\n }\n prevVar = null;\n }\n }\n\n if (prevVar !== null && !contains(symbols, prevVar)) {\n symbols.push(prevVar);\n }\n}\n\nfunction Expression(tokens, parser) {\n this.tokens = tokens;\n this.parser = parser;\n this.unaryOps = parser.unaryOps;\n this.binaryOps = parser.binaryOps;\n this.ternaryOps = parser.ternaryOps;\n this.functions = parser.functions;\n}\n\nExpression.prototype.simplify = function (values) {\n values = values || {};\n return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser);\n};\n\nExpression.prototype.substitute = function (variable, expr) {\n if (!(expr instanceof Expression)) {\n expr = this.parser.parse(String(expr));\n }\n\n return new Expression(substitute(this.tokens, variable, expr), this.parser);\n};\n\nExpression.prototype.evaluate = function (values) {\n values = values || {};\n return evaluate(this.tokens, this, values);\n};\n\nExpression.prototype.toString = function () {\n return expressionToString(this.tokens, false);\n};\n\nExpression.prototype.symbols = function (options) {\n options = options || {};\n var vars = [];\n getSymbols(this.tokens, vars, options);\n return vars;\n};\n\nExpression.prototype.variables = function (options) {\n options = options || {};\n var vars = [];\n getSymbols(this.tokens, vars, options);\n var functions = this.functions;\n return vars.filter(function (name) {\n return !(name in functions);\n });\n};\n\nExpression.prototype.toJSFunction = function (param, variables) {\n var expr = this;\n var f = new Function(param, 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString(this.simplify(variables).tokens, true) + '; }'); // eslint-disable-line no-new-func\n return function () {\n return f.apply(expr, arguments);\n };\n};\n\nvar TEOF = 'TEOF';\nvar TOP = 'TOP';\nvar TNUMBER = 'TNUMBER';\nvar TSTRING = 'TSTRING';\nvar TPAREN = 'TPAREN';\nvar TBRACKET = 'TBRACKET';\nvar TCOMMA = 'TCOMMA';\nvar TNAME = 'TNAME';\nvar TSEMICOLON = 'TSEMICOLON';\n\nfunction Token(type, value, index) {\n this.type = type;\n this.value = value;\n this.index = index;\n}\n\nToken.prototype.toString = function () {\n return this.type + ': ' + this.value;\n};\n\nfunction TokenStream(parser, expression) {\n this.pos = 0;\n this.current = null;\n this.unaryOps = parser.unaryOps;\n this.binaryOps = parser.binaryOps;\n this.ternaryOps = parser.ternaryOps;\n this.consts = parser.consts;\n this.expression = expression;\n this.savedPosition = 0;\n this.savedCurrent = null;\n this.options = parser.options;\n this.parser = parser;\n}\n\nTokenStream.prototype.newToken = function (type, value, pos) {\n return new Token(type, value, pos != null ? pos : this.pos);\n};\n\nTokenStream.prototype.save = function () {\n this.savedPosition = this.pos;\n this.savedCurrent = this.current;\n};\n\nTokenStream.prototype.restore = function () {\n this.pos = this.savedPosition;\n this.current = this.savedCurrent;\n};\n\nTokenStream.prototype.next = function () {\n if (this.pos >= this.expression.length) {\n return this.newToken(TEOF, 'EOF');\n }\n\n if (this.isWhitespace() || this.isComment()) {\n return this.next();\n } else if (this.isRadixInteger() ||\n this.isNumber() ||\n this.isOperator() ||\n this.isString() ||\n this.isParen() ||\n this.isBracket() ||\n this.isComma() ||\n this.isSemicolon() ||\n this.isNamedOp() ||\n this.isConst() ||\n this.isName()) {\n return this.current;\n } else {\n this.parseError('Unknown character \"' + this.expression.charAt(this.pos) + '\"');\n }\n};\n\nTokenStream.prototype.isString = function () {\n var r = false;\n var startPos = this.pos;\n var quote = this.expression.charAt(startPos);\n\n if (quote === '\\'' || quote === '\"') {\n var index = this.expression.indexOf(quote, startPos + 1);\n while (index >= 0 && this.pos < this.expression.length) {\n this.pos = index + 1;\n if (this.expression.charAt(index - 1) !== '\\\\') {\n var rawString = this.expression.substring(startPos + 1, index);\n this.current = this.newToken(TSTRING, this.unescape(rawString), startPos);\n r = true;\n break;\n }\n index = this.expression.indexOf(quote, index + 1);\n }\n }\n return r;\n};\n\nTokenStream.prototype.isParen = function () {\n var c = this.expression.charAt(this.pos);\n if (c === '(' || c === ')') {\n this.current = this.newToken(TPAREN, c);\n this.pos++;\n return true;\n }\n return false;\n};\n\nTokenStream.prototype.isBracket = function () {\n var c = this.expression.charAt(this.pos);\n if ((c === '[' || c === ']') && this.isOperatorEnabled('[')) {\n this.current = this.newToken(TBRACKET, c);\n this.pos++;\n return true;\n }\n return false;\n};\n\nTokenStream.prototype.isComma = function () {\n var c = this.expression.charAt(this.pos);\n if (c === ',') {\n this.current = this.newToken(TCOMMA, ',');\n this.pos++;\n return true;\n }\n return false;\n};\n\nTokenStream.prototype.isSemicolon = function () {\n var c = this.expression.charAt(this.pos);\n if (c === ';') {\n this.current = this.newToken(TSEMICOLON, ';');\n this.pos++;\n return true;\n }\n return false;\n};\n\nTokenStream.prototype.isConst = function () {\n var startPos = this.pos;\n var i = startPos;\n for (; i < this.expression.length; i++) {\n var c = this.expression.charAt(i);\n if (c.toUpperCase() === c.toLowerCase()) {\n if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) {\n break;\n }\n }\n }\n if (i > startPos) {\n var str = this.expression.substring(startPos, i);\n if (str in this.consts) {\n this.current = this.newToken(TNUMBER, this.consts[str]);\n this.pos += str.length;\n return true;\n }\n }\n return false;\n};\n\nTokenStream.prototype.isNamedOp = function () {\n var startPos = this.pos;\n var i = startPos;\n for (; i < this.expression.length; i++) {\n var c = this.expression.charAt(i);\n if (c.toUpperCase() === c.toLowerCase()) {\n if (i === this.pos || (c !== '_' && (c < '0' || c > '9'))) {\n break;\n }\n }\n }\n if (i > startPos) {\n var str = this.expression.substring(startPos, i);\n if (this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) {\n this.current = this.newToken(TOP, str);\n this.pos += str.length;\n return true;\n }\n }\n return false;\n};\n\nTokenStream.prototype.isName = function () {\n var startPos = this.pos;\n var i = startPos;\n var hasLetter = false;\n for (; i < this.expression.length; i++) {\n var c = this.expression.charAt(i);\n if (c.toUpperCase() === c.toLowerCase()) {\n if (i === this.pos && (c === '$' || c === '_')) {\n if (c === '_') {\n hasLetter = true;\n }\n continue;\n } else if (i === this.pos || !hasLetter || (c !== '_' && (c < '0' || c > '9'))) {\n break;\n }\n } else {\n hasLetter = true;\n }\n }\n if (hasLetter) {\n var str = this.expression.substring(startPos, i);\n this.current = this.newToken(TNAME, str);\n this.pos += str.length;\n return true;\n }\n return false;\n};\n\nTokenStream.prototype.isWhitespace = function () {\n var r = false;\n var c = this.expression.charAt(this.pos);\n while (c === ' ' || c === '\\t' || c === '\\n' || c === '\\r') {\n r = true;\n this.pos++;\n if (this.pos >= this.expression.length) {\n break;\n }\n c = this.expression.charAt(this.pos);\n }\n return r;\n};\n\nvar codePointPattern = /^[0-9a-f]{4}$/i;\n\nTokenStream.prototype.unescape = function (v) {\n var index = v.indexOf('\\\\');\n if (index < 0) {\n return v;\n }\n\n var buffer = v.substring(0, index);\n while (index >= 0) {\n var c = v.charAt(++index);\n switch (c) {\n case '\\'':\n buffer += '\\'';\n break;\n case '\"':\n buffer += '\"';\n break;\n case '\\\\':\n buffer += '\\\\';\n break;\n case '/':\n buffer += '/';\n break;\n case 'b':\n buffer += '\\b';\n break;\n case 'f':\n buffer += '\\f';\n break;\n case 'n':\n buffer += '\\n';\n break;\n case 'r':\n buffer += '\\r';\n break;\n case 't':\n buffer += '\\t';\n break;\n case 'u':\n // interpret the following 4 characters as the hex of the unicode code point\n var codePoint = v.substring(index + 1, index + 5);\n if (!codePointPattern.test(codePoint)) {\n this.parseError('Illegal escape sequence: \\\\u' + codePoint);\n }\n buffer += String.fromCharCode(parseInt(codePoint, 16));\n index += 4;\n break;\n default:\n throw this.parseError('Illegal escape sequence: \"\\\\' + c + '\"');\n }\n ++index;\n var backslash = v.indexOf('\\\\', index);\n buffer += v.substring(index, backslash < 0 ? v.length : backslash);\n index = backslash;\n }\n\n return buffer;\n};\n\nTokenStream.prototype.isComment = function () {\n var c = this.expression.charAt(this.pos);\n if (c === '/' && this.expression.charAt(this.pos + 1) === '*') {\n this.pos = this.expression.indexOf('*/', this.pos) + 2;\n if (this.pos === 1) {\n this.pos = this.expression.length;\n }\n return true;\n }\n return false;\n};\n\nTokenStream.prototype.isRadixInteger = function () {\n var pos = this.pos;\n\n if (pos >= this.expression.length - 2 || this.expression.charAt(pos) !== '0') {\n return false;\n }\n ++pos;\n\n var radix;\n var validDigit;\n if (this.expression.charAt(pos) === 'x') {\n radix = 16;\n validDigit = /^[0-9a-f]$/i;\n ++pos;\n } else if (this.expression.charAt(pos) === 'b') {\n radix = 2;\n validDigit = /^[01]$/i;\n ++pos;\n } else {\n return false;\n }\n\n var valid = false;\n var startPos = pos;\n\n while (pos < this.expression.length) {\n var c = this.expression.charAt(pos);\n if (validDigit.test(c)) {\n pos++;\n valid = true;\n } else {\n break;\n }\n }\n\n if (valid) {\n this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix));\n this.pos = pos;\n }\n return valid;\n};\n\nTokenStream.prototype.isNumber = function () {\n var valid = false;\n var pos = this.pos;\n var startPos = pos;\n var resetPos = pos;\n var foundDot = false;\n var foundDigits = false;\n var c;\n\n while (pos < this.expression.length) {\n c = this.expression.charAt(pos);\n if ((c >= '0' && c <= '9') || (!foundDot && c === '.')) {\n if (c === '.') {\n foundDot = true;\n } else {\n foundDigits = true;\n }\n pos++;\n valid = foundDigits;\n } else {\n break;\n }\n }\n\n if (valid) {\n resetPos = pos;\n }\n\n if (c === 'e' || c === 'E') {\n pos++;\n var acceptSign = true;\n var validExponent = false;\n while (pos < this.expression.length) {\n c = this.expression.charAt(pos);\n if (acceptSign && (c === '+' || c === '-')) {\n acceptSign = false;\n } else if (c >= '0' && c <= '9') {\n validExponent = true;\n acceptSign = false;\n } else {\n break;\n }\n pos++;\n }\n\n if (!validExponent) {\n pos = resetPos;\n }\n }\n\n if (valid) {\n this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos)));\n this.pos = pos;\n } else {\n this.pos = resetPos;\n }\n return valid;\n};\n\nTokenStream.prototype.isOperator = function () {\n var startPos = this.pos;\n var c = this.expression.charAt(this.pos);\n\n if (c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.') {\n this.current = this.newToken(TOP, c);\n } else if (c === '∙' || c === '•') {\n this.current = this.newToken(TOP, '*');\n } else if (c === '>') {\n if (this.expression.charAt(this.pos + 1) === '=') {\n this.current = this.newToken(TOP, '>=');\n this.pos++;\n } else {\n this.current = this.newToken(TOP, '>');\n }\n } else if (c === '<') {\n if (this.expression.charAt(this.pos + 1) === '=') {\n this.current = this.newToken(TOP, '<=');\n this.pos++;\n } else {\n this.current = this.newToken(TOP, '<');\n }\n } else if (c === '|') {\n if (this.expression.charAt(this.pos + 1) === '|') {\n this.current = this.newToken(TOP, '||');\n this.pos++;\n } else {\n return false;\n }\n } else if (c === '=') {\n if (this.expression.charAt(this.pos + 1) === '=') {\n this.current = this.newToken(TOP, '==');\n this.pos++;\n } else {\n this.current = this.newToken(TOP, c);\n }\n } else if (c === '!') {\n if (this.expression.charAt(this.pos + 1) === '=') {\n this.current = this.newToken(TOP, '!=');\n this.pos++;\n } else {\n this.current = this.newToken(TOP, c);\n }\n } else {\n return false;\n }\n this.pos++;\n\n if (this.isOperatorEnabled(this.current.value)) {\n return true;\n } else {\n this.pos = startPos;\n return false;\n }\n};\n\nTokenStream.prototype.isOperatorEnabled = function (op) {\n return this.parser.isOperatorEnabled(op);\n};\n\nTokenStream.prototype.getCoordinates = function () {\n var line = 0;\n var column;\n var newline = -1;\n do {\n line++;\n column = this.pos - newline;\n newline = this.expression.indexOf('\\n', newline + 1);\n } while (newline >= 0 && newline < this.pos);\n\n return {\n line: line,\n column: column\n };\n};\n\nTokenStream.prototype.parseError = function (msg) {\n var coords = this.getCoordinates();\n throw new Error('parse error [' + coords.line + ':' + coords.column + ']: ' + msg);\n};\n\nfunction ParserState(parser, tokenStream, options) {\n this.parser = parser;\n this.tokens = tokenStream;\n this.current = null;\n this.nextToken = null;\n this.next();\n this.savedCurrent = null;\n this.savedNextToken = null;\n this.allowMemberAccess = options.allowMemberAccess !== false;\n}\n\nParserState.prototype.next = function () {\n this.current = this.nextToken;\n return (this.nextToken = this.tokens.next());\n};\n\nParserState.prototype.tokenMatches = function (token, value) {\n if (typeof value === 'undefined') {\n return true;\n } else if (Array.isArray(value)) {\n return contains(value, token.value);\n } else if (typeof value === 'function') {\n return value(token);\n } else {\n return token.value === value;\n }\n};\n\nParserState.prototype.save = function () {\n this.savedCurrent = this.current;\n this.savedNextToken = this.nextToken;\n this.tokens.save();\n};\n\nParserState.prototype.restore = function () {\n this.tokens.restore();\n this.current = this.savedCurrent;\n this.nextToken = this.savedNextToken;\n};\n\nParserState.prototype.accept = function (type, value) {\n if (this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) {\n this.next();\n return true;\n }\n return false;\n};\n\nParserState.prototype.expect = function (type, value) {\n if (!this.accept(type, value)) {\n var coords = this.tokens.getCoordinates();\n throw new Error('parse error [' + coords.line + ':' + coords.column + ']: Expected ' + (value || type));\n }\n};\n\nParserState.prototype.parseAtom = function (instr) {\n var unaryOps = this.tokens.unaryOps;\n function isPrefixOperator(token) {\n return token.value in unaryOps;\n }\n\n if (this.accept(TNAME) || this.accept(TOP, isPrefixOperator)) {\n instr.push(new Instruction(IVAR, this.current.value));\n } else if (this.accept(TNUMBER)) {\n instr.push(new Instruction(INUMBER, this.current.value));\n } else if (this.accept(TSTRING)) {\n instr.push(new Instruction(INUMBER, this.current.value));\n } else if (this.accept(TPAREN, '(')) {\n this.parseExpression(instr);\n this.expect(TPAREN, ')');\n } else if (this.accept(TBRACKET, '[')) {\n if (this.accept(TBRACKET, ']')) {\n instr.push(new Instruction(IARRAY, 0));\n } else {\n var argCount = this.parseArrayList(instr);\n instr.push(new Instruction(IARRAY, argCount));\n }\n } else {\n throw new Error('unexpected ' + this.nextToken);\n }\n};\n\nParserState.prototype.parseExpression = function (instr) {\n var exprInstr = [];\n if (this.parseUntilEndStatement(instr, exprInstr)) {\n return;\n }\n this.parseVariableAssignmentExpression(exprInstr);\n if (this.parseUntilEndStatement(instr, exprInstr)) {\n return;\n }\n this.pushExpression(instr, exprInstr);\n};\n\nParserState.prototype.pushExpression = function (instr, exprInstr) {\n for (var i = 0, len = exprInstr.length; i < len; i++) {\n instr.push(exprInstr[i]);\n }\n};\n\nParserState.prototype.parseUntilEndStatement = function (instr, exprInstr) {\n if (!this.accept(TSEMICOLON)) return false;\n if (this.nextToken && this.nextToken.type !== TEOF && !(this.nextToken.type === TPAREN && this.nextToken.value === ')')) {\n exprInstr.push(new Instruction(IENDSTATEMENT));\n }\n if (this.nextToken.type !== TEOF) {\n this.parseExpression(exprInstr);\n }\n instr.push(new Instruction(IEXPR, exprInstr));\n return true;\n};\n\nParserState.prototype.parseArrayList = function (instr) {\n var argCount = 0;\n\n while (!this.accept(TBRACKET, ']')) {\n this.parseExpression(instr);\n ++argCount;\n while (this.accept(TCOMMA)) {\n this.parseExpression(instr);\n ++argCount;\n }\n }\n\n return argCount;\n};\n\nParserState.prototype.parseVariableAssignmentExpression = function (instr) {\n this.parseConditionalExpression(instr);\n while (this.accept(TOP, '=')) {\n var varName = instr.pop();\n var varValue = [];\n var lastInstrIndex = instr.length - 1;\n if (varName.type === IFUNCALL) {\n if (!this.tokens.isOperatorEnabled('()=')) {\n throw new Error('function definition is not permitted');\n }\n for (var i = 0, len = varName.value + 1; i < len; i++) {\n var index = lastInstrIndex - i;\n if (instr[index].type === IVAR) {\n instr[index] = new Instruction(IVARNAME, instr[index].value);\n }\n }\n this.parseVariableAssignmentExpression(varValue);\n instr.push(new Instruction(IEXPR, varValue));\n instr.push(new Instruction(IFUNDEF, varName.value));\n continue;\n }\n if (varName.type !== IVAR && varName.type !== IMEMBER) {\n throw new Error('expected variable for assignment');\n }\n this.parseVariableAssignmentExpression(varValue);\n instr.push(new Instruction(IVARNAME, varName.value));\n instr.push(new Instruction(IEXPR, varValue));\n instr.push(binaryInstruction('='));\n }\n};\n\nParserState.prototype.parseConditionalExpression = function (instr) {\n this.parseOrExpression(instr);\n while (this.accept(TOP, '?')) {\n var trueBranch = [];\n var falseBranch = [];\n this.parseConditionalExpression(trueBranch);\n this.expect(TOP, ':');\n this.parseConditionalExpression(falseBranch);\n instr.push(new Instruction(IEXPR, trueBranch));\n instr.push(new Instruction(IEXPR, falseBranch));\n instr.push(ternaryInstruction('?'));\n }\n};\n\nParserState.prototype.parseOrExpression = function (instr) {\n this.parseAndExpression(instr);\n while (this.accept(TOP, 'or')) {\n var falseBranch = [];\n this.parseAndExpression(falseBranch);\n instr.push(new Instruction(IEXPR, falseBranch));\n instr.push(binaryInstruction('or'));\n }\n};\n\nParserState.prototype.parseAndExpression = function (instr) {\n this.parseComparison(instr);\n while (this.accept(TOP, 'and')) {\n var trueBranch = [];\n this.parseComparison(trueBranch);\n instr.push(new Instruction(IEXPR, trueBranch));\n instr.push(binaryInstruction('and'));\n }\n};\n\nvar COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'in'];\n\nParserState.prototype.parseComparison = function (instr) {\n this.parseAddSub(instr);\n while (this.accept(TOP, COMPARISON_OPERATORS)) {\n var op = this.current;\n this.parseAddSub(instr);\n instr.push(binaryInstruction(op.value));\n }\n};\n\nvar ADD_SUB_OPERATORS = ['+', '-', '||'];\n\nParserState.prototype.parseAddSub = function (instr) {\n this.parseTerm(instr);\n while (this.accept(TOP, ADD_SUB_OPERATORS)) {\n var op = this.current;\n this.parseTerm(instr);\n instr.push(binaryInstruction(op.value));\n }\n};\n\nvar TERM_OPERATORS = ['*', '/', '%'];\n\nParserState.prototype.parseTerm = function (instr) {\n this.parseFactor(instr);\n while (this.accept(TOP, TERM_OPERATORS)) {\n var op = this.current;\n this.parseFactor(instr);\n instr.push(binaryInstruction(op.value));\n }\n};\n\nParserState.prototype.parseFactor = function (instr) {\n var unaryOps = this.tokens.unaryOps;\n function isPrefixOperator(token) {\n return token.value in unaryOps;\n }\n\n this.save();\n if (this.accept(TOP, isPrefixOperator)) {\n if (this.current.value !== '-' && this.current.value !== '+') {\n if (this.nextToken.type === TPAREN && this.nextToken.value === '(') {\n this.restore();\n this.parseExponential(instr);\n return;\n } else if (this.nextToken.type === TSEMICOLON || this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ')')) {\n this.restore();\n this.parseAtom(instr);\n return;\n }\n }\n\n var op = this.current;\n this.parseFactor(instr);\n instr.push(unaryInstruction(op.value));\n } else {\n this.parseExponential(instr);\n }\n};\n\nParserState.prototype.parseExponential = function (instr) {\n this.parsePostfixExpression(instr);\n while (this.accept(TOP, '^')) {\n this.parseFactor(instr);\n instr.push(binaryInstruction('^'));\n }\n};\n\nParserState.prototype.parsePostfixExpression = function (instr) {\n this.parseFunctionCall(instr);\n while (this.accept(TOP, '!')) {\n instr.push(unaryInstruction('!'));\n }\n};\n\nParserState.prototype.parseFunctionCall = function (instr) {\n var unaryOps = this.tokens.unaryOps;\n function isPrefixOperator(token) {\n return token.value in unaryOps;\n }\n\n if (this.accept(TOP, isPrefixOperator)) {\n var op = this.current;\n this.parseAtom(instr);\n instr.push(unaryInstruction(op.value));\n } else {\n this.parseMemberExpression(instr);\n while (this.accept(TPAREN, '(')) {\n if (this.accept(TPAREN, ')')) {\n instr.push(new Instruction(IFUNCALL, 0));\n } else {\n var argCount = this.parseArgumentList(instr);\n instr.push(new Instruction(IFUNCALL, argCount));\n }\n }\n }\n};\n\nParserState.prototype.parseArgumentList = function (instr) {\n var argCount = 0;\n\n while (!this.accept(TPAREN, ')')) {\n this.parseExpression(instr);\n ++argCount;\n while (this.accept(TCOMMA)) {\n this.parseExpression(instr);\n ++argCount;\n }\n }\n\n return argCount;\n};\n\nParserState.prototype.parseMemberExpression = function (instr) {\n this.parseAtom(instr);\n while (this.accept(TOP, '.') || this.accept(TBRACKET, '[')) {\n var op = this.current;\n\n if (op.value === '.') {\n if (!this.allowMemberAccess) {\n throw new Error('unexpected \".\", member access is not permitted');\n }\n\n this.expect(TNAME);\n instr.push(new Instruction(IMEMBER, this.current.value));\n } else if (op.value === '[') {\n if (!this.tokens.isOperatorEnabled('[')) {\n throw new Error('unexpected \"[]\", arrays are disabled');\n }\n\n this.parseExpression(instr);\n this.expect(TBRACKET, ']');\n instr.push(binaryInstruction('['));\n } else {\n throw new Error('unexpected symbol: ' + op.value);\n }\n }\n};\n\nfunction add(a, b) {\n return Number(a) + Number(b);\n}\n\nfunction sub(a, b) {\n return a - b;\n}\n\nfunction mul(a, b) {\n return a * b;\n}\n\nfunction div(a, b) {\n return a / b;\n}\n\nfunction mod(a, b) {\n return a % b;\n}\n\nfunction concat(a, b) {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.concat(b);\n }\n return '' + a + b;\n}\n\nfunction equal(a, b) {\n return a === b;\n}\n\nfunction notEqual(a, b) {\n return a !== b;\n}\n\nfunction greaterThan(a, b) {\n return a > b;\n}\n\nfunction lessThan(a, b) {\n return a < b;\n}\n\nfunction greaterThanEqual(a, b) {\n return a >= b;\n}\n\nfunction lessThanEqual(a, b) {\n return a <= b;\n}\n\nfunction andOperator(a, b) {\n return Boolean(a && b);\n}\n\nfunction orOperator(a, b) {\n return Boolean(a || b);\n}\n\nfunction inOperator(a, b) {\n return contains(b, a);\n}\n\nfunction sinh(a) {\n return ((Math.exp(a) - Math.exp(-a)) / 2);\n}\n\nfunction cosh(a) {\n return ((Math.exp(a) + Math.exp(-a)) / 2);\n}\n\nfunction tanh(a) {\n if (a === Infinity) return 1;\n if (a === -Infinity) return -1;\n return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a));\n}\n\nfunction asinh(a) {\n if (a === -Infinity) return a;\n return Math.log(a + Math.sqrt((a * a) + 1));\n}\n\nfunction acosh(a) {\n return Math.log(a + Math.sqrt((a * a) - 1));\n}\n\nfunction atanh(a) {\n return (Math.log((1 + a) / (1 - a)) / 2);\n}\n\nfunction log10(a) {\n return Math.log(a) * Math.LOG10E;\n}\n\nfunction neg(a) {\n return -a;\n}\n\nfunction not(a) {\n return !a;\n}\n\nfunction trunc(a) {\n return a < 0 ? Math.ceil(a) : Math.floor(a);\n}\n\nfunction random(a) {\n return Math.random() * (a || 1);\n}\n\nfunction factorial(a) { // a!\n return gamma(a + 1);\n}\n\nfunction isInteger(value) {\n return isFinite(value) && (value === Math.round(value));\n}\n\nvar GAMMA_G = 4.7421875;\nvar GAMMA_P = [\n 0.99999999999999709182,\n 57.156235665862923517, -59.597960355475491248,\n 14.136097974741747174, -0.49191381609762019978,\n 0.33994649984811888699e-4,\n 0.46523628927048575665e-4, -0.98374475304879564677e-4,\n 0.15808870322491248884e-3, -0.21026444172410488319e-3,\n 0.21743961811521264320e-3, -0.16431810653676389022e-3,\n 0.84418223983852743293e-4, -0.26190838401581408670e-4,\n 0.36899182659531622704e-5\n];\n\n// Gamma function from math.js\nfunction gamma(n) {\n var t, x;\n\n if (isInteger(n)) {\n if (n <= 0) {\n return isFinite(n) ? Infinity : NaN;\n }\n\n if (n > 171) {\n return Infinity; // Will overflow\n }\n\n var value = n - 2;\n var res = n - 1;\n while (value > 1) {\n res *= value;\n value--;\n }\n\n if (res === 0) {\n res = 1; // 0! is per definition 1\n }\n\n return res;\n }\n\n if (n < 0.5) {\n return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n));\n }\n\n if (n >= 171.35) {\n return Infinity; // will overflow\n }\n\n if (n > 85.0) { // Extended Stirling Approx\n var twoN = n * n;\n var threeN = twoN * n;\n var fourN = threeN * n;\n var fiveN = fourN * n;\n return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *\n (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -\n (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +\n (5246819 / (75246796800 * fiveN * n)));\n }\n\n --n;\n x = GAMMA_P[0];\n for (var i = 1; i < GAMMA_P.length; ++i) {\n x += GAMMA_P[i] / (n + i);\n }\n\n t = n + GAMMA_G + 0.5;\n return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x;\n}\n\nfunction stringOrArrayLength(s) {\n if (Array.isArray(s)) {\n return s.length;\n }\n return String(s).length;\n}\n\nfunction hypot() {\n var sum = 0;\n var larg = 0;\n for (var i = 0; i < arguments.length; i++) {\n var arg = Math.abs(arguments[i]);\n var div;\n if (larg < arg) {\n div = larg / arg;\n sum = (sum * div * div) + 1;\n larg = arg;\n } else if (arg > 0) {\n div = arg / larg;\n sum += div * div;\n } else {\n sum += arg;\n }\n }\n return larg === Infinity ? Infinity : larg * Math.sqrt(sum);\n}\n\nfunction condition(cond, yep, nope) {\n return cond ? yep : nope;\n}\n\n/**\n* Decimal adjustment of a number.\n* From @escopecz.\n*\n* @param {Number} value The number.\n* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).\n* @return {Number} The adjusted value.\n*/\nfunction roundTo(value, exp) {\n // If the exp is undefined or zero...\n if (typeof exp === 'undefined' || +exp === 0) {\n return Math.round(value);\n }\n value = +value;\n exp = -(+exp);\n // If the value is not a number or the exp is not an integer...\n if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {\n return NaN;\n }\n // Shift\n value = value.toString().split('e');\n value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));\n // Shift back\n value = value.toString().split('e');\n return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));\n}\n\nfunction setVar(name, value, variables) {\n if (variables) variables[name] = value;\n return value;\n}\n\nfunction arrayIndex(array, index) {\n return array[index | 0];\n}\n\nfunction max(array) {\n if (arguments.length === 1 && Array.isArray(array)) {\n return Math.max.apply(Math, array);\n } else {\n return Math.max.apply(Math, arguments);\n }\n}\n\nfunction min(array) {\n if (arguments.length === 1 && Array.isArray(array)) {\n return Math.min.apply(Math, array);\n } else {\n return Math.min.apply(Math, arguments);\n }\n}\n\nfunction arrayMap(f, a) {\n if (typeof f !== 'function') {\n throw new Error('First argument to map is not a function');\n }\n if (!Array.isArray(a)) {\n throw new Error('Second argument to map is not an array');\n }\n return a.map(function (x, i) {\n return f(x, i);\n });\n}\n\nfunction arrayFold(f, init, a) {\n if (typeof f !== 'function') {\n throw new Error('First argument to fold is not a function');\n }\n if (!Array.isArray(a)) {\n throw new Error('Second argument to fold is not an array');\n }\n return a.reduce(function (acc, x, i) {\n return f(acc, x, i);\n }, init);\n}\n\nfunction arrayFilter(f, a) {\n if (typeof f !== 'function') {\n throw new Error('First argument to filter is not a function');\n }\n if (!Array.isArray(a)) {\n throw new Error('Second argument to filter is not an array');\n }\n return a.filter(function (x, i) {\n return f(x, i);\n });\n}\n\nfunction stringOrArrayIndexOf(target, s) {\n if (!(Array.isArray(s) || typeof s === 'string')) {\n throw n