in poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java [373:577]
/* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) {
String dbgIndentStr = ""; // always init. to non-null just for defensive avoiding NPE
if (dbgEvaluationOutputForNextEval) {
// first evaluation call when output is desired, so iit. this evaluator instance
dbgEvaluationOutputIndent = 1;
dbgEvaluationOutputForNextEval = true;
}
if (dbgEvaluationOutputIndent > 0) {
// init. indent string to needed spaces (create as substring from very long space-only string;
// limit indentation for deep recursions)
dbgIndentStr = " ";
dbgIndentStr = dbgIndentStr.substring(0, Math.min(dbgIndentStr.length(), dbgEvaluationOutputIndent * 2));
String finalDbgIndentStr = dbgIndentStr;
EVAL_LOG.atWarn().log(() -> {
String message = finalDbgIndentStr
+ "- evaluateFormula('" + ec.getRefEvaluatorForCurrentSheet().getSheetNameRange()
+ "'/" + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString()
+ "): " + Arrays.toString(ptgs).replace("\\Qorg.apache.poi.ss.formula.ptg.\\E", "");
return new SimpleMessage(message);
});
dbgEvaluationOutputIndent++;
}
EvaluationSheet evalSheet = ec.getWorkbook().getSheet(ec.getSheetIndex());
EvaluationCell evalCell = evalSheet.getCell(ec.getRowIndex(), ec.getColumnIndex());
Stack<ValueEval> stack = new Stack<>();
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 (dbgEvaluationOutputIndent > 0) {
EVAL_LOG.atInfo().log("{} * ptg {}: {}, stack: {}", dbgIndentStr, box(i), ptg, stack);
}
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
ptg = FuncVarPtg.SUM;
}
if (attrPtg.isOptimizedChoose()) {
ValueEval arg0 = stack.pop();
int[] jumpTable = attrPtg.getJumpTable();
int dist;
int nChoices = jumpTable.length;
try {
int switchIndex = Choose.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
if (switchIndex < 1 || switchIndex > nChoices) {
stack.push(ErrorEval.VALUE_INVALID);
dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE)
} else {
dist = jumpTable[switchIndex - 1];
}
} catch (EvaluationException e) {
stack.push(e.getErrorEval());
dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE)
}
// Encoded dist for tAttrChoose includes size of jump table, but
// countTokensToBeSkipped() does not (it counts whole tokens).
dist -= nChoices * 2 + 2; // subtract jump table size
i += countTokensToBeSkipped(ptgs, i, dist);
continue;
}
if (attrPtg.isOptimizedIf()) {
if (!evalCell.isPartOfArrayFormulaGroup()) {
ValueEval arg0 = stack.pop();
boolean evaluatedPredicate;
try {
evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
} catch (EvaluationException e) {
stack.push(e.getErrorEval());
int dist = attrPtg.getData();
i += countTokensToBeSkipped(ptgs, i, dist);
attrPtg = (AttrPtg) ptgs[i];
dist = attrPtg.getData() + 1;
i += countTokensToBeSkipped(ptgs, i, dist);
continue;
}
if (evaluatedPredicate) {
// nothing to skip - true param follows
} else {
int dist = attrPtg.getData();
i += countTokensToBeSkipped(ptgs, i, dist);
Ptg nextPtg = ptgs[i + 1];
if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg &&
// in order to verify that there is no third param, we need to check
// if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()!
((FuncVarPtg) nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) {
// this is an if statement without a false param (as opposed to MissingArgPtg as the false param)
//i++;
stack.push(arg0);
stack.push(BoolEval.FALSE);
}
}
}
continue;
}
if (attrPtg.isSkip() && !evalCell.isPartOfArrayFormulaGroup()) {
int dist = attrPtg.getData() + 1;
i += countTokensToBeSkipped(ptgs, i, dist);
if (stack.peek() == MissingArgEval.instance) {
stack.pop();
stack.push(BlankEval.instance);
}
continue;
}
}
if (ptg instanceof ControlPtg) {
// skip Parentheses, Attr, etc
continue;
}
if (ptg instanceof MemFuncPtg || ptg instanceof MemAreaPtg) {
// can ignore, rest of tokens for this expression are in OK RPN order
continue;
}
if (ptg instanceof MemErrPtg) {
continue;
}
if (ptg instanceof UnionPtg) {
ValueEval v2 = stack.pop();
ValueEval v1 = stack.pop();
stack.push(new RefListEval(v1, v2));
continue;
}
ValueEval opResult;
if (ptg instanceof OperationPtg) {
OperationPtg optg = (OperationPtg) ptg;
int numops = optg.getNumberOfOperands();
ValueEval[] ops = new ValueEval[numops];
// storing the ops in reverse order since they are popping
boolean areaArg = false; // whether one of the operands is an area
for (int j = numops - 1; j >= 0; j--) {
ValueEval p = stack.pop();
ops[j] = p;
if (p instanceof AreaEval) {
areaArg = true;
}
}
boolean arrayMode = false;
if (areaArg) for (int ii = i; ii < iSize; ii++) {
if (ptgs[ii] instanceof FuncVarPtg) {
FuncVarPtg f = (FuncVarPtg) ptgs[ii];
try {
Function func = FunctionEval.getBasicFunction(f.getFunctionIndex());
if (func instanceof ArrayMode) {
arrayMode = true;
}
} catch (NotImplementedException ne) {
//FunctionEval.getBasicFunction can throw NotImplementedException
// if the function is not yet supported.
}
break;
}
}
ec.setArrayMode(arrayMode);
// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
opResult = OperationEvaluatorFactory.evaluate(optg, ops, ec);
ec.setArrayMode(false);
} else {
opResult = getEvalForPtg(ptg, ec);
}
if (opResult == null) {
throw new IllegalStateException("Evaluation result must not be null");
}
// logDebug("push " + opResult);
stack.push(opResult);
if (dbgEvaluationOutputIndent > 0) {
EVAL_LOG.atInfo().log("{} = {}", dbgIndentStr, opResult);
}
}
ValueEval value = stack.pop();
if (!stack.isEmpty()) {
throw new IllegalStateException("evaluation stack not empty");
}
ValueEval result;
if (ec.isSingleValue()) {
result = dereferenceResult(value, ec);
} else {
result = value;
}
if (dbgEvaluationOutputIndent > 0) {
EVAL_LOG.atInfo().log("{}finished eval of {}: {}", dbgIndentStr, new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString(), result);
dbgEvaluationOutputIndent--;
if (dbgEvaluationOutputIndent == 1) {
// this evaluation is done, reset indent to stop logging
dbgEvaluationOutputIndent = -1;
}
} // if
return result;
}