diff --git a/compiler/src/main/pascal/uParser.pas b/compiler/src/main/pascal/uParser.pas index 737cd0e..d26cf20 100644 --- a/compiler/src/main/pascal/uParser.pas +++ b/compiler/src/main/pascal/uParser.pas @@ -1208,6 +1208,7 @@ procedure TParser.ParseConstValue(CD: TConstDecl); var CastVal: Int64; FirstOperand: string; + ElemStr: string; begin { Array const value list: flat (e, e, ...) or nested ((..),(..)). } if CD.IsArrayConst then @@ -1321,20 +1322,40 @@ begin else if Check(tkLBracket) then begin { Set-valued constant: const Name [: SetType] = [member, member, ...] - or the empty set []. Members are enum-constant identifiers; semantic - resolves their ordinals and folds the bitmask. } + or the empty set []. A member is an enum-constant identifier, an integer + literal, or an inclusive range `lo..hi` (either endpoint an identifier or + an integer literal). Each member is stored as a string in SetElements; a + range is stored as the two endpoints joined by '..' so the semantic pass + (AnalyseSetConstDecl) can resolve and expand it. Semantic folds the + bitmask. } CD.IsSet := True; CD.SetElements := TStringList.Create(); Advance(); { consume '[' } if not Check(tkRBracket) then while True do begin - if not Check(tkIdent) then + if Check(tkIdent) then + ElemStr := FCurrent.Value + else if Check(tkIntLit) then + ElemStr := FCurrent.Value + else raise EParseError.Create(Format( - 'Expected set member identifier at line %d col %d in %s', + 'Expected set member (identifier or integer) at line %d col %d in %s', [FCurrent.Line, FCurrent.Col, FLexer.Filename])); - CD.SetElements.Add(FCurrent.Value); Advance(); + { Optional range continuation: lo..hi. } + if Check(tkDotDot) then + begin + Advance(); + if Check(tkIdent) or Check(tkIntLit) then + ElemStr := ElemStr + '..' + FCurrent.Value + else + raise EParseError.Create(Format( + 'Expected set range upper bound at line %d col %d in %s', + [FCurrent.Line, FCurrent.Col, FLexer.Filename])); + Advance(); + end; + CD.SetElements.Add(ElemStr); if Check(tkComma) then Advance() else diff --git a/compiler/src/main/pascal/uSemantic.pas b/compiler/src/main/pascal/uSemantic.pas index d6a357e..a1b0f86 100644 --- a/compiler/src/main/pascal/uSemantic.pas +++ b/compiler/src/main/pascal/uSemantic.pas @@ -153,6 +153,8 @@ type { Resolve a set-valued const decl (IsSet): fold the member bitmask into CD.IntVal and register the const symbol with its tySet type. } procedure AnalyseSetConstDecl(ACD: TConstDecl); + function ResolveSetMemberOrd(const AMember: string; ACD: TConstDecl; + var AEnumDesc: TEnumTypeDesc): Integer; procedure AnalyseArrayConstDecls(ABlock: TBlock); { Build the (possibly nested) static-array type for a range-indexed array const and validate that the flat row-major element count matches the @@ -3968,6 +3970,38 @@ begin ALine, ACol); end; +function TSemanticAnalyser.ResolveSetMemberOrd(const AMember: string; + ACD: TConstDecl; var AEnumDesc: TEnumTypeDesc): Integer; +{ Resolve one set-constant member (an integer literal, an enum constant, or a + named integer constant) to its ordinal. Enum members must all share one base + enum, tracked through AEnumDesc; integer members do not set it (the set's base + type then comes from the declared TypeName, e.g. set of Byte). } +var + Sym: TSymbol; +begin + if IsPlainInt(AMember) then + Exit(Integer(StrToInt(AMember))); + Sym := FTable.Lookup(AMember); + if (Sym = nil) or (Sym.Kind <> skConstant) then + begin + SemanticError(Format( + 'Set constant ''%s'' member ''%s'' is not a constant value', + [ACD.Name, AMember]), ACD.Line, ACD.Col); + Exit(0); + end; + { Enum member: pin / check the shared base enum. } + if (Sym.TypeDesc <> nil) and (Sym.TypeDesc.Kind = tyEnum) then + begin + if AEnumDesc = nil then + AEnumDesc := TEnumTypeDesc(Sym.TypeDesc) + else if Sym.TypeDesc <> AEnumDesc then + SemanticError(Format( + 'Set constant ''%s'' mixes members of ''%s'' and ''%s''', + [ACD.Name, AEnumDesc.Name, Sym.TypeDesc.Name]), ACD.Line, ACD.Col); + end; + Result := Integer(Sym.ConstValue); +end; + procedure TSemanticAnalyser.AnalyseSetConstDecl(ACD: TConstDecl); var I: Integer; @@ -3982,6 +4016,8 @@ var Sym: TSymbol; Ords: TStringList; Ord, BIdx, NB, BVal: Integer; + DotPos, Lo, Hi: Integer; + LoStr, HiStr: string; begin Mask := 0; EnumDesc := nil; @@ -3994,25 +4030,24 @@ begin for I := 0 to ACD.SetElements.Count - 1 do begin MemName := ACD.SetElements.Strings[I]; - MemSym := FTable.Lookup(MemName); - if (MemSym = nil) or (MemSym.Kind <> skConstant) or - (MemSym.TypeDesc = nil) or (MemSym.TypeDesc.Kind <> tyEnum) then + DotPos := Pos('..', MemName); + if DotPos >= 0 then begin - SemanticError(Format( - 'Set constant ''%s'' member ''%s'' is not an enum constant', - [ACD.Name, MemName]), ACD.Line, ACD.Col); - Exit; - end; - if EnumDesc = nil then - EnumDesc := TEnumTypeDesc(MemSym.TypeDesc) - else if MemSym.TypeDesc <> EnumDesc then - begin - SemanticError(Format( - 'Set constant ''%s'' mixes members of ''%s'' and ''%s''', - [ACD.Name, EnumDesc.Name, MemSym.TypeDesc.Name]), ACD.Line, ACD.Col); - Exit; - end; - Ords.Add(IntToStr(MemSym.ConstValue)); + { Inclusive range lo..hi — resolve both endpoints to ordinals and add + every value in between. } + LoStr := Copy(MemName, 0, DotPos); + HiStr := Copy(MemName, DotPos + 2, Length(MemName) - DotPos - 2); + Lo := Self.ResolveSetMemberOrd(LoStr, ACD, EnumDesc); + Hi := Self.ResolveSetMemberOrd(HiStr, ACD, EnumDesc); + if Hi < Lo then + SemanticError(Format( + 'Set constant ''%s'' range %s..%s is descending', [ACD.Name, LoStr, HiStr]), + ACD.Line, ACD.Col); + for Ord := Lo to Hi do + Ords.Add(IntToStr(Ord)); + end + else + Ords.Add(IntToStr(Self.ResolveSetMemberOrd(MemName, ACD, EnumDesc))); end; { Determine the set type descriptor. } diff --git a/compiler/src/test/pascal/cp.test.e2e.sets.pas b/compiler/src/test/pascal/cp.test.e2e.sets.pas index d26c3cb..7a24aac 100644 --- a/compiler/src/test/pascal/cp.test.e2e.sets.pas +++ b/compiler/src/test/pascal/cp.test.e2e.sets.pas @@ -53,6 +53,13 @@ type procedure TestRun_SetOfByte_InOperator; procedure TestRun_SetOfByte_RangeLiteral; procedure TestRun_SetOfByte_Union; + + { const-decl set literals: integer literals + ranges (not just enum idents) } + procedure TestRun_ConstSet_IntLiterals; + procedure TestRun_ConstSet_IntRange; + procedure TestRun_ConstSet_MixedRangeAndLiteral; + procedure TestRun_ConstSet_JumboWithRange; + procedure TestRun_ConstSet_EnumStillWorks; end; implementation @@ -472,6 +479,91 @@ begin '1' + LE + '2' + LE + '3' + LE, 0); end; +{ ---- const-decl set literals (integer literals + ranges) ---- } + +procedure TE2ESetOpsTests.TestRun_ConstSet_IntLiterals; +const Src = ''' + program P; + type TByteSet = set of Byte; + const C: TByteSet = [1, 2, 3]; + begin + if 2 in C then WriteLn('y') else WriteLn('n'); + if 5 in C then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'n' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_ConstSet_IntRange; +const Src = ''' + program P; + type TByteSet = set of Byte; + const C: TByteSet = [1..3]; + var I: Integer; + begin + for I := 0 to 4 do + if I in C then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'n' + LE + 'y' + LE + 'y' + LE + 'y' + LE + 'n' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_ConstSet_MixedRangeAndLiteral; +const Src = ''' + program P; + type TByteSet = set of Byte; + const C: TByteSet = [10..12, 20]; + begin + if 11 in C then WriteLn('y') else WriteLn('n'); + if 20 in C then WriteLn('y') else WriteLn('n'); + if 15 in C then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'y' + LE + 'n' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_ConstSet_JumboWithRange; +{ Edge case: a jumbo (>64-member) Byte set built from a const with a range and + high values — exercises the byte-bitmap const path. } +const Src = ''' + program P; + type TByteSet = set of Byte; + const C: TByteSet = [200, 201..205, 0]; + begin + if 203 in C then WriteLn('y') else WriteLn('n'); + if 0 in C then WriteLn('y') else WriteLn('n'); + if 100 in C then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'y' + LE + 'n' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_ConstSet_EnumStillWorks; +{ Regression: enum-member const sets (the original supported form) still work. } +const Src = ''' + program P; + type + TColor = (Red, Green, Blue, Yellow); + TColors = set of TColor; + const Warm: TColors = [Red, Yellow]; + begin + if Red in Warm then WriteLn('y') else WriteLn('n'); + if Green in Warm then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'n' + LE, 0); +end; + initialization RegisterTest(TE2ESetOpsTests);