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

  1. Fix the malformed token in the shader source (check numbers, escape sequences, and string/char literals around the reported line/column).
  2. Verify the input stream is complete and not corrupted/truncated mid-token.
  3. Install a PreprocessorListener so upstream invalid-token paths report errors instead of cascading.
  4. 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

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


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)