Vendor/phpspreadsheet/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php
private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
    {
        if ($tokens == false) {
            return false;
        }

        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
        //        so we store the parent cell collection so that we can re-attach it when necessary
        $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
        $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
        $stack = new Stack();

        //    Loop through each token in turn
        foreach ($tokens as $tokenData) {
            $token = $tokenData['value'];
            // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
            if (isset(self::$binaryOperators[$token])) {
                //    We must have two operands, error if we don't
                if (($operand2Data = $stack->pop()) === null) {
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
                }
                if (($operand1Data = $stack->pop()) === null) {
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
                }

                $operand1 = self::dataTestReference($operand1Data);
                $operand2 = self::dataTestReference($operand2Data);

                //    Log what we're doing
                if ($token == ':') {
                    $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
                } else {
                    $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
                }

                //    Process the operation in the appropriate manner
                switch ($token) {
                    //    Comparison (Boolean) Operators
                    case '>':            //    Greater than
                    case '<':            //    Less than
                    case '>=':            //    Greater than or Equal to
                    case '<=':            //    Less than or Equal to
                    case '=':            //    Equality
                    case '<>':            //    Inequality
                        $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);

                        break;
                    //    Binary Operators
                    case ':':            //    Range
                        $sheet1 = $sheet2 = '';
                        if (strpos($operand1Data['reference'], '!') !== false) {
                            list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
                        } else {
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
                        }
                        if (strpos($operand2Data['reference'], '!') !== false) {
                            list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
                        } else {
                            $sheet2 = $sheet1;
                        }
                        if ($sheet1 == $sheet2) {
                            if ($operand1Data['reference'] === null) {
                                if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
                                    $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
                                } elseif (trim($operand1Data['reference']) == '') {
                                    $operand1Data['reference'] = $pCell->getCoordinate();
                                } else {
                                    $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
                                }
                            }
                            if ($operand2Data['reference'] === null) {
                                if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
                                    $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
                                } elseif (trim($operand2Data['reference']) == '') {
                                    $operand2Data['reference'] = $pCell->getCoordinate();
                                } else {
                                    $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
                                }
                            }

                            $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
                            $oCol = $oRow = [];
                            foreach ($oData as $oDatum) {
                                $oCR = Coordinate::coordinateFromString($oDatum);
                                $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
                                $oRow[] = $oCR[1];
                            }
                            $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
                            if ($pCellParent !== null) {
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $stack->push('Cell Reference', $cellValue, $cellRef);
                        } else {
                            $stack->push('Error', Functions::REF(), null);
                        }

                        break;
                    case '+':            //    Addition
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);

                        break;
                    case '-':            //    Subtraction
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);

                        break;
                    case '*':            //    Multiplication
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);

                        break;
                    case '/':            //    Division
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);

                        break;
                    case '^':            //    Exponential
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);

                        break;
                    case '&':            //    Concatenation
                        //    If either of the operands is a matrix, we need to treat them both as matrices
                        //        (converting the other operand to a matrix if need be); then perform the required
                        //        matrix operation
                        if (is_bool($operand1)) {
                            $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
                        }
                        if (is_bool($operand2)) {
                            $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
                        }
                        if ((is_array($operand1)) || (is_array($operand2))) {
                            //    Ensure that both operands are arrays/matrices
                            self::checkMatrixOperands($operand1, $operand2, 2);

                            try {
                                //    Convert operand 1 from a PHP array to a matrix
                                $matrix = new Shared\JAMA\Matrix($operand1);
                                //    Perform the required operation against the operand 1 matrix, passing in operand 2
                                $matrixResult = $matrix->concat($operand2);
                                $result = $matrixResult->getArray();
                            } catch (\Exception $ex) {
                                $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
                                $result = '#VALUE!';
                            }
                        } else {
                            $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"';
                        }
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
                        $stack->push('Value', $result);

                        break;
                    case '|':            //    Intersect
                        $rowIntersect = array_intersect_key($operand1, $operand2);
                        $cellIntersect = $oCol = $oRow = [];
                        foreach (array_keys($rowIntersect) as $row) {
                            $oRow[] = $row;
                            foreach ($rowIntersect[$row] as $col => $data) {
                                $oCol[] = Coordinate::columnIndexFromString($col) - 1;
                                $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
                            }
                        }
                        $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
                        $stack->push('Value', $cellIntersect, $cellRef);

                        break;
                }

                // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
            } elseif (($token === '~') || ($token === '%')) {
                if (($arg = $stack->pop()) === null) {
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
                }
                $arg = $arg['value'];
                if ($token === '~') {
                    $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
                    $multiplier = -1;
                } else {
                    $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
                    $multiplier = 0.01;
                }
                if (is_array($arg)) {
                    self::checkMatrixOperands($arg, $multiplier, 2);

                    try {
                        $matrix1 = new Shared\JAMA\Matrix($arg);
                        $matrixResult = $matrix1->arrayTimesEquals($multiplier);
                        $result = $matrixResult->getArray();
                    } catch (\Exception $ex) {
                        $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
                        $result = '#VALUE!';
                    }
                    $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
                    $stack->push('Value', $result);
                } else {
                    $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
                }
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
                $cellRef = null;
                if (isset($matches[8])) {
                    if ($pCell === null) {
                        //                        We can't access the range, so return a REF error
                        $cellValue = Functions::REF();
                    } else {
                        $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
                        if ($matches[2] > '') {
                            $matches[2] = trim($matches[2], "\"'");
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
                                //    It's a Reference to an external spreadsheet (not currently supported)
                                return $this->raiseFormulaError('Unable to access External Workbook');
                            }
                            $matches[2] = trim($matches[2], "\"'");
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
                            if ($pCellParent !== null) {
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
                        } else {
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
                            if ($pCellParent !== null) {
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
                        }
                    }
                } else {
                    if ($pCell === null) {
                        //                        We can't access the cell, so return a REF error
                        $cellValue = Functions::REF();
                    } else {
                        $cellRef = $matches[6] . $matches[7];
                        if ($matches[2] > '') {
                            $matches[2] = trim($matches[2], "\"'");
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
                                //    It's a Reference to an external spreadsheet (not currently supported)
                                return $this->raiseFormulaError('Unable to access External Workbook');
                            }
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
                            if ($pCellParent !== null) {
                                $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
                                if ($cellSheet && $cellSheet->cellExists($cellRef)) {
                                    $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
                                    $pCell->attach($pCellParent);
                                } else {
                                    $cellValue = null;
                                }
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
                        } else {
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
                            if ($pCellParent->has($cellRef)) {
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
                                $pCell->attach($pCellParent);
                            } else {
                                $cellValue = null;
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
                        }
                    }
                }
                $stack->push('Value', $cellValue, $cellRef);

            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
                $functionName = $matches[1];
                $argCount = $stack->pop();
                $argCount = $argCount['value'];
                if ($functionName != 'MKMATRIX') {
                    $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
                }
                if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {    // function
                    if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
                        $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
                        $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
                    } elseif (isset(self::$controlFunctions[$functionName])) {
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
                        $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
                        $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
                    }
                    // get the arguments for this function
                    $args = $argArrayVals = [];
                    for ($i = 0; $i < $argCount; ++$i) {
                        $arg = $stack->pop();
                        $a = $argCount - $i - 1;
                        if (($passByReference) &&
                            (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
                            (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) {
                            if ($arg['reference'] === null) {
                                $args[] = $cellID;
                                if ($functionName != 'MKMATRIX') {
                                    $argArrayVals[] = $this->showValue($cellID);
                                }
                            } else {
                                $args[] = $arg['reference'];
                                if ($functionName != 'MKMATRIX') {
                                    $argArrayVals[] = $this->showValue($arg['reference']);
                                }
                            }
                        } else {
                            $args[] = self::unwrapResult($arg['value']);
                            if ($functionName != 'MKMATRIX') {
                                $argArrayVals[] = $this->showValue($arg['value']);
                            }
                        }
                    }
                    //    Reverse the order of the arguments
                    krsort($args);

                    if (($passByReference) && ($argCount == 0)) {
                        $args[] = $cellID;
                        $argArrayVals[] = $this->showValue($cellID);
                    }

                    if ($functionName != 'MKMATRIX') {
                        if ($this->debugLog->getWriteDebugLog()) {
                            krsort($argArrayVals);
                            $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )');
                        }
                    }

                    //    Process the argument with the appropriate function call
                    if ($passCellReference) {
                        $args[] = $pCell;
                    }

                    if (!is_array($functionCall)) {
                        foreach ($args as &$arg) {
                            $arg = Functions::flattenSingleValue($arg);
                        }
                        unset($arg);
                    }
                    $result = call_user_func_array($functionCall, $args);

                    if ($functionName != 'MKMATRIX') {
                        $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
                    }
                    $stack->push('Value', self::wrapResult($result));
                }
            } else {
                // if the token is a number, boolean, string or an Excel error, push it onto the stack
                if (isset(self::$excelConstants[strtoupper($token)])) {
                    $excelConstant = strtoupper($token);
                    $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
                    $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
                } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) {
                    $stack->push('Value', $token);
                // if the token is a named range, push the named range name onto the stack
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
                    $namedRange = $matches[6];
                    $this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);

                    $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false);
                    $pCell->attach($pCellParent);
                    $this->debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue));
                    $stack->push('Named Range', $cellValue, $namedRange);
                } else {
                    return $this->raiseFormulaError("undefined variable '$token'");
                }
            }
        }
        // when we're out of tokens, the stack should have a single element, the final result
        if ($stack->count() != 1) {
            return $this->raiseFormulaError('internal error');
        }
        $output = $stack->pop();
        $output = $output['value'];

        return $output;
    }
private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
    {
        if ($tokens == false) {
            return false;
        }

        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
        //        so we store the parent cell collection so that we can re-attach it when necessary
        $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
        $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
        $stack = new Stack();

        //    Loop through each token in turn
        foreach ($tokens as $tokenData) {
            $token = $tokenData['value'];
            // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
            if (isset(self::$binaryOperators[$token])) {
                //    We must have two operands, error if we don't
                if (($operand2Data = $stack->pop()) === null) {
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
                }
                if (($operand1Data = $stack->pop()) === null) {
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
                }

                $operand1 = self::dataTestReference($operand1Data);
                $operand2 = self::dataTestReference($operand2Data);

                //    Log what we're doing
                if ($token == ':') {
                    $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
                } else {
                    $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
                }

                //    Process the operation in the appropriate manner
                switch ($token) {
                    //    Comparison (Boolean) Operators
                    case '>':            //    Greater than
                    case '<':            //    Less than
                    case '>=':            //    Greater than or Equal to
                    case '<=':            //    Less than or Equal to
                    case '=':            //    Equality
                    case '<>':            //    Inequality
                        $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);

                        break;
                    //    Binary Operators
                    case ':':            //    Range
                        $sheet1 = $sheet2 = '';
                        if (strpos($operand1Data['reference'], '!') !== false) {
                            list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
                        } else {
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
                        }
                        if (strpos($operand2Data['reference'], '!') !== false) {
                            list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
                        } else {
                            $sheet2 = $sheet1;
                        }
                        if ($sheet1 == $sheet2) {
                            if ($operand1Data['reference'] === null) {
                                if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
                                    $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
                                } elseif (trim($operand1Data['reference']) == '') {
                                    $operand1Data['reference'] = $pCell->getCoordinate();
                                } else {
                                    $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
                                }
                            }
                            if ($operand2Data['reference'] === null) {
                                if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
                                    $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
                                } elseif (trim($operand2Data['reference']) == '') {
                                    $operand2Data['reference'] = $pCell->getCoordinate();
                                } else {
                                    $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
                                }
                            }

                            $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
                            $oCol = $oRow = [];
                            foreach ($oData as $oDatum) {
                                $oCR = Coordinate::coordinateFromString($oDatum);
                                $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
                                $oRow[] = $oCR[1];
                            }
                            $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
                            if ($pCellParent !== null) {
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $stack->push('Cell Reference', $cellValue, $cellRef);
                        } else {
                            $stack->push('Error', Functions::REF(), null);
                        }

                        break;
                    case '+':            //    Addition
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);

                        break;
                    case '-':            //    Subtraction
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);

                        break;
                    case '*':            //    Multiplication
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);

                        break;
                    case '/':            //    Division
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);

                        break;
                    case '^':            //    Exponential
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);

                        break;
                    case '&':            //    Concatenation
                        //    If either of the operands is a matrix, we need to treat them both as matrices
                        //        (converting the other operand to a matrix if need be); then perform the required
                        //        matrix operation
                        if (is_bool($operand1)) {
                            $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
                        }
                        if (is_bool($operand2)) {
                            $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
                        }
                        if ((is_array($operand1)) || (is_array($operand2))) {
                            //    Ensure that both operands are arrays/matrices
                            self::checkMatrixOperands($operand1, $operand2, 2);

                            try {
                                //    Convert operand 1 from a PHP array to a matrix
                                $matrix = new Shared\JAMA\Matrix($operand1);
                                //    Perform the required operation against the operand 1 matrix, passing in operand 2
                                $matrixResult = $matrix->concat($operand2);
                                $result = $matrixResult->getArray();
                            } catch (\Exception $ex) {
                                $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
                                $result = '#VALUE!';
                            }
                        } else {
                            $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"';
                        }
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
                        $stack->push('Value', $result);

                        break;
                    case '|':            //    Intersect
                        $rowIntersect = array_intersect_key($operand1, $operand2);
                        $cellIntersect = $oCol = $oRow = [];
                        foreach (array_keys($rowIntersect) as $row) {
                            $oRow[] = $row;
                            foreach ($rowIntersect[$row] as $col => $data) {
                                $oCol[] = Coordinate::columnIndexFromString($col) - 1;
                                $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
                            }
                        }
                        $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
                        $stack->push('Value', $cellIntersect, $cellRef);

                        break;
                }

                // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
            } elseif (($token === '~') || ($token === '%')) {
                if (($arg = $stack->pop()) === null) {
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
                }
                $arg = $arg['value'];
                if ($token === '~') {
                    $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
                    $multiplier = -1;
                } else {
                    $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
                    $multiplier = 0.01;
                }
                if (is_array($arg)) {
                    self::checkMatrixOperands($arg, $multiplier, 2);

                    try {
                        $matrix1 = new Shared\JAMA\Matrix($arg);
                        $matrixResult = $matrix1->arrayTimesEquals($multiplier);
                        $result = $matrixResult->getArray();
                    } catch (\Exception $ex) {
                        $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
                        $result = '#VALUE!';
                    }
                    $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
                    $stack->push('Value', $result);
                } else {
                    $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
                }
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
                $cellRef = null;
                if (isset($matches[8])) {
                    if ($pCell === null) {
                        //                        We can't access the range, so return a REF error
                        $cellValue = Functions::REF();
                    } else {
                        $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
                        if ($matches[2] > '') {
                            $matches[2] = trim($matches[2], "\"'");
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
                                //    It's a Reference to an external spreadsheet (not currently supported)
                                return $this->raiseFormulaError('Unable to access External Workbook');
                            }
                            $matches[2] = trim($matches[2], "\"'");
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
                            if ($pCellParent !== null) {
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
                        } else {
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
                            if ($pCellParent !== null) {
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
                        }
                    }
                } else {
                    if ($pCell === null) {
                        //                        We can't access the cell, so return a REF error
                        $cellValue = Functions::REF();
                    } else {
                        $cellRef = $matches[6] . $matches[7];
                        if ($matches[2] > '') {
                            $matches[2] = trim($matches[2], "\"'");
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
                                //    It's a Reference to an external spreadsheet (not currently supported)
                                return $this->raiseFormulaError('Unable to access External Workbook');
                            }
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
                            if ($pCellParent !== null) {
                                $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
                                if ($cellSheet && $cellSheet->cellExists($cellRef)) {
                                    $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
                                    $pCell->attach($pCellParent);
                                } else {
                                    $cellValue = null;
                                }
                            } else {
                                return $this->raiseFormulaError('Unable to access Cell Reference');
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
                        } else {
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
                            if ($pCellParent->has($cellRef)) {
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
                                $pCell->attach($pCellParent);
                            } else {
                                $cellValue = null;
                            }
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
                        }
                    }
                }
                $stack->push('Value', $cellValue, $cellRef);

            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
                $functionName = $matches[1];
                $argCount = $stack->pop();
                $argCount = $argCount['value'];
                if ($functionName != 'MKMATRIX') {
                    $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
                }
                if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {    // function
                    if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
                        $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
                        $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
                    } elseif (isset(self::$controlFunctions[$functionName])) {
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
                        $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
                        $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
                    }
                    // get the arguments for this function
                    $args = $argArrayVals = [];
                    for ($i = 0; $i < $argCount; ++$i) {
                        $arg = $stack->pop();
                        $a = $argCount - $i - 1;
                        if (($passByReference) &&
                            (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
                            (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) {
                            if ($arg['reference'] === null) {
                                $args[] = $cellID;
                                if ($functionName != 'MKMATRIX') {
                                    $argArrayVals[] = $this->showValue($cellID);
                                }
                            } else {
                                $args[] = $arg['reference'];
                                if ($functionName != 'MKMATRIX') {
                                    $argArrayVals[] = $this->showValue($arg['reference']);
                                }
                            }
                        } else {
                            $args[] = self::unwrapResult($arg['value']);
                            if ($functionName != 'MKMATRIX') {
                                $argArrayVals[] = $this->showValue($arg['value']);
                            }
                        }
                    }
                    //    Reverse the order of the arguments
                    krsort($args);

                    if (($passByReference) && ($argCount == 0)) {
                        $args[] = $cellID;
                        $argArrayVals[] = $this->showValue($cellID);
                    }

                    if ($functionName != 'MKMATRIX') {
                        if ($this->debugLog->getWriteDebugLog()) {
                            krsort($argArrayVals);
                            $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )');
                        }
                    }

                    //    Process the argument with the appropriate function call
                    if ($passCellReference) {
                        $args[] = $pCell;
                    }

                    if (!is_array($functionCall)) {
                        foreach ($args as &$arg) {
                            $arg = Functions::flattenSingleValue($arg);
                        }
                        unset($arg);
                    }
                    $result = call_user_func_array($functionCall, $args);

                    if ($functionName != 'MKMATRIX') {
                        $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
                    }
                    $stack->push('Value', self::wrapResult($result));
                }
            } else {
                // if the token is a number, boolean, string or an Excel error, push it onto the stack
                if (isset(self::$excelConstants[strtoupper($token)])) {
                    $excelConstant = strtoupper($token);
                    $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
                    $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
                } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) {
                    $stack->push('Value', $token);
                // if the token is a named range, push the named range name onto the stack
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
                    $namedRange = $matches[6];
                    $this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);

                    $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false);
                    $pCell->attach($pCellParent);
                    $this->debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue));
                    $stack->push('Named Range', $cellValue, $namedRange);
                } else {
                    return $this->raiseFormulaError("undefined variable '$token'");
                }
            }
        }
        // when we're out of tokens, the stack should have a single element, the final result
        if ($stack->count() != 1) {
            return $this->raiseFormulaError('internal error');
        }
        $output = $stack->pop();
        $output = $output['value'];

        return $output;
    }
private function processTokenStack ( $ tokens , $ cellID = null , Cell $ pCell = null ) { if ( $ tokens == false ) { return false ; } //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection), //        so we store the parent cell collection so that we can re-attach it when necessary $ pCellWorksheet = ( $ pCell !== null ) ? $ pCell -> getWorksheet ( ) : null ; $ pCellParent = ( $ pCell !== null ) ? $ pCell -> getParent ( ) : null ; $ stack = new Stack ( ) ; //    Loop through each token in turn foreach ( $ tokens as $ tokenData ) { $ token = $ tokenData [ 'value' ] ; // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack if ( isset ( self :: $ binaryOperators [ $ token ] ) ) { //    We must have two operands, error if we don't if ( ( $ operand2Data = $ stack -> pop ( ) ) === null ) { return $ this -> raiseFormulaError ( 'Internal error - Operand value missing from stack' ) ; } if ( ( $ operand1Data = $ stack -> pop ( ) ) === null ) { return $ this -> raiseFormulaError ( 'Internal error - Operand value missing from stack' ) ; } $ operand1 = self :: dataTestReference ( $ operand1Data ) ; $ operand2 = self :: dataTestReference ( $ operand2Data ) ; //    Log what we're doing if ( $ token == ':' ) { $ this -> debugLog -> writeDebugLog ( 'Evaluating Range ' , $ this -> showValue ( $ operand1Data [ 'reference' ] ) , ' ' , $ token , ' ' , $ this -> showValue ( $ operand2Data [ 'reference' ] ) ) ; } else { $ this -> debugLog -> writeDebugLog ( 'Evaluating ' , $ this -> showValue ( $ operand1 ) , ' ' , $ token , ' ' , $ this -> showValue ( $ operand2 ) ) ; } //    Process the operation in the appropriate manner switch ( $ token ) { //    Comparison (Boolean) Operators case '>' : //    Greater than case '<' : //    Less than case '>=' : //    Greater than or Equal to case '<=' : //    Less than or Equal to case '=' : //    Equality case '<>' : //    Inequality $ this -> executeBinaryComparisonOperation ( $ cellID , $ operand1 , $ operand2 , $ token , $ stack ) ; break ; //    Binary Operators case ':' : //    Range $ sheet1 = $ sheet2 = '' ; if ( strpos ( $ operand1Data [ 'reference' ] , '!' ) !== false ) { list ( $ sheet1 , $ operand1Data [ 'reference' ] ) = explode ( '!' , $ operand1Data [ 'reference' ] ) ; } else { $ sheet1 = ( $ pCellParent !== null ) ? $ pCellWorksheet -> getTitle ( ) : '' ; } if ( strpos ( $ operand2Data [ 'reference' ] , '!' ) !== false ) { list ( $ sheet2 , $ operand2Data [ 'reference' ] ) = explode ( '!' , $ operand2Data [ 'reference' ] ) ; } else { $ sheet2 = $ sheet1 ; } if ( $ sheet1 == $ sheet2 ) { if ( $ operand1Data [ 'reference' ] === null ) { if ( ( trim ( $ operand1Data [ 'value' ] ) != '' ) && ( is_numeric ( $ operand1Data [ 'value' ] ) ) ) { $ operand1Data [ 'reference' ] = $ pCell -> getColumn ( ) . $ operand1Data [ 'value' ] ; } elseif ( trim ( $ operand1Data [ 'reference' ] ) == '' ) { $ operand1Data [ 'reference' ] = $ pCell -> getCoordinate ( ) ; } else { $ operand1Data [ 'reference' ] = $ operand1Data [ 'value' ] . $ pCell -> getRow ( ) ; } } if ( $ operand2Data [ 'reference' ] === null ) { if ( ( trim ( $ operand2Data [ 'value' ] ) != '' ) && ( is_numeric ( $ operand2Data [ 'value' ] ) ) ) { $ operand2Data [ 'reference' ] = $ pCell -> getColumn ( ) . $ operand2Data [ 'value' ] ; } elseif ( trim ( $ operand2Data [ 'reference' ] ) == '' ) { $ operand2Data [ 'reference' ] = $ pCell -> getCoordinate ( ) ; } else { $ operand2Data [ 'reference' ] = $ operand2Data [ 'value' ] . $ pCell -> getRow ( ) ; } } $ oData = array_merge ( explode ( ':' , $ operand1Data [ 'reference' ] ) , explode ( ':' , $ operand2Data [ 'reference' ] ) ) ; $ oCol = $ oRow = [ ] ; foreach ( $ oData as $ oDatum ) { $ oCR = Coordinate :: coordinateFromString ( $ oDatum ) ; $ oCol [ ] = Coordinate :: columnIndexFromString ( $ oCR [ 0 ] ) - 1 ; $ oRow [ ] = $ oCR [ 1 ] ; } $ cellRef = Coordinate :: stringFromColumnIndex ( min ( $ oCol ) + 1 ) . min ( $ oRow ) . ':' . Coordinate :: stringFromColumnIndex ( max ( $ oCol ) + 1 ) . max ( $ oRow ) ; if ( $ pCellParent !== null ) { $ cellValue = $ this -> extractCellRange ( $ cellRef , $ this -> spreadsheet -> getSheetByName ( $ sheet1 ) , false ) ; } else { return $ this -> raiseFormulaError ( 'Unable to access Cell Reference' ) ; } $ stack -> push ( 'Cell Reference' , $ cellValue , $ cellRef ) ; } else { $ stack -> push ( 'Error' , Functions :: REF ( ) , null ) ; } break ; case '+' : //    Addition $ this -> executeNumericBinaryOperation ( $ operand1 , $ operand2 , $ token , 'plusEquals' , $ stack ) ; break ; case '-' : //    Subtraction $ this -> executeNumericBinaryOperation ( $ operand1 , $ operand2 , $ token , 'minusEquals' , $ stack ) ; break ; case '*' : //    Multiplication $ this -> executeNumericBinaryOperation ( $ operand1 , $ operand2 , $ token , 'arrayTimesEquals' , $ stack ) ; break ; case '/' : //    Division $ this -> executeNumericBinaryOperation ( $ operand1 , $ operand2 , $ token , 'arrayRightDivide' , $ stack ) ; break ; case '^' : //    Exponential $ this -> executeNumericBinaryOperation ( $ operand1 , $ operand2 , $ token , 'power' , $ stack ) ; break ; case '&' : //    Concatenation //    If either of the operands is a matrix, we need to treat them both as matrices //        (converting the other operand to a matrix if need be); then perform the required //        matrix operation if ( is_bool ( $ operand1 ) ) { $ operand1 = ( $ operand1 ) ? self :: $ localeBoolean [ 'TRUE' ] : self :: $ localeBoolean [ 'FALSE' ] ; } if ( is_bool ( $ operand2 ) ) { $ operand2 = ( $ operand2 ) ? self :: $ localeBoolean [ 'TRUE' ] : self :: $ localeBoolean [ 'FALSE' ] ; } if ( ( is_array ( $ operand1 ) ) || ( is_array ( $ operand2 ) ) ) { //    Ensure that both operands are arrays/matrices self :: checkMatrixOperands ( $ operand1 , $ operand2 , 2 ) ; try { //    Convert operand 1 from a PHP array to a matrix $ matrix = new Shared \ JAMA \ Matrix ( $ operand1 ) ; //    Perform the required operation against the operand 1 matrix, passing in operand 2 $ matrixResult = $ matrix -> concat ( $ operand2 ) ; $ result = $ matrixResult -> getArray ( ) ; } catch ( \ Exception $ ex ) { $ this -> debugLog -> writeDebugLog ( 'JAMA Matrix Exception: ' , $ ex -> getMessage ( ) ) ; $ result = '#VALUE!' ; } } else { $ result = '"' . str_replace ( '""' , '"' , self :: unwrapResult ( $ operand1 ) . self :: unwrapResult ( $ operand2 ) ) . '"' ; } $ this -> debugLog -> writeDebugLog ( 'Evaluation Result is ' , $ this -> showTypeDetails ( $ result ) ) ; $ stack -> push ( 'Value' , $ result ) ; break ; case '|' : //    Intersect $ rowIntersect = array_intersect_key ( $ operand1 , $ operand2 ) ; $ cellIntersect = $ oCol = $ oRow = [ ] ; foreach ( array_keys ( $ rowIntersect ) as $ row ) { $ oRow [ ] = $ row ; foreach ( $ rowIntersect [ $ row ] as $ col => $ data ) { $ oCol [ ] = Coordinate :: columnIndexFromString ( $ col ) - 1 ; $ cellIntersect [ $ row ] = array_intersect_key ( $ operand1 [ $ row ] , $ operand2 [ $ row ] ) ; } } $ cellRef = Coordinate :: stringFromColumnIndex ( min ( $ oCol ) + 1 ) . min ( $ oRow ) . ':' . Coordinate :: stringFromColumnIndex ( max ( $ oCol ) + 1 ) . max ( $ oRow ) ; $ this -> debugLog -> writeDebugLog ( 'Evaluation Result is ' , $ this -> showTypeDetails ( $ cellIntersect ) ) ; $ stack -> push ( 'Value' , $ cellIntersect , $ cellRef ) ; break ; } // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on } elseif ( ( $ token === '~' ) || ( $ token === '%' ) ) { if ( ( $ arg = $ stack -> pop ( ) ) === null ) { return $ this -> raiseFormulaError ( 'Internal error - Operand value missing from stack' ) ; } $ arg = $ arg [ 'value' ] ; if ( $ token === '~' ) { $ this -> debugLog -> writeDebugLog ( 'Evaluating Negation of ' , $ this -> showValue ( $ arg ) ) ; $ multiplier = - 1 ; } else { $ this -> debugLog -> writeDebugLog ( 'Evaluating Percentile of ' , $ this -> showValue ( $ arg ) ) ; $ multiplier = 0.01 ; } if ( is_array ( $ arg ) ) { self :: checkMatrixOperands ( $ arg , $ multiplier , 2 ) ; try { $ matrix1 = new Shared \ JAMA \ Matrix ( $ arg ) ; $ matrixResult = $ matrix1 -> arrayTimesEquals ( $ multiplier ) ; $ result = $ matrixResult -> getArray ( ) ; } catch ( \ Exception $ ex ) { $ this -> debugLog -> writeDebugLog ( 'JAMA Matrix Exception: ' , $ ex -> getMessage ( ) ) ; $ result = '#VALUE!' ; } $ this -> debugLog -> writeDebugLog ( 'Evaluation Result is ' , $ this -> showTypeDetails ( $ result ) ) ; $ stack -> push ( 'Value' , $ result ) ; } else { $ this -> executeNumericBinaryOperation ( $ multiplier , $ arg , '*' , 'arrayTimesEquals' , $ stack ) ; } } elseif ( preg_match ( '/^' . self :: CALCULATION_REGEXP_CELLREF . '$/i' , $ token , $ matches ) ) { $ cellRef = null ; if ( isset ( $ matches [ 8 ] ) ) { if ( $ pCell === null ) { //                        We can't access the range, so return a REF error $ cellValue = Functions :: REF ( ) ; } else { $ cellRef = $ matches [ 6 ] . $ matches [ 7 ] . ':' . $ matches [ 9 ] . $ matches [ 10 ] ; if ( $ matches [ 2 ] > '' ) { $ matches [ 2 ] = trim ( $ matches [ 2 ] , "\"'" ) ; if ( ( strpos ( $ matches [ 2 ] , '[' ) !== false ) || ( strpos ( $ matches [ 2 ] , ']' ) !== false ) ) { //    It's a Reference to an external spreadsheet (not currently supported) return $ this -> raiseFormulaError ( 'Unable to access External Workbook' ) ; } $ matches [ 2 ] = trim ( $ matches [ 2 ] , "\"'" ) ; $ this -> debugLog -> writeDebugLog ( 'Evaluating Cell Range ' , $ cellRef , ' in worksheet ' , $ matches [ 2 ] ) ; if ( $ pCellParent !== null ) { $ cellValue = $ this -> extractCellRange ( $ cellRef , $ this -> spreadsheet -> getSheetByName ( $ matches [ 2 ] ) , false ) ; } else { return $ this -> raiseFormulaError ( 'Unable to access Cell Reference' ) ; } $ this -> debugLog -> writeDebugLog ( 'Evaluation Result for cells ' , $ cellRef , ' in worksheet ' , $ matches [ 2 ] , ' is ' , $ this -> showTypeDetails ( $ cellValue ) ) ; } else { $ this -> debugLog -> writeDebugLog ( 'Evaluating Cell Range ' , $ cellRef , ' in current worksheet' ) ; if ( $ pCellParent !== null ) { $ cellValue = $ this -> extractCellRange ( $ cellRef , $ pCellWorksheet , false ) ; } else { return $ this -> raiseFormulaError ( 'Unable to access Cell Reference' ) ; } $ this -> debugLog -> writeDebugLog ( 'Evaluation Result for cells ' , $ cellRef , ' is ' , $ this -> showTypeDetails ( $ cellValue ) ) ; } } } else { if ( $ pCell === null ) { //                        We can't access the cell, so return a REF error $ cellValue = Functions :: REF ( ) ; } else { $ cellRef = $ matches [ 6 ] . $ matches [ 7 ] ; if ( $ matches [ 2 ] > '' ) { $ matches [ 2 ] = trim ( $ matches [ 2 ] , "\"'" ) ; if ( ( strpos ( $ matches [ 2 ] , '[' ) !== false ) || ( strpos ( $ matches [ 2 ] , ']' ) !== false ) ) { //    It's a Reference to an external spreadsheet (not currently supported) return $ this -> raiseFormulaError ( 'Unable to access External Workbook' ) ; } $ this -> debugLog -> writeDebugLog ( 'Evaluating Cell ' , $ cellRef , ' in worksheet ' , $ matches [ 2 ] ) ; if ( $ pCellParent !== null ) { $ cellSheet = $ this -> spreadsheet -> getSheetByName ( $ matches [ 2 ] ) ; if ( $ cellSheet && $ cellSheet -> cellExists ( $ cellRef ) ) { $ cellValue = $ this -> extractCellRange ( $ cellRef , $ this -> spreadsheet -> getSheetByName ( $ matches [ 2 ] ) , false ) ; $ pCell -> attach ( $ pCellParent ) ; } else { $ cellValue = null ; } } else { return $ this -> raiseFormulaError ( 'Unable to access Cell Reference' ) ; } $ this -> debugLog -> writeDebugLog ( 'Evaluation Result for cell ' , $ cellRef , ' in worksheet ' , $ matches [ 2 ] , ' is ' , $ this -> showTypeDetails ( $ cellValue ) ) ; } else { $ this -> debugLog -> writeDebugLog ( 'Evaluating Cell ' , $ cellRef , ' in current worksheet' ) ; if ( $ pCellParent -> has ( $ cellRef ) ) { $ cellValue = $ this -> extractCellRange ( $ cellRef , $ pCellWorksheet , false ) ; $ pCell -> attach ( $ pCellParent ) ; } else { $ cellValue = null ; } $ this -> debugLog -> writeDebugLog ( 'Evaluation Result for cell ' , $ cellRef , ' is ' , $ this -> showTypeDetails ( $ cellValue ) ) ; } } } $ stack -> push ( 'Value' , $ cellValue , $ cellRef ) ; // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on } elseif ( preg_match ( '/^' . self :: CALCULATION_REGEXP_FUNCTION . '$/i' , $ token , $ matches ) ) { $ functionName = $ matches [ 1 ] ; $ argCount = $ stack -> pop ( ) ; $ argCount = $ argCount [ 'value' ] ; if ( $ functionName != 'MKMATRIX' ) { $ this -> debugLog -> writeDebugLog ( 'Evaluating Function ' , self :: localeFunc ( $ functionName ) , '() with ' , ( ( $ argCount == 0 ) ? 'no' : $ argCount ) , ' argument' , ( ( $ argCount == 1 ) ? '' : 's' ) ) ; } if ( ( isset ( self :: $ phpSpreadsheetFunctions [ $ functionName ] ) ) || ( isset ( self :: $ controlFunctions [ $ functionName ] ) ) ) { // function if ( isset ( self :: $ phpSpreadsheetFunctions [ $ functionName ] ) ) { $ functionCall = self :: $ phpSpreadsheetFunctions [ $ functionName ] [ 'functionCall' ] ; $ passByReference = isset ( self :: $ phpSpreadsheetFunctions [ $ functionName ] [ 'passByReference' ] ) ; $ passCellReference = isset ( self :: $ phpSpreadsheetFunctions [ $ functionName ] [ 'passCellReference' ] ) ; } elseif ( isset ( self :: $ controlFunctions [ $ functionName ] ) ) { $ functionCall = self :: $ controlFunctions [ $ functionName ] [ 'functionCall' ] ; $ passByReference = isset ( self :: $ controlFunctions [ $ functionName ] [ 'passByReference' ] ) ; $ passCellReference = isset ( self :: $ controlFunctions [ $ functionName ] [ 'passCellReference' ] ) ; } // get the arguments for this function $ args = $ argArrayVals = [ ] ; for ( $ i = 0 ; $ i < $ argCount ; ++ $ i ) { $ arg = $ stack -> pop ( ) ; $ a = $ argCount - $ i - 1 ; if ( ( $ passByReference ) && ( isset ( self :: $ phpSpreadsheetFunctions [ $ functionName ] [ 'passByReference' ] [ $ a ] ) ) && ( self :: $ phpSpreadsheetFunctions [ $ functionName ] [ 'passByReference' ] [ $ a ] ) ) { if ( $ arg [ 'reference' ] === null ) { $ args [ ] = $ cellID ; if ( $ functionName != 'MKMATRIX' ) { $ argArrayVals [ ] = $ this -> showValue ( $ cellID ) ; } } else { $ args [ ] = $ arg [ 'reference' ] ; if ( $ functionName != 'MKMATRIX' ) { $ argArrayVals [ ] = $ this -> showValue ( $ arg [ 'reference' ] ) ; } } } else { $ args [ ] = self :: unwrapResult ( $ arg [ 'value' ] ) ; if ( $ functionName != 'MKMATRIX' ) { $ argArrayVals [ ] = $ this -> showValue ( $ arg [ 'value' ] ) ; } } } //    Reverse the order of the arguments krsort ( $ args ) ; if ( ( $ passByReference ) && ( $ argCount == 0 ) ) { $ args [ ] = $ cellID ; $ argArrayVals [ ] = $ this -> showValue ( $ cellID ) ; } if ( $ functionName != 'MKMATRIX' ) { if ( $ this -> debugLog -> getWriteDebugLog ( ) ) { krsort ( $ argArrayVals ) ; $ this -> debugLog -> writeDebugLog ( 'Evaluating ' , self :: localeFunc ( $ functionName ) , '( ' , implode ( self :: $ localeArgumentSeparator . ' ' , Functions :: flattenArray ( $ argArrayVals ) ) , ' )' ) ; } } //    Process the argument with the appropriate function call if ( $ passCellReference ) { $ args [ ] = $ pCell ; } if ( ! is_array ( $ functionCall ) ) { foreach ( $ args as & $ arg ) { $ arg = Functions :: flattenSingleValue ( $ arg ) ; } unset ( $ arg ) ; } $ result = call_user_func_array ( $ functionCall , $ args ) ; if ( $ functionName != 'MKMATRIX' ) { $ this -> debugLog -> writeDebugLog ( 'Evaluation Result for ' , self :: localeFunc ( $ functionName ) , '() function call is ' , $ this -> showTypeDetails ( $ result ) ) ; } $ stack -> push ( 'Value' , self :: wrapResult ( $ result ) ) ; } } else { // if the token is a number, boolean, string or an Excel error, push it onto the stack if ( isset ( self :: $ excelConstants [ strtoupper ( $ token ) ] ) ) { $ excelConstant = strtoupper ( $ token ) ; $ stack -> push ( 'Constant Value' , self :: $ excelConstants [ $ excelConstant ] ) ; $ this -> debugLog -> writeDebugLog ( 'Evaluating Constant ' , $ excelConstant , ' as ' , $ this -> showTypeDetails ( self :: $ excelConstants [ $ excelConstant ] ) ) ; } elseif ( ( is_numeric ( $ token ) ) || ( $ token === null ) || ( is_bool ( $ token ) ) || ( $ token == '' ) || ( $ token [ 0 ] == '"' ) || ( $ token [ 0 ] == '#' ) ) { $ stack -> push ( 'Value' , $ token ) ; // if the token is a named range, push the named range name onto the stack } elseif ( preg_match ( '/^' . self :: CALCULATION_REGEXP_NAMEDRANGE . '$/i' , $ token , $ matches ) ) { $ namedRange = $ matches [ 6 ] ; $ this -> debugLog -> writeDebugLog ( 'Evaluating Named Range ' , $ namedRange ) ; $ cellValue = $ this -> extractNamedRange ( $ namedRange , ( ( null !== $ pCell ) ? $ pCellWorksheet : null ) , false ) ; $ pCell -> attach ( $ pCellParent ) ; $ this -> debugLog -> writeDebugLog ( 'Evaluation Result for named range ' , $ namedRange , ' is ' , $ this -> showTypeDetails ( $ cellValue ) ) ; $ stack -> push ( 'Named Range' , $ cellValue , $ namedRange ) ; } else { return $ this -> raiseFormulaError ( "undefined variable '$token'" ) ; } } } // when we're out of tokens, the stack should have a single element, the final result if ( $ stack -> count ( ) != 1 ) { return $ this -> raiseFormulaError ( 'internal error' ) ; } $ output = $ stack -> pop ( ) ; $ output = $ output [ 'value' ] ; return $ output ; }
@param mixed $tokens
@param null|string $cellID
@param null|Cell $pCell

@return bool
@param mixed $tokens @param null|string $cellID @param null|Cell $pCell