public ParseTree parse(String ea) throws ParseError {

    String e = ea;
    if (verbose) {
      E.info("Parsing: " + e);
    }
    e = e.trim();

    if (e.lastIndexOf("(") == 0 && e.indexOf(")") == e.length() - 1) {
      e = e.substring(1, e.length() - 1);
      // E.info("Replaced with: " + e);
    }

    ArrayList<Node> nodes = tokenize(e);
    // now we've got a list of tokens, and each is linked to is neighbor on either side

    if (verbose) {
      E.info("tokens: " + nodes);
    }

    // a group node to hold the whole lot, the same as is used for the content of bracketed chunks
    GroupNode groot = new GroupNode(null);
    groot.addAll(nodes);

    // some nodes will get replaced, but the operators remain throughout and will be needed later
    // to claim their operands. Use a list here so we can sort by precedence.
    ArrayList<AbstractOperatorNode> ops = new ArrayList<AbstractOperatorNode>();
    ArrayList<GroupNode> gnodes = new ArrayList<GroupNode>();
    ArrayList<FunctionNode> fnodes = new ArrayList<FunctionNode>();

    for (Node n : nodes) {
      if (n instanceof AbstractOperatorNode) {
        ops.add((AbstractOperatorNode) n);
      }
      if (n instanceof GroupNode) {
        gnodes.add((GroupNode) n);
      }
      if (n instanceof FunctionNode) {
        fnodes.add((FunctionNode) n);
      }
    }

    // Right parentheses have been mapped to group nodes. Left parentheses
    // are still present. Make each group node claim the content back to the corresponding
    // opening parenthesis. By starting at the left and processing groups as
    // we come to them we don't need recursion

    for (GroupNode gn : gnodes) {
      gn.gatherPreceeding();
    }

    if (verbose) {
      E.info("Root group: " + groot.toString());
    }

    AbstractOperatorNode[] aops = ops.toArray(new AbstractOperatorNode[ops.size()]);
    Arrays.sort(aops, new PrecedenceComparator());

    for (FunctionNode fn : fnodes) {
      fn.claim();
    }

    for (AbstractOperatorNode op : aops) {
      op.claim();
    }

    for (GroupNode gn : gnodes) {
      gn.supplantByChild();
    }

    ParseTreeNode root = null;
    if (groot.getChildren().size() == 1) {
      Node fc = groot.getChildren().get(0);
      if (fc instanceof ParseTreeNode) {
        root = (ParseTreeNode) fc;
      } else {
        throw new ParseError("root node is not evaluable " + fc);
      }
    } else {
      StringBuilder sb = new StringBuilder();
      sb.append(" too many children left in container: " + groot.getChildren().size());
      sb.append("\n");
      for (Node n : groot.getChildren()) {
        sb.append("Node: " + n + "\n");
      }

      throw new ParseError(sb.toString());
    }

    ParseTree ret = new ParseTree(root);
    return ret;
  }
 static void addOperator(AbstractOperatorNode op) {
   opHM.put(op.getSymbol(), op);
 }