'use strict'; const arrayEqual = require('array-equal'); /* eslint-disable */ function evaluateBranch(tree, modifiers) { const result = tree.map((expression) => { if (expression.type === 'group') { return evaluateBranch(expression.values, expression.modifiers); } if (expression.modifiers.length === 0) { return ''; // This is a trigger to stringify the previous values } if (arrayEqual(['plus'], expression.modifiers)) { return 0; } if (arrayEqual(['negate', 'negate'], expression.modifiers)) { return true; } if (arrayEqual(['negate', 'plus'], expression.modifiers)) { return true; } if (arrayEqual(['plus', 'plus', 'negate'], expression.modifiers)) { return true; } if (arrayEqual(['plus', 'negate', 'negate'], expression.modifiers)) { return 1; } throw new Error(`Found unrecognized modifier pattern: ${expression.modifiers}`); }).reduce((combined, value) => { if (value === '') { return combined.toString(); } if (value === true) { value = 1; } if (typeof combined === 'string') { return combined + value.toString(); } return combined + value; }, 0); if (modifiers == null) { return result; } if (arrayEqual(['plus'], modifiers)) { return parseInt(result); } return result; } function evaluate(tree) { return evaluateBranch(tree); } function parse(string) { const length = string.length; let byte; let stateFinishedItem = false; const modifierStack = [[]]; const 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]; } function parseExpression(string) { return evaluate(parse(string)); } function findAll(regex, target) { const results = []; let match; while (match = regex.exec(target)) { results.push(match); } return results; } function cf(testcase) { let [_, parent, child, initialExpression] = /var s,t,o,p,b,r,e,a,k,i,n,g,f,\s*([a-zA-Z]+)={"([a-zA-Z]+)":([^}]+)};/.exec(testcase); const modifyingExpressions = findAll(new RegExp(`${parent}\.${child}\s*([*+-])=\s*([^;]+)`, 'g'), testcase).map(match => ({ operation: match[1], expression: match[2], })).map(({ operation, expression }) => ({ operation, expression: parseExpression(expression), })); initialExpression = parseExpression(initialExpression); return { parent, child, initialExpression, modifyingExpressions }; }; module.exports = cf;