ValueEval evaluateFormula()

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;

    }