fix(const): accept hex/octal/binary literals in array-const elements (#129)

$hex, %binary and &octal integer literals were rejected inside an
array-constant initialiser — 'array[0..1] of Int64 = ($1, $2)' failed
with 'Cannot resolve array-const element' while the scalar form '= $1'
worked.  The parser stores array elements verbatim (prefix and all), and
ResolveConstArrayElem only recognised decimal digits, so any radix-prefixed
element fell through to an identifier lookup and errored.

Fold radix-prefixed elements (with optional leading sign) through
ParseIntLiteral — the same canonical parser the rest of the compiler uses
for scalar and typed consts — so all four radixes, plus underscore digit
separators, behave identically inside an array initialiser.  Keeps FPC's
&-octal (the project prefers FPC syntax where FPC and Delphi diverge; the
lexer already tokenised all four radixes).

Documents the radix-prefix rule in language-rationale.adoc.
This commit is contained in:
Graeme Geldenhuys 2026-06-18 15:39:18 +01:00
parent fe4e5602cf
commit 5b5b95c18f
2 changed files with 38 additions and 0 deletions

View file

@ -4008,6 +4008,18 @@ var
begin
Result := AElem;
if AElem = '' then Exit;
{ Radix-prefixed integer literal $hex, %binary, &octal (with optional
leading sign). The lexer keeps the prefixed form in the token text, but
the rest of the const-array pipeline (codegen, OPDF) expects a plain
decimal string. Fold through ParseIntLiteral, the same canonical parser
the parser uses for scalar/typed consts, so all three radixes behave
identically inside an array initialiser. }
if AElem[0] = '$' then Exit(IntToStr(ParseIntLiteral(AElem)));
if AElem[0] = '%' then Exit(IntToStr(ParseIntLiteral(AElem)));
if AElem[0] = '&' then Exit(IntToStr(ParseIntLiteral(AElem)));
if (Length(AElem) > 1) and (AElem[0] = '-') and
((AElem[1] = '$') or (AElem[1] = '%') or (AElem[1] = '&')) then
Exit(IntToStr(-ParseIntLiteral(StrCopyTail(AElem, 1))));
{ Already a numeric literal (int, negative int, or float) — leave as is. }
if IsPlainInt(AElem) then Exit;
if (AElem[0] >= '0') and (AElem[0] <= '9') then Exit;

View file

@ -583,6 +583,32 @@ The bit pattern is preserved in every case; the literal type only
selects which signed/unsigned interpretation the surrounding expression
sees.
==== Radix prefixes
Integer literals may be written in four radixes, following FPC (not
Delphi):
* decimal — `42`
* hexadecimal — `$2A` (also Delphi)
* binary — `%101010` (Delphi 11+ also accepts this)
* octal — `&52` (FPC only; Delphi has never supported octal — there `&`
is the identifier-escape prefix)
Underscore digit separators are permitted in any radix (`$FF_FF`,
`%1010_1010`). All four forms are accepted everywhere an integer literal
is — scalar and typed constants, expressions, static-array bounds, and
*array-constant element lists*. The last was the gap behind issue #129:
`array[0..1] of Int64 = ($1, $2)` rejected the hex elements while the
scalar form `= $1` accepted them. Array-constant elements now fold
through the same `ParseIntLiteral` radix parser the rest of the compiler
uses, so every radix behaves identically inside an initialiser.
The decision to keep FPC's `&` octal (rather than Delphi's no-octal /
`&`-as-escape) follows the project rule of preferring FPC syntax where the
two dialects diverge; the lexer already tokenised all four radixes, so the
fix only made the array-constant path consistent with the rest of the
language.
==== Built-in constant
`MaxInt: Integer = 2147483647` is a compiler built-in constant. It