------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                               E L I S T S                                --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 1992-2022, 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- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT 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 GNAT; see file COPYING3.  If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license.          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

--  WARNING: There is a C version of this package. Any changes to this
--  source file must be properly reflected in the C header a-elists.h.

with Alloc;
with Debug;  use Debug;
with Output; use Output;
with Table;

package body Elists is

   -------------------------------------
   -- Implementation of Element Lists --
   -------------------------------------

   --  Element lists are composed of three types of entities. The element
   --  list header, which references the first and last elements of the
   --  list, the elements themselves which are singly linked and also
   --  reference the nodes on the list, and finally the nodes themselves.
   --  The following diagram shows how an element list is represented:

   --       +----------------------------------------------------+
   --       |  +------------------------------------------+      |
   --       |  |                                          |      |
   --       V  |                                          V      |
   --    +-----|--+    +-------+    +-------+         +-------+  |
   --    |  Elmt  |    |  1st  |    |  2nd  |         |  Last |  |
   --    |  List  |--->|  Elmt |--->|  Elmt  ---...-->|  Elmt ---+
   --    | Header |    |   |   |    |   |   |         |   |   |
   --    +--------+    +---|---+    +---|---+         +---|---+
   --                      |            |                 |
   --                      V            V                 V
   --                  +-------+    +-------+         +-------+
   --                  |       |    |       |         |       |
   --                  | Node1 |    | Node2 |         | Node3 |
   --                  |       |    |       |         |       |
   --                  +-------+    +-------+         +-------+

   --  The list header is an entry in the Elists table. The values used for
   --  the type Elist_Id are subscripts into this table. The First_Elmt field
   --  (Lfield1) points to the first element on the list, or to No_Elmt in the
   --  case of an empty list. Similarly the Last_Elmt field (Lfield2) points to
   --  the last element on the list or to No_Elmt in the case of an empty list.

   --  The elements themselves are entries in the Elmts table. The Next field
   --  of each entry points to the next element, or to the Elist header if this
   --  is the last item in the list. The Node field points to the node which
   --  is referenced by the corresponding list entry.

   -------------------------
   -- Element List Tables --
   -------------------------

   type Elist_Header is record
      First : Elmt_Id;
      Last  : Elmt_Id;
   end record;

   package Elists is new Table.Table (
     Table_Component_Type => Elist_Header,
     Table_Index_Type     => Elist_Id'Base,
     Table_Low_Bound      => First_Elist_Id,
     Table_Initial        => Alloc.Elists_Initial,
     Table_Increment      => Alloc.Elists_Increment,
     Table_Name           => "Elists");

   type Elmt_Item is record
      Node : Node_Or_Entity_Id;
      Next : Union_Id;
   end record;

   package Elmts is new Table.Table (
     Table_Component_Type => Elmt_Item,
     Table_Index_Type     => Elmt_Id'Base,
     Table_Low_Bound      => First_Elmt_Id,
     Table_Initial        => Alloc.Elmts_Initial,
     Table_Increment      => Alloc.Elmts_Increment,
     Table_Name           => "Elmts");

   -----------------
   -- Append_Elmt --
   -----------------

   procedure Append_Elmt (N : Node_Or_Entity_Id; To : Elist_Id) is
      L : constant Elmt_Id := Elists.Table (To).Last;

   begin
      Elmts.Increment_Last;
      Elmts.Table (Elmts.Last).Node := N;
      Elmts.Table (Elmts.Last).Next := Union_Id (To);

      if L = No_Elmt then
         Elists.Table (To).First := Elmts.Last;
      else
         Elmts.Table (L).Next := Union_Id (Elmts.Last);
      end if;

      Elists.Table (To).Last  := Elmts.Last;

      if Debug_Flag_N then
         Write_Str ("Append new element Elmt_Id = ");
         Write_Int (Int (Elmts.Last));
         Write_Str (" to list Elist_Id = ");
         Write_Int (Int (To));
         Write_Str (" referencing Node_Or_Entity_Id = ");
         Write_Int (Int (N));
         Write_Eol;
      end if;
   end Append_Elmt;

   ---------------------
   -- Append_New_Elmt --
   ---------------------

   procedure Append_New_Elmt (N : Node_Or_Entity_Id; To : in out Elist_Id) is
   begin
      if To = No_Elist then
         To := New_Elmt_List;
      end if;

      Append_Elmt (N, To);
   end Append_New_Elmt;

   ------------------------
   -- Append_Unique_Elmt --
   ------------------------

   procedure Append_Unique_Elmt (N : Node_Or_Entity_Id; To : Elist_Id) is
      Elmt : Elmt_Id;
   begin
      Elmt := First_Elmt (To);
      loop
         if No (Elmt) then
            Append_Elmt (N, To);
            return;
         elsif Node (Elmt) = N then
            return;
         else
            Next_Elmt (Elmt);
         end if;
      end loop;
   end Append_Unique_Elmt;

   --------------
   -- Contains --
   --------------

   function Contains (List : Elist_Id; N : Node_Or_Entity_Id) return Boolean is
      Elmt : Elmt_Id;

   begin
      if Present (List) then
         Elmt := First_Elmt (List);
         while Present (Elmt) loop
            if Node (Elmt) = N then
               return True;
            end if;

            Next_Elmt (Elmt);
         end loop;
      end if;

      return False;
   end Contains;

   --------------------
   -- Elists_Address --
   --------------------

   function Elists_Address return System.Address is
   begin
      return Elists.Table (First_Elist_Id)'Address;
   end Elists_Address;

   -------------------
   -- Elmts_Address --
   -------------------

   function Elmts_Address return System.Address is
   begin
      return Elmts.Table (First_Elmt_Id)'Address;
   end Elmts_Address;

   ----------------
   -- First_Elmt --
   ----------------

   function First_Elmt (List : Elist_Id) return Elmt_Id is
   begin
      pragma Assert (List > Elist_Low_Bound);
      return Elists.Table (List).First;
   end First_Elmt;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize is
   begin
      Elists.Init;
      Elmts.Init;
   end Initialize;

   -----------------------
   -- Insert_Elmt_After --
   -----------------------

   procedure Insert_Elmt_After (N : Node_Or_Entity_Id; Elmt : Elmt_Id) is
      Nxt : constant Union_Id := Elmts.Table (Elmt).Next;

   begin
      pragma Assert (Elmt /= No_Elmt);

      Elmts.Increment_Last;
      Elmts.Table (Elmts.Last).Node := N;
      Elmts.Table (Elmts.Last).Next := Nxt;

      Elmts.Table (Elmt).Next := Union_Id (Elmts.Last);

      if Nxt in Elist_Range then
         Elists.Table (Elist_Id (Nxt)).Last := Elmts.Last;
      end if;
   end Insert_Elmt_After;

   ------------------------
   -- Is_Empty_Elmt_List --
   ------------------------

   function Is_Empty_Elmt_List (List : Elist_Id) return Boolean is
   begin
      return Elists.Table (List).First = No_Elmt;
   end Is_Empty_Elmt_List;

   -------------------
   -- Last_Elist_Id --
   -------------------

   function Last_Elist_Id return Elist_Id is
   begin
      return Elists.Last;
   end Last_Elist_Id;

   ---------------
   -- Last_Elmt --
   ---------------

   function Last_Elmt (List : Elist_Id) return Elmt_Id is
   begin
      return Elists.Table (List).Last;
   end Last_Elmt;

   ------------------
   -- Last_Elmt_Id --
   ------------------

   function Last_Elmt_Id return Elmt_Id is
   begin
      return Elmts.Last;
   end Last_Elmt_Id;

   -----------------
   -- List_Length --
   -----------------

   function List_Length (List : Elist_Id) return Nat is
      Elmt : Elmt_Id;
      N    : Nat;

   begin
      if List = No_Elist then
         return 0;

      else
         N := 0;
         Elmt := First_Elmt (List);
         loop
            if No (Elmt) then
               return N;
            else
               N := N + 1;
               Next_Elmt (Elmt);
            end if;
         end loop;
      end if;
   end List_Length;

   ----------
   -- Lock --
   ----------

   procedure Lock is
   begin
      Elists.Release;
      Elists.Locked := True;
      Elmts.Release;
      Elmts.Locked := True;
   end Lock;

   --------------------
   -- New_Copy_Elist --
   --------------------

   function New_Copy_Elist (List : Elist_Id) return Elist_Id is
      Result : Elist_Id;
      Elmt   : Elmt_Id;

   begin
      if List = No_Elist then
         return No_Elist;

      --  Replicate the contents of the input list while preserving the
      --  original order.

      else
         Result := New_Elmt_List;

         Elmt := First_Elmt (List);
         while Present (Elmt) loop
            Append_Elmt (Node (Elmt), Result);
            Next_Elmt (Elmt);
         end loop;

         return Result;
      end if;
   end New_Copy_Elist;

   -------------------
   -- New_Elmt_List --
   -------------------

   function New_Elmt_List return Elist_Id is
   begin
      Elists.Increment_Last;
      Elists.Table (Elists.Last).First := No_Elmt;
      Elists.Table (Elists.Last).Last  := No_Elmt;

      if Debug_Flag_N then
         Write_Str ("Allocate new element list, returned ID = ");
         Write_Int (Int (Elists.Last));
         Write_Eol;
      end if;

      return Elists.Last;
   end New_Elmt_List;

   -------------------
   -- New_Elmt_List --
   -------------------

   function New_Elmt_List (Elmt1 : Node_Or_Entity_Id)
     return Elist_Id
   is
      L : constant Elist_Id := New_Elmt_List;
   begin
      Append_Elmt (Elmt1, L);
      return L;
   end New_Elmt_List;

   -------------------
   -- New_Elmt_List --
   -------------------

   function New_Elmt_List
     (Elmt1 : Node_Or_Entity_Id;
      Elmt2 : Node_Or_Entity_Id) return Elist_Id
   is
      L : constant Elist_Id := New_Elmt_List (Elmt1);
   begin
      Append_Elmt (Elmt2, L);
      return L;
   end New_Elmt_List;

   -------------------
   -- New_Elmt_List --
   -------------------

   function New_Elmt_List
     (Elmt1 : Node_Or_Entity_Id;
      Elmt2 : Node_Or_Entity_Id;
      Elmt3 : Node_Or_Entity_Id) return Elist_Id
   is
      L : constant Elist_Id := New_Elmt_List (Elmt1, Elmt2);
   begin
      Append_Elmt (Elmt3, L);
      return L;
   end New_Elmt_List;

   -------------------
   -- New_Elmt_List --
   -------------------

   function New_Elmt_List
     (Elmt1 : Node_Or_Entity_Id;
      Elmt2 : Node_Or_Entity_Id;
      Elmt3 : Node_Or_Entity_Id;
      Elmt4 : Node_Or_Entity_Id) return Elist_Id
   is
      L : constant Elist_Id := New_Elmt_List (Elmt1, Elmt2, Elmt3);
   begin
      Append_Elmt (Elmt4, L);
      return L;
   end New_Elmt_List;

   ---------------
   -- Next_Elmt --
   ---------------

   function Next_Elmt (Elmt : Elmt_Id) return Elmt_Id is
      N : constant Union_Id := Elmts.Table (Elmt).Next;

   begin
      if N in Elist_Range then
         return No_Elmt;
      else
         return Elmt_Id (N);
      end if;
   end Next_Elmt;

   procedure Next_Elmt (Elmt : in out Elmt_Id) is
   begin
      Elmt := Next_Elmt (Elmt);
   end Next_Elmt;

   --------
   -- No --
   --------

   function No (List : Elist_Id) return Boolean is
   begin
      return List = No_Elist;
   end No;

   function No (Elmt : Elmt_Id) return Boolean is
   begin
      return Elmt = No_Elmt;
   end No;

   ----------
   -- Node --
   ----------

   function Node (Elmt : Elmt_Id) return Node_Or_Entity_Id is
   begin
      if Elmt = No_Elmt then
         return Empty;
      else
         return Elmts.Table (Elmt).Node;
      end if;
   end Node;

   ----------------
   -- Num_Elists --
   ----------------

   function Num_Elists return Nat is
   begin
      return Int (Elmts.Last) - Int (Elmts.First) + 1;
   end Num_Elists;

   ------------------
   -- Prepend_Elmt --
   ------------------

   procedure Prepend_Elmt (N : Node_Or_Entity_Id; To : Elist_Id) is
      F : constant Elmt_Id := Elists.Table (To).First;

   begin
      Elmts.Increment_Last;
      Elmts.Table (Elmts.Last).Node := N;

      if F = No_Elmt then
         Elists.Table (To).Last := Elmts.Last;
         Elmts.Table (Elmts.Last).Next := Union_Id (To);
      else
         Elmts.Table (Elmts.Last).Next := Union_Id (F);
      end if;

      Elists.Table (To).First  := Elmts.Last;
   end Prepend_Elmt;

   -------------------------
   -- Prepend_Unique_Elmt --
   -------------------------

   procedure Prepend_Unique_Elmt (N : Node_Or_Entity_Id; To : Elist_Id) is
   begin
      if not Contains (To, N) then
         Prepend_Elmt (N, To);
      end if;
   end Prepend_Unique_Elmt;

   -------------
   -- Present --
   -------------

   function Present (List : Elist_Id) return Boolean is
   begin
      return List /= No_Elist;
   end Present;

   function Present (Elmt : Elmt_Id) return Boolean is
   begin
      return Elmt /= No_Elmt;
   end Present;

   ------------
   -- Remove --
   ------------

   procedure Remove (List : Elist_Id; N : Node_Or_Entity_Id) is
      Elmt : Elmt_Id;

   begin
      if Present (List) then
         Elmt := First_Elmt (List);
         while Present (Elmt) loop
            if Node (Elmt) = N then
               Remove_Elmt (List, Elmt);
               exit;
            end if;

            Next_Elmt (Elmt);
         end loop;
      end if;
   end Remove;

   -----------------
   -- Remove_Elmt --
   -----------------

   procedure Remove_Elmt (List : Elist_Id; Elmt : Elmt_Id) is
      Nxt : Elmt_Id;
      Prv : Elmt_Id;

   begin
      Nxt := Elists.Table (List).First;

      --  Case of removing only element in the list

      if Elmts.Table (Nxt).Next in Elist_Range then
         pragma Assert (Nxt = Elmt);

         Elists.Table (List).First := No_Elmt;
         Elists.Table (List).Last  := No_Elmt;

      --  Case of removing the first element in the list

      elsif Nxt = Elmt then
         Elists.Table (List).First := Elmt_Id (Elmts.Table (Nxt).Next);

      --  Case of removing second or later element in the list

      else
         loop
            Prv := Nxt;
            Nxt := Elmt_Id (Elmts.Table (Prv).Next);
            exit when Nxt = Elmt
              or else Elmts.Table (Nxt).Next in Elist_Range;
         end loop;

         pragma Assert (Nxt = Elmt);

         Elmts.Table (Prv).Next := Elmts.Table (Nxt).Next;

         if Elmts.Table (Prv).Next in Elist_Range then
            Elists.Table (List).Last := Prv;
         end if;
      end if;
   end Remove_Elmt;

   ----------------------
   -- Remove_Last_Elmt --
   ----------------------

   procedure Remove_Last_Elmt (List : Elist_Id) is
      Nxt : Elmt_Id;
      Prv : Elmt_Id;

   begin
      Nxt := Elists.Table (List).First;

      --  Case of removing only element in the list

      if Elmts.Table (Nxt).Next in Elist_Range then
         Elists.Table (List).First := No_Elmt;
         Elists.Table (List).Last  := No_Elmt;

      --  Case of at least two elements in list

      else
         loop
            Prv := Nxt;
            Nxt := Elmt_Id (Elmts.Table (Prv).Next);
            exit when Elmts.Table (Nxt).Next in Elist_Range;
         end loop;

         Elmts.Table (Prv).Next   := Elmts.Table (Nxt).Next;
         Elists.Table (List).Last := Prv;
      end if;
   end Remove_Last_Elmt;

   ------------------
   -- Replace_Elmt --
   ------------------

   procedure Replace_Elmt (Elmt : Elmt_Id; New_Node : Node_Or_Entity_Id) is
   begin
      Elmts.Table (Elmt).Node := New_Node;
   end Replace_Elmt;

   ------------
   -- Unlock --
   ------------

   procedure Unlock is
   begin
      Elists.Locked := False;
      Elmts.Locked := False;
   end Unlock;

end Elists;
