1
votes

As part of learning Ada, I am working some basic coding challenges. I have come across a situation where I would like to create a fixed-size 2D array of integers (size determined at runtime). My idea was to have a small utility function that could be passed the size of the array, create it, fill it and return it for use in other functions.

How do I do this? The other answers I have seen keep the created array in function-scope and do not return it.

So far, this is my main procedure:

with Ada.Integer_Text_IO;
with Ada.Text_IO;

with Coord;
with Grid;

procedure test is

   boundary : Coord.Box;

   -- This is the array I want to create and fill
   -- Note sure about what I put here for the "initial" size
   new_grid : Grid.GridType (0 .. 1,  0 .. 1);

begin

   --  This is just for the example, actually these
   --  values are calculated from values in a text file 
   Ada.Text_IO.Put ("X Min?");
   Ada.Integer_Text_IO.Get (boundary.min.x);
   Ada.Text_IO.Put ("X Max?");
   Ada.Integer_Text_IO.Get (boundary.max.x);
   Ada.Text_IO.Put ("Y Min?");
   Ada.Integer_Text_IO.Get (boundary.min.y);
   Ada.Text_IO.Put ("Y Max?");
   Ada.Integer_Text_IO.Get (boundary.max.y);

   new_grid := Grid.get_grid (boundary);

   Grid.print (new_grid);

end test;

And this is the grid.adb in which is the get_grid function:

with Ada.Integer_Text_IO;
with Ada.Text_IO;

package body Grid is

   function get_grid (bounds : Coord.Box) return GridType is
      --  This is the grid I'd like to return
      new_grid : Grid.GridType (bounds.min.x .. bounds.max.x, bounds.min.y .. bounds.max.y);
   begin
      for X in bounds.min.x .. bounds.max.x loop
         for Y in bounds.min.y .. bounds.max.y loop
            new_grid (X, Y) := X + Y;
         end loop;
      end loop;
      return new_grid; -- Needs to persist outsde this function
   end get_grid;

   --  Print function removed for clarity (this works)

end Grid;

Grid_Type is declared in grid.ads as:

type GridType is array (Integer range <>, Integer range <>) of Integer;

In these files, Coords.Box is just a simple record that holds X/Y min/max integers.

If I run this and input sensible numbers for the grid size I get a CONSTRAINT_ERROR.

I've read this answer and this answer and some other less-related answers and I'm just not getting it.

I am new to Ada, but proficient in other languages.

2

2 Answers

7
votes

The size of an array object cannot be changed after the object is declared. While this may appear to be a problem, Ada provides the solution of declaring the object in an inner block.

The example below defines your array type as an unconstrained array of element_type. Substitute whatever type you want your array to contain in your actual code. This unconstrained type allows you to create instances of the type with any needed dimensions.

type Grid_Type is array(Natural range <>, Natural range <>) of element_type;

In your function read the array bounds information then declare an instance using those array bounds.

function Make_Grid return Grid_Type is
   Dim1_Min, Dim1_Max : Natural;
   Dim2_Min, Dim2_Max : Natural;
begin
   get(Dim1_Min);
   get(Dim1_Max);
   get(Dim2_Min);
   get(Dim2_Max);

   declare
      New_Grid : Grid_Type(Dim1_Min..Dim1_Max, Dim2_Min..Dim2_Max);
   begin
      return New_Grid;
   end;
end Make_Grid;

The inner block creates a new instance of Grid_Type using the values read from input. The function simply returns the object from the inner block. Section 5.6 of the Ada Reference Manual describes Ada block statements.

The following example demonstrates how this works for an array of integers:

package Grids is
   type Grid_type is array(Natural range <>, Natural range <>) of Integer;
   function make_grid return Grid_Type;
end Grids;


with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

package body Grids is

   ---------------
   -- make_grid --
   ---------------

   function make_grid return Grid_Type is
      Dim1_Min, Dim1_Max : Natural;
      Dim2_Min, Dim2_Max : Natural;
   begin
      Get(Dim1_Min);
      Get(Dim1_Max);
      Get(Dim2_Min);
      Get(Dim2_Max);

      declare
         New_Grid : Grid_Type(Dim1_Min..Dim1_Max, Dim2_Min..Dim2_Max) :=
           (Others =>(Others => 0));
      begin
         return New_Grid;
      end;

   end make_grid;

end Grids;

A test main for this program is:

with Ada.Text_IO; use Ada.Text_IO;
with Grids; use Grids;

procedure Grids_Test is
   The_Grid : Grid_type := Make_Grid;
begin
   Put_Line("Grid Dimensions");
   Put_Line(Natural'Image(The_Grid'First(1)) & ".." &
              Natural'Image(The_Grid'Last(1)) & " , " &
              Natural'Image(The_Grid'First(2)) & "..." &
                Natural'Image(The_Grid'Last(2)));
end Grids_Test;

The input and output of a sample execution are:

0
10
20
30
Grid Dimensions
 0.. 10 ,  20... 30
1
votes

In addition to using declare blocks to temporarily hold the result of your function, you can also use Ada.Containers.Indefinite_Holders to store the result more permanently.

You would need to instantiate the generic

use type Grid.Grid_Type;  -- necessary to get the "=" operation
package Grid_Holders is new Ada.Containers.Indefinite_Holders(Grid.Grid_Type);

You would declare it as

New_Grid : Grid_Holders.Holder;

and at runtime you can initialize it using

New_Grid := Grid_Holders.To_Holder(Grid.Get_Grid(Boundary));

once you have done that, you can access the grid using the Reference operation:

Grid.Print(New_Grid.Reference);

If you want to access the elements directly you might have to do something like:

Grid.Reference.Element(1,2) := 23;

Note that some versions of GNAT have a bug where this can generate a Finalization exception. If that is the case, you can usually work around it by using a declare block or a function. Declare block example:

declare
   The_Grid : Grid.Grid_Type renames New_Grid.Reference;
begin
   The_Grid(1,2) := 23;
end;

This is different from the other answer's declare block example because this declare block isn't creating any new grids, it's just accessing existing memory. It's purely for working around a compiler bug. In this example, New_Grid exists outside the declare block so it stays available.