← board

#$NN / #%NN / #&NN char-code literals broken (hex/bin/oct escape)

Symptom

The #NN char-code literal only accepts a decimal code. The FPC forms with a radix prefix — #$ (hex), #% (binary), #& (octal) — are mis-lexed:

var c: char;
begin c := #$41; writeln(Ord(c)); end.   { prints 0, must be 65 }

Decimal #65 works everywhere; only the radix-prefixed forms break.

Minimal repros (all on pinned v49)

program p; const C: char = #$FF; begin end.              { ERROR }
program p; type T=set of char; const C:T=[#$FF]; begin end.   { ERROR }
program p; var c:char; begin c:=#$41; writeln(Ord(c)); end.   { compiles, prints 0 }

Root cause

compiler/lexer.inc:1592-1598 — the unified string/char-code literal lexer reads the code after # with a decimal-only digit loop:

else if Source[SrcPos] = '#' then
begin
  Inc(SrcPos); n := 0;
  while (SrcPos <= Length(Source)) and (Source[SrcPos] in ['0'..'9']) do
    begin n := n*10 + (Ord(Source[SrcPos])-48); Inc(SrcPos); end;
  AppendChar(s, Chr(n));
end

For #$FF: consumes #, the loop sees $ (not 0'..'9), consumes zero digits, appends Chr(0), then the outer loop breaks on $ (not ' or #). The token ends as a 1-char string #0 and $FF dangles as the next token — hence the hard error in const-expr context and the silent Chr(0) in statement context.

Fix

After Inc(SrcPos) past #, dispatch on the radix prefix before reading digits (same set the integer lexer already handles — $/%/&):

Reuse the existing radix scan from the integer-literal path (lexer.inc has $ hex and % binary integer scanners already; factor or mirror them). Guard the empty-digit case (a lone #$ with no digits) with a lex error instead of silently appending Chr(0).

Done when