1--  Copyright 1994 Grady Booch
2--  Copyright 2003-2014 Simon Wright <simon@pushface.org>
3--  Copyright 2005 Martin Krischik
4
5--  This package is free software; you can redistribute it and/or
6--  modify it under terms of the GNU General Public License as
7--  published by the Free Software Foundation; either version 2, or
8--  (at your option) any later version. This package is distributed in
9--  the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10--  even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11--  PARTICULAR PURPOSE. See the GNU General Public License for more
12--  details. You should have received a copy of the GNU General Public
13--  License distributed with this package; see file COPYING.  If not,
14--  write to the Free Software Foundation, 59 Temple Place - Suite
15--  330, Boston, MA 02111-1307, USA.
16
17--  As a special exception, if other files instantiate generics from
18--  this unit, or you link this unit with other files to produce an
19--  executable, this unit does not by itself cause the resulting
20--  executable to be covered by the GNU General Public License.  This
21--  exception does not however invalidate any other reasons why the
22--  executable file might be covered by the GNU Public License.
23
24with Ada.Unchecked_Deallocation;
25with System.Address_To_Access_Conversions;
26
27package body BC.Support.Indefinite_Unmanaged is
28   use type IR.Pointer;
29
30   --  We can't take 'Access of components of constant (in parameter)
31   --  objects; but we need to be able to do this so that we can
32   --  update the cache (which doesn't violate the abstraction, just
33   --  the Ada restriction). This technique is due to Matthew Heaney.
34   package Allow_Access
35   is new System.Address_To_Access_Conversions (Unm_Node);
36
37   function Create (I : Item; Previous, Next : Node_Ref) return Node_Ref;
38   pragma Inline (Create);
39
40   function Create (I : Item; Previous, Next : Node_Ref) return Node_Ref is
41      Result : Node_Ref;
42   begin
43      Result := new Node'(Element => IR.Create (Value => I),
44                          Previous => Previous,
45                          Next => Next);
46      if Previous /= null then
47         Previous.Next := Result;
48      end if;
49      if Next /= null then
50         Next.Previous := Result;
51      end if;
52      return Result;
53   end Create;
54
55   procedure Delete_Node is new
56     Ada.Unchecked_Deallocation (Node, Node_Ref);
57
58   procedure Update_Cache (Obj : in out Unm_Node; Index : Positive);
59
60   procedure Update_Cache (Obj : in out Unm_Node; Index : Positive) is
61   begin
62      if Index > Obj.Size then
63         raise BC.Range_Error;
64      end if;
65      if Obj.Cache /= null then
66         if Index = Obj.Cache_Index then
67            return;
68         elsif Index = Obj.Cache_Index + 1 then
69            Obj.Cache := Obj.Cache.Next;
70            Obj.Cache_Index := Index;
71            return;
72         elsif Index = Obj.Cache_Index - 1 then
73            Obj.Cache := Obj.Cache.Previous;
74            Obj.Cache_Index := Index;
75            return;
76         end if;
77      end if;
78      declare
79         Ptr : Node_Ref := Obj.Rep;
80      begin
81         for I in 1 .. Index - 1 loop
82            Ptr := Ptr.Next;
83         end loop;
84         Obj.Cache := Ptr;
85         Obj.Cache_Index := Index;
86      end;
87   end Update_Cache;
88
89   function "=" (Left, Right : in Unm_Node) return Boolean is
90   begin
91      if Left.Size = Right.Size then
92         declare
93            Temp_L : Node_Ref := Left.Rep;
94            Temp_R : Node_Ref := Right.Rep;
95         begin
96            while Temp_L /= null loop
97               if IR.Value (Temp_L.Element)
98                 /= IR.Value (Temp_R.Element)
99               then
100                  return False;
101               end if;
102               Temp_L := Temp_L.Next;
103               Temp_R := Temp_R.Next;
104            end loop;
105            return True;
106         end;
107      else
108         return False;
109      end if;
110   end "=";
111
112   procedure Clear (Obj : in out Unm_Node) is
113      Ptr : Node_Ref;
114   begin
115      while Obj.Rep /= null loop
116         Ptr := Obj.Rep;
117         Obj.Rep := Obj.Rep.Next;
118         Delete_Node (Ptr);
119      end loop;
120      Obj.Last := null;
121      Obj.Size := 0;
122      Obj.Cache := null;
123      Obj.Cache_Index := 0;
124   end Clear;
125
126   procedure Insert (Obj : in out Unm_Node; Elem : Item) is
127   begin
128      Obj.Rep := Create (Elem, Previous => null, Next => Obj.Rep);
129      if Obj.Last = null then
130         Obj.Last := Obj.Rep;
131      end if;
132      Obj.Size := Obj.Size + 1;
133      Obj.Cache := Obj.Rep;
134      Obj.Cache_Index := 1;
135   end Insert;
136
137   procedure Insert (Obj : in out Unm_Node; Elem : Item; Before : Positive) is
138   begin
139      if Before > Obj.Size then
140         raise BC.Range_Error;
141      end if;
142      if Obj.Size = 0 or else Before = 1 then
143         Insert (Obj, Elem);
144      else
145         declare
146            Temp_Node : Node_Ref;
147         begin
148            Update_Cache (Obj, Before);
149            Temp_Node := Create (Elem,
150                                 Previous => Obj.Cache.Previous,
151                                 Next => Obj.Cache);
152            if Temp_Node.Previous = null then
153               Obj.Rep := Temp_Node;
154            end if;
155            Obj.Size := Obj.Size + 1;
156            Obj.Cache := Temp_Node;
157         end;
158      end if;
159   end Insert;
160
161   procedure Append (Obj : in out Unm_Node; Elem : Item) is
162   begin
163      Obj.Last := Create (Elem, Previous => Obj.Last, Next => null);
164      if Obj.Last.Previous /= null then
165         Obj.Last.Previous.Next := Obj.Last;
166      end if;
167      if Obj.Rep = null then
168         Obj.Rep := Obj.Last;
169      end if;
170      Obj.Size := Obj.Size + 1;
171      Obj.Cache := Obj.Last;
172      Obj.Cache_Index := Obj.Size;
173   end Append;
174
175   procedure Append (Obj : in out Unm_Node; Elem : Item; After : Positive) is
176   begin
177      if After > Obj.Size then
178         raise BC.Range_Error;
179      end if;
180      if Obj.Size = 0 then
181         Append (Obj, Elem);
182      else
183         declare
184            Temp_Node : Node_Ref;
185         begin
186            Update_Cache (Obj, After);
187            Temp_Node := Create (Elem,
188                                 Previous => Obj.Cache,
189                                 Next => Obj.Cache.Next);
190            if Temp_Node.Previous /= null then
191               Temp_Node.Previous.Next := Temp_Node;
192            end if;
193            if Temp_Node.Next = null then
194               Obj.Last := Temp_Node;
195            end if;
196            Obj.Size := Obj.Size + 1;
197            Obj.Cache := Temp_Node;
198            Obj.Cache_Index := Obj.Cache_Index + 1;
199         end;
200      end if;
201   end Append;
202
203   procedure Remove (Obj : in out Unm_Node; From : Positive) is
204   begin
205      if From > Obj.Size then
206         raise BC.Range_Error;
207      end if;
208      if Obj.Size = 0 then
209         raise BC.Underflow;
210      end if;
211      if Obj.Size = 1 then
212         Clear (Obj);
213      else
214         declare
215            Ptr : Node_Ref;
216         begin
217            Update_Cache (Obj, From);
218            Ptr := Obj.Cache;
219            if Ptr.Previous = null then
220               Obj.Rep := Ptr.Next;
221            else
222               Ptr.Previous.Next := Ptr.Next;
223            end if;
224            if Ptr.Next = null then
225               Obj.Last := Ptr.Previous;
226            else
227               Ptr.Next.Previous := Ptr.Previous;
228            end if;
229            Obj.Size := Obj.Size - 1;
230            if Ptr.Next /= null then
231               Obj.Cache := Ptr.Next;
232            elsif Ptr.Previous /= null then
233               Obj.Cache := Ptr.Previous;
234               Obj.Cache_Index := Obj.Cache_Index - 1;
235            else
236               Obj.Cache := null;
237               Obj.Cache_Index := 0;
238            end if;
239            Delete_Node (Ptr);
240         end;
241      end if;
242   end Remove;
243
244   procedure Replace (Obj : in out Unm_Node; Index : Positive; Elem : Item) is
245   begin
246      if Index > Obj.Size then
247         raise BC.Range_Error;
248      end if;
249      if not ((Obj.Cache /= null) and then (Index = Obj.Cache_Index)) then
250         declare
251            Ptr : Node_Ref := Obj.Rep;
252         begin
253            for I in 1 .. Obj.Size loop
254               if I = Index then
255                  Obj.Cache := Ptr;
256                  Obj.Cache_Index := I;
257                  exit;
258               else
259                  Ptr := Ptr.Next;
260               end if;
261            end loop;
262         end;
263      end if;
264      Obj.Cache.Element := IR.Create (Value => Elem);
265   end Replace;
266
267   function Length (Obj : Unm_Node) return Natural is
268   begin
269      return Obj.Size;
270   end Length;
271
272   function First (Obj : Unm_Node) return Item is
273   begin
274      if Obj.Size = 0 then
275         raise BC.Underflow;
276      end if;
277      return IR.Value (Obj.Rep.Element);
278   end First;
279
280   function Last (Obj : Unm_Node) return Item is
281   begin
282      if Obj.Size = 0 then
283         raise BC.Underflow;
284      end if;
285      return IR.Value (Obj.Last.Element);
286   end Last;
287
288   function Item_At (Obj : Unm_Node; Index : Positive) return Item is
289      Tmp : Item_Ptr;
290   begin
291      if Index > Obj.Size then
292         raise BC.Range_Error;
293      end if;
294      Tmp := Item_At (Obj, Index);
295      return Tmp.all;
296   end Item_At;
297
298   function Item_At (Obj : Unm_Node; Index : Positive) return Item_Ptr is
299      U : constant Allow_Access.Object_Pointer
300        := Allow_Access.To_Pointer (Obj'Address);
301      --  Note, although (GNAT 3.11p) the value in Obj is successfully
302      --  updated via U, the optimiser can get fooled; when we return
303      --  next/previous cache hits, we must return via U. I don't
304      --  think this is a bug; the pointer aliasing is a nasty trick,
305      --  after all.
306   begin
307      if Index > Obj.Size then
308         raise BC.Range_Error;
309      end if;
310      Update_Cache (U.all, Index);
311      return IR.Value_Access (U.Cache.Element);
312   end Item_At;
313
314   function Location (Obj : Unm_Node; Elem : Item; Start : Positive := 1)
315                     return Natural is
316      Ptr : Node_Ref := Obj.Rep;
317      U : constant Allow_Access.Object_Pointer
318        := Allow_Access.To_Pointer (Obj'Address);
319   begin
320      --  XXX the C++ (which indexes from 0) nevertheless checks
321      --  "start <= count". We have to special-case the empty Node;
322      --  the C++ indexes from 0, so it can legally start with index 0
323      --  when the Node is empty.
324      if Obj.Size = 0 then
325         return 0;
326      end if;
327      if Start > Obj.Size then
328         raise BC.Range_Error;
329      end if;
330      if Start = Obj.Cache_Index
331        and then Elem = IR.Value (Obj.Cache.Element)
332      then
333         return Obj.Cache_Index;
334      end if;
335      for I in 1 .. Start - 1 loop
336         Ptr := Ptr.Next; -- advance to Start point
337      end loop;
338      for I in Start .. Obj.Size loop
339         if Ptr.Element = Elem then
340            U.Cache := Ptr;
341            U.Cache_Index := I;
342            return I;
343         else
344            Ptr := Ptr.Next;
345         end if;
346      end loop;
347      return 0;
348   end Location;
349
350   procedure Adjust (U : in out Unm_Node) is
351      Tmp : Node_Ref := U.Last;
352   begin
353      if Tmp /= null then
354         U.Last := Create (IR.Value (Tmp.Element),
355                           Previous => null,
356                           Next => null);
357         U.Rep := U.Last;
358         Tmp := Tmp.Previous;  -- move to previous node from orig list
359         while Tmp /= null loop
360            U.Rep := Create (IR.Value (Tmp.Element),
361                             Previous => null,
362                             Next => U.Rep);
363            Tmp := Tmp.Previous;
364         end loop;
365      end if;
366      U.Cache := null;
367      U.Cache_Index := 0;
368   end Adjust;
369
370   procedure Finalize (U : in out Unm_Node) is
371      Ptr : Node_Ref;
372   begin
373      --  code to delete Rep copied from Clear()
374      while U.Rep /= null loop
375         Ptr := U.Rep;
376         U.Rep := U.Rep.Next;
377         Delete_Node (Ptr);
378      end loop;
379   end Finalize;
380
381   procedure Write_Unm_Node
382     (Stream : access Ada.Streams.Root_Stream_Type'Class;
383      Obj : Unm_Node) is
384      N : Node_Ref := Obj.Rep;
385   begin
386      Integer'Write (Stream, Obj.Size);
387      while N /= null loop
388         Item'Output (Stream, IR.Value (N.Element));
389         N := N.Next;
390      end loop;
391   end Write_Unm_Node;
392
393   procedure Read_Unm_Node
394     (Stream : access Ada.Streams.Root_Stream_Type'Class;
395      Obj : out Unm_Node) is
396      Count : Integer;
397   begin
398      Clear (Obj);
399      Integer'Read (Stream, Count);
400      for I in 1 .. Count loop
401         declare
402            Elem : constant Item := Item'Input (Stream);
403         begin
404            Append (Obj, Elem);
405         end;
406      end loop;
407   end Read_Unm_Node;
408
409end BC.Support.Indefinite_Unmanaged;
410