private static boolean isSingleArgSum(Ptg token) { if (token instanceof AttrPtg) { AttrPtg attrPtg = (AttrPtg) token; return attrPtg.isSum(); } return false; }
/** * 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(new FuncVarPtg(name, (byte) (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 = new FuncVarPtg(name, (byte) numArgs); } else { retval = new FuncPtg(funcIx); } return new ParseNode(retval, args); }
// visibility raised for testing /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) { Stack<ValueEval> stack = new Stack<ValueEval>(); for (int i = 0, iSize = ptgs.length; i < iSize; i++) { // since we don't know how to handle these yet :( Ptg ptg = ptgs[i]; if (ptg instanceof AttrPtg) { AttrPtg attrPtg = (AttrPtg) ptg; if (attrPtg.isSum()) { // Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator // expects the equivalent function token byte nArgs = 1; // tAttrSum always has 1 parameter ptg = new FuncVarPtg("SUM", nArgs); } } if (ptg instanceof ControlPtg) { // skip Parentheses, Attr, etc continue; } if (ptg instanceof MemFuncPtg) { // can ignore, rest of tokens for this expression are in OK RPN order continue; } if (ptg instanceof MemErrPtg) { continue; } ValueEval opResult; if (ptg instanceof OperationPtg) { OperationPtg optg = (OperationPtg) ptg; if (optg instanceof UnionPtg) { continue; } OperationEval operation = OperationEvaluatorFactory.create(optg); int numops = operation.getNumberOfOperands(); ValueEval[] ops = new ValueEval[numops]; // storing the ops in reverse order since they are popping for (int j = numops - 1; j >= 0; j--) { ValueEval p = stack.pop(); ops[j] = p; } // logDebug("invoke " + operation + " (nAgs=" + numops + ")"); opResult = operation.evaluate(ops, ec); if (opResult == MissingArgEval.instance) { opResult = BlankEval.INSTANCE; } } else { opResult = getEvalForPtg(ptg, ec); } if (opResult == null) { throw new RuntimeException("Evaluation result must not be null"); } // logDebug("push " + opResult); stack.push(opResult); } ValueEval value = stack.pop(); if (!stack.isEmpty()) { throw new IllegalStateException("evaluation stack not empty"); } value = dereferenceValue(value, ec.getRowIndex(), ec.getColumnIndex()); if (value == BlankEval.INSTANCE) { // Note Excel behaviour here. A blank final final value is converted to zero. return NumberEval.ZERO; // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to // blank, the actual value is empty string. This can be verified with ISBLANK(). } return value; }