{"record":{"id":"da1982e22805ca82","repo":"PHPOffice/PhpSpreadsheet","slug":"syntax-error-comma-expected-in-function-function","errorCode":null,"errorMessage":"Syntax error: comma expected in function $function, arg #{$num_args}","messagePattern":"Syntax error: comma expected in function \\$function, arg #(.+?)","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Writer\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Writer/Xls/Parser.php","lineNumber":1535,"sourceCode":"    /**\n     * It parses a function call. It assumes the following rule:\n     * Func -> ( Expr [,Expr]* ).\n     *\n     * @return mixed[] The parsed ptg'd tree on success\n     */\n    private function func(): array\n    {\n        $num_args = 0; // number of arguments received\n        $function = strtoupper($this->currentToken);\n        $result = ''; // initialize result\n        $this->advance();\n        $this->advance(); // eat the \"(\"\n        while ($this->currentToken !== ')') {\n            if ($num_args > 0) {\n                if ($this->currentToken === ',' || $this->currentToken === ';') {\n                    $this->advance(); // eat the \",\" or \";\"\n                } else {\n                    throw new WriterException(\"Syntax error: comma expected in function $function, arg #{$num_args}\");\n                }\n                $result2 = $this->condition();\n                $result = $this->createTree('arg', $result, $result2);\n            } else { // first argument\n                $result2 = $this->condition();\n                $result = $this->createTree('arg', '', $result2);\n            }\n            ++$num_args;\n        }\n        if (!isset($this->functions[$function])) {\n            throw new WriterException(\"Function $function() doesn't exist\");\n        }\n        $args = $this->functions[$function][1];\n        // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid.\n        if (($args >= 0) && ($args != $num_args)) {\n            throw new WriterException(\"Incorrect number of arguments in function $function() \");\n        }\n","sourceCodeStart":1517,"sourceCodeEnd":1553,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Writer/Xls/Parser.php#L1517-L1553","documentation":"Thrown by the Xls (Excel 5/BIFF8) writer's formula parser while it converts a cell formula into the binary token stream the .xls format requires. In Parser::func(), every argument after the first must be preceded by a ',' or ';' token before the closing ')' is reached; any other token at that position aborts the save. It means the formula string assigned to the cell is syntactically malformed - the error surfaces at save() time, not when the formula is set.","triggerScenarios":"Saving to .xls a workbook containing a formula with missing argument separators, e.g. setCellValue('A1', '=IF(A1>10 \"big\" \"small\")'); dynamically concatenated formula strings that drop a comma; unbalanced parentheses that make the internal condition() parser stop before an argument.","commonSituations":"Formulas assembled from user input or templates that use locale-specific separators; typos in hand-built formula strings; formulas that work in the Calculation engine but are written in a shape the Xls parser cannot tokenize.","solutions":["Fix the formula so every argument after the first is comma-separated, e.g. '=IF(A1>10,\"big\",\"small\")'","Check parenthesis balance and separator count in programmatically built formula strings before save","Log which cell triggers it (cells are written in sorted coordinate order) to locate the malformed formula","If the formula only matters to your app, write the precomputed value instead of the formula when exporting .xls"],"exampleFix":"// before\n$sheet->setCellValue('A1', '=IF(A1>10 \"big\" \"small\")');\n\n// after\n$sheet->setCellValue('A1', '=IF(A1>10,\"big\",\"small\")');","handlingStrategy":"validation","validationCode":"/** Cheap lint: strip string literals, then every top-level argument\n * inside parentheses must be non-empty and comma-separated. */\nfunction formulaSeparatorsLookValid(string $formula): bool\n{\n    if (!str_starts_with($formula, '=')) {\n        return true; // not a formula, parser not involved\n    }\n    $clean = preg_replace('/\"(?:[^\"]|\"\")*\"/', '', $formula) ?? $formula;\n    // no two adjacent identifiers/references without an operator or comma between them\n    return !preg_match('/[A-Za-z0-9_)\\]]\\s+[A-Za-z0-9_$]/', substr($clean, 1));\n}\n\nif (!formulaSeparatorsLookValid($cellFormula)) {\n    throw new InvalidArgumentException(\"Malformed formula: $cellFormula\");\n}\n$spreadsheet->getActiveSheet()->setCellValue('A1', $cellFormula);","typeGuard":null,"tryCatchPattern":"try {\n    $writer = IOFactory::createWriter($spreadsheet, 'Xls');\n    $writer->save($path);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Writer\\Exception $e) {\n    if (str_contains($e->getMessage(), 'comma expected')) {\n        // message names the function and arg#, e.g. arg #2 of IF(...)\n        throw new UserInputException('Formula syntax error: ' . $e->getMessage(), 0, $e);\n    }\n    throw $e;\n}","preventionTips":["Build formulas from a single template with explicit ',' placeholders instead of concatenation","Unit-test every generated formula against the Xls writer in CI, not just with the Calculation engine","Normalize locale input: convert ';' argument separators used in some locales to ',' before assigning"],"tags":["phpspreadsheet","xls","formula","parser","writer"],"backgroundTag":"formula-parse-error","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}