Skip to content

Commit e2f2c59

Browse files
committed
Add SourceCode.Language unit & tests to DUnitX tests
Also added Exceptions unit that is required by SourceCode.Language unit, but is not tested directly.
1 parent 050adee commit e2f2c59

5 files changed

+586
-1
lines changed

cupola/src/CSLE.Exceptions.pas

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
This Source Code Form is subject to the terms of the Mozilla Public License,
3+
v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
obtain one at https://mozilla.org/MPL/2.0/
5+
6+
Copyright (C) 2024, Peter Johnson (gravatar.com/delphidabbler).
7+
8+
Definitions of CodeSnip LE's custom exception classes.
9+
10+
NOTE:
11+
EBase is derived from EAssignable from the UExceptions unit in the CodeSnip
12+
master branch.
13+
}
14+
15+
unit CSLE.Exceptions;
16+
17+
interface
18+
19+
uses
20+
System.SysUtils;
21+
22+
type
23+
/// <summary>Base exception class for all CodeSnip LE's native exceptions.
24+
/// </summary>
25+
/// <remarks>All descendants inherited the ability to assign one exception to
26+
/// another.</remarks>
27+
EBase = class(Exception)
28+
public
29+
/// <summary>Constructs the exception object that is shallow copy of
30+
/// exception <c>E</c>.</summary>
31+
/// <remarks>Note that any inner exception of <c>E</c> is not copied.
32+
/// </remarks>
33+
constructor Create(const E: Exception); overload;
34+
/// <summary>Sets this exception object's properties to a shallow copy of
35+
/// exception <c>E</c>.</summary>
36+
/// <remarks>
37+
/// <para>Note that any inner exception of <c>E</c> is not copied.</para>
38+
/// <para>Descendants should overload if any new properties are added to
39+
/// those of the <c>Exception</c> class.</para>
40+
/// </remarks>
41+
procedure Assign(const E: Exception); virtual;
42+
end;
43+
44+
/// <summary>Exceptions that represent expected errors and are handled
45+
/// specially by the program.</summary>
46+
/// <remarks>This class can either be used as-is or used as base class for
47+
/// other expected exception types.</remarks>
48+
EExpected = class(EBase);
49+
50+
/// <summary>Exceptions that are not expected, i.e. may be considered as bugs
51+
/// and not handled epxlicitly by the program.</summary>
52+
/// <remarks>This class can either be used as-is or used as base class for
53+
/// other unexpected exception types.</remarks>
54+
EUnexpected = class(EBase);
55+
56+
implementation
57+
58+
{ EBase }
59+
60+
procedure EBase.Assign(const E: Exception);
61+
begin
62+
Self.Message := E.Message; // only copy Message property
63+
end;
64+
65+
constructor EBase.Create(const E: Exception);
66+
begin
67+
inherited Create('');
68+
Assign(E); // we call assign so that descendants can copy extra properties
69+
end;
70+
71+
end.
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
{
2+
This Source Code Form is subject to the terms of the Mozilla Public License,
3+
v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
obtain one at https://mozilla.org/MPL/2.0/
5+
6+
Copyright (C) 2024, Peter Johnson (gravatar.com/delphidabbler).
7+
8+
Data type that encapsulates a source code language ID.
9+
}
10+
11+
unit CSLE.SourceCode.Language;
12+
13+
interface
14+
15+
uses
16+
System.SysUtils,
17+
System.Generics.Defaults;
18+
19+
type
20+
TSourceCodeLanguageID = record
21+
public
22+
type
23+
/// <summary>Comparator for source code language IDs.</summary>
24+
/// <remarks>Source code language IDs are not case sensitive.</remarks>
25+
TComparator = class(TInterfacedObject,
26+
IComparer<TSourceCodeLanguageID>,
27+
IEqualityComparer<TSourceCodeLanguageID>
28+
)
29+
public
30+
/// <summary>Compares the two given source code language IDs.</summary>
31+
/// <remarks>Returns zero if Left is the same as Right, -ve if Left is
32+
/// less than Right or +ve if Left is greater than Right.</remarks>
33+
function Compare(const Left, Right: TSourceCodeLanguageID): Integer;
34+
/// <summary>Checks if the two given source code language IDs are
35+
/// equal.</summary>
36+
function Equals(const Left, Right: TSourceCodeLanguageID): Boolean;
37+
reintroduce; overload;
38+
/// <summary>Returns the hash code of the given source code language
39+
/// ID.</summary>
40+
function GetHashCode(const Value: TSourceCodeLanguageID): Integer;
41+
reintroduce; overload;
42+
end;
43+
strict private
44+
// Default language ID. This name is reserved: it must not be used as the
45+
// ID for any source code language. To prevent this being done by accident
46+
// the ID is not valid: IsValidIDString will return False for this ID and
47+
// Create will raise an exception if this ID is passed to it.
48+
// To create a default ID call the CreateDefault method.
49+
const
50+
DefaultLanguageID = '_Default_';
51+
var
52+
fID: string;
53+
class function Compare(const Left, Right: TSourceCodeLanguageID): Integer; static;
54+
public
55+
const
56+
/// <summary>Maximum length of an ID.</summary>
57+
MaxLength = 32;
58+
/// <summary>Required name for Pascal language ID.</summary>
59+
/// <remarks>This name is special since Pascal code is treated specially
60+
/// by the program and such special treatment requires the use of this
61+
/// ID.</summary>
62+
PascalLanguageID = 'Pascal';
63+
64+
/// <summary>Creates a new record with ID set to <c>AID</c>.</summary>
65+
/// <exception>Raises exception if <c>AId</c> is not a valid ID and is not
66+
/// the empty string.</exception>
67+
/// <remarks>
68+
/// <para>If <c>AID</c> is empty then the default language ID is created.
69+
/// </para>
70+
/// <para>A non-empty <c>AID</c> must be between 1 and 32 characters,
71+
/// must start with a letter or digit and subsequent characters must be
72+
/// either letters, digits, symbols or punctuation characters.</para>
73+
/// </remarks>
74+
constructor Create(const AID: string);
75+
76+
/// <summary>Creates a default source code language ID.</summary>
77+
class function CreateDefault: TSourceCodeLanguageID; static;
78+
79+
/// <summary>Checks if <c>AStr</c> is valid source code language ID string.
80+
/// </summary>
81+
/// <remarks>A valid ID string is between 1 and 32 characters long, must
82+
/// start with a letter or digit and subsequent characters must be either
83+
/// letters, digits, symbols or punctuation characters.</remarks>
84+
class function IsValidIDString(const AStr: string): Boolean; static;
85+
86+
/// <summary>Returns string representation of ID.</summary>
87+
function ToString: string; inline;
88+
89+
/// <summary>Checks if the current ID is the default ID.</summary>
90+
function IsDefault: Boolean; inline;
91+
92+
/// <summary>Checks if the current ID is that of the Pascal language.
93+
/// </summary>
94+
/// <remarks>Detects if the ID is that specified by the
95+
/// <c>PascalLanguageID</c> constant.</remarks>
96+
function IsPascal: Boolean; inline;
97+
98+
// Default ctor: creates a default source code language ID.
99+
class operator Initialize(out Dest: TSourceCodeLanguageID);
100+
101+
// Comparison operators
102+
class operator Equal(const Left, Right: TSourceCodeLanguageID): Boolean;
103+
class operator NotEqual(const Left, Right: TSourceCodeLanguageID): Boolean;
104+
end;
105+
106+
implementation
107+
108+
uses
109+
System.Character,
110+
System.Hash,
111+
System.Types,
112+
CSLE.Exceptions;
113+
114+
{ TSourceCodeLanguageID }
115+
116+
class function TSourceCodeLanguageID.Compare(const Left,
117+
Right: TSourceCodeLanguageID): Integer;
118+
begin
119+
Result := string.CompareText(Left.fID, Right.fID);
120+
end;
121+
122+
constructor TSourceCodeLanguageID.Create(const AID: string);
123+
begin
124+
if not AID.IsEmpty then
125+
begin
126+
if not IsValidIDString(AID) then
127+
raise EUnexpected.CreateFmt(
128+
'TSourceCodeLanguageID.Create: Invalid ID string "%s"', [AID]
129+
);
130+
fID := AID;
131+
end
132+
else
133+
fID := DefaultLanguageID;
134+
end;
135+
136+
class function TSourceCodeLanguageID.CreateDefault: TSourceCodeLanguageID;
137+
begin
138+
Result := TSourceCodeLanguageID.Create(string.Empty);
139+
end;
140+
141+
class operator TSourceCodeLanguageID.Equal(const Left,
142+
Right: TSourceCodeLanguageID): Boolean;
143+
begin
144+
Result := Compare(Left, Right) = EqualsValue;
145+
end;
146+
147+
class operator TSourceCodeLanguageID.Initialize(out Dest: TSourceCodeLanguageID);
148+
begin
149+
Dest.fID := DefaultLanguageID;
150+
end;
151+
152+
function TSourceCodeLanguageID.IsDefault: Boolean;
153+
begin
154+
Result := fID = DefaultLanguageID;
155+
end;
156+
157+
function TSourceCodeLanguageID.IsPascal: Boolean;
158+
begin
159+
// Use Equal operator to ensure test allows for case diiferences between this
160+
// record's ID and PascalLangauageID.
161+
Result := TSourceCodeLanguageID.Create(PascalLanguageID) = Self;
162+
end;
163+
164+
class function TSourceCodeLanguageID.IsValidIDString(const AStr: string):
165+
Boolean;
166+
begin
167+
// Per docs:
168+
// [ID] must start with a Unicode letter or digit and be followed by
169+
// a sequence of zero or more Unicode letters, digits and punctuation
170+
// characters.
171+
Result := False;
172+
if AStr.IsEmpty then
173+
Exit;
174+
if AStr.Length > MaxLength then
175+
Exit;
176+
if not AStr[1].IsLetterOrDigit then
177+
Exit;
178+
for var Idx := 2 to AStr.Length do
179+
if not AStr[Idx].IsLetterOrDigit and not AStr[Idx].IsPunctuation
180+
and not AStr[Idx].IsSymbol then
181+
Exit;
182+
Result := True;
183+
end;
184+
185+
class operator TSourceCodeLanguageID.NotEqual(const Left,
186+
Right: TSourceCodeLanguageID): Boolean;
187+
begin
188+
Result := Compare(Left, Right) <> EqualsValue;
189+
end;
190+
191+
function TSourceCodeLanguageID.ToString: string;
192+
begin
193+
Result := fID;
194+
end;
195+
196+
{ TSourceCodeLanguageID.TComparator }
197+
198+
function TSourceCodeLanguageID.TComparator.Compare(const Left,
199+
Right: TSourceCodeLanguageID): Integer;
200+
begin
201+
Result := TSourceCodeLanguageID.Compare(Left, Right);
202+
end;
203+
204+
function TSourceCodeLanguageID.TComparator.Equals(const Left,
205+
Right: TSourceCodeLanguageID): Boolean;
206+
begin
207+
Result := Left = Right;
208+
end;
209+
210+
function TSourceCodeLanguageID.TComparator.GetHashCode(
211+
const Value: TSourceCodeLanguageID): Integer;
212+
begin
213+
Result := THashBobJenkins.GetHashValue(Value.fID);
214+
end;
215+
216+
end.

cupola/tests/CodeSnip.Cupola.Tests.dpr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ uses
2020
Test.TextData in 'Test.TextData.pas',
2121
CSLE.TextData in '..\src\CSLE.TextData.pas',
2222
CSLE.Streams.Wrapper in '..\src\CSLE.Streams.Wrapper.pas',
23-
Test.Streams.Wrapper in 'Test.Streams.Wrapper.pas';
23+
Test.Streams.Wrapper in 'Test.Streams.Wrapper.pas',
24+
CSLE.Exceptions in '..\src\CSLE.Exceptions.pas',
25+
CSLE.SourceCode.Language in '..\src\CSLE.SourceCode.Language.pas',
26+
Test.SourceCode.Language in 'Test.SourceCode.Language.pas';
2427

2528
{$IFNDEF TESTINSIGHT}
2629
var

cupola/tests/CodeSnip.Cupola.Tests.dproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
<DCCReference Include="..\src\CSLE.TextData.pas"/>
7878
<DCCReference Include="..\src\CSLE.Streams.Wrapper.pas"/>
7979
<DCCReference Include="Test.Streams.Wrapper.pas"/>
80+
<DCCReference Include="..\src\CSLE.Exceptions.pas"/>
81+
<DCCReference Include="..\src\CSLE.SourceCode.Language.pas"/>
82+
<DCCReference Include="Test.SourceCode.Language.pas"/>
8083
<BuildConfiguration Include="Base">
8184
<Key>Base</Key>
8285
</BuildConfiguration>

0 commit comments

Comments
 (0)