3

I have a large array in C that I wish to move into my ada project. That array is used to store filenames for assets which will later be loaded. It looks something like:

const char *filenames[NUMBER_OF_FILES] = {
    /* file[0] */ "res/0.file",
    /* ... */
    /* file[n] */ "res/some_more/complicated/file.name"
};

I want to move this into an ada package body, but can't find a decent way to do it. Obviously my first attempt was:

filenames : constant array (File_Index) of String := (
    index_0 => "res/0.file",
    --  ...
    index_n => "res/some_more/complicated/file.name"
);

But of course String is an unconstrained type, so Ada won't allow that. I switched it to use Unbounded_Strings, which worked, but was very ugly (having to wrap each string with To_Unbounded_String.

Is there any way to make an array of unconstrained types whose size will be known at compile time like this, or do I have to use unbounded strings?

LambdaBeta
  • 1,479
  • 1
  • 13
  • 25
  • 1
    An alternative approach would simply be to import the existing array using the mechanisms in Interfaces.C.Strings, including `function Value (Item : in chars_ptr) return String;` to convert an element of the array to an Ada String. Building a mixed language program is a bit more complex than pure Ada since the compiler can't track the non-Ada dependencies automatically, if you're OK with a traditional Makefile or a .gpr project file. –  Dec 15 '16 at 16:16

3 Answers3

3

It’s a bit low-level and repetitive, but perhaps you can create a little program (maybe even in Ada!) to generate something like

with Ada.Text_IO; use Ada.Text_IO;
procedure Lambdabeta is
   F1 : aliased constant String := "res/0.file";
   F2 : aliased constant String := "res/some_more/complicated/file.name";
   type Strings is array (Positive range <>) of access constant String;
   Filenames : constant Strings := (F1'Access,
                                    F2'Access);
begin
   for J in Filenames'Range loop
      Put_Line (Filenames (J).all);
   end loop;
end Lambdabeta;

See also this answer on minimising the pain of using To_Unbounded_String.

Community
  • 1
  • 1
Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • Thanks. I take it there's just no way to make a 'ragged' array in Ada. I essentially used this method, using Vim to generate it. My style checks are going crazy from it, but a bit more vimming should have that cleaned up pretty quick. – LambdaBeta Dec 14 '16 at 22:07
  • This *is* the way in Ada! The syntax is clumsy compared to the C way, but the compiled binary will be very similar (except that, where each C string has a trailing `\0`, each Ada string will carry its discriminants). – Simon Wright Dec 14 '16 at 22:14
  • Neither arrays nor strings are actual data types in C, they are abstractions related to pointers. The problem with this abstraction is that the compiler seldom knows the length of an array, or the length of the char array that is interpreted as a string. One can use the strlen() function to return a "length" of a string, but that only indicates where the first \0 is found, not the size of the array. The length may be greater than, or less than the size of the array. It is seldom the size of the array, and if it is the program is erroneous. – Jim Rogers Dec 15 '16 at 03:16
  • @JimRogers, in general, true: but the C solution to this particular problem (especially if sprinkled with appropriate `const`s) wouldn’t cause problems – Simon Wright Dec 15 '16 at 08:13
  • @SimonWright the C solution in this particular problem, as it is narrowly constrained, may not cause problems, but problems will abound if the file names must be updated in some part of the program operational life cycle. Even make files get updated during the life of an application, as new files are added or old files are removed. Furthermore, the array of const char pointers has NUMBER_OF_FILES elements, but there is no way for the language to ensure that it is initialized with the proper number of string pointers. Such coordination is left as an exercise for the programmer. – Jim Rogers Dec 15 '16 at 14:12
  • @JimRogers, (1) this question was specifically about constant arrays of string literals, (2) even C would allow the array to be declared without specific NUMBER_OF_FILES so as to take its size automatically from the actual elements supplied – Simon Wright Dec 16 '16 at 17:06
  • @SimonWright While C allows this, the code sample represented does not. The pointer array is declared as const char *filenames[NUMBER_OF_FILES]. This will allocate NUMBER_OF_FILES char pointers for the array on the stack. Note that C does not warn if some of the char pointers are null pointers. The code accessing the array of char pointers must check to see if a particular element contains a null pointer. If not the program will attempt to dereference a null pointer. Note, in this case any resulting null pointers would be const null pointers. – Jim Rogers Dec 16 '16 at 18:04
  • @JimRogers, I was thinking that the code sample should probably have been `const char * const filenames[] = {....};` – Simon Wright Dec 17 '16 at 12:16
3

Arrays can't contain objects of indefinite types.

Basically you have two options:

  1. Use another container than an array.
  2. Encapsulate the strings in a definite type.

So you could use Ada.Containers.Indefinite_Vectors instead of an array:

with Ada.Containers.Indefinite_Vectors;

package Settings is
   ------------------------------------------------------------------
   --  You may want to put this block in a separate package:
   package String_Vectors is
     new Ada.Containers.Indefinite_Vectors (Index_Type   => Positive,
                                            Element_Type => String);
   function "+" (Left, Right : String) return String_Vectors.Vector
     renames String_Vectors."&";
   function "+" (Left  : String_Vectors.Vector;
                 Right : String) return String_Vectors.Vector
     renames String_Vectors."&";
   ------------------------------------------------------------------

   File_Names : constant String_Vectors.Vector :=
                  "/some/file/name" +
                  "/var/spool/mail/mine" +
                  "/etc/passwd";
end Settings;

So you could use Ada.Strings.Unbounded.Unbounded_String instead of String:

with Ada.Strings.Unbounded;

package Settings_V2 is
   function "+" (Item : in String) return Ada.Strings.Unbounded.Unbounded_String
     renames Ada.Strings.Unbounded.To_Unbounded_String;
   type String_Array is array (Positive range <>)
     of Ada.Strings.Unbounded.Unbounded_String;

   File_Names : constant String_Array :=
                  (+"/some/file/name",
                   +"/var/spool/mail/mine",
                   +"/etc/passwd");
end Settings_V2;
Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
1

What has not yet been mentioned is that you can just use a function:

subtype File_Index is Integer range 1 .. 3;
function Filename (Index : File_Index) return String is
begin
   case Index is
   when 1 => return "res/0.file";
   when 2 => return "res/1.file";
   when 3 => return "res/some_more/complicated/file.name";
   end case;
end Filename;

Using Filename (1) in your code is identical to accessing an array element.

flyx
  • 35,506
  • 7
  • 89
  • 126