Skip to content

Accept a constant expression as an array bound - #12

Open
partouf wants to merge 1 commit into
jimmckeeth:mainfrom
GDKsoftware:fix/array-bound-expression
Open

Accept a constant expression as an array bound#12
partouf wants to merge 1 commit into
jimmckeeth:mainfrom
GDKsoftware:fix/array-bound-expression

Conversation

@partouf

@partouf partouf commented Aug 31, 2026

Copy link
Copy Markdown

An array bound may be any constant expression, but OrdinalType only ever accepted a constant or a type name:

const
mlab = 4;
mlog = 12;
type
TRanges = record
iu: array[mlab + 1..mlog] of Integer;
end;

failed with 'SquareClose' expected found '+'. OrdinalType decided on a single token of lookahead: an identifier followed by anything but '(' or '..' was a type name, so it read the bound as the type mlab, returned, and left ArrayBounds looking for the ']' it found a '+' at.

The upper bound already worked, which is what makes the gap easy to miss. array[mlab..mlog + 1] sends the first bound to ConstantExpression on the '..' lookahead, and OrdinalType's own trailing '..' branch parses the rest as an expression. Only the first bound of a subrange went down the type-name path.

The lookahead now also routes the operators SimpleExpression and Term accept to ConstantExpression. Every one of those tokens is a parse error in this position today, so no input that parses now takes a different path: an identifier in an OrdinalType is followed by ']', ',', '..', 'of' or ';', never by an operator. Set types and variant record tag types go through the same procedure and gain the same forms.

A single token is enough here and a full ahead-parse would be worse. Coming from the identifier, SimpleType's AheadParse.NextToken; AheadParse.Simple- Expression idiom would meet the ']' of the common array[TIndex] of Byte and hand it to Factor as a set constructor.

Parenthesized bounds are deliberately left alone. array[(mlab + 1)..mlog] goes to EnumeratedType, and dcc32 reads it the same way - it reports "Identifier redeclared: 'mlab'" - so the parser already agrees with the compiler.

Test/Snippets/arrayboundexpression.pas covers the first bound, both bounds, two dimensions, *, - and shl, and a set of a computed subrange; dcc32 compiles it clean. Without the parser change it fails with the original 'SquareClose' expected found '+', so it guards the actual bug.

Suite: 43 tests, 42 passing, with the pre-existing Serialization.BinaryRoundTrip failure unchanged (line_seq holds a pointer value that does not survive a round trip).

An array bound may be any constant expression, but OrdinalType only ever
accepted a constant or a type name:

  const
    mlab = 4;
    mlog = 12;
  type
    TRanges = record
      iu: array[mlab + 1..mlog] of Integer;
    end;

failed with 'SquareClose' expected found '+'. OrdinalType decided on a single
token of lookahead: an identifier followed by anything but '(' or '..' was a
type name, so it read the bound as the type `mlab`, returned, and left
ArrayBounds looking for the ']' it found a '+' at.

The upper bound already worked, which is what makes the gap easy to miss.
`array[mlab..mlog + 1]` sends the first bound to ConstantExpression on the
'..' lookahead, and OrdinalType's own trailing '..' branch parses the rest as
an expression. Only the first bound of a subrange went down the type-name
path.

The lookahead now also routes the operators SimpleExpression and Term accept
to ConstantExpression. Every one of those tokens is a parse error in this
position today, so no input that parses now takes a different path: an
identifier in an OrdinalType is followed by ']', ',', '..', 'of' or ';', never
by an operator. Set types and variant record tag types go through the same
procedure and gain the same forms.

A single token is enough here and a full ahead-parse would be worse. Coming
from the identifier, SimpleType's `AheadParse.NextToken; AheadParse.Simple-
Expression` idiom would meet the ']' of the common `array[TIndex] of Byte` and
hand it to Factor as a set constructor.

Parenthesized bounds are deliberately left alone. `array[(mlab + 1)..mlog]`
goes to EnumeratedType, and dcc32 reads it the same way - it reports
"Identifier redeclared: 'mlab'" - so the parser already agrees with the
compiler.

Test/Snippets/arrayboundexpression.pas covers the first bound, both bounds,
two dimensions, `*`, `-` and `shl`, and a set of a computed subrange; dcc32
compiles it clean. Without the parser change it fails with the original
'SquareClose' expected found '+', so it guards the actual bug.

Suite: 43 tests, 42 passing, with the pre-existing Serialization.BinaryRoundTrip
failure unchanged (line_seq holds a pointer value that does not survive a
round trip).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant