'use strict'; const arrayEqual = require("array-equal"); module.exports = function(string) { return evaluate(parse(string)); } function evaluateBranch(tree, modifiers) { let result = tree.map((expression) => { if (expression.type === "group") { return evaluateBranch(expression.values, expression.modifiers); } else if (expression.modifiers.length === 0) { return ""; // This is a trigger to stringify the previous values } else if (arrayEqual(["plus"], expression.modifiers)) { return 0; } else if (arrayEqual(["negate", "negate"], expression.modifiers)) { return true; } else if (arrayEqual(["negate", "plus"], expression.modifiers)) { return true; } else if (arrayEqual(["plus", "plus", "negate"], expression.modifiers)) { return true; } else if (arrayEqual(["plus", "negate", "negate"], expression.modifiers)) { return 1; } else { throw new Error(`Found unrecognized modifier pattern: ${expression.modifiers}`) } }).reduce((combined, value) => { if (value === "") { return combined.toString(); } else { if (value === true) { value = 1; } if (typeof combined === "string") { return combined + value.toString(); } else { return combined + value; } } }, 0); if (modifiers == null) { return result; } else { if (arrayEqual(["plus"], modifiers)) { return parseInt(result); } else { return result; } } } function evaluate(tree) { return evaluateBranch(tree); } function parse(string) { let length = string.length; let byte; let stateFinishedItem = false; let modifierStack = [[]]; let itemStack = [[]]; let currentStack = itemStack[0]; let currentModifiers = modifierStack[0]; let stackLevel = 0; for (let pos = 0; pos < length; pos++) { byte = string[pos]; switch (byte) { case "+": if (pos === 0 || stateFinishedItem === false) { // Modifier / number-cast currentModifiers.push("plus"); stateFinishedItem = false; } else { // Addition, we don't need to do anything here } break; case "!": stateFinishedItem = false; currentModifiers.push("negate"); break; case "(": stateFinishedItem = false; stackLevel++; itemStack[stackLevel] = currentStack = []; modifierStack[stackLevel] = currentModifiers = []; break; case ")": if (stackLevel === 0) { throw new Error(`Encountered ) without matching (`); } stackLevel--; stateFinishedItem = true; currentStack = itemStack[stackLevel]; currentStack.push({ type: "group", values: itemStack[stackLevel + 1], modifiers: modifierStack[stackLevel] }); currentModifiers = modifierStack[stackLevel] = []; break; case "[": if (string[pos + 1] === "]") { // Reached the brackets; end of the modifier sequence currentStack.push({ type: "brackets", modifiers: currentModifiers }); currentModifiers = []; pos += 1; // Skip over the closing bracket stateFinishedItem = true; } else { throw new Error(`Invalid byte found; expected ] but got ${string[pos + 1]}`); } } } return itemStack[0]; }