private Ptg parseNumber() {
    String number2 = null;
    String exponent = null;
    String number1 = GetNum();

    if (look == '.') {
      GetChar();
      number2 = GetNum();
    }

    if (look == 'E') {
      GetChar();

      String sign = "";
      if (look == '+') {
        GetChar();
      } else if (look == '-') {
        GetChar();
        sign = "-";
      }

      String number = GetNum();
      if (number == null) {
        throw expected("Integer");
      }
      exponent = sign + number;
    }

    if (number1 == null && number2 == null) {
      throw expected("Integer");
    }

    return getNumberPtgFromString(number1, number2, exponent);
  }
  private ParseNode parseRangeExpression() {
    ParseNode result = parseRangeable();
    boolean hasRange = false;
    while (look == ':') {
      int pos = _pointer;
      GetChar();
      ParseNode nextPart = parseRangeable();
      // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be
      // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5"
      // Furthermore, Excel doesn't seem to simplify
      // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2"

      checkValidRangeOperand("LHS", pos, result);
      checkValidRangeOperand("RHS", pos, nextPart);

      ParseNode[] children = {
        result, nextPart,
      };
      result = new ParseNode(RangePtg.instance, children);
      hasRange = true;
    }
    if (hasRange) {
      return augmentWithMemPtg(result);
    }
    return result;
  }
  private String parseStringLiteral() {
    Match('"');

    StringBuffer token = new StringBuffer();
    while (true) {
      if (look == '"') {
        GetChar();
        if (look != '"') {
          break;
        }
      }
      token.append(look);
      GetChar();
    }
    return token.toString();
  }
  /** Get a Number */
  private String GetNum() {
    StringBuffer value = new StringBuffer();

    while (IsDigit(this.look)) {
      value.append(this.look);
      GetChar();
    }
    return value.length() == 0 ? null : value.toString();
  }
  /**
   * Note - caller should reset {@link #_pointer} upon <code>null</code> result
   *
   * @return The sheet name as an identifier <code>null</code> if '!' is not found in the right
   *     place
   */
  private SheetIdentifier parseSheetName() {

    String bookName;
    if (look == '[') {
      StringBuilder sb = new StringBuilder();
      GetChar();
      while (look != ']') {
        sb.append(look);
        GetChar();
      }
      GetChar();
      bookName = sb.toString();
    } else {
      bookName = null;
    }

    if (look == '\'') {
      StringBuffer sb = new StringBuffer();

      Match('\'');
      boolean done = look == '\'';
      while (!done) {
        sb.append(look);
        GetChar();
        if (look == '\'') {
          Match('\'');
          done = look != '\'';
        }
      }

      Identifier iden = new Identifier(sb.toString(), true);
      // quoted identifier - can't concatenate anything more
      SkipWhite();
      if (look == '!') {
        GetChar();
        return new SheetIdentifier(bookName, iden, _book);
      }
      return null;
    }

    // unquoted sheet names must start with underscore or a letter
    if (look == '_' || Character.isLetter(look)) {
      StringBuilder sb = new StringBuilder();
      // can concatenate idens with dots
      while (isUnquotedSheetNameChar(look)) {
        sb.append(look);
        GetChar();
      }
      SkipWhite();
      if (look == '!') {
        GetChar();
        return new SheetIdentifier(bookName, new Identifier(sb.toString(), false), _book);
      }
      return null;
    }
    return null;
  }
  /**
   * Parses simple factors that are not primitive ranges or range components i.e. '!', ':'(and equiv
   * '...') do not appear Examples
   *
   * <pre>
   *   my.named...range.
   *   foo.bar(123.456, "abc")
   *   123.456
   *   "abc"
   *   true
   * </pre>
   */
  private ParseNode parseNonRange(int savePointer) {
    resetPointer(savePointer);

    if (Character.isDigit(look)) {
      return new ParseNode(parseNumber());
    }
    if (look == '"') {
      return new ParseNode(new StringPtg(parseStringLiteral()));
    }
    // from now on we can only be dealing with non-quoted identifiers
    // which will either be named ranges or functions
    StringBuilder sb = new StringBuilder();

    // defined names may begin with a letter or underscore
    if (!Character.isLetter(look) && look != '_') {
      throw expected("number, string, or defined name");
    }
    while (isValidDefinedNameChar(look)) {
      sb.append(look);
      GetChar();
    }
    SkipWhite();
    String name = sb.toString();
    if (look == '(') {
      return function(name);
    }
    if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
      return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
    }
    if (_book == null) {
      // Only test cases omit the book (expecting it not to be needed)
      throw new IllegalStateException("Need book to evaluate name '" + name + "'");
    }
    // 20101115, [email protected]: shall provide a temporary defined named record
    // EvaluationName evalName = _book.getName(name, _sheetIndex);
    EvaluationName evalName = _book.getOrCreateName(name, _sheetIndex);
    if (evalName == null) {
      // 20101112, [email protected]: shall return #NAME? error
      // throw new FormulaParseException("Specified named range '"
      //		+ name + "' does not exist in the current workbook.");
      return new ParseNode(ErrPtg.NAME_INVALID);
    }
    // 20101115, [email protected]: unnecessary check
    // if (evalName.isRange()) {
    //	return new ParseNode(evalName.createPtg());
    // }
    // TODO - what about NameX ?
    // throw new FormulaParseException("Specified name '"
    //		+ name + "' is not a range as expected.");
    return new ParseNode(evalName.createPtg());
  }
  /** API call to execute the parsing of the formula */
  private void parse() {
    _pointer = 0;
    GetChar();
    _rootNode = unionExpression();

    if (_pointer <= _formulaLength) {
      String msg =
          "Unused input ["
              + _formulaString.substring(_pointer - 1)
              + "] after attempting to parse the formula ["
              + _formulaString
              + "]";
      throw new FormulaParseException(msg);
    }
  }
  private String parseUnquotedIdentifier() {
    if (look == '\'') {
      throw expected("unquoted identifier");
    }
    StringBuilder sb = new StringBuilder();
    while (Character.isLetterOrDigit(look) || look == '.') {
      sb.append(look);
      GetChar();
    }
    if (sb.length() < 1) {
      return null;
    }

    return sb.toString();
  }
 private ParseNode unionExpression() {
   ParseNode result = comparisonExpression();
   boolean hasUnions = false;
   while (true) {
     SkipWhite();
     switch (look) {
       case ',':
         GetChar();
         hasUnions = true;
         ParseNode other = comparisonExpression();
         result = new ParseNode(UnionPtg.instance, result, other);
         continue;
     }
     if (hasUnions) {
       return augmentWithMemPtg(result);
     }
     return result;
   }
 }
  /**
   * Parses area refs (things which could be the operand of ':') and simple factors Examples
   *
   * <pre>
   *   A$1
   *   $A$1 :  $B1
   *   A1 .......	C2
   *   Sheet1 !$A1
   *   a..b!A1
   *   'my sheet'!A1
   *   .my.sheet!A1
   *   my.named..range.
   *   foo.bar(123.456, "abc")
   *   123.456
   *   "abc"
   *   true
   * </pre>
   */
  private ParseNode parseRangeable() {
    SkipWhite();
    int savePointer = _pointer;
    SheetIdentifier sheetIden = parseSheetName();
    if (sheetIden == null) {
      resetPointer(savePointer);
    } else {
      SkipWhite();
      savePointer = _pointer;
    }

    SimpleRangePart part1 = parseSimpleRangePart();
    if (part1 == null) {
      if (sheetIden != null) {
        if (look == '#') { // error ref like MySheet!#REF!
          return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
        } else {
          throw new FormulaParseException(
              "Cell reference expected after sheet name at index " + _pointer + ".");
        }
      }
      return parseNonRange(savePointer);
    }
    boolean whiteAfterPart1 = IsWhite(look);
    if (whiteAfterPart1) {
      SkipWhite();
    }

    if (look == ':') {
      int colonPos = _pointer;
      GetChar();
      SkipWhite();
      SimpleRangePart part2 = parseSimpleRangePart();
      if (part2 != null && !part1.isCompatibleForArea(part2)) {
        // second part is not compatible with an area ref e.g. S!A1:S!B2
        // where S might be a sheet name (that looks like a column name)

        part2 = null;
      }
      if (part2 == null) {
        // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2)
        // reset and let caller use explicit range operator
        resetPointer(colonPos);
        if (!part1.isCell()) {
          String prefix;
          if (sheetIden == null) {
            prefix = "";
          } else {
            prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!';
          }
          throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference.");
        }
        return createAreaRefParseNode(sheetIden, part1, part2);
      }
      return createAreaRefParseNode(sheetIden, part1, part2);
    }

    if (look == '.') {
      GetChar();
      int dotCount = 1;
      while (look == '.') {
        dotCount++;
        GetChar();
      }
      boolean whiteBeforePart2 = IsWhite(look);

      SkipWhite();
      SimpleRangePart part2 = parseSimpleRangePart();
      String part1And2 = _formulaString.substring(savePointer - 1, _pointer - 1);
      if (part2 == null) {
        if (sheetIden != null) {
          throw new FormulaParseException(
              "Complete area reference expected after sheet name at index " + _pointer + ".");
        }
        return parseNonRange(savePointer);
      }

      if (whiteAfterPart1 || whiteBeforePart2) {
        if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
          // "A .. B" not valid syntax for "A:B"
          // and there's no other valid expression that fits this grammar
          throw new FormulaParseException(
              "Dotted range (full row or column) expression '"
                  + part1And2
                  + "' must not contain whitespace.");
        }
        return createAreaRefParseNode(sheetIden, part1, part2);
      }

      if (dotCount == 1 && part1.isRow() && part2.isRow()) {
        // actually, this is looking more like a number
        return parseNonRange(savePointer);
      }

      if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
        if (dotCount != 2) {
          // [email protected]: shall return #NAME?
          // throw new FormulaParseException("Dotted range (full row or column) expression '" +
          // part1And2
          //		+ "' must have exactly 2 dots.");
          return parseNonRange(savePointer);
        }
      }
      return createAreaRefParseNode(sheetIden, part1, part2);
    }
    if (part1.isCell() && isValidCellReference(part1.getRep())) {
      return createAreaRefParseNode(sheetIden, part1, null);
    }
    if (sheetIden != null) {
      throw new FormulaParseException(
          "Second part of cell reference expected after sheet name at index " + _pointer + ".");
    }

    return parseNonRange(savePointer);
  }
 /**
  * Consumes the next input character if it is equal to the one specified otherwise throws an
  * unchecked exception. This method does <b>not</b> consume whitespace (before or after the
  * matched character).
  */
 private void Match(char x) {
   if (look != x) {
     throw expected("'" + x + "'");
   }
   GetChar();
 }
 /** Skip Over Leading White Space */
 private void SkipWhite() {
   while (IsWhite(look)) {
     GetChar();
   }
 }