feat(sets): range syntax in set literals — [lo..hi] (#105)
A set literal element may now be a constant range:
e := [Red..Blue]; { {Red, Green, Blue} }
e := [m0, m2..m4, m7]; { ranges mix with single members }
The parser builds a transient TSetRangeExpr for each lo..hi element; the
semantic pass (AnalyseSetLiteralExpr) expands it into the individual member
idents before any other consumer runs, so overload resolution, the bitmask
folder, the jumbo-set path, and both code generators see an ordinary member
list and need no range-specific logic.
Both bounds must be compile-time constants of the set's base type. A
reversed constant range [Blue..Red] is a compile-time error rather than a
silently empty set — matching Blaise's preference for rejecting
confusing-but-legal constructs over FPC's empty-set-with-warning behaviour.
Variable bounds [lo..hi] are rejected ("set range bound must be a
constant"); runtime-variable ranges are deferred.
TSetRangeExpr is wired into the .bif serialiser and the AST cloner so a
generic template body containing an unexpanded range round-trips through
separate compilation (verified by hand: a generic returning [m1..m3]
compiled to .o, then instantiated from the .bif with source hidden).
Set base types remain enumerations; the issue's set-of-byte example needs
ordinal-base set types, tracked separately in docs/future-improvements.adoc.
Range expansion is base-type-agnostic, so ranges will work for those
automatically once they land.
Docs: grammar.ebnf SetElement rule; language-rationale.adoc decision +
alternatives. bif-coverage.status regenerated for the new node.
FIXPOINT_OK + NATIVE_FIXPOINT_OK; full suite OK (3201 tests, 11 new).
This commit is contained in:
parent
4e90165b73
commit
9362114ee1
|
|
@ -71,6 +71,19 @@ type
|
|||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ A `lo..hi` range appearing as an element of a set literal, e.g.
|
||||
[1..3] or [Red..Blue]. Both bounds must be compile-time constants;
|
||||
uSemantic.AnalyseSetLiteralExpr expands the range into individual
|
||||
member nodes (so codegen never sees this node), and rejects reversed
|
||||
or non-constant ranges. Carried through the .bif serialiser because a
|
||||
generic template body may contain an unexpanded range. }
|
||||
TSetRangeExpr = class(TASTExpr)
|
||||
public
|
||||
LowExpr: TASTExpr; { owned }
|
||||
HighExpr: TASTExpr; { owned }
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
TNilLiteral = class(TASTExpr); { nil keyword — type is tyNil }
|
||||
|
||||
TParamMode = (
|
||||
|
|
@ -1236,6 +1249,12 @@ begin
|
|||
inherited Destroy();
|
||||
end;
|
||||
|
||||
destructor TSetRangeExpr.Destroy;
|
||||
begin
|
||||
{ LowExpr/HighExpr released by ARC field cleanup. }
|
||||
inherited Destroy();
|
||||
end;
|
||||
|
||||
{ TProcCall }
|
||||
|
||||
constructor TProcCall.Create;
|
||||
|
|
@ -1718,6 +1737,7 @@ var
|
|||
SL: TStringLiteral;
|
||||
SS: TStringSubscriptExpr;
|
||||
AL: TArrayLiteralExpr;
|
||||
SRE: TSetRangeExpr;
|
||||
IE: TIdentExpr;
|
||||
FA: TFieldAccessExpr;
|
||||
IsE: TIsExpr;
|
||||
|
|
@ -1771,6 +1791,13 @@ begin
|
|||
AL.IsConstArray := TArrayLiteralExpr(AExpr).IsConstArray;
|
||||
Result := AL;
|
||||
end
|
||||
else if AExpr is TSetRangeExpr then
|
||||
begin
|
||||
SRE := TSetRangeExpr.Create();
|
||||
SRE.LowExpr := CloneExpr(TSetRangeExpr(AExpr).LowExpr);
|
||||
SRE.HighExpr := CloneExpr(TSetRangeExpr(AExpr).HighExpr);
|
||||
Result := SRE;
|
||||
end
|
||||
else if AExpr is TIdentExpr then
|
||||
begin
|
||||
IE := TIdentExpr.Create();
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ type
|
|||
function ParseForwardDecl(IsFunction: Boolean): TMethodDecl;
|
||||
function ParseCompoundStmt: TCompoundStmt;
|
||||
function ParseExpr: TASTExpr;
|
||||
function ParseSetOrArrayElement: TASTExpr;
|
||||
function ParseAddSub: TASTExpr;
|
||||
function ParseTerm: TASTExpr;
|
||||
function ParseFactor: TASTExpr;
|
||||
|
|
@ -3620,6 +3621,28 @@ end;
|
|||
{ Expression parsing — standard precedence climbing }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
{ Parse one element of a bracket literal [ ... ]. An ordinary element is a
|
||||
plain expression; a `lo..hi` form (set range, issue #105) becomes a
|
||||
TSetRangeExpr that uSemantic expands into individual set members. The
|
||||
range form is only meaningful for set literals — uSemantic rejects it in
|
||||
array-literal context. }
|
||||
function TParser.ParseSetOrArrayElement: TASTExpr;
|
||||
var
|
||||
LowE: TASTExpr;
|
||||
Range: TSetRangeExpr;
|
||||
begin
|
||||
LowE := Self.ParseExpr();
|
||||
if not Check(tkDotDot) then
|
||||
Exit(LowE);
|
||||
Range := TSetRangeExpr.Create();
|
||||
Range.Line := LowE.Line;
|
||||
Range.Col := LowE.Col;
|
||||
Range.LowExpr := LowE;
|
||||
Advance(); { consume '..' }
|
||||
Range.HighExpr := Self.ParseExpr();
|
||||
Result := Range;
|
||||
end;
|
||||
|
||||
function TParser.ParseExpr: TASTExpr;
|
||||
var
|
||||
Right: TASTExpr;
|
||||
|
|
@ -4125,11 +4148,11 @@ begin
|
|||
Advance(); { consume '[' }
|
||||
if not Check(tkRBracket) then
|
||||
begin
|
||||
ArrNode.Elements.Add(ParseExpr());
|
||||
ArrNode.Elements.Add(Self.ParseSetOrArrayElement());
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance(); { consume ',' }
|
||||
ArrNode.Elements.Add(ParseExpr());
|
||||
ArrNode.Elements.Add(Self.ParseSetOrArrayElement());
|
||||
end;
|
||||
end;
|
||||
Expect(tkRBracket);
|
||||
|
|
|
|||
|
|
@ -240,6 +240,11 @@ type
|
|||
function AnalyseStringSubscriptExpr(AExpr: TStringSubscriptExpr): TTypeDesc;
|
||||
function AnalyseArrayLiteralExpr(AExpr: TArrayLiteralExpr): TTypeDesc;
|
||||
function AnalyseSetLiteralExpr(AExpr: TArrayLiteralExpr; ASetType: TSetTypeDesc): TTypeDesc;
|
||||
{ Expand [lo..hi] range elements of a set literal into individual member
|
||||
idents (issue #105). Constant ascending ranges only. }
|
||||
procedure ExpandSetRanges(AExpr: TArrayLiteralExpr; ABaseEnum: TEnumTypeDesc);
|
||||
function SetRangeBoundOrdinal(ABound: TASTExpr; ABaseEnum: TEnumTypeDesc;
|
||||
const AWhich: string): Integer;
|
||||
{ Width (in bits) needed for the anonymous set type of an 'X in [a,b,c]'
|
||||
literal: largest listed member ordinal + 1 when every element is a
|
||||
compile-time enum constant; otherwise the full base-enum member count
|
||||
|
|
@ -10228,15 +10233,127 @@ begin
|
|||
Result := MaxOrd + 1;
|
||||
end;
|
||||
|
||||
function TSemanticAnalyser.SetRangeBoundOrdinal(ABound: TASTExpr;
|
||||
ABaseEnum: TEnumTypeDesc; const AWhich: string): Integer;
|
||||
{ Resolve a set-range bound (lo or hi) to a compile-time ordinal of the
|
||||
base enum. Rejects non-constant and wrong-type bounds. Issue #105. }
|
||||
var
|
||||
BType: TTypeDesc;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
BType := AnalyseExpr(ABound);
|
||||
if BType <> ABaseEnum then
|
||||
SemanticError(
|
||||
Format('Set range %s bound has type ''%s''; expected ''%s''',
|
||||
[AWhich, BType.Name, ABaseEnum.Name]),
|
||||
ABound.Line, ABound.Col);
|
||||
if not ((ABound is TIdentExpr) and TIdentExpr(ABound).IsConstant) then
|
||||
SemanticError(
|
||||
Format('Set range %s bound must be a constant', [AWhich]),
|
||||
ABound.Line, ABound.Col);
|
||||
Sym := FTable.Lookup(TIdentExpr(ABound).Name);
|
||||
if (Sym = nil) or (Sym.Kind <> skConstant) then
|
||||
SemanticError(
|
||||
Format('Set range %s bound ''%s'' is not a constant',
|
||||
[AWhich, TIdentExpr(ABound).Name]),
|
||||
ABound.Line, ABound.Col);
|
||||
Result := Sym.ConstValue;
|
||||
end;
|
||||
|
||||
procedure TSemanticAnalyser.ExpandSetRanges(AExpr: TArrayLiteralExpr;
|
||||
ABaseEnum: TEnumTypeDesc);
|
||||
{ Replace every TSetRangeExpr element [lo..hi] with the individual member
|
||||
idents lo, lo+1, ..., hi. Constant ascending ranges only; a reversed
|
||||
range is a compile-time error (not a silent empty set). Runs before the
|
||||
element validation loop so the rest of the pipeline only ever sees plain
|
||||
member idents. Issue #105. }
|
||||
var
|
||||
I, J: Integer;
|
||||
Elem: TASTExpr;
|
||||
Range: TSetRangeExpr;
|
||||
LoOrd: Integer;
|
||||
HiOrd: Integer;
|
||||
NewList: TObjectList;
|
||||
MemberId: TIdentExpr;
|
||||
begin
|
||||
{ Quick scan — nothing to do if there are no ranges. }
|
||||
J := -1;
|
||||
for I := 0 to AExpr.Elements.Count - 1 do
|
||||
if TASTExpr(AExpr.Elements.Items[I]) is TSetRangeExpr then
|
||||
begin
|
||||
J := I;
|
||||
Break;
|
||||
end;
|
||||
if J < 0 then Exit;
|
||||
|
||||
if ABaseEnum = nil then
|
||||
SemanticError('Set range requires an enumeration base type',
|
||||
AExpr.Line, AExpr.Col);
|
||||
|
||||
{ Build a fresh element list, expanding ranges into members. NewList does
|
||||
NOT own its items — ownership of plain elements transfers from the old
|
||||
list, and the generated member idents are handed to it too; the swap
|
||||
below frees the old list (with the consumed ranges) without touching the
|
||||
surviving plain elements. }
|
||||
NewList := TObjectList.Create(False);
|
||||
try
|
||||
for I := 0 to AExpr.Elements.Count - 1 do
|
||||
begin
|
||||
Elem := TASTExpr(AExpr.Elements.Items[I]);
|
||||
if not (Elem is TSetRangeExpr) then
|
||||
begin
|
||||
NewList.Add(Elem);
|
||||
Continue;
|
||||
end;
|
||||
Range := TSetRangeExpr(Elem);
|
||||
LoOrd := SetRangeBoundOrdinal(Range.LowExpr, ABaseEnum, 'low');
|
||||
HiOrd := SetRangeBoundOrdinal(Range.HighExpr, ABaseEnum, 'high');
|
||||
if HiOrd < LoOrd then
|
||||
SemanticError(
|
||||
Format('Set range is reversed (low ordinal %d > high ordinal %d) — ' +
|
||||
'an empty range is almost always a mistake; use [] for an ' +
|
||||
'empty set', [LoOrd, HiOrd]),
|
||||
Range.Line, Range.Col);
|
||||
for J := LoOrd to HiOrd do
|
||||
begin
|
||||
MemberId := TIdentExpr.Create();
|
||||
MemberId.Line := Range.Line;
|
||||
MemberId.Col := Range.Col;
|
||||
MemberId.Name := ABaseEnum.Members.Strings[J];
|
||||
MemberId.IsConstant := True;
|
||||
MemberId.ConstValue := J;
|
||||
MemberId.ResolvedType := ABaseEnum;
|
||||
NewList.Add(MemberId);
|
||||
end;
|
||||
end;
|
||||
{ Transfer expanded elements into a new owning list and swap. The old
|
||||
Elements list owns its items; detach the survivors first so freeing it
|
||||
only disposes the consumed TSetRangeExpr nodes. }
|
||||
for I := AExpr.Elements.Count - 1 downto 0 do
|
||||
if not (TASTExpr(AExpr.Elements.Items[I]) is TSetRangeExpr) then
|
||||
AExpr.Elements.Extract(AExpr.Elements.Items[I]);
|
||||
AExpr.Elements.Clear(); { frees the remaining TSetRangeExpr nodes }
|
||||
for I := 0 to NewList.Count - 1 do
|
||||
AExpr.Elements.Add(NewList.Items[I]);
|
||||
finally
|
||||
NewList.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
function TSemanticAnalyser.AnalyseSetLiteralExpr(AExpr: TArrayLiteralExpr;
|
||||
ASetType: TSetTypeDesc): TTypeDesc;
|
||||
{ Validates a set literal [elem, ...] against ASetType.
|
||||
Elements must be constants or variables of the set's base enum type.
|
||||
Range elements [lo..hi] are expanded to members first (issue #105).
|
||||
An empty literal [] is valid and yields the set type with bitmask 0. }
|
||||
var
|
||||
ElemType: TTypeDesc;
|
||||
I: Integer;
|
||||
begin
|
||||
if ASetType.BaseType is TEnumTypeDesc then
|
||||
ExpandSetRanges(AExpr, TEnumTypeDesc(ASetType.BaseType))
|
||||
else
|
||||
ExpandSetRanges(AExpr, nil);
|
||||
for I := 0 to AExpr.Elements.Count - 1 do
|
||||
begin
|
||||
ElemType := AnalyseExpr(TASTExpr(AExpr.Elements.Items[I]));
|
||||
|
|
|
|||
|
|
@ -714,6 +714,10 @@ begin
|
|||
else if AE is TArrayLiteralExpr then
|
||||
Result := EncodeLpstr('alit') +
|
||||
EncodeExprList(TArrayLiteralExpr(AE).Elements)
|
||||
else if AE is TSetRangeExpr then
|
||||
Result := EncodeLpstr('srng') +
|
||||
EncodeExpr(TSetRangeExpr(AE).LowExpr) +
|
||||
EncodeExpr(TSetRangeExpr(AE).HighExpr)
|
||||
else if AE is TIsExpr then
|
||||
Result := EncodeLpstr('isop') +
|
||||
EncodeExpr(TIsExpr(AE).Obj) +
|
||||
|
|
@ -1281,6 +1285,7 @@ var
|
|||
AoE: TAddrOfExpr;
|
||||
SS: TStringSubscriptExpr;
|
||||
AL: TArrayLiteralExpr;
|
||||
SRE: TSetRangeExpr;
|
||||
IsE: TIsExpr;
|
||||
AsE: TAsExpr;
|
||||
SuE: TSupportsExpr;
|
||||
|
|
@ -1385,6 +1390,13 @@ begin
|
|||
ReadExprList(AText, APos, AL.Elements);
|
||||
Result := AL;
|
||||
end
|
||||
else if Kind = 'srng' then
|
||||
begin
|
||||
SRE := TSetRangeExpr.Create();
|
||||
SRE.LowExpr := ReadExpr(AText, APos);
|
||||
SRE.HighExpr := ReadExpr(AText, APos);
|
||||
Result := SRE;
|
||||
end
|
||||
else if Kind = 'isop' then
|
||||
begin
|
||||
IsE := TIsExpr.Create();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ type
|
|||
procedure TestRun_Set_LiteralArgument;
|
||||
procedure TestRun_Set_EqualityWithLiteral;
|
||||
procedure TestRun_Set_ForIn_PrintsMembers;
|
||||
procedure TestRun_Set_RangeLiteral_Membership;
|
||||
|
||||
{ 33..64-member sets (the QBE 'l' / 64-bit register boundary) }
|
||||
procedure TestRun_Set64_InOperator_HighBit;
|
||||
|
|
@ -168,6 +169,27 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
{ Set range literal (issue #105): [m1..m3] and a mixed [m0, m2..m4, m7]. }
|
||||
SrcSetRange = '''
|
||||
program Prg;
|
||||
type
|
||||
TC = (m0, m1, m2, m3, m4, m5, m6, m7);
|
||||
TCS = set of TC;
|
||||
var
|
||||
e: TCS;
|
||||
begin
|
||||
e := [m1..m3];
|
||||
if m0 in e then WriteLn('y') else WriteLn('n');
|
||||
if m2 in e then WriteLn('y') else WriteLn('n');
|
||||
if m3 in e then WriteLn('y') else WriteLn('n');
|
||||
if m4 in e then WriteLn('y') else WriteLn('n');
|
||||
e := [m0, m2..m4, m7];
|
||||
if m1 in e then WriteLn('y') else WriteLn('n');
|
||||
if m2 in e then WriteLn('y') else WriteLn('n');
|
||||
if m7 in e then WriteLn('y') else WriteLn('n');
|
||||
end.
|
||||
''';
|
||||
|
||||
BigEnum64 =
|
||||
'''
|
||||
type
|
||||
|
|
@ -293,6 +315,15 @@ begin
|
|||
AssertRunsOnAll(SrcForInSet, '0' + LE + '2' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2ESetOpsTests.TestRun_Set_RangeLiteral_Membership;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
{ [m1..m3]: m0=n m2=y m3=y m4=n; [m0,m2..m4,m7]: m1=n m2=y m7=y }
|
||||
AssertRunsOnAll(SrcSetRange,
|
||||
'n' + LE + 'y' + LE + 'y' + LE + 'n' + LE +
|
||||
'n' + LE + 'y' + LE + 'y' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2ESetOpsTests.TestRun_Set64_InOperator_HighBit;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,20 @@ type
|
|||
procedure TestSemantic_Set_BaseTypeMustBeEnum;
|
||||
procedure TestSemantic_Set_LiteralElementMustMatchBase;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ ranges in set literals — [lo..hi] (issue #105) }
|
||||
{ ------------------------------------------------------------------ }
|
||||
procedure TestParse_Set_RangeLiteral;
|
||||
procedure TestSemantic_Set_RangeLiteral_OK;
|
||||
procedure TestSemantic_Set_RangeMixedWithSingles_OK;
|
||||
procedure TestSemantic_Set_RangeEnum_OK;
|
||||
procedure TestSemantic_Set_RangeReversed_Fails;
|
||||
procedure TestSemantic_Set_RangeNonConstBound_Fails;
|
||||
procedure TestSemantic_Set_RangeWrongBaseType_Fails;
|
||||
procedure TestCodegen_Set_RangeExpandsToBitmask;
|
||||
procedure TestCodegen_Set_RangeSingleElement;
|
||||
procedure TestCodegen_Set_RangeMixed;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ codegen }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
@ -839,6 +853,102 @@ begin
|
|||
SemanticFail(SrcSetBadLiteralElement);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ ranges in set literals — [lo..hi] (issue #105) }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
{ Blaise set base types are enumerations (set of byte is a separate, tracked
|
||||
feature — see docs/future-improvements.adoc). Use an enum with enough
|
||||
members to exercise the ranges below. }
|
||||
const
|
||||
SetEnumDecl =
|
||||
'type TC = (m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10); ' +
|
||||
'TCS = set of TC; ';
|
||||
SrcSetRange =
|
||||
'program P; ' + SetEnumDecl + 'var e: TCS; ' +
|
||||
'begin e := [m1..m3]; end.';
|
||||
SrcSetRangeMixed =
|
||||
'program P; ' + SetEnumDecl + 'var e: TCS; ' +
|
||||
'begin e := [m1, m5..m7, m10]; end.';
|
||||
SrcSetRangeEnum =
|
||||
'program P; type TC = (Red, Green, Blue, Yellow); TCS = set of TC; var e: TCS; ' +
|
||||
'begin e := [Red..Blue]; end.';
|
||||
SrcSetRangeReversed =
|
||||
'program P; ' + SetEnumDecl + 'var e: TCS; ' +
|
||||
'begin e := [m5..m3]; end.';
|
||||
SrcSetRangeNonConst =
|
||||
'program P; ' + SetEnumDecl + 'var e: TCS; lo, hi: TC; ' +
|
||||
'begin lo := m1; hi := m4; e := [lo..hi]; end.';
|
||||
SrcSetRangeWrongBase =
|
||||
'program P; type TC = (Red, Green); TCS = set of TC; ' +
|
||||
'TD = (xa, xb, xc); var e: TCS; ' +
|
||||
'begin e := [xa..xb]; end.';
|
||||
|
||||
procedure TSetTests.TestParse_Set_RangeLiteral;
|
||||
begin
|
||||
ParseOK(SrcSetRange);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_RangeLiteral_OK;
|
||||
begin
|
||||
SemanticOK(SrcSetRange);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_RangeMixedWithSingles_OK;
|
||||
begin
|
||||
SemanticOK(SrcSetRangeMixed);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_RangeEnum_OK;
|
||||
begin
|
||||
SemanticOK(SrcSetRangeEnum);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_RangeReversed_Fails;
|
||||
begin
|
||||
{ A constant reverse range [5..3] is a mistake, not a silent empty set. }
|
||||
SemanticFail(SrcSetRangeReversed);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_RangeNonConstBound_Fails;
|
||||
begin
|
||||
{ Variable bounds are not supported — both ends must be constant. }
|
||||
SemanticFail(SrcSetRangeNonConst);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_RangeWrongBaseType_Fails;
|
||||
begin
|
||||
SemanticFail(SrcSetRangeWrongBase);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestCodegen_Set_RangeExpandsToBitmask;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ [1..3] → bits 1,2,3 → mask = 2 + 4 + 8 = 14 → "copy 14" }
|
||||
IR := GenIR(SrcSetRange);
|
||||
AssertTrue('range [1..3] folds to mask 14', Pos('copy 14', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestCodegen_Set_RangeSingleElement;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ [m3..m3] → just bit 3 → mask 8 }
|
||||
IR := GenIR('program P; ' + SetEnumDecl + 'var e: TCS; ' +
|
||||
'begin e := [m3..m3]; end.');
|
||||
AssertTrue('range [m3..m3] folds to mask 8', Pos('copy 8', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestCodegen_Set_RangeMixed;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ [1, 5..7, 10] → bits 1,5,6,7,10 → 2+32+64+128+1024 = 1250 }
|
||||
IR := GenIR(SrcSetRangeMixed);
|
||||
AssertTrue('mixed range folds to mask 1250', Pos('copy 1250', IR) > 0);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ codegen }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
|
|
@ -34,6 +34,25 @@ prioritise them.
|
|||
- reference to function/procedure (anonymous methods)
|
||||
- cdecl/stdcall calling-convention markers on procedural types
|
||||
|
||||
=== Ordinal-based set types (`set of byte`, `set of 0..255`)
|
||||
|
||||
Set types currently require an enumeration base type; `set of byte` (and,
|
||||
more generally, a set over an integer subrange) is rejected with
|
||||
"Set base type 'byte' must be an enumeration type". FPC and Delphi accept
|
||||
ordinal base types, so `set of byte` is standard Pascal.
|
||||
|
||||
Supporting it means teaching the set machinery to treat an ordinal range as
|
||||
an implicit member set: the bit count is the range width (0..255 → 256 bits,
|
||||
the jumbo-set path), element ordinals are the integer values themselves, and
|
||||
membership/include/exclude lower exactly as they do for enum sets. The work
|
||||
spans the semantic pass (base-type validation, bit-count derivation) and both
|
||||
codegen backends, including the jumbo-set path for `set of byte`.
|
||||
|
||||
This was surfaced by issue #105, whose example used `set of byte`. The set
|
||||
range syntax `[lo..hi]` requested by that issue was implemented independently
|
||||
(see `docs/language-rationale.adoc`) and is base-type-agnostic — ranges will
|
||||
work for ordinal sets automatically once this lands.
|
||||
|
||||
=== Method level attributes
|
||||
|
||||
Blaise only has class-level attribute RTTI today — there's no per-method attribute reflection, and adding it is a layout-changing compiler change (new method-table slot, stage-2 rebuild).
|
||||
|
|
|
|||
|
|
@ -695,9 +695,20 @@ ProceduralType
|
|||
The semantic pass distinguishes them by assignment-target type context:
|
||||
when the LHS is a set type, LBRACKET ExprList RBRACKET is a set literal;
|
||||
otherwise it is an array literal. An empty LBRACKET RBRACKET is valid
|
||||
only as a set literal (the empty set). *)
|
||||
only as a set literal (the empty set).
|
||||
|
||||
A set element may be a single value or a range `lo .. hi` (issue #105).
|
||||
Both range bounds must be compile-time constants of the set's base type;
|
||||
the semantic pass expands the range into its individual members and
|
||||
rejects a reversed range (low ordinal > high ordinal). The range form is
|
||||
meaningful only in set context — in array-literal context DOTDOT is a
|
||||
syntax error. *)
|
||||
SetLiteral
|
||||
= LBRACKET [ Expr { COMMA Expr } ] RBRACKET
|
||||
= LBRACKET [ SetElement { COMMA SetElement } ] RBRACKET
|
||||
;
|
||||
|
||||
SetElement
|
||||
= Expr [ DOTDOT Expr ]
|
||||
;
|
||||
|
||||
TypeArgList
|
||||
|
|
|
|||
|
|
@ -4277,3 +4277,63 @@ identifier.
|
|||
* *Restrict the rule to reference types (classes, interfaces) only.* Rejected
|
||||
as arbitrary — the confusion is identical for records, enums, aliases, and
|
||||
built-ins, and a name-based rule is simpler to explain than a kind-based one.
|
||||
|
||||
== Set Literal Ranges
|
||||
|
||||
=== Decision
|
||||
|
||||
A set literal element may be a range `lo..hi`, expanding to every member from
|
||||
`lo` to `hi` inclusive:
|
||||
|
||||
[source,pascal]
|
||||
----
|
||||
type
|
||||
TColor = (Red, Green, Blue, Yellow, Cyan);
|
||||
TColorSet = set of TColor;
|
||||
var
|
||||
e: TColorSet;
|
||||
begin
|
||||
e := [Red..Blue]; // {Red, Green, Blue}
|
||||
e := [Red, Blue..Cyan]; // {Red, Blue, Yellow, Cyan} — ranges mix with singles
|
||||
end;
|
||||
----
|
||||
|
||||
Both bounds must be compile-time constants of the set's base type. A reversed
|
||||
range (`[Blue..Red]`, low ordinal greater than high) is a compile-time error,
|
||||
not a silently empty set. Variable bounds (`[lo..hi]` where `lo`/`hi` are
|
||||
variables) are rejected with "set range bound must be a constant".
|
||||
|
||||
This matches the range form FPC and Delphi accept in set constructors, with two
|
||||
deliberate restrictions (see below).
|
||||
|
||||
=== Rationale
|
||||
|
||||
`[1..10]` (or `[Red..Cyan]`) is far clearer than spelling out every member, and
|
||||
the construct is standard Pascal — the reporter of issue #105 reasonably
|
||||
expected it to work. The implementation expands each range into individual
|
||||
member nodes during semantic analysis, so the rest of the pipeline — overload
|
||||
resolution, the bitmask folder, the jumbo-set path, and both code generators —
|
||||
sees an ordinary member list and needs no range-specific logic. A transient
|
||||
`TSetRangeExpr` AST node carries the unexpanded range from the parser to that
|
||||
expansion point; it is also wired into the `.bif` serialiser and the AST cloner
|
||||
because a generic template body may contain an unexpanded range that is
|
||||
serialised and re-instantiated later.
|
||||
|
||||
=== Alternatives considered
|
||||
|
||||
* *Treat a reversed constant range as an empty set (FPC behaviour).* Rejected.
|
||||
FPC accepts `[5..3]` as empty (with a warning); when both bounds are
|
||||
compile-time constants there is no legitimate reason to write a reversed
|
||||
range — it is a typo, and `[]` already expresses the empty set
|
||||
unambiguously. Erroring matches Blaise's preference for rejecting
|
||||
confusing-but-legal constructs (cf. variable/type name clashes).
|
||||
* *Support variable range bounds `[lo..hi]`.* Deferred. Runtime-variable
|
||||
bounds require emitting a bit-setting loop in both backends across the
|
||||
small-set and jumbo-set paths; the constant form covers the reported case and
|
||||
the overwhelmingly common usage. The restriction is enforced with a clear
|
||||
diagnostic rather than silently mis-compiling.
|
||||
* *Support ranges over an ordinal base type (`set of byte`, `[1..3]`).* Out of
|
||||
scope: Blaise set base types are enumerations today. Ordinal-based set types
|
||||
are tracked separately (see `docs/future-improvements.adoc`); because range
|
||||
expansion is base-type-agnostic, `[1..3]` will work automatically once they
|
||||
land.
|
||||
|
|
|
|||
|
|
@ -24,9 +24,13 @@ TStringSubscriptExpr.IndexExpr serialise
|
|||
TArrayLiteralExpr.Elements serialise
|
||||
TArrayLiteralExpr.IsConstArray safe
|
||||
|
||||
# TNilLiteral (uAST.pas:74)
|
||||
# TSetRangeExpr (uAST.pas:80)
|
||||
TSetRangeExpr.LowExpr serialise
|
||||
TSetRangeExpr.HighExpr serialise
|
||||
|
||||
# TIdentExpr (uAST.pas:83)
|
||||
# TNilLiteral (uAST.pas:87)
|
||||
|
||||
# TIdentExpr (uAST.pas:97)
|
||||
TIdentExpr.Name serialise
|
||||
TIdentExpr.ParamMode safe
|
||||
TIdentExpr.IsConstant safe
|
||||
|
|
@ -39,7 +43,7 @@ TIdentExpr.IsImplicitSelfMethod safe
|
|||
TIdentExpr.IsMetaclassRef safe
|
||||
TIdentExpr.ConstArraySymbol safe
|
||||
|
||||
# TFieldAccessExpr (uAST.pas:104)
|
||||
# TFieldAccessExpr (uAST.pas:118)
|
||||
TFieldAccessExpr.RecordName serialise
|
||||
TFieldAccessExpr.FieldName serialise
|
||||
TFieldAccessExpr.Base serialise
|
||||
|
|
@ -64,29 +68,29 @@ TFieldAccessExpr.PropIndexExpr serialise
|
|||
TFieldAccessExpr.IsCharAccess safe
|
||||
TFieldAccessExpr.IsArrayAccess safe
|
||||
|
||||
# TIsExpr (uAST.pas:136)
|
||||
# TIsExpr (uAST.pas:150)
|
||||
TIsExpr.Obj serialise
|
||||
TIsExpr.TypeName serialise
|
||||
|
||||
# TAsExpr (uAST.pas:144)
|
||||
# TAsExpr (uAST.pas:158)
|
||||
TAsExpr.Obj serialise
|
||||
TAsExpr.TypeName serialise
|
||||
|
||||
# TSupportsExpr (uAST.pas:153)
|
||||
# TSupportsExpr (uAST.pas:167)
|
||||
TSupportsExpr.Obj serialise
|
||||
TSupportsExpr.IntfTypeName serialise
|
||||
TSupportsExpr.OutVarName serialise
|
||||
TSupportsExpr.OutVarIsGlobal safe
|
||||
|
||||
# TBinaryExpr (uAST.pas:169)
|
||||
# TBinaryExpr (uAST.pas:183)
|
||||
TBinaryExpr.Op serialise
|
||||
TBinaryExpr.Left serialise
|
||||
TBinaryExpr.Right serialise
|
||||
|
||||
# TNotExpr (uAST.pas:177)
|
||||
# TNotExpr (uAST.pas:191)
|
||||
TNotExpr.Expr serialise
|
||||
|
||||
# TAssignment (uAST.pas:189)
|
||||
# TAssignment (uAST.pas:203)
|
||||
TAssignment.Name serialise
|
||||
TAssignment.Expr serialise
|
||||
TAssignment.IsVarParam safe
|
||||
|
|
@ -95,23 +99,23 @@ TAssignment.IsThreadVar safe
|
|||
TAssignment.IsWeakLhs safe
|
||||
TAssignment.ImplicitSelfField safe
|
||||
|
||||
# TIfStmt (uAST.pas:205)
|
||||
# TIfStmt (uAST.pas:219)
|
||||
TIfStmt.Condition serialise
|
||||
TIfStmt.ThenStmt serialise
|
||||
TIfStmt.ElseStmt serialise
|
||||
|
||||
# TCompoundStmt (uAST.pas:213)
|
||||
# TCompoundStmt (uAST.pas:227)
|
||||
TCompoundStmt.Stmts serialise
|
||||
|
||||
# TWhileStmt (uAST.pas:220)
|
||||
# TWhileStmt (uAST.pas:234)
|
||||
TWhileStmt.Condition serialise
|
||||
TWhileStmt.Body serialise
|
||||
|
||||
# TRepeatStmt (uAST.pas:227)
|
||||
# TRepeatStmt (uAST.pas:241)
|
||||
TRepeatStmt.Body serialise
|
||||
TRepeatStmt.Condition serialise
|
||||
|
||||
# TForStmt (uAST.pas:234)
|
||||
# TForStmt (uAST.pas:248)
|
||||
TForStmt.VarName serialise
|
||||
TForStmt.IsGlobal safe
|
||||
TForStmt.StartExpr serialise
|
||||
|
|
@ -119,7 +123,7 @@ TForStmt.EndExpr serialise
|
|||
TForStmt.IsDownTo serialise
|
||||
TForStmt.Body serialise
|
||||
|
||||
# TForInStmt (uAST.pas:245)
|
||||
# TForInStmt (uAST.pas:259)
|
||||
TForInStmt.VarName serialise
|
||||
TForInStmt.VarIsGlobal safe
|
||||
TForInStmt.CollExpr serialise
|
||||
|
|
@ -139,34 +143,34 @@ TForInStmt.SetBitCount safe
|
|||
TForInStmt.SetMaskVarName safe
|
||||
TForInStmt.SetIsJumbo safe
|
||||
|
||||
# TTryFinallyStmt (uAST.pas:282)
|
||||
# TTryFinallyStmt (uAST.pas:296)
|
||||
TTryFinallyStmt.TryBody serialise
|
||||
TTryFinallyStmt.FinallyBody serialise
|
||||
|
||||
# TTryExceptStmt (uAST.pas:299)
|
||||
# TTryExceptStmt (uAST.pas:313)
|
||||
TTryExceptStmt.TryBody serialise
|
||||
TTryExceptStmt.Handlers serialise
|
||||
TTryExceptStmt.ElseBody serialise
|
||||
TTryExceptStmt.ExceptBody serialise
|
||||
|
||||
# TRaiseStmt (uAST.pas:312)
|
||||
# TRaiseStmt (uAST.pas:326)
|
||||
TRaiseStmt.Expr serialise
|
||||
|
||||
# TExitStmt (uAST.pas:323)
|
||||
# TExitStmt (uAST.pas:337)
|
||||
TExitStmt.Value safe
|
||||
TExitStmt.ResultAssign safe
|
||||
|
||||
# TBreakStmt (uAST.pas:331)
|
||||
# TBreakStmt (uAST.pas:345)
|
||||
|
||||
# TContinueStmt (uAST.pas:334)
|
||||
# TContinueStmt (uAST.pas:348)
|
||||
|
||||
# TCaseStmt (uAST.pas:345)
|
||||
# TCaseStmt (uAST.pas:359)
|
||||
TCaseStmt.Selector serialise
|
||||
TCaseStmt.Branches serialise
|
||||
TCaseStmt.ElseStmt serialise
|
||||
TCaseStmt.IsStringCase safe
|
||||
|
||||
# TFieldAssignment (uAST.pas:356)
|
||||
# TFieldAssignment (uAST.pas:370)
|
||||
TFieldAssignment.RecordName serialise
|
||||
TFieldAssignment.FieldName serialise
|
||||
TFieldAssignment.Expr serialise
|
||||
|
|
@ -180,7 +184,7 @@ TFieldAssignment.PropIndexExpr serialise
|
|||
TFieldAssignment.PropOwnerType safe
|
||||
TFieldAssignment.IsElemWrite safe
|
||||
|
||||
# TStaticSubscriptAssign (uAST.pas:383)
|
||||
# TStaticSubscriptAssign (uAST.pas:397)
|
||||
TStaticSubscriptAssign.ArrayName serialise
|
||||
TStaticSubscriptAssign.IndexExpr serialise
|
||||
TStaticSubscriptAssign.ValueExpr serialise
|
||||
|
|
@ -189,18 +193,18 @@ TStaticSubscriptAssign.IsGlobal safe
|
|||
TStaticSubscriptAssign.IsVarParam safe
|
||||
TStaticSubscriptAssign.IsImplicitSelf safe
|
||||
|
||||
# TPointerWriteStmt (uAST.pas:406)
|
||||
# TPointerWriteStmt (uAST.pas:420)
|
||||
TPointerWriteStmt.PtrExpr serialise
|
||||
TPointerWriteStmt.ValExpr serialise
|
||||
|
||||
# TProcCall (uAST.pas:414)
|
||||
# TProcCall (uAST.pas:428)
|
||||
TProcCall.Name serialise
|
||||
TProcCall.Args serialise
|
||||
TProcCall.IsImplicitSelfMethod safe
|
||||
TProcCall.IsIndirectCall safe
|
||||
TProcCall.IndirectCallIsGlobal safe
|
||||
|
||||
# TFuncCallExpr (uAST.pas:427)
|
||||
# TFuncCallExpr (uAST.pas:441)
|
||||
TFuncCallExpr.Name serialise
|
||||
TFuncCallExpr.Args serialise
|
||||
TFuncCallExpr.IsImplicitSelfMethod safe
|
||||
|
|
@ -210,18 +214,18 @@ TFuncCallExpr.IsBuiltinHasClassAttr safe
|
|||
TFuncCallExpr.HasClassAttrClass safe
|
||||
TFuncCallExpr.HasClassAttrAttr safe
|
||||
|
||||
# TIndirectFuncCallExpr (uAST.pas:453)
|
||||
# TIndirectFuncCallExpr (uAST.pas:467)
|
||||
TIndirectFuncCallExpr.CalleeExpr serialise
|
||||
TIndirectFuncCallExpr.Args serialise
|
||||
|
||||
# TDerefExpr (uAST.pas:463)
|
||||
# TDerefExpr (uAST.pas:477)
|
||||
TDerefExpr.Expr serialise
|
||||
|
||||
# TAddrOfExpr (uAST.pas:470)
|
||||
# TAddrOfExpr (uAST.pas:484)
|
||||
TAddrOfExpr.Expr serialise
|
||||
TAddrOfExpr.ResolvedFreeRoutine safe
|
||||
|
||||
# TMethodCallStmt (uAST.pas:482)
|
||||
# TMethodCallStmt (uAST.pas:496)
|
||||
TMethodCallStmt.ObjectName serialise
|
||||
TMethodCallStmt.Name serialise
|
||||
TMethodCallStmt.Args serialise
|
||||
|
|
@ -232,11 +236,11 @@ TMethodCallStmt.IsVarParam safe
|
|||
TMethodCallStmt.IsBuiltinToString safe
|
||||
TMethodCallStmt.IsProcFieldCall safe
|
||||
|
||||
# TInheritedCallStmt (uAST.pas:514)
|
||||
# TInheritedCallStmt (uAST.pas:528)
|
||||
TInheritedCallStmt.Name serialise
|
||||
TInheritedCallStmt.Args serialise
|
||||
|
||||
# TMethodCallExpr (uAST.pas:789)
|
||||
# TMethodCallExpr (uAST.pas:803)
|
||||
TMethodCallExpr.ObjectName serialise
|
||||
TMethodCallExpr.Name serialise
|
||||
TMethodCallExpr.Args serialise
|
||||
|
|
|
|||
Loading…
Reference in a new issue