|
| 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. |
0 commit comments