3

I'm a long time C++ programmer learning Ada for fun. If any of the following is bad form, please feel free to point it out. I'm trying to learn the Ada way to do things, but old habits are hard to break (and I miss Boost!)

I'm trying to load a file that contains an integer, a space, and then a string of characters. There may be a better way to do this, but I thought that I ought to load the line into a string buffer that I know won't be more than 80 characters. I declare a buffer variable like the following in the appropriate place:

 Line_Buffer : String(1..80);

After opening the file, I loop through each line and split the buffer at the space character:

 while not Ada.Text_IO.End_Of_File(File_Handle) loop
   Ada.Text_IO.Get_Line(File_Handle, Item=>Line_Buffer, Last=>Last);
   -- Break line at space to get match id and entry
   for String_Index in Line_Buffer'Range loop
     if Line_Buffer(String_Index) = ' ' then
       Add_Entry(Root_Link=>Root_Node,
        ID_String=> Line_Buffer(1..String_Index-1),
        Entry_String=> Line_Buffer(String_Index+1..Last-1)
        );
     end if;
   end loop;
 end loop;

What happens in Add_Entry is not that important, but its specification looks like this:

 procedure Add_Entry(
   Root_Link : in out Link;
   ID_String : in String;
   Entry_String : in String);

I wanted to use unbounded strings rather than bounded strings because I don't want to worry about having to specify size here and there. This compiles and works fine, but inside Add_Entry, when I try to loop over each character in Entry_String, rather than having indexes starting from 1, they start from the offset in the original string. For example, if Line_Buffer was "14 silicon", if I loop as follows, the index goes from 4 to 10.

for Index in Entry_String'Range loop
  Ada.Text_IO.Put("Index: " & Integer'Image(Index));
  Ada.Text_IO.New_Line;  
end loop;

Is there a better way to do this parsing so that the strings I pass to Add_Entry have boundaries that begin with 1? Also, when I pass a sliced string as an "in" parameter to a procedure, is a copy created on the stack, or is a reference to the original string used?

Dr. Watson
  • 3,752
  • 4
  • 32
  • 43
  • In Ada 2005+, there’s `function Get_Line(File : in File_Type) return String;` (and the parameterless version, using standard input): the ARM 2012 text is at [A.10.7(17.1)](http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-A-10-7.html#p17.1). This means you can declare a local constant string and initialize it with the contents of the next line, without worrying about the length. – Simon Wright Jan 01 '20 at 19:54

2 Answers2

9

First off, my sympathies. Ada strings are probably the single most different thing between C++ and Ada. What makes it worse is that the differences are under the surface, so naive C/C++ coders start their Ada careers thinking they maybe aren't there, and they can treat Ada strings like they do C strings. Now for your specific question:

Ada arrays (including strings) all have implicit boundaries passed around with them. This means there is generally never a need for a special sentinel value (like nul), and rarely a need for separate length variables. It also means there is nothing special about 1 or 0 or any other index.

So the proper way to deal with arrays in Ada is that you don't assume inside a subroutine what your starting and ending boundaries are. You figure them out. The language provides 'first, 'last, and 'range specifically for that purpose. From your example, if you wanted to print the offset from the start of the given string (for some weird reason) it would be:

for Index in Entry_String'Range loop
  Ada.Text_IO.Put("Index offset: " & Integer'Image(Index-Entry_string'first));
  Ada.Text_IO.New_Line;  
end loop;

OK. Now for difference two between Ada and C. Your in parameter is not copied. This one is very important, so I will shout a bit: Ada parameters are not passed like C parameters! The exact rules are a bit complicated, but for your purpose the principle is that Ada will do the sensible thing. If the parameter can fit in a register, it will be passed by copy (or perhaps register). If the parameter is too big for that, it will be passed by reference. You don't get to decide this. It is an issue of optimization, and will be done by the compiler. But you can count on your compiler not creating copies of large arrays just to pass them in to a routine that isn't allowed to modify them anyway. That would be stoopid. Only a total idiot (or a C++ compiler) would do such a thing. If you ever find an Ada compiler doing it report it as a bug. It would be.

Lastly, in most cases creative use of Ada's scoping rules will allow you to use perfectly-sized constant "fixed" strings. You should almost never need to use dynamic strings or separate length variables. Sadly, Ada.Text_IO.Get_Line is one of the exceptions. If you don't care too much about performance (which you shouldn't if you are reading this string from the user), you can use Carlisle's routine to read in a perfectly-sized fixed string from Text_IO.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
  • Those attributes are quite slick. It is nice to not have to worry about index values. Yet another feature of Ada I am learning to enjoy (in addition to types and pure readability thus far) – Dr. Watson Feb 07 '11 at 21:19
  • as is implied by TED, you can define subtypes and then use the ranges of those types to create and index arrays, even ranges that are non standard like (-5 .. 5) which removes the need to offset any indexing to 0 or 1 :) – NWS Feb 15 '11 at 15:12
5

If you're okay with using GNAT implementation defined packages, the package Ada.Strings.Unbounded.Text_IO is available.

Also, the Ada.Strings child packages (specific to Fixed, Bounded, or Unbounded strings) provide some helpful subprograms for string processing, like Index() for finding specific strings within other strings--useful for locating embedded blanks :-)

There's another GNAT package, GNAT.Array_Split (which comes preinstantiated with strings as GNAT.String_Split) that supplies more subprograms oriented towards breaking apart arrays (and strings).

Marc C
  • 8,664
  • 1
  • 24
  • 29
  • Thank you. I'll look more into the packages. GNAT is the only Ada compiler I work with, so I'll check out the GNAT ones too. – Dr. Watson Feb 07 '11 at 21:21
  • There's also a nice little recursive read trick I've seen for Get_Line that will get you the line into a perfectly-sized fixed string. http://www.adapower.com/index.php?Command=Class&ClassID=Basics&CID=202 – T.E.D. Feb 07 '11 at 21:34
  • Oh, and extra props for steering him to Ada.Strings.Fixed and friends. If he doesn't need those yet, he will soon. – T.E.D. Feb 07 '11 at 21:41