Skip to content

Instantly share code, notes, and snippets.

@danneu
Forked from joepie91/index.js
Created March 21, 2020 04:44
Show Gist options
  • Save danneu/425f41997d871d4f3a31596393a14656 to your computer and use it in GitHub Desktop.
Save danneu/425f41997d871d4f3a31596393a14656 to your computer and use it in GitHub Desktop.

Revisions

  1. @joepie91 joepie91 revised this gist Jul 14, 2016. No changes.
  2. @joepie91 joepie91 created this gist Jul 14, 2016.
    35 changes: 35 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    'use strict';

    const parseExpression = require("./parse-expression");

    function findAll(regex, target) {
    let results = [], match;

    while (match = regex.exec(target)) {
    results.push(match);
    }

    return results;
    }

    module.exports = function(testcase) {
    let result;

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

    let modifyingExpressions = findAll(new RegExp(`${parent}\.${child}\s*([*+-])=\s*([^;]+)`, "g"), testcase).map((match) => {
    return {
    operation: match[1],
    expression: match[2]
    }
    }).map(({operation, expression}) => {
    return {
    operation,
    expression: parseExpression(expression)
    }
    });

    initialExpression = parseExpression(initialExpression);

    return {parent, child, initialExpression, modifyingExpressions};
    }
    128 changes: 128 additions & 0 deletions parse-expression.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,128 @@
    '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];
    }