diff --git a/compiler/src/test/pascal/cp.test.e2e.gaps.pas b/compiler/src/test/pascal/cp.test.e2e.gaps.pas index 86f433d..bada6f6 100644 --- a/compiler/src/test/pascal/cp.test.e2e.gaps.pas +++ b/compiler/src/test/pascal/cp.test.e2e.gaps.pas @@ -46,6 +46,11 @@ type procedure TestRun_GenericRecord_FieldAccess; procedure TestRun_GenericRecord_MethodCall; + { TDictionary default property d[key] } + procedure TestRun_TDictionary_DefaultProp_IntKeys; + procedure TestRun_TDictionary_DefaultProp_StringKeys; + procedure TestRun_TDictionary_DefaultProp_Update; + { Published RTTI + MethodAddress } procedure TestRun_PublishedRTTI_MethodAddress; @@ -265,6 +270,232 @@ begin AssertRunsOnAll(Src, '2' + Chr(10) + 'ok' + Chr(10), 0); end; +{ ---- TDictionary default property ---- } + +procedure TE2EGapTests.TestRun_TDictionary_DefaultProp_IntKeys; +const Src = ''' + program T; + type + TMap = class + FKeys: ^K; + FValues: ^V; + FCount: Integer; + FCapacity: Integer; + procedure Grow; + function FindKey(Key: K): Integer; + procedure Add(Key: K; Value: V); + function GetItem(Key: K): V; + procedure SetItem(Key: K; Value: V); + property Items[Key: K]: V read GetItem write SetItem; default; + end; + procedure TMap.Grow; + var NewCap: Integer; + begin + if Self.FCapacity = 0 then NewCap := 8 + else NewCap := Self.FCapacity * 2; + Self.FKeys := ReallocMem(Self.FKeys, NewCap * SizeOf(K)); + Self.FValues := ReallocMem(Self.FValues, NewCap * SizeOf(V)); + Self.FCapacity := NewCap + end; + function TMap.FindKey(Key: K): Integer; + var I: Integer; Ptr: ^K; + begin + Result := -1; + I := 0; + while I < Self.FCount do + begin + Ptr := Self.FKeys + I * SizeOf(K); + if Ptr^ = Key then begin Result := I; I := Self.FCount end + else I := I + 1 + end + end; + procedure TMap.Add(Key: K; Value: V); + var Idx: Integer; KPtr: ^K; VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin VPtr := Self.FValues + Idx * SizeOf(V); VPtr^ := Value end + else begin + if Self.FCount = Self.FCapacity then Self.Grow(); + KPtr := Self.FKeys + Self.FCount * SizeOf(K); + VPtr := Self.FValues + Self.FCount * SizeOf(V); + KPtr^ := Key; VPtr^ := Value; + Self.FCount := Self.FCount + 1 + end + end; + function TMap.GetItem(Key: K): V; + var Idx: Integer; VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin VPtr := Self.FValues + Idx * SizeOf(V); Result := VPtr^ end + else Halt(1) + end; + procedure TMap.SetItem(Key: K; Value: V); + begin Self.Add(Key, Value) end; + var D: TMap; + begin + D := TMap.Create(); + D[1] := 100; + D[2] := 200; + WriteLn(D[1]); + WriteLn(D[2]); + D.Free() + end. + '''; +begin + AssertRunsOnAll(Src, '100' + Chr(10) + '200' + Chr(10), 0); +end; + +procedure TE2EGapTests.TestRun_TDictionary_DefaultProp_StringKeys; +const Src = ''' + program T; + type + TMap = class + FKeys: ^K; + FValues: ^V; + FCount: Integer; + FCapacity: Integer; + procedure Grow; + function FindKey(Key: K): Integer; + procedure Add(Key: K; Value: V); + function GetItem(Key: K): V; + procedure SetItem(Key: K; Value: V); + property Items[Key: K]: V read GetItem write SetItem; default; + end; + procedure TMap.Grow; + var NewCap: Integer; + begin + if Self.FCapacity = 0 then NewCap := 8 + else NewCap := Self.FCapacity * 2; + Self.FKeys := ReallocMem(Self.FKeys, NewCap * SizeOf(K)); + Self.FValues := ReallocMem(Self.FValues, NewCap * SizeOf(V)); + Self.FCapacity := NewCap + end; + function TMap.FindKey(Key: K): Integer; + var I: Integer; Ptr: ^K; + begin + Result := -1; + I := 0; + while I < Self.FCount do + begin + Ptr := Self.FKeys + I * SizeOf(K); + if Ptr^ = Key then begin Result := I; I := Self.FCount end + else I := I + 1 + end + end; + procedure TMap.Add(Key: K; Value: V); + var Idx: Integer; KPtr: ^K; VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin VPtr := Self.FValues + Idx * SizeOf(V); VPtr^ := Value end + else begin + if Self.FCount = Self.FCapacity then Self.Grow(); + KPtr := Self.FKeys + Self.FCount * SizeOf(K); + VPtr := Self.FValues + Self.FCount * SizeOf(V); + KPtr^ := Key; VPtr^ := Value; + Self.FCount := Self.FCount + 1 + end + end; + function TMap.GetItem(Key: K): V; + var Idx: Integer; VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin VPtr := Self.FValues + Idx * SizeOf(V); Result := VPtr^ end + else Halt(1) + end; + procedure TMap.SetItem(Key: K; Value: V); + begin Self.Add(Key, Value) end; + var D: TMap; + begin + D := TMap.Create(); + D['one'] := 1; + D['two'] := 2; + WriteLn(D['one']); + WriteLn(D['two']); + D.Free() + end. + '''; +begin + AssertRunsOnAll(Src, '1' + Chr(10) + '2' + Chr(10), 0); +end; + +procedure TE2EGapTests.TestRun_TDictionary_DefaultProp_Update; +const Src = ''' + program T; + type + TMap = class + FKeys: ^K; + FValues: ^V; + FCount: Integer; + FCapacity: Integer; + procedure Grow; + function FindKey(Key: K): Integer; + procedure Add(Key: K; Value: V); + function GetItem(Key: K): V; + procedure SetItem(Key: K; Value: V); + property Items[Key: K]: V read GetItem write SetItem; default; + end; + procedure TMap.Grow; + var NewCap: Integer; + begin + if Self.FCapacity = 0 then NewCap := 8 + else NewCap := Self.FCapacity * 2; + Self.FKeys := ReallocMem(Self.FKeys, NewCap * SizeOf(K)); + Self.FValues := ReallocMem(Self.FValues, NewCap * SizeOf(V)); + Self.FCapacity := NewCap + end; + function TMap.FindKey(Key: K): Integer; + var I: Integer; Ptr: ^K; + begin + Result := -1; + I := 0; + while I < Self.FCount do + begin + Ptr := Self.FKeys + I * SizeOf(K); + if Ptr^ = Key then begin Result := I; I := Self.FCount end + else I := I + 1 + end + end; + procedure TMap.Add(Key: K; Value: V); + var Idx: Integer; KPtr: ^K; VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin VPtr := Self.FValues + Idx * SizeOf(V); VPtr^ := Value end + else begin + if Self.FCount = Self.FCapacity then Self.Grow(); + KPtr := Self.FKeys + Self.FCount * SizeOf(K); + VPtr := Self.FValues + Self.FCount * SizeOf(V); + KPtr^ := Key; VPtr^ := Value; + Self.FCount := Self.FCount + 1 + end + end; + function TMap.GetItem(Key: K): V; + var Idx: Integer; VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin VPtr := Self.FValues + Idx * SizeOf(V); Result := VPtr^ end + else Halt(1) + end; + procedure TMap.SetItem(Key: K; Value: V); + begin Self.Add(Key, Value) end; + var D: TMap; + begin + D := TMap.Create(); + D['x'] := 10; + D['x'] := 42; + WriteLn(D['x']); + D.Free() + end. + '''; +begin + AssertRunsOnAll(Src, '42' + Chr(10), 0); +end; + { ---- Generic records ---- } procedure TE2EGapTests.TestRun_GenericRecord_FieldAccess; diff --git a/compiler/src/test/pascal/cp.test.tdictionary.pas b/compiler/src/test/pascal/cp.test.tdictionary.pas index b0280ca..ca49582 100644 --- a/compiler/src/test/pascal/cp.test.tdictionary.pas +++ b/compiler/src/test/pascal/cp.test.tdictionary.pas @@ -52,6 +52,10 @@ type procedure TestCodegen_TryGetValueEmitted; procedure TestCodegen_ContainsKeyEmitted; procedure TestCodegen_RemoveEmitted; + + { Default property d[key] } + procedure TestCodegen_DefaultProperty_Read_EmitsGetItem; + procedure TestCodegen_DefaultProperty_Write_EmitsSetItem; end; implementation @@ -73,10 +77,13 @@ const procedure Grow; function FindKey(Key: K): Integer; procedure Add(Key: K; Value: V); + function GetItem(Key: K): V; + procedure SetItem(Key: K; Value: V); function TryGetValue(Key: K; var Value: V): Boolean; function ContainsKey(Key: K): Boolean; procedure Remove(Key: K); property Count: Integer read FCount; + property Items[Key: K]: V read GetItem write SetItem; default; end; '''; @@ -137,6 +144,24 @@ const Self.FCount := Self.FCount + 1 end end; + function TDictionary.GetItem(Key: K): V; + var + Idx: Integer; + VPtr: ^V; + begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin + VPtr := Self.FValues + Idx * SizeOf(V); + Result := VPtr^ + end + else + Halt(1) + end; + procedure TDictionary.SetItem(Key: K; Value: V); + begin + Self.Add(Key, Value) + end; function TDictionary.TryGetValue(Key: K; var Value: V): Boolean; var Idx: Integer; @@ -243,6 +268,21 @@ const end. '''; + SrcDefaultProp = + 'program P;' + #10 + + DictDecl + + DictImpls + + ''' + var + D: TDictionary; + V: Integer; + begin + D := TDictionary.Create(); + D[1] := 100; + V := D[1] + end. + '''; + { ------------------------------------------------------------------ } { Helpers } { ------------------------------------------------------------------ } @@ -321,7 +361,7 @@ begin Prog := ParseSrc(SrcCreate); try GD := TGenericTypeDef(TTypeDecl(Prog.Block.TypeDecls[0]).Def); - AssertEquals('Six methods in class', 6, GD.ClassDef.Methods.Count); + AssertEquals('Eight methods in class', 8, GD.ClassDef.Methods.Count); finally Prog.Free(); end; @@ -439,6 +479,28 @@ begin Pos('$TDictionary_Integer_Integer_Remove', IR) > 0); end; +{ ------------------------------------------------------------------ } +{ Default property tests } +{ ------------------------------------------------------------------ } + +procedure TTDictionaryTests.TestCodegen_DefaultProperty_Read_EmitsGetItem; +var + IR: string; +begin + IR := GenIR(SrcDefaultProp); + AssertTrue('GetItem getter called', + Pos('$TDictionary_Integer_Integer_GetItem', IR) >= 0); +end; + +procedure TTDictionaryTests.TestCodegen_DefaultProperty_Write_EmitsSetItem; +var + IR: string; +begin + IR := GenIR(SrcDefaultProp); + AssertTrue('SetItem setter called', + Pos('$TDictionary_Integer_Integer_SetItem', IR) >= 0); +end; + initialization RegisterTest(TTDictionaryTests); diff --git a/stdlib/src/main/pascal/generics.collections.pas b/stdlib/src/main/pascal/generics.collections.pas index 60d4a18..7c393dd 100644 --- a/stdlib/src/main/pascal/generics.collections.pas +++ b/stdlib/src/main/pascal/generics.collections.pas @@ -131,12 +131,15 @@ type procedure HashRebuild; function FindKey(Key: K): Integer; procedure Add(Key: K; Value: V); + function GetItem(Key: K): V; + procedure SetItem(Key: K; Value: V); function TryGetValue(Key: K; var Value: V): Boolean; function ContainsKey(Key: K): Boolean; procedure Remove(Key: K); function GetCount: Integer; procedure Destroy; property Count: Integer read FCount; + property Items[Key: K]: V read GetItem write SetItem; default; end; { Generic insertion-ordered map. Entries are stored in the order they were @@ -157,6 +160,8 @@ type procedure HashRebuild; function FindKey(Key: K): Integer; procedure Add(Key: K; Value: V); + function GetItem(Key: K): V; + procedure SetItem(Key: K; Value: V); function TryGetValue(Key: K; var Value: V): Boolean; function ContainsKey(Key: K): Boolean; procedure Remove(Key: K); @@ -165,6 +170,7 @@ type function GetCount: Integer; procedure Destroy; property Count: Integer read FCount; + property Items[Key: K]: V read GetItem write SetItem; default; property Keys[Index: Integer]: K read GetKey; property Values[Index: Integer]: V read GetValue; end; @@ -865,6 +871,26 @@ begin end end; +function TDictionary.GetItem(Key: K): V; +var + Idx: Integer; + VPtr: ^V; +begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin + VPtr := Self.FValues + Idx * SizeOf(V); + Result := VPtr^ + end + else + Halt(1) +end; + +procedure TDictionary.SetItem(Key: K; Value: V); +begin + Self.Add(Key, Value) +end; + function TDictionary.GetCount: Integer; begin Result := Self.FCount @@ -1088,6 +1114,26 @@ begin Result := Ptr^ end; +function TOrderedDictionary.GetItem(Key: K): V; +var + Idx: Integer; + VPtr: ^V; +begin + Idx := Self.FindKey(Key); + if Idx >= 0 then + begin + VPtr := Self.FValues + Idx * SizeOf(V); + Result := VPtr^ + end + else + Halt(1) +end; + +procedure TOrderedDictionary.SetItem(Key: K; Value: V); +begin + Self.Add(Key, Value) +end; + function TOrderedDictionary.GetCount: Integer; begin Result := Self.FCount