traxxx/src/utils/cf.js

154 lines
3.7 KiB
JavaScript
Executable File

'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;