/**
   * @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;
  }
  /**
   * @param callerForceArrayFlag <code>true</code> if one of the current node's parents is a
   *     function Ptg which has been changed from default 'V' to 'A' type (due to requirements on
   *     the function return value).
   */
  private void transformNode(
      ParseNode node, byte desiredOperandClass, boolean callerForceArrayFlag) {
    Ptg token = node.getToken();
    ParseNode[] children = node.getChildren();
    boolean isSimpleValueFunc = isSimpleValueFunction(token);

    if (isSimpleValueFunc) {
      boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY;
      for (int i = 0; i < children.length; i++) {
        transformNode(children[i], desiredOperandClass, localForceArray);
      }
      setSimpleValueFuncClass(
          (AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag);
      return;
    }

    if (isSingleArgSum(token)) {
      // Need to process the argument of SUM with transformFunctionNode below
      // so make a dummy FuncVarPtg for that call.
      token = FuncVarPtg.SUM;
      // Note - the tAttrSum token (node.getToken()) is a base
      // token so does not need to have its operand class set
    }
    if (token instanceof ValueOperatorPtg
        || token instanceof ControlPtg
        || token instanceof MemFuncPtg
        || token instanceof MemAreaPtg
        || token instanceof UnionPtg) {
      // Value Operator Ptgs and Control are base tokens, so token will be unchanged
      // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag

      // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
      // All direct operands of value operators that are initially 'R' type will
      // be converted to 'V' type.
      byte localDesiredOperandClass =
          desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
      for (int i = 0; i < children.length; i++) {
        transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
      }
      return;
    }
    if (token instanceof AbstractFunctionPtg) {
      transformFunctionNode(
          (AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
      return;
    }
    if (children.length > 0) {
      if (token == RangePtg.instance) {
        // TODO is any token transformation required under the various ref operators?
        return;
      }
      throw new IllegalStateException("Node should not have any children");
    }

    if (token.isBaseToken()) {
      // nothing to do
      return;
    }
    token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
  }
  /**
   * From OOO doc: "Whenever one operand of the reference subexpression is a function, a defined
   * name, a 3D reference, or an external reference (and no error occurs), a tMemFunc token is used"
   */
  private static boolean needsMemFunc(ParseNode root) {
    Ptg token = root.getToken();
    if (token instanceof AbstractFunctionPtg) {
      return true;
    }
    if (token instanceof ExternSheetReferenceToken) { // 3D refs
      return true;
    }
    if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs
      return true;
    }

    if (token instanceof OperationPtg || token instanceof ParenthesisPtg) {
      // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc
      for (ParseNode child : root.getChildren()) {
        if (needsMemFunc(child)) {
          return true;
        }
      }
      return false;
    }
    if (token instanceof OperandPtg) {
      return false;
    }
    if (token instanceof OperationPtg) {
      return true;
    }

    return false;
  }
  public ParseNode not(ParseNode child) {
    if (child instanceof ExistsParseNode) {
      return exists(child.getChildren().get(0), !((ExistsParseNode) child).isNegate());
    }

    return new NotParseNode(child);
  }