-- --
-- B o d y --
-- --
--- Copyright (C) 2004-2005 Free Software Foundation, Inc. --
--- --
--- This specification is derived from the Ada Reference Manual for use with --
--- GNAT. The copyright notice above, and the license provisions that follow --
--- apply solely to the contents of the part following the private keyword. --
+-- Copyright (C) 2004-2006, Free Software Foundation, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- Local Subprograms --
-----------------------
+ procedure Assign (Node : Node_Access; Item : Element_Type);
+ pragma Inline (Assign);
+
function Copy_Node (Source : Node_Access) return Node_Access;
pragma Inline (Copy_Node);
(R_HT : Hash_Table_Type;
L_Node : Node_Access) return Boolean;
+ procedure Free (X : in out Node_Access);
+
function Hash_Node (Node : Node_Access) return Hash_Type;
pragma Inline (Hash_Node);
+ procedure Insert
+ (HT : in out Hash_Table_Type;
+ New_Item : Element_Type;
+ Node : out Node_Access;
+ Inserted : out Boolean);
+
function Is_In
(HT : Hash_Table_Type;
Key : Node_Access) return Boolean;
function Next (Node : Node_Access) return Node_Access;
pragma Inline (Next);
- function Read_Node (Stream : access Root_Stream_Type'Class)
+ function Read_Node (Stream : not null access Root_Stream_Type'Class)
return Node_Access;
pragma Inline (Read_Node);
- procedure Replace_Element
- (HT : in out Hash_Table_Type;
- Node : Node_Access;
- Element : Element_Type);
-
procedure Set_Next (Node : Node_Access; Next : Node_Access);
pragma Inline (Set_Next);
+ function Vet (Position : Cursor) return Boolean;
+
procedure Write_Node
- (Stream : access Root_Stream_Type'Class;
+ (Stream : not null access Root_Stream_Type'Class;
Node : Node_Access);
pragma Inline (Write_Node);
-- Local Instantiations --
--------------------------
- procedure Free is
- new Ada.Unchecked_Deallocation (Node_Type, Node_Access);
-
package HT_Ops is
new Hash_Tables.Generic_Operations
(HT_Types => HT_Types,
procedure Read_Nodes is
new HT_Ops.Generic_Read (Read_Node);
+ procedure Replace_Element is
+ new Element_Keys.Generic_Replace_Element (Hash_Node, Assign);
+
procedure Write_Nodes is
new HT_Ops.Generic_Write (Write_Node);
HT_Ops.Adjust (Container.HT);
end Adjust;
+ ------------
+ -- Assign --
+ ------------
+
+ procedure Assign (Node : Node_Access; Item : Element_Type) is
+ begin
+ Node.Element := Item;
+ end Assign;
+
--------------
-- Capacity --
--------------
Element_Keys.Delete_Key_Sans_Free (Container.HT, Item, X);
if X = null then
- raise Constraint_Error;
+ raise Constraint_Error with "attempt to delete element not in set";
end if;
Free (X);
is
begin
if Position.Node = null then
- raise Constraint_Error;
+ raise Constraint_Error with "Position cursor equals No_Element";
end if;
- if Position.Container /= Set_Access'(Container'Unchecked_Access) then
- raise Program_Error;
+ if Position.Container /= Container'Unrestricted_Access then
+ raise Program_Error with "Position cursor designates wrong set";
end if;
if Container.HT.Busy > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with elements (set is busy)";
end if;
+ pragma Assert (Vet (Position), "bad cursor in Delete");
+
HT_Ops.Delete_Node_Sans_Free (Container.HT, Position.Node);
Free (Position.Node);
-
Position.Container := null;
end Delete;
return;
end if;
- if Source.Length = 0 then
+ if Source.HT.Length = 0 then
return;
end if;
if Target.HT.Busy > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with elements (set is busy)";
end if;
- -- TODO: This can be written in terms of a loop instead as
- -- active-iterator style, sort of like a passive iterator.
+ if Source.HT.Length < Target.HT.Length then
+ declare
+ Src_Node : Node_Access;
- Tgt_Node := HT_Ops.First (Target.HT);
- while Tgt_Node /= null loop
- if Is_In (Source.HT, Tgt_Node) then
- declare
- X : Node_Access := Tgt_Node;
- begin
- Tgt_Node := HT_Ops.Next (Target.HT, Tgt_Node);
- HT_Ops.Delete_Node_Sans_Free (Target.HT, X);
- Free (X);
- end;
+ begin
+ Src_Node := HT_Ops.First (Source.HT);
+ while Src_Node /= null loop
+ Tgt_Node := Element_Keys.Find (Target.HT, Src_Node.Element);
- else
- Tgt_Node := HT_Ops.Next (Target.HT, Tgt_Node);
- end if;
- end loop;
+ if Tgt_Node /= null then
+ HT_Ops.Delete_Node_Sans_Free (Target.HT, Tgt_Node);
+ Free (Tgt_Node);
+ end if;
+
+ Src_Node := HT_Ops.Next (Source.HT, Src_Node);
+ end loop;
+ end;
+
+ else
+ Tgt_Node := HT_Ops.First (Target.HT);
+ while Tgt_Node /= null loop
+ if Is_In (Source.HT, Tgt_Node) then
+ declare
+ X : Node_Access := Tgt_Node;
+ begin
+ Tgt_Node := HT_Ops.Next (Target.HT, Tgt_Node);
+ HT_Ops.Delete_Node_Sans_Free (Target.HT, X);
+ Free (X);
+ end;
+
+ else
+ Tgt_Node := HT_Ops.Next (Target.HT, Tgt_Node);
+ end if;
+ end loop;
+ end if;
end Difference;
function Difference (Left, Right : Set) return Set is
return Empty_Set;
end if;
- if Left.Length = 0 then
+ if Left.HT.Length = 0 then
return Empty_Set;
end if;
- if Right.Length = 0 then
+ if Right.HT.Length = 0 then
return Left;
end if;
function Element (Position : Cursor) return Element_Type is
begin
+ if Position.Node = null then
+ raise Constraint_Error with "Position cursor equals No_Element";
+ end if;
+
+ pragma Assert (Vet (Position), "bad cursor in function Element");
+
return Position.Node.Element;
end Element;
function Equivalent_Elements (Left, Right : Cursor)
return Boolean is
begin
+ if Left.Node = null then
+ raise Constraint_Error with
+ "Left cursor of Equivalent_Elements equals No_Element";
+ end if;
+
+ if Right.Node = null then
+ raise Constraint_Error with
+ "Right cursor of Equivalent_Elements equals No_Element";
+ end if;
+
+ pragma Assert (Vet (Left), "bad Left cursor in Equivalent_Elements");
+ pragma Assert (Vet (Right), "bad Right cursor in Equivalent_Elements");
+
return Equivalent_Elements (Left.Node.Element, Right.Node.Element);
end Equivalent_Elements;
function Equivalent_Elements (Left : Cursor; Right : Element_Type)
return Boolean is
begin
+ if Left.Node = null then
+ raise Constraint_Error with
+ "Left cursor of Equivalent_Elements equals No_Element";
+ end if;
+
+ pragma Assert (Vet (Left), "Left cursor in Equivalent_Elements is bad");
+
return Equivalent_Elements (Left.Node.Element, Right);
end Equivalent_Elements;
function Equivalent_Elements (Left : Element_Type; Right : Cursor)
return Boolean is
begin
+ if Right.Node = null then
+ raise Constraint_Error with
+ "Right cursor of Equivalent_Elements equals No_Element";
+ end if;
+
+ pragma Assert
+ (Vet (Right),
+ "Right cursor of Equivalent_Elements is bad");
+
return Equivalent_Elements (Left, Right.Node.Element);
end Equivalent_Elements;
return Cursor'(Container'Unrestricted_Access, Node);
end First;
+ ----------
+ -- Free --
+ ----------
+
+ procedure Free (X : in out Node_Access) is
+ procedure Deallocate is
+ new Ada.Unchecked_Deallocation (Node_Type, Node_Access);
+
+ begin
+ if X /= null then
+ X.Next := X; -- detect mischief (in Vet)
+ Deallocate (X);
+ end if;
+ end Free;
+
-----------------
-- Has_Element --
-----------------
function Has_Element (Position : Cursor) return Boolean is
begin
- if Position.Node = null then
- pragma Assert (Position.Container = null);
- return False;
- end if;
-
- return True;
+ pragma Assert (Vet (Position), "bad cursor in Has_Element");
+ return Position.Node /= null;
end Has_Element;
---------------
if not Inserted then
if Container.HT.Lock > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with cursors (set is locked)";
end if;
Position.Node.Element := New_Item;
Position : out Cursor;
Inserted : out Boolean)
is
+ begin
+ Insert (Container.HT, New_Item, Position.Node, Inserted);
+ Position.Container := Container'Unchecked_Access;
+ end Insert;
+
+ procedure Insert
+ (Container : in out Set;
+ New_Item : Element_Type)
+ is
+ Position : Cursor;
+ Inserted : Boolean;
+
+ begin
+ Insert (Container, New_Item, Position, Inserted);
+
+ if not Inserted then
+ raise Constraint_Error with
+ "attempt to insert element already in set";
+ end if;
+ end Insert;
+
+ procedure Insert
+ (HT : in out Hash_Table_Type;
+ New_Item : Element_Type;
+ Node : out Node_Access;
+ Inserted : out Boolean)
+ is
function New_Node (Next : Node_Access) return Node_Access;
pragma Inline (New_Node);
--------------
function New_Node (Next : Node_Access) return Node_Access is
- Node : constant Node_Access := new Node_Type'(New_Item, Next);
begin
- return Node;
+ return new Node_Type'(New_Item, Next);
end New_Node;
- HT : Hash_Table_Type renames Container.HT;
-
-- Start of processing for Insert
begin
- if HT.Length >= HT_Ops.Capacity (HT) then
-
- -- TODO:
- -- Perform the insertion first, and then reserve
- -- capacity, but only if the insertion succeeds and
- -- the (new) length is greater then current capacity.
- -- END TODO.
-
- HT_Ops.Reserve_Capacity (HT, HT.Length + 1);
+ if HT_Ops.Capacity (HT) = 0 then
+ HT_Ops.Reserve_Capacity (HT, 1);
end if;
- Local_Insert (HT, New_Item, Position.Node, Inserted);
- Position.Container := Container'Unchecked_Access;
- end Insert;
-
- procedure Insert
- (Container : in out Set;
- New_Item : Element_Type)
- is
- Position : Cursor;
- Inserted : Boolean;
-
- begin
- Insert (Container, New_Item, Position, Inserted);
+ Local_Insert (HT, New_Item, Node, Inserted);
- if not Inserted then
- raise Constraint_Error;
+ if Inserted
+ and then HT.Length > HT_Ops.Capacity (HT)
+ then
+ HT_Ops.Reserve_Capacity (HT, HT.Length);
end if;
end Insert;
return;
end if;
- if Source.Length = 0 then
+ if Source.HT.Length = 0 then
Clear (Target);
return;
end if;
if Target.HT.Busy > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with elements (set is busy)";
end if;
- -- TODO: optimize this to use an explicit
- -- loop instead of an active iterator
- -- (similar to how a passive iterator is
- -- implemented).
- --
- -- Another possibility is to test which
- -- set is smaller, and iterate over the
- -- smaller set.
-
Tgt_Node := HT_Ops.First (Target.HT);
while Tgt_Node /= null loop
if Is_In (Source.HT, Tgt_Node) then
function Is_Empty (Container : Set) return Boolean is
begin
- return Container.Length = 0;
+ return Container.HT.Length = 0;
end Is_Empty;
-----------
return False;
end if;
- -- TODO: rewrite this to loop in the
- -- style of a passive iterator.
-
Subset_Node := HT_Ops.First (Subset.HT);
while Subset_Node /= null loop
if not Is_In (Of_Set.HT, Subset_Node) then
Process (Cursor'(Container'Unrestricted_Access, Node));
end Process_Node;
- HT : Hash_Table_Type renames Container'Unrestricted_Access.all.HT;
- B : Natural renames HT.Busy;
+ B : Natural renames Container'Unrestricted_Access.HT.Busy;
-- Start of processing for Iterate
B := B + 1;
begin
- Iterate (HT);
+ Iterate (Container.HT);
exception
when others =>
B := B - 1;
function Next (Position : Cursor) return Cursor is
begin
if Position.Node = null then
- pragma Assert (Position.Container = null);
return No_Element;
end if;
+ pragma Assert (Vet (Position), "bad cursor in Next");
+
declare
HT : Hash_Table_Type renames Position.Container.HT;
Node : constant Node_Access := HT_Ops.Next (HT, Position.Node);
(Position : Cursor;
Process : not null access procedure (Element : Element_Type))
is
- E : Element_Type renames Position.Node.Element;
+ begin
+ if Position.Node = null then
+ raise Constraint_Error with
+ "Position cursor of Query_Element equals No_Element";
+ end if;
- HT : Hash_Table_Type renames Position.Container.HT;
+ pragma Assert (Vet (Position), "bad cursor in Query_Element");
- B : Natural renames HT.Busy;
- L : Natural renames HT.Lock;
+ declare
+ HT : Hash_Table_Type renames Position.Container.HT;
- begin
- B := B + 1;
- L := L + 1;
+ B : Natural renames HT.Busy;
+ L : Natural renames HT.Lock;
begin
- Process (E);
- exception
- when others =>
- L := L - 1;
- B := B - 1;
- raise;
- end;
+ B := B + 1;
+ L := L + 1;
- L := L - 1;
- B := B - 1;
+ begin
+ Process (Position.Node.Element);
+ exception
+ when others =>
+ L := L - 1;
+ B := B - 1;
+ raise;
+ end;
+
+ L := L - 1;
+ B := B - 1;
+ end;
end Query_Element;
----------
----------
procedure Read
- (Stream : access Root_Stream_Type'Class;
- Container : out Set)
+ (Stream : not null access Root_Stream_Type'Class;
+ Container : out Set)
is
begin
Read_Nodes (Stream, Container.HT);
end Read;
+ procedure Read
+ (Stream : not null access Root_Stream_Type'Class;
+ Item : out Cursor)
+ is
+ begin
+ raise Program_Error with "attempt to stream set cursor";
+ end Read;
+
---------------
-- Read_Node --
---------------
- function Read_Node (Stream : access Root_Stream_Type'Class)
+ function Read_Node (Stream : not null access Root_Stream_Type'Class)
return Node_Access
is
Node : Node_Access := new Node_Type;
-------------
procedure Replace
- (Container : in out Set; -- TODO: need ruling from ARG
+ (Container : in out Set;
New_Item : Element_Type)
is
Node : constant Node_Access :=
begin
if Node = null then
- raise Constraint_Error;
+ raise Constraint_Error with
+ "attempt to replace element not in set";
end if;
if Container.HT.Lock > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with cursors (set is locked)";
end if;
Node.Element := New_Item;
end Replace;
- ---------------------
- -- Replace_Element --
- ---------------------
-
- procedure Replace_Element
- (HT : in out Hash_Table_Type;
- Node : Node_Access;
- Element : Element_Type)
- is
- begin
- if Equivalent_Elements (Node.Element, Element) then
- pragma Assert (Hash (Node.Element) = Hash (Element));
-
- if HT.Lock > 0 then
- raise Program_Error;
- end if;
-
- Node.Element := Element; -- Note that this assignment can fail
- return;
- end if;
-
- if HT.Busy > 0 then
- raise Program_Error;
- end if;
-
- HT_Ops.Delete_Node_Sans_Free (HT, Node);
-
- Insert_New_Element : declare
- function New_Node (Next : Node_Access) return Node_Access;
- pragma Inline (New_Node);
-
- procedure Local_Insert is
- new Element_Keys.Generic_Conditional_Insert (New_Node);
-
- --------------
- -- New_Node --
- --------------
-
- function New_Node (Next : Node_Access) return Node_Access is
- begin
- Node.Element := Element; -- Note that this assignment can fail
- Node.Next := Next;
- return Node;
- end New_Node;
-
- Result : Node_Access;
- Inserted : Boolean;
-
- -- Start of processing for Insert_New_Element
-
- begin
- Local_Insert
- (HT => HT,
- Key => Element,
- Node => Result,
- Inserted => Inserted);
-
- if Inserted then
- pragma Assert (Result = Node);
- return;
- end if;
- exception
- when others =>
- null; -- Assignment must have failed
- end Insert_New_Element;
-
- Reinsert_Old_Element : declare
- function New_Node (Next : Node_Access) return Node_Access;
- pragma Inline (New_Node);
-
- procedure Local_Insert is
- new Element_Keys.Generic_Conditional_Insert (New_Node);
-
- --------------
- -- New_Node --
- --------------
-
- function New_Node (Next : Node_Access) return Node_Access is
- begin
- Node.Next := Next;
- return Node;
- end New_Node;
-
- Result : Node_Access;
- Inserted : Boolean;
-
- -- Start of processing for Reinsert_Old_Element
-
- begin
- Local_Insert
- (HT => HT,
- Key => Node.Element,
- Node => Result,
- Inserted => Inserted);
- exception
- when others =>
- null;
- end Reinsert_Old_Element;
-
- raise Program_Error;
- end Replace_Element;
-
procedure Replace_Element
- (Container : Set;
+ (Container : in out Set;
Position : Cursor;
- By : Element_Type)
+ New_Item : Element_Type)
is
- HT : Hash_Table_Type renames Container'Unrestricted_Access.all.HT;
-
begin
if Position.Node = null then
- raise Constraint_Error;
+ raise Constraint_Error with
+ "Position cursor equals No_Element";
end if;
- if Position.Container /= Set_Access'(Container'Unrestricted_Access) then
- raise Program_Error;
+ if Position.Container /= Container'Unrestricted_Access then
+ raise Program_Error with
+ "Position cursor designates wrong set";
end if;
- Replace_Element (HT, Position.Node, By);
+ pragma Assert (Vet (Position), "bad cursor in Replace_Element");
+
+ Replace_Element (Container.HT, Position.Node, New_Item);
end Replace_Element;
----------------------
end if;
if Target.HT.Busy > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with elements (set is busy)";
end if;
declare
return (Controlled with HT => (Buckets, Length, 0, 0));
end Symmetric_Difference;
+ ------------
+ -- To_Set --
+ ------------
+
+ function To_Set (New_Item : Element_Type) return Set is
+ HT : Hash_Table_Type;
+ Node : Node_Access;
+ Inserted : Boolean;
+
+ begin
+ Insert (HT, New_Item, Node, Inserted);
+ return Set'(Controlled with HT);
+ end To_Set;
+
-----------
-- Union --
-----------
end if;
if Target.HT.Busy > 0 then
- raise Program_Error;
+ raise Program_Error with
+ "attempt to tamper with elements (set is busy)";
end if;
declare
return (Controlled with HT => (Buckets, Length, 0, 0));
end Union;
+ ---------
+ -- Vet --
+ ---------
+
+ function Vet (Position : Cursor) return Boolean is
+ begin
+ if Position.Node = null then
+ return Position.Container = null;
+ end if;
+
+ if Position.Container = null then
+ return False;
+ end if;
+
+ if Position.Node.Next = Position.Node then
+ return False;
+ end if;
+
+ declare
+ HT : Hash_Table_Type renames Position.Container.HT;
+ X : Node_Access;
+
+ begin
+ if HT.Length = 0 then
+ return False;
+ end if;
+
+ if HT.Buckets = null
+ or else HT.Buckets'Length = 0
+ then
+ return False;
+ end if;
+
+ X := HT.Buckets (Element_Keys.Index (HT, Position.Node.Element));
+
+ for J in 1 .. HT.Length loop
+ if X = Position.Node then
+ return True;
+ end if;
+
+ if X = null then
+ return False;
+ end if;
+
+ if X = X.Next then -- to prevent unnecessary looping
+ return False;
+ end if;
+
+ X := X.Next;
+ end loop;
+
+ return False;
+ end;
+ end Vet;
+
-----------
-- Write --
-----------
procedure Write
- (Stream : access Root_Stream_Type'Class;
+ (Stream : not null access Root_Stream_Type'Class;
Container : Set)
is
begin
Write_Nodes (Stream, Container.HT);
end Write;
+ procedure Write
+ (Stream : not null access Root_Stream_Type'Class;
+ Item : Cursor)
+ is
+ begin
+ raise Program_Error with "attempt to stream set cursor";
+ end Write;
+
----------------
-- Write_Node --
----------------
procedure Write_Node
- (Stream : access Root_Stream_Type'Class;
+ (Stream : not null access Root_Stream_Type'Class;
Node : Node_Access)
is
begin
Key_Keys.Delete_Key_Sans_Free (Container.HT, Key, X);
if X = null then
- raise Constraint_Error;
+ raise Constraint_Error with "attempt to delete key not in set";
end if;
Free (X);
Node : constant Node_Access := Key_Keys.Find (Container.HT, Key);
begin
+ if Node = null then
+ raise Constraint_Error with "key not in map";
+ end if;
+
return Node.Element;
end Element;
Node : Node_Access) return Boolean
is
begin
- return Equivalent_Keys (Key, Node.Element);
+ return Equivalent_Keys (Key, Generic_Keys.Key (Node.Element));
end Equivalent_Key_Node;
- ---------------------
- -- Equivalent_Keys --
- ---------------------
-
- function Equivalent_Keys
- (Left : Cursor;
- Right : Key_Type) return Boolean is
- begin
- return Equivalent_Keys (Right, Left.Node.Element);
- end Equivalent_Keys;
-
- function Equivalent_Keys
- (Left : Key_Type;
- Right : Cursor) return Boolean is
- begin
- return Equivalent_Keys (Left, Right.Node.Element);
- end Equivalent_Keys;
-
-------------
-- Exclude --
-------------
function Key (Position : Cursor) return Key_Type is
begin
+ if Position.Node = null then
+ raise Constraint_Error with
+ "Position cursor equals No_Element";
+ end if;
+
+ pragma Assert (Vet (Position), "bad cursor in function Key");
+
return Key (Position.Node.Element);
end Key;
begin
if Node = null then
- raise Constraint_Error;
+ raise Constraint_Error with
+ "attempt to replace key not in set";
end if;
Replace_Element (Container.HT, Node, New_Item);
Process : not null access
procedure (Element : in out Element_Type))
is
- HT : Hash_Table_Type renames Container.HT;
+ HT : Hash_Table_Type renames Container.HT;
+ Indx : Hash_Type;
begin
if Position.Node = null then
- raise Constraint_Error;
+ raise Constraint_Error with
+ "Position cursor equals No_Element";
+ end if;
+
+ if Position.Container /= Container'Unrestricted_Access then
+ raise Program_Error with
+ "Position cursor designates wrong set";
end if;
- if Position.Container /= Set_Access'(Container'Unchecked_Access) then
- raise Program_Error;
+ if HT.Buckets = null
+ or else HT.Buckets'Length = 0
+ or else HT.Length = 0
+ or else Position.Node.Next = Position.Node
+ then
+ raise Program_Error with "Position cursor is bad (set is empty)";
end if;
+ pragma Assert
+ (Vet (Position),
+ "bad cursor in Update_Element_Preserving_Key");
+
+ Indx := HT_Ops.Index (HT, Position.Node);
+
declare
E : Element_Type renames Position.Node.Element;
- K : Key_Type renames Key (E);
+ K : constant Key_Type := Key (E);
B : Natural renames HT.Busy;
L : Natural renames HT.Lock;
L := L - 1;
B := B - 1;
- if Equivalent_Keys (K, E) then
+ if Equivalent_Keys (K, Key (E)) then
pragma Assert (Hash (K) = Hash (E));
return;
end if;
end;
+ if HT.Buckets (Indx) = Position.Node then
+ HT.Buckets (Indx) := Position.Node.Next;
+
+ else
+ declare
+ Prev : Node_Access := HT.Buckets (Indx);
+
+ begin
+ while Prev.Next /= Position.Node loop
+ Prev := Prev.Next;
+
+ if Prev = null then
+ raise Program_Error with
+ "Position cursor is bad (node not found)";
+ end if;
+ end loop;
+
+ Prev.Next := Position.Node.Next;
+ end;
+ end if;
+
+ HT.Length := HT.Length - 1;
+
declare
X : Node_Access := Position.Node;
+
begin
- HT_Ops.Delete_Node_Sans_Free (HT, X);
Free (X);
end;
- raise Program_Error;
+ raise Program_Error with "key was modified";
end Update_Element_Preserving_Key;
end Generic_Keys;