stride3d/stride · error · InvalidOperationException
Cannot unget another character!
Error message
Cannot unget another character!
What it means
LexerSource.unread() can push back at most two characters (u0, u1); when a third consecutive unread is requested, this InvalidOperationException is thrown. It is an internal lexer invariant: the token scanners (invalid, cppcomment, escape, character, String, _number) must never need to push back more than two characters.
Solutions
- Fix the malformed token in the shader source (check numbers, escape sequences, and string/char literals around the reported line/column).
- Verify the input stream is complete and not corrupted/truncated mid-token.
- Install a PreprocessorListener so upstream invalid-token paths report errors instead of cascading.
- If you extended the lexer, increase the pushback buffer (add u2/ucount case 2) or reduce look-ahead to at most two chars.
Example fix
// before: scanner unreads three times in a row unread(d); unread(e); unread(f); // throws: Cannot unget another character! // after: re-read instead of a third unget unread(d); unread(e); f = read(); // keep pushback depth <= 2
Defensive patterns
Strategy: validation
Validate before calling
// Lint shader source for tokens that break look-ahead assumptions
foreach (var m in Regex.Matches(src, @"\d+\.\d*\.|\\[^n'\"\\]|\z\\"))
report("Malformed numeric/escape sequence near " + m.Index); Try / catch
try
{
var token = lexerSource.token();
}
catch (InvalidOperationException ex) when (ex.Message == "Cannot unget another character!")
{
report(line, column, "Malformed token pushed lexer look-ahead past 2 characters");
} Prevention
- Validate numeric literals, escapes, and string/char literals in shader sources before preprocessing.
- Never leave custom scanners with more than 2 characters of push-back.
- Keep input streams intact — truncated files can strand the scanner mid-token.
When it happens
Trigger: A lexer scan path (e.g. _number, escape, string/char literal scanning, comment handling) calls unread() three times without an intervening read(), exhausting the two-slot pushback buffer — typically caused by malformed input that breaks the scanner's assumptions about look-ahead depth.
Common situations: Malformed numeric literals or escape sequences in shader source (e.g. '1.2.3', dangling backslash in a string); corrupted or truncated input streams; bugs in custom token scanners that over-look-ahead.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Cannot read from
- SortedList::internal error (
- Symbol could not be imported because it was not found in…
- {msg}
- Warning at
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/f9f753cd2f8095c1.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/CppNet/LexerSource.cs:265
if (isLineSeparator(c)) {
line--;
column = lastcolumn;
cr = false;
}
else {
column--;
}
switch (ucount) {
case 0:
u0 = c;
ucount = 1;
break;
case 1:
u1 = c;
ucount = 2;
break;
default:
throw new InvalidOperationException(
"Cannot unget another character!"
);
}
// reader.unread(c);
}
}
/* Consumes the rest of the current line into an invalid. */
private Token invalid(StringBuilder text, String reason) {
int d = read();
while (!isLineSeparator(d)) {
text.Append((char)d);
d = read();
}
unread(d);
return new Token(Token.INVALID, text.ToString(), reason);
}
View on GitHub (pinned to 96fad776d2)