private void validateNumArgs(int numArgs, FunctionMetadata fm) {
    if (numArgs < fm.getMinParams()) {
      String msg = "Too few arguments to function '" + fm.getName() + "'. ";
      if (fm.hasFixedArgsLength()) {
        msg += "Expected " + fm.getMinParams();
      } else {
        msg += "At least " + fm.getMinParams() + " were expected";
      }
      msg += " but got " + numArgs + ".";
      throw new FormulaParseException(msg);
    }
    // the maximum number of arguments depends on the Excel version
    int maxArgs;
    if (fm.hasUnlimitedVarags()) {
      if (_book != null) {
        maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs();
      } else {
        // _book can be omitted by test cases
        maxArgs = fm.getMaxParams(); // just use BIFF8
      }
    } else {
      maxArgs = fm.getMaxParams();
    }

    if (numArgs > maxArgs) {
      String msg = "Too many arguments to function '" + fm.getName() + "'. ";
      if (fm.hasFixedArgsLength()) {
        msg += "Expected " + maxArgs;
      } else {
        msg += "At most " + maxArgs + " were expected";
      }
      msg += " but got " + numArgs + ".";
      throw new FormulaParseException(msg);
    }
  }
  /**
   * Generates the variable function ptg for the formula.
   *
   * <p>For IF Formulas, additional PTGs are added to the tokens
   *
   * @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
   * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is
   *     handled in this function
   */
  private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {

    FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase());
    int numArgs = args.length;
    if (fm == null) {
      if (namePtg == null) {
        throw new IllegalStateException("NamePtg must be supplied for external functions");
      }
      // must be external function
      ParseNode[] allArgs = new ParseNode[numArgs + 1];
      allArgs[0] = new ParseNode(namePtg);
      System.arraycopy(args, 0, allArgs, 1, numArgs);
      return new ParseNode(FuncVarPtg.create(name, numArgs + 1), allArgs);
    }

    if (namePtg != null) {
      throw new IllegalStateException("NamePtg no applicable to internal functions");
    }
    boolean isVarArgs = !fm.hasFixedArgsLength();
    int funcIx = fm.getIndex();
    if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
      // Excel encodes the sum of a single argument as tAttrSum
      // POI does the same for consistency, but this is not critical
      return new ParseNode(AttrPtg.getSumSingle(), args);
      // The code below would encode tFuncVar(SUM) which seems to do no harm
    }
    validateNumArgs(args.length, fm);

    AbstractFunctionPtg retval;
    if (isVarArgs) {
      retval = FuncVarPtg.create(name, numArgs);
    } else {
      retval = FuncPtg.create(funcIx);
    }
    return new ParseNode(retval, args);
  }