Search code examples
arrayssliceadaada2012

Wrap-around Semantics for accessing Array Slices indexed by a Modular Type


I would like to create an array and access it in the following way for read and write slice operations (i.e. more than one element at once):

  • If the indices are within range access them as usual
  • If the second index is smaller than the first index, access the data as follows: First .. A'Last & A'First .. (First + 5) (turns out this doesn't work as-is due to concatenation result upper bound out of range)

I have come up with the following example to demonstrate the issue:

with Ada.Text_IO;
use  Ada.Text_IO;

procedure Test_Modular is
    type Idx is mod 10;
    type My_Array is array (Idx range <>) of Integer;

    A: My_Array(Idx) := (
        0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5,
        6 => 6, 7 => 7, 8 => 8, 9 => 9
    );

    First: constant Idx := 7;

    S: constant My_Array := A(First .. First + 5);
begin

    for I in S'range loop
        Put_Line(Idx'Image(I) & " --> " & Integer'Image(S(I)));
    end loop;

end Test_Modular;

As in the example, the 5 is static, the compiler warns me as follows:

$ gnatmake -o test_modular test_modular.adb
x86_64-linux-gnu-gcc-10 -c test_modular.adb
test_modular.adb:18:19: warning: loop range is null, loop will not execute
x86_64-linux-gnu-gnatbind-10 -x test_modular.ali
x86_64-linux-gnu-gnatlink-10 test_modular.ali -o test_modular

When running the program, I observe the following:

$ ./test_modular

i.e. no output as predicted by the compiler warning.

Now I wonder: Is there a way to write the slice like A(First .. First + 5) and make it “wrap around” such that the data accessed will be the same as in this modified example program except without having to distinguish the two cases in the code explicitly?

with Ada.Text_IO;
use  Ada.Text_IO;

procedure Test_Modular_2 is
    type Idx is mod 10;
    type My_Array is array (Idx range <>) of Integer;

    A: My_Array(Idx) := (
        0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5,
        6 => 6, 7 => 7, 8 => 8, 9 => 9
    );

    First: constant Idx := 7;

    S1: constant My_Array := A(First .. A'Last);
    S2: constant My_Array := A(A'First .. (First + 5));
begin

    for I in S1'range loop
        Put_Line(Idx'Image(I) & " --> " & Integer'Image(S1(I)));
    end loop;
    for I in S2'range loop
        Put_Line(Idx'Image(I) & " --> " & Integer'Image(S2(I)));
    end loop;

end Test_Modular_2;

Solution

  • Try the following approach:

    with Ada.Text_IO; use Ada.Text_IO;
    
    procedure Main is
       type Idx is mod 10;
       type My_Array is array (Idx range <>) of Integer;
    
       function rotate (arr : My_Array; head : Idx; tail : Idx) return My_Array is
          P1_Len : Natural;
          P2_Len : Natural;
       begin
          P1_Len :=
            (if head <= tail then Natural (tail) - Natural (head) + 1
             else Natural (arr'Last) - Natural (head) + 1);
          P2_Len := (if head <= tail then 0 else Natural (tail) + 1);
          declare
             Result : My_Array (0 .. Idx (P1_Len + P2_Len - 1));
          begin
             if head <= tail then
                Result := arr (head .. tail);
             else
                Result (0 .. Idx (P1_Len - 1))       := arr (head .. arr'Last);
                Result (Idx (P1_Len) .. Result'Last) := arr (0 .. tail);
             end if;
             return Result;
          end;
       end rotate;
    
       procedure print (A : My_Array) is
       begin
          for V of A loop
             Put (V'Image);
          end loop;
          New_Line;
       end print;
    
       A : My_Array :=
         (0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8,
          9 => 9);
       head : Idx := 7;
       tail : Idx := head + 5;
    
    begin
       Put_Line ("Head: " & head'Image & " Tail:" & tail'Image);
       Put_Line ("Initial value order:");
       print (A);
       declare
          S1 : My_Array := rotate (A, head, tail);
       begin
    
          Put_Line ("Rotated value order:");
          print (S1);
       end;
    
    end Main;
    

    The rotate function above rotates the set of values indicated by the head and tail index values, placing the head value at the start of the array returned by rotate. This example shows that the resulting array may contain fewer elements than the array passed as a parameter to the function.