From 87fb75a01a955a4b83bd692d76d55a145370aefa Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:04:12 +0100 Subject: [PATCH 01/15] Add new TIntegerRange adv record to structs category A new source code file was added for TIntegerRange. Meta data for TIntegerRange was added to structs.ini. --- collection/706.dat | 261 +++++++++++++++++++++++++++++++++++++++++ collection/structs.ini | 20 +++- 2 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 collection/706.dat diff --git a/collection/706.dat b/collection/706.dat new file mode 100644 index 0000000..d5b92f7 --- /dev/null +++ b/collection/706.dat @@ -0,0 +1,261 @@ +type + TIntegerRange = record + strict private + var + fLowerBound: Integer; + fUpperBound: Integer; + function GetLowerBound: Integer; + function GetUpperBound: Integer; + function IsSubrangeOf(const ARange: TIntegerRange): Boolean; + public + // Constructs a range whose bounds are A and B. The lowest of the two + // parameters is taken as the lower bound of the range with the other + // parameter taken as the upper bound. + // Valid bounds must fall in the range -MaxInt..MaxInt. An + // EArgumentException exception is raised otherwise. + constructor Create(const A, B: Integer); + + // Constructs an empty range. + class function CreateEmpty: TIntegerRange; static; + + // Checks if the range is empty. + function IsEmpty: Boolean; + + // Returns the length of the range, i.e. the number of integers in the range. + function Length: Cardinal; + + // Constrains AValue to fall within this range. If the value lies within the + // range it is returned unchanged. If it is outside the range then either + // LowerBound or UpperBound is returned, depending on whether the value + // falls below or above the range, respectively. + // An EInvalidOpException exception is raised if the range is empty. + function Constrain(const AValue: Integer): Integer; + + // Checks if this range overlaps with ARange, i.e. the interection of the + // ranges is non empty. Empty ranges cannot overlap with any range. + function OverlapsWith(const ARange: TIntegerRange): Boolean; + + // Checks if this range is immediately adjacent to ARange, with no overlap. + // Empty ranges are never contiguous with other ranges or themselves. + function IsContiguousWith(const ARange: TIntegerRange): Boolean; + + // Checks if the set of all values in this range and ARange form a + // continuous sequence. This implies that a range is continuous with itself. + // Since adding an empty range to a non-empty range doesn't change the + // non-empty range we define empty ranges to be continuous with any range. + function IsContinuousWith(const ARange: TIntegerRange): Boolean; + + // Checks if ranges A and B are the same + class operator Equal(const A, B: TIntegerRange): Boolean; + + // Checks if ranges A and B are not the same + class operator NotEqual(const A, B: TIntegerRange): Boolean; + + // Checks if range A is contained in, or is the same as, range B. + // An empty range is deemed to be contained in any other range. + class operator LessThanOrEqual(const A, B: TIntegerRange): Boolean; + + // Checks if range A is contains, or is the same as, range B. + // A non-empty range is never contained in an empty range. + class operator GreaterThanOrEqual(const A, B: TIntegerRange): Boolean; + + // Combine two ranges, A and B. The result is the smallest range that + // contains both A and B. + // If A and B are not continuous the resulting range will contain values + // that were not in either A or B. + // Combining any range either with itself or with an empty range is a no-op. + class operator Add(const A, B: TIntegerRange): TIntegerRange; + + // Returns a range that is the intersection of ranges A and B. + // Returns an empty range if A and B do not overlap. + class operator Multiply(const A, B: TIntegerRange): TIntegerRange; + + // Checks if integer AValue is contained within range ARange. + class operator In(const AValue: Integer; const ARange: TIntegerRange): + Boolean; + + // Implicitly casts ARange to a string. If ARange is non-empty the string + // has format [X..Y], where X and Y are the lower and upper bounds of + // ARange respectively. If ARange is empty then [] is returned. + // This means that ARange can be assigned directly to a string. + class operator Implicit(const ARange: TIntegerRange): string; + + // Explicitly casts ARange to a string. If ARange is non-empty the string + // has format [X..Y], where X and Y are the lower and upper bounds of + // ARange respectively. If ARange is empty then [] is returned. + // This means that ARange can be explicitly cast to a string using + // string(ARange). + class operator Explicit(const ARange: TIntegerRange): string; + + // The lower bound of a non-empty range. + // EInvalidOpException is raised if the property is read when the range is + // empty. + property LowerBound: Integer read GetLowerBound; + + // The upper bound of a non-empty range. + // EInvalidOpException is raised if the property is read when the range is + // empty. + property UpperBound: Integer read GetUpperBound; + end; + +class operator TIntegerRange.Add(const A, B: TIntegerRange): TIntegerRange; +begin + if A.IsEmpty then + Exit(B); + if B.IsEmpty then + Exit(A); + Result := TIntegerRange.Create( + Math.Min(A.fLowerBound, B.fLowerBound), + Math.Max(A.fUpperBound, B.fUpperBound) + ); +end; + +function TIntegerRange.Constrain(const AValue: Integer): Integer; +begin + if IsEmpty then + raise Sysutils.EInvalidOpException.Create( + 'TIntegerRange.Constrain not valid for an empty range.' + ); + Result := Math.EnsureRange(AValue, fLowerBound, fUpperBound); +end; + +constructor TIntegerRange.Create(const A, B: Integer); +begin + // Normalise range so that smallest parameter is the lower bound + fLowerBound := Math.Min(A, B); + fUpperBound := Math.Max(A, B); + if fLowerBound = Low(Integer) then + // This restriction is required to prevent the Length method's Cardinal + // return value from wrapping around / overflowing + raise SysUtils.EArgumentException.CreateFmt( + 'TIntegerRange.Create: Arguments must be greater than %d', [Low(Integer)] + ); +end; + +class function TIntegerRange.CreateEmpty: TIntegerRange; +begin + Result.fLowerBound := High(Integer); + Result.fUpperBound := Low(Integer); +end; + +class operator TIntegerRange.Equal(const A, B: TIntegerRange): Boolean; +begin + if A.IsEmpty or B.IsEmpty then + Exit(A.IsEmpty and B.IsEmpty); + Result := (A.fLowerBound = B.fLowerBound) and (A.fUpperBound = B.fUpperBound); +end; + +class operator TIntegerRange.Explicit(const ARange: TIntegerRange): string; +begin + if ARange.IsEmpty then + Exit('[]'); + Result := SysUtils.Format( + '[%d..%d]', [ARange.fLowerBound, ARange.fUpperBound] + ); +end; + +function TIntegerRange.GetLowerBound: Integer; +begin + if IsEmpty then + raise Sysutils.EInvalidOpException.Create( + 'TIntegerRange.LowerBound not valid for an empty range.' + ); + Result := fLowerBound; +end; + +function TIntegerRange.GetUpperBound: Integer; +begin + if IsEmpty then + raise Sysutils.EInvalidOpException.Create( + 'TIntegerRange.LowerBound not valid for an empty range.' + ); + Result := fUpperBound; +end; + +class operator TIntegerRange.GreaterThanOrEqual(const A, B: TIntegerRange): + Boolean; +begin + Result := B.IsSubrangeOf(A); +end; + +class operator TIntegerRange.Implicit(const ARange: TIntegerRange): string; +begin + Result := string(ARange); // calls Explicit cast operator +end; + +class operator TIntegerRange.In(const AValue: Integer; + const ARange: TIntegerRange): Boolean; +begin + if ARange.IsEmpty then + Exit(False); + Result := (AValue >= ARange.fLowerBound) and (AValue <= ARange.fUpperBound); +end; + +function TIntegerRange.IsContiguousWith(const ARange: TIntegerRange): Boolean; +begin + if Self.IsEmpty or ARange.IsEmpty then + Exit(False); + Result := (Self + ARange).Length = (Self.Length + ARange.Length); +end; + +function TIntegerRange.IsContinuousWith(const ARange: TIntegerRange): Boolean; +begin + if Self.IsEmpty or ARange.IsEmpty then + // Empty ranges are only continuous with other empty ranges + Exit(True); + Result := IsContiguousWith(ARange) or OverlapsWith(ARange); +end; + +function TIntegerRange.IsEmpty: Boolean; +begin + Result := fLowerBound > fUpperBound; +end; + +function TIntegerRange.IsSubrangeOf(const ARange: TIntegerRange): Boolean; +begin + if ARange.IsEmpty then + Exit(Self.IsEmpty); + Result := (Self.fLowerBound >= ARange.fLowerBound) + and (Self.fUpperBound <= ARange.fUpperBound) + or Self.IsEmpty +end; + +function TIntegerRange.Length: Cardinal; +begin + if IsEmpty then + Exit(0); + Result := fUpperBound - fLowerBound + 1 +end; + +class operator TIntegerRange.LessThanOrEqual(const A, B: TIntegerRange): + Boolean; +begin + Result := A.IsSubrangeOf(B); +end; + +class operator TIntegerRange.Multiply(const A, B: TIntegerRange): TIntegerRange; +var + Up, Lo: Integer; +begin + if A.IsEmpty or B.IsEmpty then + Exit(TIntegerRange.CreateEmpty); + Lo := Math.Max(A.fLowerBound, B.fLowerBound); + Up := Math.Min(A.fUpperBound, B.fUpperBound); + if Lo <= Up then + Result := TIntegerRange.Create(Lo, Up) + else + Result := TIntegerRange.CreateEmpty; +end; + +class operator TIntegerRange.NotEqual(const A, B: TIntegerRange): Boolean; +begin + if A.IsEmpty or B.IsEmpty then + Exit(A.IsEmpty <> B.IsEmpty); + Result := (A.fLowerBound <> B.fLowerBound) + or (A.fUpperBound <> B.fUpperBound); +end; + +function TIntegerRange.OverlapsWith(const ARange: TIntegerRange): Boolean; +begin + Result := not (Self * ARange).IsEmpty; +end; \ No newline at end of file diff --git a/collection/structs.ini b/collection/structs.ini index f338d9d..2fb328f 100644 --- a/collection/structs.ini +++ b/collection/structs.ini @@ -57,7 +57,7 @@ FPC=Y DescEx="
Constructs and returns a TRange record with bounds A and B.
The smaller of A and B is used as the lower bound and the larger as the upper bound. If both values are equal then the range will be empty.
" Depends=TRange SeeAlso=TRange -SeeAlso=TRange,TRangeEx +SeeAlso=TRange,TRangeEx,TIntegerRange Snip=580.dat Delphi7=Y Delphi2005Win32=Y @@ -122,6 +122,20 @@ Delphi10S=Y Delphi12A=Y FPC=Y +[TIntegerRange] +DisplayName=TIntegerRange +DescEx="An advanced record that encapsulates an integer range along with operations on it.
The range is immutable, so the constructor must be used to instantiate a non-empty range.
" +Extra="TIntegerRange supports various operator overloads, which operate in a way similar to the Pascal set operators:
Encapsulates a point with double precision floating point coordinates.
" @@ -150,7 +164,7 @@ FPC=Y Kind=type DescEx="Encapsulates the upper and lower bounds of a range of values.
" SeeAlso=Range -SeeAlso=Range,TRangeEx +SeeAlso=Range,TRangeEx,TIntegerRange Snip=579.dat Delphi7=Y Delphi2005Win32=Y @@ -195,7 +209,7 @@ FPC=Y Kind=class DescEx="Encapsulates a range of integers with a methods to test whether a value falls within the range and to adjust the value to fit.
" Units=Math -SeeAlso=Range,TRange +SeeAlso=Range,TRange,TIntegerRange Snip=578.dat Delphi2=N Delphi3=N From 857a29a5ba4f664af2f4bc4782285b2022e6f6df Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:06:01 +0100 Subject: [PATCH 02/15] Add unit tests for new TIntegerRange adv records Created a new DUnit test project for the Structures category, for compilation with Delphi XE only. This is TestCatStructsXE.dpr/.dproj Added unit tests for TIntegerRange to the a new TestUStructCatSnippets unit. Generated a new UStructCatSnippets unit from CodeSnip that includes TIntegerRange. --- tests/Cat-Structs/TestCatStructsXE.dpr | 35 + tests/Cat-Structs/TestCatStructsXE.dproj | 118 ++++ tests/Cat-Structs/TestCatStructsXE.res | Bin 0 -> 91336 bytes tests/Cat-Structs/TestUStructCatSnippets.pas | 656 +++++++++++++++++++ tests/Cat-Structs/UStructCatSnippets.pas | 519 +++++++++++++++ 5 files changed, 1328 insertions(+) create mode 100644 tests/Cat-Structs/TestCatStructsXE.dpr create mode 100644 tests/Cat-Structs/TestCatStructsXE.dproj create mode 100644 tests/Cat-Structs/TestCatStructsXE.res create mode 100644 tests/Cat-Structs/TestUStructCatSnippets.pas create mode 100644 tests/Cat-Structs/UStructCatSnippets.pas diff --git a/tests/Cat-Structs/TestCatStructsXE.dpr b/tests/Cat-Structs/TestCatStructsXE.dpr new file mode 100644 index 0000000..cc5d08d --- /dev/null +++ b/tests/Cat-Structs/TestCatStructsXE.dpr @@ -0,0 +1,35 @@ +program TestCatStructsXE; +{ + +Delphi DUnit Test Project +------------------------- +This project contains the DUnit test framework and the GUI/Console test runners. +Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options +to use the console test runner. Otherwise the GUI test runner will be used by +default. + +} + +{$IFDEF CONSOLE_TESTRUNNER} +{$APPTYPE CONSOLE} +{$ENDIF} + +uses + Forms, + TestFramework, + GUITestRunner, + TextTestRunner, + TestUStructCatSnippets in 'TestUStructCatSnippets.pas', + UStructCatSnippets in 'UStructCatSnippets.pas'; + +{$R *.RES} + +begin + Application.Initialize; + if IsConsole then + with TextTestRunner.RunRegisteredTests do + Free + else + GUITestRunner.RunRegisteredTests; +end. + diff --git a/tests/Cat-Structs/TestCatStructsXE.dproj b/tests/Cat-Structs/TestCatStructsXE.dproj new file mode 100644 index 0000000..49810fe --- /dev/null +++ b/tests/Cat-Structs/TestCatStructsXE.dproj @@ -0,0 +1,118 @@ +WHJFi`;Hsn+BHMy=9oM?o&=IzD=K_xylCfxS{FTdSFzoc
zkXx>7Drx&=;gG9Z*`cJGc(cty2@Di|LivRwm60W+)ps!j5KQ7pGv}W`K0wP~%jD4Q
z&v4<7!D&53 -($fEPC~4OT9Ii9t*d8fvRA04RYFnG{6-iSXp0R0_T1{)fo}U|}!K?S>f$V9vk%
z8~n7nC3>SGt0xIRve4WdH-7l xUN|6$sOVze+x2u!!Vae>t
zw8`cno=92q0y&4^#49hohCRFYBH+LHn02KDtY^MA75Od}3**L{ufyzl(-H0sQwU(-
z$jL^$@aiV)KYAKh%$s=d=BsD_aKe}oKNj1FC6n53o_g~)FTA|9U)fHCr|16=x^XuQ)O+$LRNrA}-+z+s5>3q~wmy5f?AA&}%l-b^b
z_`B~Q{P^$1|8kQ1&y!Q6Gmxc{bk!WhDyB?O(!DNdEDM%L?oDA( ?-9F0E{ey&eF%+JY)et
z)|kR!vj$65ht~%I0N(9=lV=J548|IYDnF)-%2MS(GwWWFb{5|4yL(|ak@2^8Bh?!b
z8LtHE>XWzH5{*&(Z#TzrxR9j#*NKLb5vVV(MU*>Q#Vyy>I4+*bQyJ97
zUP1R0j}JQ_xDa
cXg%(NtzVI}9dJ1|Tkaa_jETOEURKNb)GRD9}QxJydNAUzf-
z$Pls4Gp8Hz((1JoW}gt%A&$|N4xxdHiVA%0{<{#!@v6zv=KHZJWyHGEDWtsDUV8&|
z`;VZvC#nE|fGDB)ks?DY%g@KPi>H2i>m-%l=#SHcfN+x~=>E{^yUcUcr*wIvKr-
z0DuKG5@hTJ^vBDVFU0IwQ$(Ve--In_kZxMI*olh!pTg;eW<2`X(+G8U!^Q2CObT)V
z6lbsⅇBW=
zS&NpI4!GQI8G@41{VEqgp7-Z&Te{||MU%hdbb8liAA(DI-+gVvd;jw{&%T9V&<|%S
z3P&O$8TDv1DjMhae(IAbEXtLhWLvgKoyfr47M^a(v;DSi-i8h9x6*6&%Dgs$8!NzE?NAH*rf{
zJ^T*ZVcU)i_n#4pRYk5{aUJft_mgPv?6fkXStq4gv;O)uF=|