dianping/cat · error · PHP.ParseError
syntax error, unexpected + terminals[tokenId] + expectedStr
Error message
syntax error, unexpected + terminals[tokenId] + expectedString (e.g. ', expecting ' + expected.join(' or ')) What it means
PHP.ParseError thrown by the generated LALR parser tables inside the ACE PHP worker (worker-php.js, error at line 2403 in the yyerror/reporting path). It means the token sequence is grammatically invalid PHP: the parser computed the set of terminals acceptable in the current state, listed them as 'expecting ...', and reports the unexpected token it actually got, along with the start line of the offending statement.
Source
Thrown at cat-home/src/main/webapp/assets/js/editor/worker-php.js:2403
&& (yyn = yybase[ state + this.YYNLSTATES] + i)
&& yyn < this.YYLAST && yycheck[ yyn ] == i
) {
if (yyaction[ yyn ] != this.YYUNEXPECTED) {
if (expected.length == 4) {
expected = [];
break;
}
expected.push( this.terminals[ i ] );
}
}
}
var expectedString = '';
if (expected.length) {
expectedString = ', expecting ' + expected.join(' or ');
}
throw new PHP.ParseError('syntax error, unexpected ' + terminals[ tokenId ] + expectedString, this.startAttributes['startLine']);
} else {
return this.startAttributes['startLine'];
}
}
if (state < this.YYNLSTATES)
break;
yyn = state - this.YYNLSTATES;
}
}
};
PHP.ParseError = function( msg, line ) {
this.message = msg;
this.line = line;
};
View on GitHub (pinned to e815e74d4c)
Solutions
- Look at the reported start line and the 'expecting' list: insert or fix the listed punctuation/token (most often a missing semicolon, brace, or parenthesis).
- If the code targets a newer PHP than the worker understands, treat the annotation as a false positive and upgrade the PHP worker/parser tables.
- Check the previous line as well — LALR errors frequently surface one token after the actual mistake.
- Run php -l on the file to cross-check the real PHP error position.
Example fix
// before <?php $a = 1 echo $a; // after <?php $a = 1; echo $a;
Defensive patterns
Strategy: try-catch
Validate before calling
// Cheap pre-flight in the editor host before shipping PHP to the worker:
function plausiblyParseable(src) {
if (!/^<\?php|<\?=/m.test(src)) return false;
const bal = (s, a, b) => (s.split(a).length - 1) - (s.split(b).length - 1);
return bal(src, '{', '}') === 0 && bal(src, '(', ')') === 0;
} Type guard
function isParseError(ex) {
return ex instanceof PHP.ParseError;
} Try / catch
try {
new PHP.Parser(lexer, ast).parse(src);
} catch (ex) {
if (isParseError(ex)) {
annotate({ line: ex.line, message: ex.message }); // line lives on the error
} else throw ex;
} Prevention
- Trust the 'expecting' list — it states exactly which token completes the construct.
- Check the line BEFORE the reported one; LALR lags by a token.
- Cross-check with php -l before assuming an editor false positive.
- If writing PHP 8 syntax, confirm the bundled worker tables know that version.
When it happens
Trigger: Any syntactically broken PHP fed to the worker: missing ';' before a statement, unbalanced braces/parens, 'foreach (array as' with missing variable, using a reserved word as a function name. The parser reaches a state where the lookahead tokenId has no action, builds the 'expected' list from this.terminals, and throws at line 2409.
Common situations: Live typing in the ACE PHP editor (the worker parses on each change, so half-written statements routinely produce this), pasted PHP with smart quotes, PHP 8 syntax (match, arrow fns, attributes) fed to an old parser that only knows PHP 5.x, or a file whose opening '<?php' tag is missing.
Related errors
- __halt_compiler() can only be used from the outermost scope
- Expected "{pattern}" at line {line}, col {col}.
- Expected {name} at line {line}, col {col}.
- Unknown @ rule.
- @charset not allowed here.
AI-assisted analysis of dianping/cat@e815e74d4c (2026-08-14).
Data as JSON: /api/errors/6f4f8ba077ab9930.
Report an issue: GitHub.