/**
   * @return <code>false</code> if sub-expression represented the specified ParseNode definitely
   *     cannot appear on either side of the range (':') operator
   */
  private static boolean isValidRangeOperand(ParseNode a) {
    Ptg tkn = a.getToken();
    // Note - order is important for these instance-of checks
    if (tkn instanceof OperandPtg) {
      // notably cell refs and area refs
      return true;
    }

    // next 2 are special cases of OperationPtg
    if (tkn instanceof AbstractFunctionPtg) {
      AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
      byte returnClass = afp.getDefaultOperandClass();
      return Ptg.CLASS_REF == returnClass;
    }
    if (tkn instanceof ValueOperatorPtg) {
      return false;
    }
    if (tkn instanceof OperationPtg) {
      return true;
    }

    // one special case of ControlPtg
    if (tkn instanceof ParenthesisPtg) {
      // parenthesis Ptg should have only one child
      return isValidRangeOperand(a.getChildren()[0]);
    }

    // one special case of ScalarConstantPtg
    if (tkn == ErrPtg.REF_INVALID) {
      return true;
    }

    // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
    return false;
  }
  /**
   * Note - Excel function names are 'case aware but not case sensitive'. This method may end up
   * creating a defined name record in the workbook if the specified name is not an internal Excel
   * function, and has not been encountered before.
   *
   * @param name case preserved function name (as it was entered/appeared in the formula).
   */
  private ParseNode function(String name) {
    Ptg nameToken = null;
    if (!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
      // user defined function
      // in the token tree, the name is more or less the first argument

      if (_book == null) {
        // Only test cases omit the book (expecting it not to be needed)
        throw new IllegalStateException("Need book to evaluate name '" + name + "'");
      }
      // 20101112, [email protected]: shall provide a temporary defined named record
      // EvaluationName hName = _book.getName(name, _sheetIndex);
      EvaluationName hName = _book.getOrCreateName(name, _sheetIndex);
      if (hName == null) {

        nameToken = _book.getNameXPtg(name);
        if (nameToken == null) {
          throw new FormulaParseException(
              "Name '" + name + "' is completely unknown in the current workbook");
        }
      } else {
        // 20101112, [email protected]: unnecessary check
        /*				if (!hName.isFunctionName()) {
        					throw new FormulaParseException("Attempt to use name '" + name
        							+ "' as a function, but defined name in workbook does not refer to a function");
        				}
        */
        // calls to user-defined functions within the workbook
        // get a Name token which points to a defined name record
        nameToken = hName.createPtg();
      }
    }

    Match('(');
    ParseNode[] args = Arguments();
    Match(')');

    return getFunction(name, nameToken, args);
  }