public Calculator(String[] sa, Map<String, String> vars) {
    /* Populates the atom list.
     * Also collapses 2-part negative numbers into single Atoms. */
    if (vars.size() < 1) throw new IllegalArgumentException("No expression supplied");
    this.vars = vars;
    Atom atom = null, prePrevAtom;
    int prevIndex;
    NEXT_TOKEN:
    for (String token : sa)
      try {
        atom = new Atom(token);
        prevIndex = atoms.size() - 1;
        if (prevIndex < 0) continue;
        if (atoms.get(prevIndex).op != MathOp.SUBTRACT) continue;
        prePrevAtom = (prevIndex > 0) ? atoms.get(prevIndex - 1) : null;
        if (prePrevAtom != null && !TradOrLParen.contains(prePrevAtom.op)) continue;

        if (atom.op == null) {
          atoms.remove(prevIndex);
          atom.val *= -1;
        } else if (atom.op == MathOp.LPAREN) {
          atoms.remove(prevIndex);
          atoms.add(new Atom(-1L));
          atoms.add(new Atom(MathOp.MULTIPLY));
        }
      } finally {
        atoms.add(atom);
      }
  }
  /**
   * If atoms[startAtomIndex] == '(', then last visited atoms will be the next top-level (un-paired)
   * ')'. Otherwise, all remainign atoms will be visited. Every visited atom will be removed from
   * 'atoms'.
   *
   * @return Value that all visited atoms reduce to.
   */
  public long reduce(int startAtomIndex, boolean stopAtParenClose) {
    // Every occurence of atoms.remove() below is an instance of reduction.
    int i;
    Long prevValue = null;
    Atom atom;
    // Reduce parens via recursion
    i = startAtomIndex - 1;
    PAREN_SEEKER:
    while (atoms.size() >= ++i) {
      if (atoms.size() == i) {
        if (stopAtParenClose) throw new IllegalStateException("Unbalanced '" + MathOp.LPAREN + "'");
        break;
      }
      atom = atoms.get(i);
      if (atom.op != null)
        switch (atom.op) {
          case RPAREN:
            if (!stopAtParenClose)
              throw new IllegalStateException("Unbalanced '" + MathOp.RPAREN + "'");
            atoms.remove(i);
            break PAREN_SEEKER;
          case LPAREN: // Recurse.  Reduction inside of reduce().
            atoms.remove(i);
            atoms.add(i, new Atom(reduce(i, true)));
            break;
          default:
            // Intentionally empty
        }
    }
    int remaining = i - startAtomIndex;
    if (remaining < 1) throw new IllegalStateException("Empty expression");
    // System.out.println("Need to consume " + remaining + " after parens removed");
    Atom nextAtom;
    MathOp op;

    // Reduce powers
    i = startAtomIndex;
    atom = atoms.get(i);
    if (atom.op != null)
      throw new IllegalStateException(
          "Expected initial value expected but got operation " + atom.op);
    while (startAtomIndex + remaining > i + 1) {
      if (startAtomIndex + remaining < i + 3)
        throw new IllegalStateException("No operator/operand pairing remaining");
      nextAtom = atoms.get(i + 1);
      if (nextAtom.op == null)
        throw new IllegalStateException("Operator expected but got value " + nextAtom.val);
      op = nextAtom.op;
      nextAtom = atoms.get(i + 2);
      if (nextAtom.op != null)
        throw new IllegalStateException("Value expected but got operator " + nextAtom.op);
      if (op != MathOp.POWER) {
        // Skip 'atom' (current) and the operand that we'll handle later
        i += 2;
        atom = nextAtom;
        continue;
      }
      // Reduce the operator and right operand Atoms
      remaining -= 2;
      atoms.remove(i + 1);
      atoms.remove(i + 1);
      long origVal = atom.val;
      atom.val = 1;
      for (int j = 0; j < nextAtom.val; j++) atom.val *= origVal;
    }

    // Reduce multiplication and division
    i = startAtomIndex;
    atom = atoms.get(i);
    if (atom.op != null)
      throw new IllegalStateException(
          "Expected initial value expected but got operation " + atom.op);
    while (startAtomIndex + remaining > i + 1) {
      if (startAtomIndex + remaining < i + 3)
        throw new IllegalStateException("No operator/operand pairing remaining");
      nextAtom = atoms.get(i + 1);
      if (nextAtom.op == null)
        throw new IllegalStateException("Operator expected but got value " + nextAtom.val);
      op = nextAtom.op;
      nextAtom = atoms.get(i + 2);
      if (nextAtom.op != null)
        throw new IllegalStateException("Value expected but got operator " + nextAtom.op);
      if (op != MathOp.MULTIPLY && op != MathOp.DIVIDE && op != MathOp.REM) {
        // Skip 'atom' (current) and the operand that we'll handle later
        i += 2;
        atom = nextAtom;
        continue;
      }
      // Reduce the operator and right operand Atoms
      remaining -= 2;
      atoms.remove(i + 1);
      atoms.remove(i + 1);
      if (op == MathOp.MULTIPLY) atom.val *= nextAtom.val;
      else if (op == MathOp.DIVIDE) atom.val /= nextAtom.val;
      else atom.val %= nextAtom.val;
    }

    // Reduce addition and subtraction
    // Reduce the leading value
    atom = atoms.remove(startAtomIndex);
    remaining--;
    if (atom.op != null)
      throw new IllegalStateException("Value expected but got operation " + atom.op);
    long total = atom.val;
    while (remaining > 0) {
      // Reduce the operator Atom
      --remaining;
      atom = atoms.remove(startAtomIndex);
      op = atom.op;
      // System.err.println("Trying +/- for " + op);
      if (op == null)
        throw new IllegalStateException("Operator expected but got value " + atom.val);
      if (remaining <= 0) throw new IllegalStateException("No operand for operator " + op);
      // Reduce the right operand
      --remaining;
      atom = atoms.remove(startAtomIndex);
      if (atom.op != null)
        throw new IllegalStateException("Value expected but got operation " + atom.op);
      switch (op) {
        case ADD:
          total += atom.val;
          break;
        case SUBTRACT:
          total -= atom.val;
          break;
        default:
          throw new IllegalStateException("Unknown operator: " + op);
      }
    }

    return total;
  }