unit Myc.Data.Keyword; interface uses System.SysUtils, System.Generics.Collections, System.Generics.Defaults, System.SyncObjs, Myc.Data.Tuple; type // The runtime representation of an interned keyword IKeyword = interface {$region 'private'} function GetIdx: Integer; function GetName: string; {$endregion} property Idx: Integer read GetIdx; property Name: string read GetName; end; // Factory and registry for all keywords (Flyweight Pattern) TKeywordRegistry = record strict private type TKeyword = class(TInterfacedObject, IKeyword) private FName: string; FIdx: Integer; function GetName: string; function GetIdx: Integer; public constructor Create(const AName: string; AIdx: Integer); end; strict private class var FLock: TSpinLock; FRegistry: TDictionary; FReverseMap: TList; class constructor Create; class destructor Destroy; public // Gets or creates the interned keyword for the given name. class function Intern(const AName: string): IKeyword; static; // Gets the name for a given keyword index. class function GetName(AIdx: Integer): string; static; end; // Defines a mapping from Keywords to a generic value T IKeywordMapping = interface(ITuple) {$region 'private'} function GetFields(const Key: IKeyword): T; function GetKeywords(Idx: Integer): IKeyword; {$endregion} // Finds the 0-based index for a given keyword. Returns -1 if not found. function IndexOf(const Key: IKeyword): Integer; property Keywords[Idx: Integer]: IKeyword read GetKeywords; property Fields[const Key: IKeyword]: T read GetFields; end; // Factory for creating IKeywordMapping instances TKeywordMappingRegistry = record strict private type TMapping = class(TInterfacedObject, IKeywordMapping) private FMap: TArray; FFields: TArray>; FFirst, FLast: Integer; function GetItems(Idx: Integer): T; function GetFields(const Key: IKeyword): T; function GetKeywords(Idx: Integer): IKeyword; function GetCount: Integer; public // Expects AFields to be in the desired (canonical) order constructor Create(const AFields: TArray>); function IndexOf(const Key: IKeyword): Integer; end; strict private // Cache implementation class var FLock: TSpinLock; FRegistry: TDictionary, IKeywordMapping>; class constructor Create; class destructor Destroy; public // Creates or gets the cached keyword mapping. class function Intern(const AFields: TArray>): IKeywordMapping; static; end; TGenericRecord = class(TInterfacedObject, IKeywordMapping) private FFields: TArray>; function GetItems(Idx: Integer): T; function GetKeywords(Idx: Integer): IKeyword; function GetFields(const Key: IKeyword): T; function GetCount: Integer; public constructor Create(const AFields: TArray>); function IndexOf(const Key: IKeyword): Integer; end; implementation uses Winapi.Windows; { TKeywordRegistry.TKeyword } constructor TKeywordRegistry.TKeyword.Create(const AName: string; AIdx: Integer); begin inherited Create; FName := AName; FIdx := AIdx; end; function TKeywordRegistry.TKeyword.GetIdx: Integer; begin Result := FIdx; end; function TKeywordRegistry.TKeyword.GetName: string; begin Result := FName; end; { TKeywordRegistry } class constructor TKeywordRegistry.Create; begin FRegistry := TDictionary.Create; FReverseMap := TList.Create; FLock := TSpinLock.Create(False); end; class destructor TKeywordRegistry.Destroy; begin FRegistry.Free; FReverseMap.Free; end; class function TKeywordRegistry.Intern(const AName: string): IKeyword; var idx: Integer; begin FLock.Enter; try if not FRegistry.TryGetValue(AName, idx) then begin idx := FReverseMap.Count; Result := TKeyword.Create(AName, idx); FReverseMap.Add(Result); FRegistry.Add(AName, idx); end else begin Result := FReverseMap[idx]; end; finally FLock.Exit; end; end; class function TKeywordRegistry.GetName(AIdx: Integer): string; begin if (AIdx >= 0) and (AIdx < FReverseMap.Count) then Result := FReverseMap[AIdx].Name else Result := ''; end; { TKeywordMappingRegistry.TMapping } constructor TKeywordMappingRegistry.TMapping.Create(const AFields: TArray>); var i: Integer; keyIdx: Integer; begin inherited Create; FFields := AFields; if Length(FFields) = 0 then begin FFirst := 0; FLast := -1; FMap := nil; Exit; end; FFirst := FFields[0].Key.Idx; FLast := FFirst; for i := 1 to High(FFields) do begin keyIdx := FFields[i].Key.Idx; if keyIdx < FFirst then FFirst := keyIdx; if keyIdx > FLast then FLast := keyIdx; end; SetLength(FMap, FLast - FFirst + 1); for i := 0 to High(FMap) do FMap[i] := -1; for i := 0 to High(FFields) do begin keyIdx := FFields[i].Key.Idx; FMap[keyIdx - FFirst] := i; end; end; function TKeywordMappingRegistry.TMapping.GetCount: Integer; begin Result := Length(FFields); end; // Interface method for Items property function TKeywordMappingRegistry.TMapping.GetItems(Idx: Integer): T; begin Result := FFields[Idx].Value; end; // Interface method for Fields property function TKeywordMappingRegistry.TMapping.GetFields(const Key: IKeyword): T; var idx: Integer; begin idx := IndexOf(Key); if idx < 0 then raise EArgumentException.CreateFmt('Keyword "%s" not found in mapping.', [Key.Name]); Result := FFields[idx].Value; end; function TKeywordMappingRegistry.TMapping.GetKeywords(Idx: Integer): IKeyword; begin Result := FFields[Idx].Key; end; function TKeywordMappingRegistry.TMapping.IndexOf(const Key: IKeyword): Integer; var keyIdx, mapIdx: Integer; begin keyIdx := Key.Idx; if (keyIdx < FFirst) or (keyIdx > FLast) then Exit(-1); mapIdx := keyIdx - FFirst; if (mapIdx < Length(FMap)) then Result := FMap[mapIdx] else Result := -1; end; { TKeywordMappingRegistry } class constructor TKeywordMappingRegistry.Create; begin FRegistry := TDictionary, IKeywordMapping>.Create(TEqualityComparer>.Default); FLock := TSpinLock.Create(False); end; class destructor TKeywordMappingRegistry.Destroy; begin FRegistry.Free; end; class function TKeywordMappingRegistry.Intern(const AFields: TArray>): IKeywordMapping; var signature: TArray; i: Integer; begin SetLength(signature, Length(AFields)); for i := 0 to High(AFields) do signature[i] := AFields[i].Key.Idx; FLock.Enter; try if not FRegistry.TryGetValue(signature, Result) then begin Result := TMapping.Create(AFields); FRegistry.Add(signature, Result); end; finally FLock.Exit; end; end; { TGenericRecord } constructor TGenericRecord.Create(const AFields: TArray>); begin inherited Create; FFields := AFields; end; function TGenericRecord.GetCount: Integer; begin Result := Length(FFields); end; function TGenericRecord.GetFields(const Key: IKeyword): T; var idx: Integer; begin idx := IndexOf(Key); if idx < 0 then raise EArgumentException.CreateFmt('Field "%s" not found in dynamic record.', [Key.Name]); Result := FFields[idx].Value; end; function TGenericRecord.GetItems(Idx: Integer): T; begin Result := FFields[Idx].Value; end; function TGenericRecord.GetKeywords(Idx: Integer): IKeyword; begin Result := FFields[Idx].Key; end; function TGenericRecord.IndexOf(const Key: IKeyword): Integer; var i: Integer; begin // Linear search (O(n)). Fast enough for typical records. // Keyword comparison is integer-based (Idx), so this is efficient. for i := 0 to High(FFields) do begin if FFields[i].Key.Idx = Key.Idx then begin Result := i; exit; end; end; Result := -1; end; end.