Skip to content

Instantly share code, notes, and snippets.

@joepie91
Last active December 17, 2024 10:45
Show Gist options
  • Save joepie91/c5949279cd52ce5cb646d7bd03c3ea36 to your computer and use it in GitHub Desktop.
Save joepie91/c5949279cd52ce5cb646d7bd03c3ea36 to your computer and use it in GitHub Desktop.

Revisions

  1. joepie91 revised this gist Jul 14, 2016. No changes.
  2. 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];
    }