From 5b5b95c18f02de09c2ad11c46dbd11fca37d102a Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Thu, 18 Jun 2026 15:39:18 +0100 Subject: [PATCH] fix(const): accept hex/octal/binary literals in array-const elements (#129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $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. --- compiler/src/main/pascal/uSemantic.pas | 12 ++++++++++++ docs/language-rationale.adoc | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/compiler/src/main/pascal/uSemantic.pas b/compiler/src/main/pascal/uSemantic.pas index d3909ba..0a720c4 100644 --- a/compiler/src/main/pascal/uSemantic.pas +++ b/compiler/src/main/pascal/uSemantic.pas @@ -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; diff --git a/docs/language-rationale.adoc b/docs/language-rationale.adoc index e313e12..a86ff96 100644 --- a/docs/language-rationale.adoc +++ b/docs/language-rationale.adoc @@ -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