Jossi
Jossi

Reputation: 1080

How to use Ada 2012 user defined iterators?

Ada 2012 user defined iterator

This feature allows user to make a custom iterator. Instead of writing Get (List, Index) one could write List (Index) and instead of writing for Index in 1 .. List.Max one could write for Index in List'Range. I am not sure if List'Range is possible if list is not an array, maybe there is another syntax for user defined iterator.

Example

This is a Stack or LIFO example. Next step is to hide type Stack members and implement user defined iterator for type Stack List.

with Ada.Text_IO;
with Ada.Integer_Text_IO;

procedure Main is

   use Ada.Text_IO;
   use Ada.Integer_Text_IO;

   type Integer_Array is array (Integer range <>) of Integer;

   type Stack (Count : Natural) is record
      List : Integer_Array (1 .. Count);
      Top : Natural := 0;
   end record;

   procedure Push (Item : Integer; Result : in out Stack) is
   begin
      Result.Top := Result.Top + 1;
      Result.List (Result.Top) := Item;
   end;

   S : Stack (10);

begin

   Push (5, S);
   Push (3, S);
   Push (8, S);

   for I in S.List'First .. S.Top loop
      Put (S.List (I), 2);
      New_Line;
   end loop;

   --for I in S'Range loop
      --Put (S (I), 2);
      --New_Line;
   --end loop;
end;

Upvotes: 2

Views: 1161

Answers (3)

Simon Wright
Simon Wright

Reputation: 25501

I'm not sure that being able to iterate over the contents of a stack is part of the normal use case for stacks! That aside, this is the sort of code you can write using generalized iteration (ARM 5.5.2):

with Ada.Text_IO;
with Stacks;
procedure Iteration is
   use Ada.Text_IO;
   S : Stacks.Stack (10);
begin
   Stacks.Push (S, 5);
   Stacks.Push (S, 3);
   Stacks.Push (S, 8);

   for C in Stacks.Iterate (S) loop
      Put_Line (Stacks.Element (C)'Img);                                            --'
   end loop;
end Iteration;

This is a possible spec for Stacks. Note that System is only used in the private part.

with Ada.Iterator_Interfaces;
private with System;
package Stacks is

These are part of the fundamental Stack abstraction. Note that if you wanted to be able to write an indexed access (My_Stack (42) or the loop for E of My_Stack loop...) things get much more complicated. For a start, Stack would have to be tagged.

   type Stack (Count : Natural) is private;

   procedure Push (To : in out Stack; Item : Integer);

   function Element (S : Stack; Index : Positive) return Integer;

The remainder of the public part is to support generalized iteration.

   type Cursor is private;
   function Has_Element (Pos : Cursor) return Boolean;
   function Element (C : Cursor) return Integer;

   package Stack_Iterators
     is new Ada.Iterator_Interfaces (Cursor, Has_Element);

   function Iterate (S : Stack)
                    return Stack_Iterators.Forward_Iterator’Class;                  --'
private                                                                             --'
   type Integer_Array is array (Positive range <>) of Integer;

   type Stack (Count : Natural) is record
      List : Integer_Array (1 .. Count);
      Top : Natural := 0;
   end record;

   function Element (S : Stack; Index : Positive) return Integer
     is (S.List (Index));

Cursor needs a reference to the object it's a cursor for. I've used System.Address to avoid using access types and needing to make Stacks aliased.

   type Cursor is record
      The_Stack : System.Address;
      List_Index : Positive := 1;
   end record;
end Stacks;

The package body ...

with System.Address_To_Access_Conversions;
package body Stacks is
   procedure Push (To : in out Stack; Item : Integer) is
   begin
      To.Top := To.Top + 1;
      To.List (To.Top) := Item;
   end Push;

We're going to have to convert Stack addresses to pointers-to-Stacks.

   package Address_Conversions
     is new System.Address_To_Access_Conversions (Stack);

   function Has_Element (Pos : Cursor) return Boolean is
      S : Stack renames Address_Conversions.To_Pointer (Pos.The_Stack).all;
   begin
      return Pos.List_Index <= S.Top;
   end Has_Element;

   function Element (C : Cursor) return Integer is
      S : Stack renames Address_Conversions.To_Pointer (C.The_Stack).all;
   begin
      return S.List (C.List_Index);
   end Element;

Now for the actual iterator; I've only done the forward version.

   type Iterator is new Stack_Iterators.Forward_Iterator with record
      The_Stack : System.Address;
   end record;
   overriding function First (Object : Iterator) return Cursor;
   overriding function Next (Object : Iterator; Pos : Cursor) return Cursor;

   function Iterate (S : Stack)
                    return Stack_Iterators.Forward_Iterator'Class is                   --'
   begin
      --  I seem to be relying on S being passed by reference.
      --  This would be OK anyway if Stack was tagged; but it is a
      --  reasonable bet that the compiler will pass a large object
      --  by reference.
      return It : Iterator do
         It.The_Stack := S’Address;                                                     --'
      end return;                                                                   --'
   end Iterate;

   function First (Object : Iterator) return Cursor is
   begin
      return C : Cursor do
         C.The_Stack := Object.The_Stack;
         C.List_Index := 1;
      end return;
   end First;

   function Next (Object : Iterator; Pos : Cursor) return Cursor is
      pragma Unreferenced (Object);
   begin
      return C : Cursor do
         C.The_Stack := Pos.The_Stack;
         C.List_Index := Pos.List_Index + 1;
      end return;
   end Next;
end Stacks;

Upvotes: 3

manuBriot
manuBriot

Reputation: 2715

The use of S'Range is not available for containers, only for standard arrays. This cannot be changed.

You likely meant to say for C in S.Iterate loop which calls the function Iterate and returns cursors for each step of the loop. At that point, you need to use Element (C) to get access to the actual element (or perhaps more efficiently Reference (C).

A third version is for E of S loop which returns directly the element. You do not have access to the corresponding cursor. This is in general the preferred way to write the loop, except perhaps when iterating over the whole contents of a map, since there is no way to access the key, only the value.

For more information on how to add support for these loops in your own data structures, you could take a look at the two gems published by AdaCore:

which should provide all the information you need, I hope.

Upvotes: 3

Jacob Sparre Andersen
Jacob Sparre Andersen

Reputation: 6611

You use the iterators in for loops like this:

for C in S.Iterator loop
   Put (S (C));
   New_Line;
end loop;

Or in the more lazy version:

for E of S loop
   Put (E);
   New_Line;
end loop;

Implementing iterators is a rather long story, and I refer you to the Ada 2012 rationale (which @trashgod also mentioned) for a detailed explanation.

Upvotes: 1

Related Questions