-- Copyright 1994 Grady Booch -- Copyright 2003 Simon Wright -- This package is free software; you can redistribute it and/or -- modify it under terms of the GNU General Public License as -- published by the Free Software Foundation; either version 2, or -- (at your option) any later version. This package is distributed in -- the hope that it will be useful, but WITHOUT ANY WARRANTY; without -- even the implied warranty of MERCHANTABILITY or FITNESS FOR A -- PARTICULAR PURPOSE. See the GNU General Public License for more -- details. You should have received a copy of the GNU General Public -- License distributed with this package; see file COPYING. If not, -- write to the Free Software Foundation, 59 Temple Place - Suite -- 330, Boston, MA 02111-1307, USA. -- As a special exception, if other files instantiate generics from -- this unit, or you link this unit with other files to produce an -- executable, this unit does not by itself cause the resulting -- executable to be covered by the GNU General Public License. This -- exception does not however invalidate any other reasons why the -- executable file might be covered by the GNU Public License. -- $RCSfile: bc-support-unmanaged.adb,v $ -- $Revision: 1.1.2.1 $ -- $Date: 2003/01/15 05:52:00 $ -- $Author: simon $ with Ada.Unchecked_Deallocation; with BC.Support.Exceptions; with System.Address_To_Access_Conversions; package body BC.Support.Unmanaged is package BSE renames BC.Support.Exceptions; procedure Assert is new BSE.Assert ("BC.Support.Unmanaged"); -- We can't take 'Access of components of constant (in parameter) -- objects; but we need to be able to do this so that we can -- update the cache (which doesn't violate the abstraction, just -- the Ada restriction). This technique is due to Matthew Heaney. package Allow_Access is new System.Address_To_Access_Conversions (Unm_Node); -- We can't take 'Access of non-aliased components. But if we -- alias discriminated objects they become constrained - even if -- the discriminant has a default. package Allow_Element_Access is new System.Address_To_Access_Conversions (Item); function Create (I : Item; Previous, Next : Node_Ref) return Node_Ref; pragma Inline (Create); function Create (I : Item; Previous, Next : Node_Ref) return Node_Ref is Result : Node_Ref; begin Result := new Node'(Element => I, Previous => Previous, Next => Next); if Previous /= null then Previous.Next := Result; end if; if Next /= null then Next.Previous := Result; end if; return Result; end Create; procedure Delete_Node is new Ada.Unchecked_Deallocation (Node, Node_Ref); procedure Update_Cache (Obj : in out Unm_Node; Index : Positive); procedure Update_Cache (Obj : in out Unm_Node; Index : Positive) is begin Assert (Index <= Obj.Size, BC.Range_Error'Identity, "Update_Cache", BSE.Invalid_Index); if Obj.Cache /= null then if Index = Obj.Cache_Index then return; elsif Index = Obj.Cache_Index + 1 then Obj.Cache := Obj.Cache.Next; Obj.Cache_Index := Index; return; elsif Index = Obj.Cache_Index - 1 then Obj.Cache := Obj.Cache.Previous; Obj.Cache_Index := Index; return; end if; end if; declare Ptr : Node_Ref := Obj.Rep; begin for I in 1 .. Index - 1 loop Ptr := Ptr.Next; end loop; Obj.Cache := Ptr; Obj.Cache_Index := Index; end; end Update_Cache; function "=" (Left, Right : in Unm_Node) return Boolean is begin if Left.Size = Right.Size then declare Temp_L : Node_Ref := Left.Rep; Temp_R : Node_Ref := Right.Rep; begin while Temp_L /= null loop if Temp_L.Element /= Temp_R.Element then return False; end if; Temp_L := Temp_L.Next; Temp_R := Temp_R.Next; end loop; return True; end; else return False; end if; end "="; procedure Clear (Obj : in out Unm_Node) is Empty_Node : Unm_Node; Ptr : Node_Ref; begin while Obj.Rep /= null loop Ptr := Obj.Rep; Obj.Rep := Obj.Rep.Next; Delete_Node (Ptr); end loop; Obj := Empty_Node; end Clear; procedure Insert (Obj : in out Unm_Node; Elem : Item) is begin Obj.Rep := Create (Elem, Previous => null, Next => Obj.Rep); if Obj.Last = null then Obj.Last := Obj.Rep; end if; Obj.Size := Obj.Size + 1; Obj.Cache := Obj.Rep; Obj.Cache_Index := 1; end Insert; procedure Insert (Obj : in out Unm_Node; Elem : Item; Before : Positive) is begin Assert (Before <= Obj.Size, BC.Range_Error'Identity, "Insert", BSE.Invalid_Index); if Obj.Size = 0 or else Before = 1 then Insert (Obj, Elem); else declare Temp_Node : Node_Ref; begin Update_Cache (Obj, Before); Temp_Node := Create (Elem, Previous => Obj.Cache.Previous, Next => Obj.Cache); if Temp_Node.Previous = null then Obj.Rep := Temp_Node; end if; Obj.Size := Obj.Size + 1; Obj.Cache := Temp_Node; end; end if; end Insert; procedure Append (Obj : in out Unm_Node; Elem : Item) is begin Obj.Last := Create (Elem, Previous => Obj.Last, Next => null); if Obj.Last.Previous /= null then Obj.Last.Previous.Next := Obj.Last; end if; if Obj.Rep = null then Obj.Rep := Obj.Last; end if; Obj.Size := Obj.Size + 1; Obj.Cache := Obj.Last; Obj.Cache_Index := Obj.Size; end Append; procedure Append (Obj : in out Unm_Node; Elem : Item; After : Positive) is begin Assert (After <= Obj.Size, BC.Range_Error'Identity, "Append", BSE.Invalid_Index); if Obj.Size = 0 then Append (Obj, Elem); else declare Temp_Node : Node_Ref; begin Update_Cache (Obj, After); Temp_Node := Create (Elem, Previous => Obj.Cache, Next => Obj.Cache.Next); if Temp_Node.Previous /= null then Temp_Node.Previous.Next := Temp_Node; end if; if Temp_Node.Next = null then Obj.Last := Temp_Node; end if; Obj.Size := Obj.Size + 1; Obj.Cache := Temp_Node; Obj.Cache_Index := Obj.Cache_Index + 1; end; end if; end Append; procedure Remove (Obj : in out Unm_Node; From : Positive) is begin Assert (From <= Obj.Size, BC.Range_Error'Identity, "Remove", BSE.Invalid_Index); Assert (Obj.Size > 0, BC.Underflow'Identity, "Remove", BSE.Empty); if Obj.Size = 1 then Clear (Obj); else declare Ptr : Node_Ref; begin Update_Cache (Obj, From); Ptr := Obj.Cache; if Ptr.Previous = null then Obj.Rep := Ptr.Next; else Ptr.Previous.Next := Ptr.Next; end if; if Ptr.Next = null then Obj.Last := Ptr.Previous; else Ptr.Next.Previous := Ptr.Previous; end if; Obj.Size := Obj.Size - 1; if Ptr.Next /= null then Obj.Cache := Ptr.Next; elsif Ptr.Previous /= null then Obj.Cache := Ptr.Previous; Obj.Cache_Index := Obj.Cache_Index - 1; else Obj.Cache := null; Obj.Cache_Index := 0; end if; Delete_Node (Ptr); end; end if; end Remove; procedure Replace (Obj : in out Unm_Node; Index : Positive; Elem : Item) is begin Assert (Index <= Obj.Size, BC.Range_Error'Identity, "Replace", BSE.Invalid_Index); if not ((Obj.Cache /= null) and then (Index = Obj.Cache_Index)) then declare Ptr : Node_Ref := Obj.Rep; begin for I in 1 .. Obj.Size loop if I = Index then Obj.Cache := Ptr; Obj.Cache_Index := I; exit; else Ptr := Ptr.Next; end if; end loop; end; end if; Obj.Cache.Element := Elem; end Replace; function Length (Obj : Unm_Node) return Natural is begin return Obj.Size; end Length; function First (Obj : Unm_Node) return Item is begin Assert (Obj.Size > 0, BC.Underflow'Identity, "First", BSE.Empty); return Obj.Rep.Element; end First; function Last (Obj : Unm_Node) return Item is begin Assert (Obj.Size > 0, BC.Underflow'Identity, "Last", BSE.Empty); return Obj.Last.Element; end Last; function Item_At (Obj : Unm_Node; Index : Positive) return Item is Tmp : Item_Ptr; begin Assert (Index <= Obj.Size, BC.Range_Error'Identity, "Item_At", BSE.Invalid_Index); Tmp := Item_At (Obj, Index); return Tmp.all; end Item_At; function Item_At (Obj : Unm_Node; Index : Positive) return Item_Ptr is U : Allow_Access.Object_Pointer := Allow_Access.To_Pointer (Obj'Address); -- Note, although (GNAT 3.11p) the value in Obj is successfully -- updated via U, the optimiser can get fooled; when we return -- next/previous cache hits, we must return via U. I don't -- think this is a bug; the pointer aliasing is a nasty trick, -- after all. begin Assert (Index <= Obj.Size, BC.Range_Error'Identity, "Item_At", BSE.Invalid_Index); Update_Cache (U.all, Index); return Item_Ptr (Allow_Element_Access.To_Pointer (U.Cache.Element'Address)); end Item_At; function Location (Obj : Unm_Node; Elem : Item; Start : Positive := 1) return Natural is Ptr : Node_Ref := Obj.Rep; U : Allow_Access.Object_Pointer := Allow_Access.To_Pointer (Obj'Address); begin -- XXX the C++ (which indexes from 0) nevertheless checks -- "start <= count". We have to special-case the empty Node; -- the C++ indexes from 0, so it can legally start with index 0 -- when the Node is empty. if Obj.Size = 0 then return 0; end if; Assert (Start <= Obj.Size, BC.Range_Error'Identity, "Location", BSE.Invalid_Index); if (Start = Obj.Cache_Index) and then (Elem = Obj.Cache.Element) then return Obj.Cache_Index; end if; for I in 1 .. Start - 1 loop Ptr := Ptr.Next; -- advance to Start point end loop; for I in Start .. Obj.Size loop if Ptr.Element = Elem then U.Cache := Ptr; U.Cache_Index := I; return I; else Ptr := Ptr.Next; end if; end loop; return 0; end Location; procedure Adjust (U : in out Unm_Node) is Tmp : Node_Ref := U.Last; begin if Tmp /= null then U.Last := Create (Tmp.Element, Previous => null, Next => null); U.Rep := U.Last; Tmp := Tmp.Previous; -- move to previous node from orig list while Tmp /= null loop U.Rep := Create (Tmp.Element, Previous => null, Next => U.Rep); Tmp := Tmp.Previous; end loop; end if; U.Cache := null; U.Cache_Index := 0; end Adjust; procedure Finalize (U : in out Unm_Node) is Ptr : Node_Ref; begin -- code to delete Rep copied from Clear() while U.Rep /= null loop Ptr := U.Rep; U.Rep := U.Rep.Next; Delete_Node (Ptr); end loop; end Finalize; procedure Write_Unm_Node (Stream : access Ada.Streams.Root_Stream_Type'Class; Obj : Unm_Node) is N : Node_Ref := Obj.Rep; begin Integer'Write (Stream, Obj.Size); while N /= null loop Item'Output (Stream, N.Element); N := N.Next; end loop; end Write_Unm_Node; procedure Read_Unm_Node (Stream : access Ada.Streams.Root_Stream_Type'Class; Obj : out Unm_Node) is Count : Integer; begin Clear (Obj); Integer'Read (Stream, Count); for I in 1 .. Count loop declare Elem : constant Item := Item'Input (Stream); begin Append (Obj, Elem); end; end loop; end Read_Unm_Node; end BC.Support.Unmanaged;