diff --git a/compiler/src/main/pascal/uParser.pas b/compiler/src/main/pascal/uParser.pas index fb92f82..4931e13 100644 --- a/compiler/src/main/pascal/uParser.pas +++ b/compiler/src/main/pascal/uParser.pas @@ -347,16 +347,36 @@ begin the set from this string on demand — no parser-side symbol table is needed, and the encoding is self-contained. } Exit('set of ' + Self.ParseAnonEnumName()) - else + else if Check(tkIdent) and (PeekKind() <> tkDotDot) then begin - if not Check(tkIdent) then - raise EParseError.Create(Format( - 'Expected enum type or ''('' after ''set of'' at line %d col %d in %s', - [FCurrent.Line, FCurrent.Col, FLexer.Filename])); + { Named base type: 'set of Byte', 'set of Boolean', 'set of TEnum'. } Result := 'set of ' + FCurrent.Value; Advance(); Exit; - end; + end + else if Check(tkIntLit) or Check(tkIdent) then + begin + { Integer subrange base type: 'set of 0..255', 'set of 1..10', or a named + constant bound ('set of Lo..Hi'). Each bound is a single integer literal + or identifier; encode as 'set of lo..hi' for the semantic pass to + validate (0..255) and size. (A bracket-context bound reader is wrong + here — the subrange is not delimited by ']'/','.) } + LStr := FCurrent.Value; + Advance(); + Expect(tkDotDot); + if not (Check(tkIntLit) or Check(tkIdent)) then + raise EParseError.Create(Format( + 'Expected set subrange upper bound at line %d col %d in %s', + [FCurrent.Line, FCurrent.Col, FLexer.Filename])); + HStr := FCurrent.Value; + Advance(); + Exit('set of ' + LStr + '..' + HStr); + end + else + raise EParseError.Create(Format( + 'Expected enum type, integer subrange, or ''('' after ''set of'' ' + + 'at line %d col %d in %s', + [FCurrent.Line, FCurrent.Col, FLexer.Filename])); end; if not Check(tkIdent) then raise EParseError.Create(Format('Expected type name at line %d col %d in %s', @@ -1508,6 +1528,8 @@ begin end; function TParser.ParseSetDef: TSetTypeDef; +var + Lo, Hi: string; begin Result := TSetTypeDef.Create(); try @@ -1515,12 +1537,32 @@ begin Result.Col := FCurrent.Col; Expect(tkSet); Expect(tkOf); - if not Check(tkIdent) then + if Check(tkIdent) and (PeekKind() <> tkDotDot) then + begin + { Named base type: set of Byte / Boolean / TEnum. } + Result.BaseTypeName := FCurrent.Value; + Advance(); + end + else if Check(tkIntLit) or Check(tkIdent) then + begin + { Integer subrange base type: set of 0..255 / 1..10 / Lo..Hi. Stored as + 'lo..hi' in BaseTypeName; the semantic pass resolves and validates it. } + Lo := FCurrent.Value; + Advance(); + Expect(tkDotDot); + if not (Check(tkIntLit) or Check(tkIdent)) then + raise EParseError.Create(Format( + 'Expected set subrange upper bound at line %d col %d in %s', + [FCurrent.Line, FCurrent.Col, FLexer.Filename])); + Hi := FCurrent.Value; + Advance(); + Result.BaseTypeName := Lo + '..' + Hi; + end + else raise EParseError.Create(Format( - 'Expected base type name after ''set of'' at line %d col %d in %s', + 'Expected base type or integer subrange after ''set of'' ' + + 'at line %d col %d in %s', [FCurrent.Line, FCurrent.Col, FLexer.Filename])); - Result.BaseTypeName := FCurrent.Value; - Advance(); except Result.Free(); raise; diff --git a/compiler/src/main/pascal/uSemantic.pas b/compiler/src/main/pascal/uSemantic.pas index 31775d4..d3909ba 100644 --- a/compiler/src/main/pascal/uSemantic.pas +++ b/compiler/src/main/pascal/uSemantic.pas @@ -179,6 +179,7 @@ type function EvalConstFloatExpr(AExpr: TASTExpr; ALine, ACol: Integer): string; function IsFloatConstExpr(AExpr: TASTExpr): Boolean; function ResolveArrayBound(const ABoundText: string): Integer; + function ResolveSubrangeSetType(const ASubrange: string): TSetTypeDesc; function ResolveConstArrayElem(const AElem: string; AElemType: TTypeDesc; ALine, ACol: Integer): string; procedure AnalyseTypeDecls(ABlock: TBlock); @@ -2474,6 +2475,16 @@ begin if (Length(AName) > 7) and (StrHead(AName, 7) = 'set of ') then begin BaseName := StrCopyTail(AName, 7); + { Integer-subrange base type 'set of lo..hi' (e.g. set of 0..255). Like + Delphi/FPC, the element ordinals are the integer values themselves and the + type is capped at 256 elements (ordinals 0..255). Resolve and validate + the bounds, then build an ordinal set sized by the high bound — reusing the + same Byte-set machinery (bits 0..hi). } + if StrPos('..', BaseName) >= 0 then + begin + Result := Self.ResolveSubrangeSetType(BaseName); + Exit; + end; { Anonymous enum element 'set of (a,b,c)' — synthesise the enum from the encoded member list; otherwise resolve a named element type. } if (BaseName <> '') and (StrAt(BaseName, 0) = Ord('(')) then @@ -3951,6 +3962,40 @@ begin end; end; +function TSemanticAnalyser.ResolveSubrangeSetType(const ASubrange: string): TSetTypeDesc; +{ Resolve a 'set of lo..hi' integer-subrange set type, e.g. 'set of 0..255'. + Element ordinals are the integer values themselves; like Delphi/FPC the type is + capped at 256 elements (ordinals 0..255). The bitmap is sized by the high + bound (bits 0..hi), reusing the Byte-backed ordinal-set machinery — a low bound + > 0 simply leaves the lower bits unused, matching FPC. } +var + DDotPos, Lo, Hi: Integer; + LStr, HStr, CanonName: string; +begin + DDotPos := StrPos('..', ASubrange); + LStr := StrCopyFrom(ASubrange, 0, DDotPos); + HStr := StrCopyTail(ASubrange, DDotPos + 2); + Lo := ResolveArrayBound(LStr); + Hi := ResolveArrayBound(HStr); + if Lo < 0 then + SemanticError(Format( + 'Set subrange ''%s'': lower bound must be >= 0', [ASubrange]), 0, 0); + if Hi > 255 then + SemanticError(Format( + 'Set subrange ''%s'': upper bound must be <= 255 (a set has at most 256 ' + + 'elements)', [ASubrange]), 0, 0); + if Hi < Lo then + SemanticError(Format( + 'Set subrange ''%s'' is descending', [ASubrange]), 0, 0); + CanonName := Format('set of %d..%d', [Lo, Hi]); + Result := TSetTypeDesc(FTable.FindType(CanonName)); + if Result = nil then + begin + Result := FTable.NewOrdinalSetType(CanonName, FTable.TypeByte, Hi + 1); + FTable.DefineGlobal(TSymbol.Create(CanonName, skType, Result)); + end; +end; + function TSemanticAnalyser.ResolveConstArrayElem(const AElem: string; AElemType: TTypeDesc; ALine, ACol: Integer): string; { Resolve one array-const element to the numeric string codegen needs. The @@ -4460,6 +4505,7 @@ var EnumDesc: TEnumTypeDesc; EnumDef: TEnumTypeDef; SetDesc: TSetTypeDesc; + SetSubDesc: TSetTypeDesc; SetDef: TSetTypeDef; AttrIdx: Integer; RawAttr: string; @@ -4549,6 +4595,23 @@ begin else if TD.Def is TSetTypeDef then begin SetDef := TSetTypeDef(TD.Def); + { Integer-subrange base type ('lo..hi', e.g. set of 0..255): build an + ordinal set sized by the high bound (validated 0..255), bound to this + type's name. } + if StrPos('..', SetDef.BaseTypeName) >= 0 then + begin + SetSubDesc := Self.ResolveSubrangeSetType(SetDef.BaseTypeName); + SetDesc := FTable.NewOrdinalSetType(TD.Name, SetSubDesc.BaseType, + SetSubDesc.BitCount); + Sym := TSymbol.Create(TD.Name, skType, SetDesc); + if not FTable.Define(Sym) then + begin + Sym.Free(); + SemanticError(Format('Duplicate type name ''%s''', [TD.Name]), + TD.Line, TD.Col); + end; + Continue; + end; BaseSym := FTable.Lookup(SetDef.BaseTypeName); if (BaseSym = nil) or (BaseSym.Kind <> skType) then SemanticError( @@ -8507,9 +8570,13 @@ begin if TSetTypeDesc(TASTExpr(ACall.Args.Items[0]).ResolvedType).BaseType.Kind in [tyByte, tyBoolean] then begin - if not ArgType.IsNumeric() then + { Ordinal-base set: accept a numeric ordinal, and a Boolean operand + for a Boolean-base set (Include(s, True)). } + if not (ArgType.IsNumeric() or + ((TSetTypeDesc(TASTExpr(ACall.Args.Items[0]).ResolvedType).BaseType.Kind = tyBoolean) and + (ArgType.Kind = tyBoolean))) then SemanticError( - Format('Second argument of ''%s'' must be numeric for ''set of %s'', got ''%s''', + Format('Second argument of ''%s'' must be ordinal for ''set of %s'', got ''%s''', [ACall.Name, TSetTypeDesc(TASTExpr(ACall.Args.Items[0]).ResolvedType).BaseType.Name, ArgType.Name]), @@ -10725,10 +10792,16 @@ begin ABin.Line, ABin.Col); if TSetTypeDesc(RType).BaseType.Kind in [tyByte, tyBoolean] then begin - if not LType.IsNumeric() then + { Ordinal-base set (Byte/Boolean or an integer subrange): the left operand + is the element ordinal. Accept a numeric operand, and — for a Boolean + base — a Boolean operand (True/False), since `True in s` is the natural + form. Both lower to the operand's ordinal. } + if not (LType.IsNumeric() or + ((TSetTypeDesc(RType).BaseType.Kind = tyBoolean) and + (LType.Kind = tyBoolean))) then SemanticError( - Format('Left operand of ''in'' must be numeric for ''set of %s'', got ''%s''', - [TSetTypeDesc(RType).BaseType.Name, LType.Name]), + Format('Left operand of ''in'' must be %s for ''set of %s'', got ''%s''', + ['ordinal', TSetTypeDesc(RType).BaseType.Name, LType.Name]), ABin.Line, ABin.Col); end else if LType <> TSetTypeDesc(RType).BaseType then diff --git a/compiler/src/test/pascal/cp.test.e2e.sets.pas b/compiler/src/test/pascal/cp.test.e2e.sets.pas index 7a24aac..c9d5c2d 100644 --- a/compiler/src/test/pascal/cp.test.e2e.sets.pas +++ b/compiler/src/test/pascal/cp.test.e2e.sets.pas @@ -60,6 +60,13 @@ type procedure TestRun_ConstSet_MixedRangeAndLiteral; procedure TestRun_ConstSet_JumboWithRange; procedure TestRun_ConstSet_EnumStillWorks; + + { Integer-subrange base type: set of 0..255 / set of 1..10 (issue from + future-improvements) + set of Boolean membership } + procedure TestRun_SetOfSubrange_0to255; + procedure TestRun_SetOfSubrange_TypeDecl; + procedure TestRun_SetOfSubrange_NamedConstBounds; + procedure TestRun_SetOfBoolean_Membership; end; implementation @@ -564,6 +571,79 @@ begin AssertRunsOnAll(Src, 'y' + LE + 'n' + LE, 0); end; +procedure TE2ESetOpsTests.TestRun_SetOfSubrange_0to255; +{ Integer-subrange base type 'set of 0..255' — equivalent to set of Byte. } +const Src = ''' + program P; + var s: set of 0..255; + begin + s := [10, 20, 200]; + if 20 in s then WriteLn('y') else WriteLn('n'); + if 50 in s then WriteLn('y') else WriteLn('n'); + Include(s, 50); + if 50 in s then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'n' + LE + 'y' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_SetOfSubrange_TypeDecl; +{ 'type T = set of 0..63' (small, <=64-bit path) + range literal. } +const Src = ''' + program P; + type TS = set of 0..63; + var s: TS; + begin + s := [3..7]; + if 5 in s then WriteLn('y') else WriteLn('n'); + if 63 in s then WriteLn('y') else WriteLn('n'); + Include(s, 63); + if 63 in s then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'n' + LE + 'y' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_SetOfSubrange_NamedConstBounds; +{ Subrange bounds may be named constants: set of Lo..Hi. } +const Src = ''' + program P; + const Lo = 0; Hi = 100; + var s: set of Lo..Hi; + begin + s := [50]; + if 50 in s then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE, 0); +end; + +procedure TE2ESetOpsTests.TestRun_SetOfBoolean_Membership; +{ set of Boolean: a Boolean operand on the left of 'in' (True in s) and as the + Include/Exclude element. } +const Src = ''' + program P; + var s: set of Boolean; b: Boolean; + begin + s := [True]; + if True in s then WriteLn('y') else WriteLn('n'); + if False in s then WriteLn('y') else WriteLn('n'); + Include(s, False); + b := False; + if b in s then WriteLn('y') else WriteLn('n') + end. + '''; +begin + if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end; + AssertRunsOnAll(Src, 'y' + LE + 'n' + LE + 'y' + LE, 0); +end; + initialization RegisterTest(TE2ESetOpsTests); diff --git a/compiler/src/test/pascal/cp.test.sets.pas b/compiler/src/test/pascal/cp.test.sets.pas index dfbf5fe..f3e78ff 100644 --- a/compiler/src/test/pascal/cp.test.sets.pas +++ b/compiler/src/test/pascal/cp.test.sets.pas @@ -130,6 +130,14 @@ type procedure TestCodegen_SetOfByte_Union; procedure TestCodegen_SetOfByte_IsJumbo; + { ------------------------------------------------------------------ } + { Integer-subrange base type: set of 0..255 / set of lo..hi } + { ------------------------------------------------------------------ } + procedure TestSemantic_SetOfSubrange_VarDecl_OK; + procedure TestSemantic_SetOfSubrange_TypeDecl_OK; + procedure TestSemantic_SetOfSubrange_OverBound_Fails; + procedure TestSemantic_SetOfSubrange_Descending_Fails; + { ------------------------------------------------------------------ } { 64-bit sets (>32 members) } { ------------------------------------------------------------------ } @@ -1380,6 +1388,28 @@ const end. '''; + SrcSetOfSubrangeVar = + ''' + program P; + var s: set of 0..255; + begin + s := [10, 200] + end. + '''; + SrcSetOfSubrangeType = + ''' + program P; + type TS = set of 0..63; + var s: TS; + begin + s := [3, 63] + end. + '''; + SrcSetOfSubrangeOverBound = + 'program P; var s: set of 0..1000; begin s := [1] end.'; + SrcSetOfSubrangeDescending = + 'program P; var s: set of 5..3; begin s := [] end.'; + SrcSetOfByteRange = ''' program P; @@ -1577,6 +1607,30 @@ begin end; end; +procedure TSetTests.TestSemantic_SetOfSubrange_VarDecl_OK; +begin + { 'set of 0..255' (integer-subrange base) is accepted. } + SemanticOK(SrcSetOfSubrangeVar); +end; + +procedure TSetTests.TestSemantic_SetOfSubrange_TypeDecl_OK; +begin + { 'type TS = set of 0..63' is accepted. } + SemanticOK(SrcSetOfSubrangeType); +end; + +procedure TSetTests.TestSemantic_SetOfSubrange_OverBound_Fails; +begin + { A set has at most 256 elements (ordinals 0..255); 0..1000 is rejected. } + SemanticFail(SrcSetOfSubrangeOverBound); +end; + +procedure TSetTests.TestSemantic_SetOfSubrange_Descending_Fails; +begin + { A descending subrange (5..3) is a mistake, not a silent empty set. } + SemanticFail(SrcSetOfSubrangeDescending); +end; + initialization RegisterTest(TSetTests); diff --git a/docs/future-improvements.adoc b/docs/future-improvements.adoc index e37a802..9f802c2 100644 --- a/docs/future-improvements.adoc +++ b/docs/future-improvements.adoc @@ -34,30 +34,51 @@ prioritise them. - reference to function/procedure (anonymous methods) - cdecl/stdcall calling-convention markers on procedural types -=== Set types over an integer subrange (`set of 0..255`) - -`set of byte` and `set of Boolean` are *implemented* (issue #105): the named -ordinal base types lower to a bit set — `set of byte` uses the 256-bit jumbo -path, membership/Include/Exclude and `[lo..hi]` range literals all work on both -backends. See `docs/language-rationale.adoc` and the `set of Byte` tests in -`cp.test.sets.pas` / `cp.test.e2e.sets.pas`. - -What remains is the *anonymous integer-subrange* base form, -`set of 0..255` / `set of 1..10`, which is still rejected at parse time -("Expected enum type or '(' after 'set of'"). Standard Pascal accepts a -subrange there; the bit count is the range width and element ordinals are the -integer values themselves — the same machinery `set of byte` already uses, so -this is mostly parser + base-type-resolution work, not new codegen. - -Minor follow-on: for `set of Boolean`, a Boolean operand on the left of `in` -(e.g. `True in s`) is rejected by the `in` type-check ("operand must be -numeric"); `Ord(b) in s` works. The check should accept a Boolean (or any -ordinal) operand against a matching ordinal-base set. - === 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). +=== If Conditional Operator (Ternary Conditional Operator) + +More details here: https://en.wikipedia.org/wiki/Ternary_conditional_operator + +Many languages use the ?: syntax as follows: + +`result = condition ? value_if_true : value_if_false` + +But that's not very pascal-like. Oxygene language has implemented this years +ago with te following syntax: + +For instance + +[source] +---- +var s := if (a<>0) then Sin(1/a) else 1; +---- + +being equivalent to + +[source,pascal] +---- +var s : Float; +if (a<>0) then + s := Sin(1/a) +else s := 1; +---- + +So for Blaise we could do similar: + +X := if Left < 100 then 22 else 45; +// the operator can be used as part of any expression, as follows: +ShowMessage(if Left < 100 then 'Small' else 'Big'); + +NOTE: If the two types do not match and are incompatible, the compiler should issue an error message + +NOTE: It is important to note that the if-then-else expression differs from the if-then-else statement, the expression has a single type. When the type of the then and else expressions differ, the expression's type is the common type of the two expressions' types; also known as the Least Upper Bound (LUB) type. + +See also Delphi's documentation: https://docwiki.embarcadero.com/RADStudio/Florence/en/Conditional_Operators_(Delphi) + + == Language — Features Required to Complete the punit Port The following features were identified as missing from Blaise while porting @@ -142,7 +163,7 @@ record's own memory. Used before `FreeMem` when the record was heap-allocated. allocated records that contain strings leak the string's reference count. *Current workaround in punit.* The `Finalize` calls were dropped. In Blaise, -ARC on assignment should automatically manage string fields inside records, so +ARC on assfignment should automatically manage string fields inside records, so the string is released when the record's string field is overwritten or when the last reference to the containing heap block is released. The correctness of this assumption depends on the ARC implementation for record fields — if the @@ -157,46 +178,6 @@ local record variables at scope exit. *Effort.* Small to moderate — shares infrastructure with ARC scope-exit emit. -=== If Conditional Operator (Ternary Conditional Operator) - -More details here: https://en.wikipedia.org/wiki/Ternary_conditional_operator - -Many languages use the ?: syntax as follows: - -`result = condition ? value_if_true : value_if_false` - -But that's not very pascal-like. Oxygene language has implemented this years -ago with te following syntax: - -For instance - -[source] ----- -var s := if (a<>0) then Sin(1/a) else 1; ----- - -being equivalent to - -[source,pascal] ----- -var s : Float; -if (a<>0) then - s := Sin(1/a) -else s := 1; ----- - -So for Blaise we could do similar: - -X := if Left < 100 then 22 else 45; -// the operator can be used as part of any expression, as follows: -ShowMessage(if Left < 100 then 'Small' else 'Big'); - -NOTE: If the two types do not match and are incompatible, the compiler should issue an error message - -NOTE: It is important to note that the if-then-else expression differs from the if-then-else statement, the expression has a single type. When the type of the then and else expressions differ, the expression's type is the common type of the two expressions' types; also known as the Least Upper Bound (LUB) type. - -See also Delphi's documentation: https://docwiki.embarcadero.com/RADStudio/Florence/en/Conditional_Operators_(Delphi) - == StdLib Packages — Numeric Types === `Currency` and `BigDecimal` as StdLib units diff --git a/docs/grammar.ebnf b/docs/grammar.ebnf index a3b9f52..e678255 100644 --- a/docs/grammar.ebnf +++ b/docs/grammar.ebnf @@ -673,7 +673,11 @@ EnumMember * The anonymous enum form synthesises an enum type whose members become * ordinary enum constants in scope. *) SetType - = SET OF ( IDENT | EnumDef ) + = SET OF ( IDENT | EnumDef | Subrange ) + ; + +Subrange + = ConstExpr DOTDOT ConstExpr ; (* The base type must have at most 256 ordinal values. Storage scales with @@ -685,9 +689,14 @@ SetType * aggregate (record/static array) * Value ordinal N maps to bit (N mod 8) of byte (N div 8) in all forms. * Types with more than 256 possible values (e.g. Integer, Word) are rejected - * by the semantic analyser — only Byte (256 values), Boolean (2 values), and - * enumerations with at most 256 members are accepted. Set literals with - * ordinal base types accept integer literals and ranges: [1, 2, 10..20]. *) + * by the semantic analyser — only Byte (256 values), Boolean (2 values), + * enumerations with at most 256 members, and integer subranges within + * 0..255 are accepted. The subrange form `set of L..H` (e.g. `set of 0..255`, + * `set of 1..10`) sizes the bitmap to H+1 bits; both bounds may be integer + * literals, named constants, or constant expressions. L must be >= 0, H must + * be <= 255, and the range must be ascending (H >= L) — a descending or + * out-of-range subrange is a semantic error. Set literals with ordinal base + * types accept integer literals and ranges: [1, 2, 10..20]. *) (* Procedural type — bare or method pointer. * diff --git a/docs/language-rationale.adoc b/docs/language-rationale.adoc index 8c1d4b0..e313e12 100644 --- a/docs/language-rationale.adoc +++ b/docs/language-rationale.adoc @@ -1293,15 +1293,16 @@ code. _Deferred to Phase 6 (LLVM backend + Windows target). No implementation required for the current Linux x86_64 target._ -=== Set Types (`set of TEnum` / `set of Byte`) +=== Set Types (`set of TEnum` / `set of Byte` / `set of 0..255`) ==== Decision Blaise supports Pascal bit-set types declared as `set of BaseType`, where -`BaseType` is either a previously-defined enumeration or one of the small -ordinal types `Byte` (256 values, 0..255) or `Boolean` (2 values). A set -variable holds a bitmask in which bit N is set when the member with ordinal N -is included. +`BaseType` is a previously-defined enumeration, one of the small ordinal +types `Byte` (256 values, 0..255) or `Boolean` (2 values), or an integer +subrange within 0..255 (e.g. `0..9`, `1..10`, `0..255`). A set variable +holds a bitmask in which bit N is set when the member with ordinal N is +included. ==== Ordinal-based sets @@ -1328,9 +1329,45 @@ Integer range expressions `[lo..hi]` are expanded to individual members at compile time using `EvalConstIntExpr`. Bounds are validated against the set's bit count (0..255 for Byte, 0..1 for Boolean). +A Boolean operand is accepted on either side of `in` and as the second +argument of `Include`/`Exclude` for a `set of Boolean`; the operand only +needs to be ordinal-compatible with the set's base type, not strictly +numeric. `True in s` and `Include(s, False)` therefore work directly, +without an intervening `Ord()`. + Larger ordinal types (`Integer`, `Word`, `Int64`) are rejected because they would require impractically large bitmaps. +==== Integer-subrange base (`set of 0..255`) + +The set base may also be an anonymous integer subrange, `set of L..H`, as +in standard Pascal, Delphi, and FPC: + +[source,pascal] +---- +type TDigits = set of 0..9; (* type-declaration form *) +var Small: set of 1..10; (* inline form in a var declaration *) +begin + Small := [1, 5, 8]; + if 5 in Small then WriteLn('yes'); +end. +---- + +The subrange lowers to the same bitmap machinery as `set of Byte`: the +bitmap is sized to `H + 1` bits (member ordinals are the integer values +themselves), so `set of 0..63` is a 64-bit small set and `set of 0..255` is +a 256-bit jumbo set. Both bounds may be integer literals, named constants, +or constant expressions, resolved through the same `ResolveArrayBound` path +used for static-array bounds. + +The lower bound must be `>= 0`, the upper bound `<= 255`, and the range must +be ascending (`H >= L`). A descending subrange (`set of 5..3`) is a +semantic error rather than a silently-empty set, and an upper bound above +255 is rejected for the same 256-element ceiling that applies to every set. +The bitmap always starts at ordinal 0 — `set of 1..10` still reserves bits +for 0 — which keeps the representation identical to `set of Byte` and avoids +a base-offset in the `_Set*` RTL helpers. + ==== Storage model Storage scales with the base type's value count, so the common small-set