Passing strings as task creation discriminants in Ada - ada

I'm moving my first steps with Ada, and I'm finding that I struggle to understand how to do common, even banal, operations that in other languages would be immediate.
In this case, I defined the following task type (and access type so I can create new instances):
task type Passenger(
Name : String_Ref;
Workplace_Station : String_Ref;
Home_Station : String_Ref
);
type Passenger_Ref is access all Passenger;
As you can see, it's a simple task that has 3 discriminants that can be passed to it when creating an instance. String_Ref is defined as:
type String_Ref is access all String;
and I use it because apparently you cannot use "normal" types as task discriminants, only references or primitive types.
So I want to create an instance of such a task, but whatever I do, I get an error. I cannot pass the strings directly by simply doing:
Passenger1 := new Passenger(Name => "foo", Workplace_Station => "man", Home_Station => "bar");
Because those are strings and not references to strings, fair enough.
So I tried:
task body Some_Task_That_Tries_To_Use_Passenger is
Passenger1 : Passenger_Ref;
Name1 : aliased String := "Foo";
Home1 : aliased String := "Man";
Work1 : aliased String := "Bar";
begin
Passenger1 := new Passenger(Name => Name1'Access, Workplace_Station => Work1'Access, Home_Station => Home1'Access);
But this doesn't work either, as, from what I understand, the Home1/Name1/Work1 variables are local to task Some_Task_That_Tries_To_Use_Passenger and so cannot be used by Passenger's "constructor".
I don't understand how I have to do it to be honest. I've used several programming languages in the past, but I never had so much trouble passing a simple String to a constructor, I feel like a total idiot but I don't understand why such a common operation would be so complicated, I'm sure I'm approaching the problem incorrectly, please enlighten me and show me the proper way to do this, because I'm going crazy :D

Yes, I agree it is a serious problem with the language that discriminates of task and record types have to be discrete. Fortunately there is a simple solution for task types -- the data can be passed via an "entry" point.
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
procedure Main is
task type Task_Passenger is
entry Construct(Name, Workplace, Home : in String);
end Passenger;
task body Task_Passenger is
N, W, H : Unbounded_String;
begin
accept Construct(Name, Workplace, Home : in String) do
N := To_Unbounded_String(Name);
W := To_Unbounded_String(Workplace);
H := To_Unbounded_String(Home);
end Construct;
--...
end Passenger;
Passenger : Task_Passenger;
begin
Passenger.Construct("Any", "length", "strings!");
--...
end Main;

Ada doesn't really have constructors. In other languages, a constructor is, in essence, a method that takes parameters and has a body that does stuff with those parameters. Trying to get discriminants to serve as a constructor doesn't work well, since there's no subprogram body to do anything with the discriminants. Maybe it looks like it should, because the syntax involves a type followed by a list of discriminant values in parentheses and separated by commas. But that's a superficial similarity. The purpose of discriminants isn't to emulate constructors.
For a "normal" record type, the best substitute for a constructor is a function that returns an object of the type. (Think of this as similar to using a static "factory method" instead of a constructor in a language like Java.) The function can take String parameters or parameters of any other type.
For a task type, it's a little trickier, but you can write a function that returns an access to a task.
type Passenger_Acc is access all Passenger;
function Make_Passenger (Name : String;
Workplace_Station : String;
Home_Station : String) return Passenger_Acc;
To implement it, you'll need to define an entry in the Passenger task (see Roger Wilco's answer), and then you can use it in the body:
function Make_Passenger (Name : String;
Workplace_Station : String;
Home_Station : String) return Passenger_Acc is
Result : Passenger_Acc;
begin
Result := new Passenger;
Result.Construct (Name, Workplace_Station, Home_Station);
return Result;
end Make_Passenger;
(You have to do this by returning a task access. I don't think you can get the function to return a task itself, because you'd have to use an extended return to set up the task object and the task object isn't activated until after the function returns and thus can't accept an entry.)

You say
"I don't understand how I have to do it to be honest. I've used several programming languages in the past, but I never had so much trouble passing a simple String to a constructor, I feel like a total idiot but I don't understand why such a common operation would be so complicated, I'm sure I'm approaching the problem incorrectly, please enlighten me and show me the proper way to do this, because I'm going crazy :D"
Ada's access types are often a source of confusion. The main issue is that Ada doesn't have automatic garbage collection, and wants to ensure you can't suffer from the problem of returning pointers to local variables. The combination of these two results in a curious set of rules that force you to design your solution carefully.
If you are sure your code is good, then you can always used 'Unrestricted_Access on an aliased String. This puts all the responsibility on you to ensure the accessed variable won't disappear from underneath the task though.

It doesn't have to be all that complicated. You can use an anonymous access type and allocate the strings on demand, but please consider if you really want the strings to be discriminants.
Here is a complete, working example:
with Ada.Text_IO;
procedure String_Discriminants is
task type Demo (Name : not null access String);
task body Demo is
begin
Ada.Text_IO.Put_Line ("Demo task named """ & Name.all & """.");
exception
when others =>
Ada.Text_IO.Put_Line ("Demo task terminated by an exception.");
end Demo;
Run_Demo : Demo (new String'("example 1"));
Second_Demo : Demo (new String'("example 2"));
begin
null;
end String_Discriminants;
Another option is to declare the strings as aliased constants in a library level package, but then you are quite close to just having an enumerated discriminant, and should consider that option carefully before discarding it.

I think another solution would be the following:
task body Some_Task_That_Tries_To_Use_Passenger is
Name1 : aliased String := "Foo";
Home1 : aliased String := "Man";
Work1 : aliased String := "Bar";
Passenger1 : aliased Passenger(
Name => Name1'Access,
Workplace_Station => Work1'Access,
Home_Station => Home1'Access
);
begin
--...

Related

Ada: Manipulate a private type

I'm kinda new with Ada and recently got an error that I don't seem to know how to solve.
I have the following code:
data.ads
with Text_IO; use text_io;
with ada.Integer_Text_IO; use ada.Integer_Text_IO;
package data is
type file is private;
type file_set is array (Integer range <>) of file;
procedure file_Print (T : in out file); --Not used
private
type file is record
start, deadline : integer;
end record;
end data;
Main.adb
with ada.Integer_Text_IO; use ada.Integer_Text_IO;
procedure Main is
Num_files: integer:=3;
Files:file_set(1..Num_files);
begin
Files(1):=(2,10); -- Expected private type "file" defined at data.ads
for i in 1..Num_Files loop
Put(integer'Image(i));
New_Line;
data.File_Print(Files(i));
But I'm getting this error Expected private type "file" defined at data.ads
How can I access the file type and declare a new array of values in main?
That's right - you don't get to see or manipulate what's inside a private type. That would be breaking encapsulation. Bugs and security risks follow.
You can only interact with a private type via its methods : functions and procedures declared in the package where it's declared.
Now file_set is NOT a private type (you might consider making it private later, for better encapsulation, but for now ....) you can index it to access a file within it (using one of those procedures).
Files(1):=(2,10);
As you want to create a file here, you need a method to create a file ... a bit similar to a constructor in C++, but really more like the Object Factory design pattern. Add this to the package:
function new_file(start, deadline : integer) return file;
And implement it in the package body:
package body data is
function new_file(start, deadline : integer) return file is
begin
-- check these values are valid so we can guarantee a proper file
-- I have NO idea what start, deadline mean, so write your own checks!
-- also there are better ways, using preconditions in Ada-2012
-- without writing explicit checks, but this illustrates the idea
if deadline < NOW or start < 0 then
raise Program_Error;
end if;
return (start => start, deadline => deadline);
end new_file;
procedure file_Print (T : in out file) is ...
end package body;
and that gives the users of your package permission to write
Files(1):= new_file(2,10);
Files(2):= new_file(start => 3, deadline => 15);
but if they attempt to create garbage to exploit your system
Files(3):= new_file(-99,-10); -- Oh no you don't!
this is the ONLY way to create a file, so they can't bypass your checks.

Dining Philosopher problem Ada- Implementing ID Dispenser

I have the following code, related to the dining philosopher problem. I am very new to Ada so am not sure about how to implement the Id_Dispenser package.
with Ada.Text_IO; use Ada.Text_IO;
with Id_Dispenser;
with Semaphores; use Semaphores;
procedure Philos is
No_of_Philos : constant Positive := 5; -- Number of philosophers
Meditation : constant Duration := 0.0;
type Table_Ix is mod No_of_Philos;
Forks : array (Table_Ix) of Binary_Semaphore (Initially_Available => True);
package Index_Dispenser is new Id_Dispenser (Element => Table_Ix);
use Index_Dispenser;
task type Philo;
task body Philo is
Philo_Nr : Table_Ix; -- Philisopher number
begin
Dispenser.Draw_Id (Id => Philo_Nr);
Put_Line ("Philosopher" & Table_Ix'Image (Philo_Nr) & " looks for forks.");
Forks (Philo_Nr).Wait; delay Meditation; Forks (Philo_Nr + 1).Wait;
Put_Line ("Philosopher" & Table_Ix'Image (Philo_Nr) & " eats.");
Forks (Philo_Nr).Signal; Forks (Philo_Nr + 1).Signal;
Put_Line ("Philosopher" & Table_Ix'Image (Philo_Nr) & " dropped forks.");
end Philo;
Table : array (Table_Ix) of Philo; pragma Unreferenced (Table);
begin
null;
end Philos;
I have implemented the following semaphore, which I think should be correct
package body semaphores is
protected body Binary_Semaphore is
entry Wait when Count > 0 is
begin
Count := Count - 1;
end Wait;
entry Release when Count < 1 is
begin
Count := Count + 1;
end Signal
end Binary_Semaphore;
end semaphores;
What does the Id_Dispenser need?
Looking at your code,
type Table_Ix is mod No_of_Philos;
...
package Index_Dispenser is new Id_Dispenser (Element => Table_Ix);
we can tell that Id_Dispenser is a generic package with a formal type named Element, and that the formal type is modular:
generic
type Element is mod <>;
package Id_Dispenser is
This
Philo_Nr : Table_Ix; -- Philisopher number
begin
Dispenser.Draw_Id (Id => Philo_Nr);
tells us that Id_Dispenser has some sort of component called Dispenser with a subprogram Draw_Id with an out parameter named Id which returns an Element.
Now, since this is a concurrent program, I'm going to guess that Dispenser is a protected object:
protected Dispenser is
procedure Draw_Id (Id : out Element);
private
...
end Dispenser;
The private part could simply be an array of Boolean indexed by Element,
Available : array (Element) of Boolean := (others => True);
but unfortunately you can't have an anonymous array as a component, so you need a proper type, giving
generic
type Element is mod <>;
package Id_Dispenser is
type Availability is array (Element) of Boolean;
protected Dispenser is
procedure Draw_Id (Id : out Element);
private
Available : Availability := (others => True);
end Dispenser;
end Id_Dispenser;
I'm not happy that the type Availability is visible, but the package now just needs implementing (!)
We could make Availability invisible by making Id_Dispenser.Dispenser a package, with Availability and the actual PO declared in the body. But that may be getting a little too purist for Ben’s context.
Firstly, you shouldn't really shorten identifiers, so you should have task type Philosophers... You can always use a renaming later on.
Shouldn't you model the forks and the philosophers? Each Philosopher as a task (hint array of task types).
Look at protected objects to model the forks.
Id_dispenser needs to implement a Draw_ID method.
Since the Dispenser variable is not declared here, it must presumably be declared in Id_dispenser. This hidden declaration is not very good style, as you can see it causes confusion; I would use a qualified name to make it obvious where it came from, as Index_Dispenser.Dispenser (which can then be renamed to reduce clutter in the rest of the code).
Id_dispenser may also need to provide an object factory method to initialise the Dispenser variable at its declaration.
Or, the intent may be that Dispenser will be the only one of its type, in which case you can treat Id_dispenser as a singleton package with Dispenser as the only object.

in Delphi 5, is a TList parameter always passed by reference?

I am migrating some code from Delphi 5 to a modern platform. Currently I have the compiled code (which works in my environment) and the source code (which cannot be compiled in my environment). This means I can't really experiment with the code by changing it or inserting breakpoints or dumping values. In looking at one particular passage of code, I see that one Procedure (ProcedureA) is calling another (ProcedureB) and passing in parameters that must be by reference, since otherwise ProcedureB would have no effect. It's my understanding that a var prefix must be added to parameters in a Procedure's parameter list in order for them to be passed by reference, but this is not being done here. One of the parameters, though, is of type TList, which I know to be essentially an array of pointers. My question is: are parameters of type TList (as well as others having to do with pointers) implicitly passed by reference?
Here's the code:
Procedure ProcedureB(PartyHeaderInformationPtr : PartyHeaderInformationPointer;
PartyHeaderTable : TTable;
_PrisonCode : String;
_FineType : TFineTypes;
PartyHeaderInformationList : TList);
begin
with PartyHeaderInformationPtr^, PartyHeaderTable do
begin
AssessmentYear := FieldByName('TaxRollYr').Text;
PartyType := FieldByName('PartyType').Text;
PartyNumber := FieldByName('PartyNo').AsInteger;
PrisonCode := _PrisonCode;
FineType := _FineType;
end; {with PartyHeaderInformationPtr^ ...}
PartyHeaderInformationList.Add(PartyHeaderInformationPtr);
end; {AddPartyHeaderPointerInformation}
{=================================================================}
Procedure ProcedureA(PartyHeaderTable : TTable;
PartyDetailTable : TTable;
PartyHeaderInformationList : TList);
var
Done, FirstTimeThrough : Boolean;
PrisonPartyFound, JunglePartyFound : Boolean;
PrisonPartyYear, PrisonCode, PartyType : String;
PartyHeaderInformationPtr : PartyHeaderInformationPointer;
begin
PartyHeaderTable.Last;
PrisonPartyYear := '';
PrisonPartyFound := False;
JunglePartyFound := False;
Done := False;
FirstTimeThrough := True;
repeat
If FirstTimeThrough
then FirstTimeThrough := False
else PartyHeaderTable.Prior;
If PartyHeaderTable.BOF
then Done := True;
If not Done
then
begin
PartyType := PartyHeaderTable.FieldByName('PartyType').Text;
If ((not JunglePartyFound) and
((PartyType = 'MU') or
(PartyType = 'TO')))
then
begin
JunglePartyFound := True;
New(PartyHeaderInformationPtr);
AddPartyHeaderPointerInformation(PartyHeaderInformationPtr,
PartyHeaderTable,
'', ltPlace,
PartyHeaderInformationList);
end; {If ((not JunglePartyFound) and ...}
end; {If not Done}
until Done;
end; {FillPartyHeaderInformationList}
Yes.
In Delphi, classes are reference types.
Every variable of type TBitmap, TList, TButton, TStringList, TForm etc. is nothing but a pointer to the object, so an object is always passed "by reference". It is only this address, this native-sized integer, that is given to the called routine.
Consequently, even without var, the called routine can alter the object since it, like the caller, has the address to it. But the pointer itself is passed by value, so if the called routine alters the parameter pointer to point to a different object, the caller will not see that; only the called routine's copy of the address is changed. With var, the pointer itself is passed by reference, so the called routine can change that too: it can change the original object, and it can make the caller's variable point to a different object, if it wants to.
On the other hand, value types like integers, booleans, sets, static arrays, and records are passed by value, so -- without any parameter decoration such as var -- the called routine gets a copy, and any changes made are only made to that copy. The caller will not see its variable being modified. If you use a var parameter, however, the variable will be passed by reference.
So, in your case, it has nothing to do with TList being a "list" or being something that "contains pointers". It's about TList being a class.

Circular dependency between new vector package and procedure

I am attempting to understand how to fix this circular dependency. All the examples I can find online suggest using a limited with, but then they demonstrate the use with two basic types, whereas this is a bit more advanced. The circular dependency is between the two files below. I thought it was between package Chessboard ... and the Piece type, but now I am not so sure. Attempting to put the package Chessboard ... line within chess_types.ads after the Piece type is declared and removing the use and with of Chessboard results in an error: this primitive operation is declared too late for the Move procedure. I am stuck on how to get out of this dependency. Any help would be much appreciated!
Thank you
chessboard.ads:
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
with Chess_Types;
use Chess_Types;
package Chessboard is new Indefinite_Vectors(Board_Index, Piece'Class);
chess_types.ads:
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
with Chessboard;
use Chessboard;
package Chess_Types is
subtype Board_Index is Integer range 1 .. 64;
type Color is (Black, White);
type Piece is tagged
record
Name : String (1 .. 3) := " ";
Alive : Boolean := False;
Team : Color;
Coordinate : Integer;
end record;
procedure Move_Piece(Board: in Vector; P: in Piece; Move_To: in Integer);
end Chess_Types;
More Code for question in comments:
Chess_Types.Piece_Types.ads:
package Chess_Types.Piece_Types is
type Pawn is new Piece with
record
First_Move : Boolean := True;
end record;
overriding
procedure Move_Piece(Board: in CB_Vector'Class; Po: in Pawn; Move_To: in Board_Index);
-- Other piece types declared here
end Chess_Types.Piece_Types;
Chess_Types.Piece_Types.adb:
with Ada.Text_IO;
use Ada.Text_IO;
package body Chess_Types.Piece_Types is
procedure Move_Piece(Board: in CB_Vector'Class; Po: in Pawn; Move_To: in Board_Index) is
Index_From, Index_To : Board_Index;
Move_From : Board_Index := Po.Coordinate;
begin
-- Obtain locations of Pawn to move from (Index_From) and to (Index_To)
-- in terms of the single dimension vector
for I in Board.First_Index .. Board.Last_Index loop
if Board.Element(I).Coordinate = Move_From then
Index_From := I;
end if;
if Board.Element(I).Coordinate = Move_To then
Index_To := I;
end if;
end loop;
-- Determine if the requested move is legal, and if so, do the move.
-- More possibilties to be entered, very primitive for simple checking.
if Move_To - Move_From = 2 and then Po.First_Move = True then
Board.Swap(I => Index_From, J => Index_To); -- "actual for "Container" must be a variable"
Board.Element(Index_From).First_Move := False; -- "no selector for "First_Move" for type "Piece'Class"
elsif Move_To - Po.Coordinate = 1 then
Board.Swap(Index_From, Index_To); -- "actual for "Container" must be a variable"
end if;
-- Test to make sure we are in the right Move_Piece procedure
Put_Line("1");
end Move_Piece;
-- Other piece type move_piece procedures defined here
end Chess_types.Piece_Types;
As a note to understand further, the Coordinate component of each piece correspond to ICCF numeric notation, which is two digits, so there needs to be some type of conversion between the vector and the ICCF notation, hence the reason for the whole for loop at the start.
This is a tough one. It looks like limited with and generics don't play nice together. The only way to make it work is to go back to using your own access type:
with Ada.Containers.Vectors;
use Ada.Containers;
limited with Chess_Types;
use Chess_Types;
package Chessboard_Package is
subtype Board_Index is Integer range 1 .. 64;
type Piece_Acc is access all Piece'Class;
package Chessboard is new Vectors(Board_Index, Piece_Acc);
end Chessboard_Package;
I had to put the instantiation into a new package, and move the Board_Index there too. Also, I changed it to Vectors since Piece_Acc is a definite type and there's no point in using Indefinite_Vectors. But in any event, this defeats the purpose. I'm just not sure Ada gives you a way to do what you want with two packages like this.
Even doing it in one package is not easy:
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
package Chess_Types is
subtype Board_Index is Integer range 1 .. 64;
type Color is (Black, White);
type Piece is tagged record ... end record;
type CB_Vector is tagged;
procedure Move_Piece (Board : in CB_Vector'Class;
P : in Piece;
Move_To : in Board_Index);
package Chessboard is new Indefinite_Vectors(Board_Index, Piece'Class);
type CB_Vector is new Chessboard.Vector with null record;
end Chess_Types;
This compiles, but I had to add extra stuff to get around some of the language rules (in particular, when you instantiate a generic, that "freezes" all prior tagged types so that you can no longer declare a new primitive operation of the type); also, I had to make the Board parameter a classwide type to avoid running into the rule about primitive operations of multiple tagged types.
As I understand it, this will do what you want.
with Ada.Containers.Indefinite_Vectors;
use Ada.Containers;
package Chess_Types is
subtype Board_Index is Integer range 1 .. 64;
type Color is (Black, White);
type Piece is abstract tagged
record
Name : String (1 .. 3) := " ";
Alive : Boolean := False;
Team : Color;
Coordinate : Board_Index;
end record;
type Piece_Ptr is access all Piece'Class;
package Chessboard is new Indefinite_Vectors(Board_Index, Piece_Ptr);
procedure Move_Piece (Board : in Chessboard.Vector;
P : in Piece'Class;
Move_To : in Board_Index) is abstract;
end Chess_Types;
NOTES:
Piece is now abstract, as is the Move_Piece method. This will mean you now need to derive your other piece types (package piece_type-rook.ads, with a move_piece method for rook) etc...
Chessboard now contains pointers (Class wide pointers), beware allocating, deallocating, deep copy, shallow copy issues when using it.
You should now be able to call Move_Piece on any dereference of a piece_ptr and have it dispatch to the correct method.
The Move_To parameter is now the same type as the Board_Index. (Coordinate also brought in line) -- this seems a bit clunky, perhaps rethink this. (Row & Column Indices defining a 2D array perhaps? --No need for Indefinite_Vectors)
To answer the second question in the comment:
To use First_Move, the procedure has to know that it's a Pawn. If the object is declared with type Piece'Class, you can't access components that are defined only for one of the derived types. (That's true in most OO languages.) This may indicate a flaw in your design; if you have a procedure that takes a Piece'Class as a parameter, but you want to do something that makes sense only for a Pawn, then maybe you should add another operation to your Piece that does a default action for most pieces (perhaps it does nothing) and then override it for Pawn. Other possibilities are to use a type conversion:
procedure Something (P : Piece'Class) is ...
if Pawn(P).First_Move then ...
which will raise an exception if P isn't a Pawn. If you want to test first, you can say "if P in Pawn". I sometimes write code like:
if P in Pawn then
declare
P_Pawn : Pawn renames Pawn(P);
begin
if P_Pawn.First_Move then ...
end;
end if;
But defining a new polymorphic operation is preferable. (Note: I haven't tested the above code, hope I didn't make a syntax error somewhere.)

Ada String Concatenation

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;
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.
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.
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!
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.
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

Resources