function calc(expr, context, repeat = []) {
  let stack = [];
  while (expr.length) {
    let { name, value, type } = expr.shift();
    if (type === 'variable') {
      let result = context[value];
      if (is_invalid_number(result)) {
        result = Math[value];
      }
      if (is_invalid_number(result)) {
        result = expand(value, context, repeat);
      }
      if (is_invalid_number(result)) {
        if (/^\-\D/.test(value)) {
          result = expand('-1' + value.substr(1), context, repeat);
        }
      }
      if (result === undefined) {
        result = 0;
      }
      if (typeof result !== 'number') {
        repeat.push(result);
        if (is_cycle(repeat)) {
          result = 0;
          repeat = [];
        } else {
          result = calc(infix_to_postfix(result), context, repeat)
        }
      }
      stack.push(result);
    }
    else if (type === 'function') {
      let negative = false;
      if (/^\-/.test(name)) {
        negative = true;
        name = name.substr(1);
      }
      let output = value.map(v => calc(v, context, repeat));
      let fns = name.split('.');
      let fname;
      while (fname = fns.pop()) {
        if (!fname) continue;
        let fn = context[fname] || Math[fname];
        output = (typeof fn === 'function')
          ? (Array.isArray(output) ? fn(...output) : fn(output))
          : 0;
      }
      if (negative) {
        output = -1 * output;
      }
      stack.push(output);
    } else {
      if (/\d+/.test(value)) stack.push(value);
      else {
        let right = stack.pop();
        let left = stack.pop();
        stack.push(compute(
          value, Number(left), Number(right)
        ));
      }
    }
  }
  return Number(stack[0]) || 0;
}
Based on the Shunting-yard algorithm.