5
votes

I have a function that returns a string for a particular item, and I need to call that function numerous times and combine those strings into one. The combined string is bounded. I've made sure to fill it when space characters when it initializes but I keep getting "length check failed" errors. Is there something basic I'm doing wrong here?

FOR I IN 1..Collection.Size LOOP  
    Combined_String :=  combined_string & Tostring(Collection.Book(I));  
END LOOP;
5

5 Answers

5
votes

Unbounded_String is probably the easiest way to go:

with Ada.Strings.Unbounded;
use Ada.Strings.unbounded;

   ...

Temp_Unbounded_String : Unbounded_String;  -- Is empty by default.

   ...

for I in 1 .. Collection.Size loop
   Append(Temp_Unbounded_String, ToString(Collection.Book(I));
end loop;

If you then need to have the result placed in your fixed length standard string:

declare
   Temp_String : constant String := To_String(Temp_Unbounded_String);
begin
   -- Beware! If the length of the Temp_String is greater than that of the
   -- fixed-length string, a Constraint_Error will be raised.  Some verification
   -- of source and target string lengths must be performed!
   Combined_String(Temp_String'Range) := Temp_String;
end;

Alternatively, you can use the Ada.Strings.Fixed Move() procedure to bring the Unbounded_String into the target fixed-length string:

Ada.Strings.Fixed.Move(To_String(Temp_Unbounded_String), Combined_String);

In this case, if the source string is "too long", by default a Length_Error exception is raised. There are other parameters to Move() that can modify the behavior in that situation, see the provided link on Move for more detail.

3
votes

In order to assign Combined_String, you must assign the full correct length at once. You can't "build up" a string and assign it that way in Ada.

Without seeing the rest of your code, I think Ada.Strings.Unbounded is probably what you should be using.

3
votes

I know this is an ancient question, but now that Ada 2012 is out I thought I'd share an idiom I've been finding myself using...

declare
  function Concatenate(i: Collection'index)
  is
    (tostring(Collection(i) &
      if (i = Collection'last) then
        ("")
      else
        (Concatenate(i+1))
    );

  s: string := Concatenate(Collection'first);
begin
  Put_Line(s);
end;

Typed off the top of my head, so it'll be full of typos; and if you want it to work on empty collections you'll need to tweak the logic (should be obvious).

Ada 2012's expression functions are awesome!

2
votes

Ada works best when you can use perfectly-sized arrays and strings. This works wonderfully for 99% of string uses, but causes problems any time you need to progressively build a string from something else.

Given that, I'd really like to know why you need that combined string.

If you really need it like that, there are two good ways I know of to do it. The first is to use "unbounded" (dynamically-sized) strings from Ada.Strings.Unbounded, as Dave and Marc C suggested.

The other is to use a bit of functional programming (in this case, recursion) to create your fixed string. Eg:

function Combined_String (String_Collection : in String_Collection_Type) return String is
begin
    if String_Collection'length = 1 then 
        return String_Collection(String_Collection'first);
    end if;    
    return String_Collection(String_Collection'first) & 
           Combined_String (String_Collection'first + 1 .. String_Collection'last);
end Combined_String;

I don't know what type you used for Collection, so I'm making some guesses. In particular, I'm assuming its an unconstrained array of fixed strings. If it's not, you will need to replace some of the above code with whatever your container uses to return its bounds, access elements, and perform slicing.

1
votes

From AdaPower.com:

function Next_Line(File : in Ada.Text_IO.File_Type :=
   Ada.Text_Io.Standard_Input) return String is
   Answer : String(1..256);
   Last   : Natural;
begin
   Ada.Text_IO.Get_Line(File => File,
      Item => Answer,
      Last => Last);
   if Last = Answer'Last then
      return Answer & Next_Line(File);
   else
      return Answer(1..Last);
   end if;
end Next_Line;

As you can see, this method builds a string (using Get_Line) of unlimited* length from the file it's reading from. So what you'll need to do, in order to keep what you have is something on the order of:

function Combined_String (String_Collection : in String_Collection_Type)
                   Return String is      
begin
    if String_Collection'length = 1 then 
        Return String_Collection(String_Collection'First).All;
    end if;

      Recursion:
      Declare
         Data : String:= String_Collection(String_Collection'First).All;
         SubType Constraint is Positive Range
           Positive'Succ(String_Collection'First)..String_Collection'Last;
      Begin
         Return Data & Combined_String( String_Collection(Constraint'Range) );
      End Recursion;
end Combined_String;

Assuming that String_Collection is defined as:

 Type String_Collection is Array (Positive Range <>) of Access String;

*Actually limited by Integer'Range, IIRC