Array of strings in Ada record - arrays

I have the following record in some Ada code:
type My_Type (Width, Height : Positive) is
record
Data : array (1 .. Height) of String (1 .. Width);
-- Some other stuff
end record;
Of course I can't have anonymous arrays in my record, but I can't come up with a way to name the array beforehand.
I know that I could make the package depend on width and height to redefine the string of the correct length and name an array of them, but that makes it clunky to have many different sized records at once (which I want).
Does anybody know of a more elegant solution to the problem?
Note: a 2-D array of characters would work, but only if I could extract strings from it in a straightforward way, which is again something I'm not sure how to do.

There are several possible solutions to your problem, including the use of unbounded strings. Below is a solution using a generic package:
generic
width : Positive;
height : Positive;
package Gen_String_Matrix is
Subtype My_String is String(1..width);
type My_Matrix is array(1..height) of My_string;
end Gen_String_Matrix;
An example of using this package is:
with Ada.Text_IO; use Ada.Text_IO;
with gen_string_matrix;
procedure Gen_String_Main is
package My_Matrices is new Gen_String_Matrix(width => 3,
height => 10);
use My_Matrices;
Mat : My_Matrices.My_Matrix;
begin
for Str of Mat loop
Str := "XXX";
end loop;
for Str of Mat loop
Put_Line(Str);
end loop;
end Gen_String_Main;

Related

Unable to print the contents of an Array String of String Constants?

I'm a bit of a beginner when it comes to Ada, and I'm trying to declare and use an array of strings of different lengths.
Using Ada'83 I can declare an array of variable length string constants as follows (example taken from the Ada FAQ)
type table is access String;
TESTS : constant array (Positive range 1..3) of table
:= ( 1 => new String'("One"),
2 => new String'("Two"),
3 => new String'("Three")
);
However much to my frustration even though the result appears to be an array of character arrays they don't behave as strings. When I try to compile the following code I get an error message 'Inconsistency detected during overload resolution [LRM 8.7]'
for COUNT in TESTS'Range loop
Put(TESTS(COUNT));
New_Line;
end loop;
However, I can print out the content of each of the 'strings' using the following code.
for COUNT in TEST'Range loop
for COUNTER in TEST(COUNT)'Range loop
Put(TEST(COUNT)(COUNTER));
end loop;
New_Line;
end loop;
Unfortunately I want to use the values to test some code that takes a string as a parameter, so this doesn't really help...
Is there a way to be to iterate over an array of string constants of varying length in Ada'83, or to convert the character arrays into strings of varying length.
Thanks
No, this isn't homework, and yes, I know I'm using an ancient compiler!
Test is undefined; I'll presume you mean Tests.
Table is not a string type; it is an access type. To reference the value that an access value designates, one uses .all:
Tests (Tests'First).all
is a string. Ada contains some shortcuts for access-to-array types to make them easier to use, allowing .all to be left off before attributes and indexing, which is why Tests (Count)'Range and Tests (Count) (Counter) work. To reference the whole value, though, .all is required:
Text_IO.Put_Line (Item => Tests (Counter).all);
However, a better approach would be to define a variable-length string abstraction and use that instead of an access type.
Thank you that works a treat - however, how would I go about defining
'a variable-length string abstraction' to do the same job?
Use private-types + access-types, perhaps like the following:
Package String_Abstraction is
Type DString is private;
Function "+"( Right : DString ) return String;
Function "+"( Right : String ) return DString;
Function "&"( Left, Right : String ) return DString;
--...
Private
Type Data(<>);
Type DString is access Data;
End String_Abstraction;
with implementation of:
Package Body String_Abstraction is
Type Data( Length : Natural ) is record
Text : String(1..Length) := (others => ASCII.NUL);
end record;
Function "+"( Right : String ) return DString is
Begin
Return New Data'( Text => Right, Length => Right'Length );
End "+";
Function "&"( Left, Right : String ) return DString is
Begin
Return +(Left & Right);
End "&";
Function "+"( Right : DString ) return String is
Begin
Return Right.Text;
End "+";
--...
End String_Abstraction;
Which could be used as follows:
Table : Constant Array(Positive range <>) of String_Abstraction.DString:=
( String_Abstraction."+"( "This" ),
String_Abstraction."+"( "EXAMPLE" ),
String_Abstraction."+"( "list" ),
String_Abstraction."+"( "exists." )
);
and
Print_Table:
For Index in Table'Range Loop
Declare
Use String_Abstraction;
Item : DString renames Table(Index);
Begin
Ada.Text_IO.Put_Line( +Item );
End;
End loop Print_Table;
If you had a use prior to the declaration of table, you could have:
Use String_Abstraction;
Table : Constant Array(Positive range <>) of String_Abstraction.DString:=
( +"This",
+"EXAMPLE",
+"list",
+"exists."
);
It's certainly not complete, but that gives you the basic idea of how to do it.
GNAT Studio Community Edition is free to use for freelance use and hobby going coders. It comes pre-installed with a decent Ada 2012 compiler. I had to also install a small add on called Ada GDE to get the code to run.
If your looking for an up to date book to study from, I highly recommend Ada 2012 by john Barnes. It's a bit of a beast at over 900 pages long, but he goes into good detail on concepts and provides plenty of sample code to help. He also wrote one for Ada 95 I believe.

Ada How to get input a list of integer from a user and put it into an array

Im sorry to ask this question but Ada is really strict on an input and output system so I cant figure out how to get the input from a the user and put it into an array.
with Ada.Text_IO;
use Ada.Text_IO;
with Ada.Integer_Text_IO;
use Ada;
procedure Main is
type MY_ARRAY is array(1..9) of INTEGER;
Data : MY_ARRAY;
begin
Put("Please input the series of numbers");
Get_Line(Data);
end Main;
I know this is completely wrong but I research everywhere and I cant find how people get the input to an array LOL. Thank yall for help.
I think it's easier to use only the package Ada.Text_IO so that you can read each number as a String and then store it as an integer one by one using a for loop and Integer'Value, which converts from String to Integer.
with Ada.Text_IO;
use Ada.Text_IO;
procedure Main is
type MY_ARRAY is array(1..9) of Integer;
Data : MY_ARRAY;
begin
Put_Line("Please input the series of numbers");
for I in 1..MY_ARRAY'Length loop
Data(I) := Integer'Value(Get_Line);
end loop;
end Main;

Ada constant array of string literals

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?
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.
Arrays can't contain objects of indefinite types.
Basically you have two options:
Use another container than an array.
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;
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.

String Arrays in Ada

I have a program in Ada95, in which I have to create an array of strings. This array can contain strings of variable length.
Example:
I have declared the array in which all the indexes can store strings of size 50. When I assign a smaller string to the above array, I get "Constraint Error".
Code:
procedure anyname is
input_array : array(1..5) of String(1..50);
begin
input_array(1):="12345";
end anyname;
I have tried to create the array of Unbounded_Strings. But that doesn't work either. Can anyone tell me how to store this "12345" in the above string array?
If you use Unbounded_String, you cannot assign a string literal to it directly. String literals can have type String, Wide_String, or Wide_Wide_String, but nothing else; and assignment in Ada usually requires that the destination and source be the same type. To convert a String to an Unbounded_String, you need to call the To_Unbounded_String function:
procedure anyname is
input_array : array(1..5) of Ada.Strings.Unbounded.Unbounded_String;
begin
input_array(1) := Ada.Strings.Unbounded.To_Unbounded_String ("12345");
end anyname;
You can shorten the name by using a use clause; some other programmers might define their own renaming function, possibly even using the unary "+" operator:
function "+" (Source : String) return Ada.Strings.Unbounded.Unbounded_String
renames Ada.Strings.Unbounded.To_Unbounded_String;
procedure anyname is
input_array : array(1..5) of Ada.Strings.Unbounded.Unbounded_String;
begin
input_array(1) := +"12345"; -- uses renaming "+" operator
end anyname;
Not everyone likes this style.
You can use Ada.Strings.Unbounded, illustrated here, or you can use a static ragged array, illustrated here. The latter approach uses an array of aliased components, each of which may have a different length.
type String_Access is access constant String;
String_5: aliased constant String := "12345";
String_6: aliased constant String := "123456";
String_7: aliased constant String := "1234567";
...
Input_Array: array (1..N) of
String_Access :=
(1 => String_5'Access,
2 => String_6'Access,
3 => String_7'Access,
-- etc. up to N
);
Strings in Ada are arrays of characters of fixed length. In order to use strings of variable length (which may often be the case when arrays of strings are needed, e.g. arrays of names, each name being of variable length), each individual string may be declared as an Unbounded_String. The only caveat is that this allocates from the heap memory. Below is a complete example of an array of strings in Ada.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
procedure arrayAda is
type DaysArray is array(1..7) of Unbounded_String;
days: DaysArray;
begin
days(1):=To_Unbounded_String("Sunday");
days(2):=To_Unbounded_String("Monday");
days(3):=To_Unbounded_String("Tuesday");
days(4):=To_Unbounded_String("Wednesday");
days(5):=To_Unbounded_String("Thursday");
days(6):=To_Unbounded_String("Friday");
days(7):=To_Unbounded_String("Saturday");
for index in 1..7 loop
Put(days(index));
Put(" ");
end loop;
end arrayAda;
This produces the following output:
$ ./arrayAda
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
I've had a lot of joy from instantiating a container package, e.g.:
package String_Vectors is
new Ada.Containers.Indefinite_Vectors (Positive, String);
It's still a bit fiddly, compared to how easy it is to mess about with strings in a lot of other programming languages, but it's okay.
Fundamentally, Ada is a language designed to be usable without using the heap (at all :-) Most other languages would fall down in a, well, a heap, without the heap.

Cannot use SetLength() on a variant array

I have inherited this code:
var
FSavedRecords : Variant; { actually, a private property in an ancestor }
lFieldsArray : Variant;
lClientDataSet: TClientDataSet;
FSavedRecords := VarArrayCreate([0, lCount], varVariant);
for lRow := 0 to lCount do
begin
FSavedRecords[lRow] := VarArrayCreate([0, lClientDataSet.FieldCount-1], varVariant);
with lClientDataSet do
begin
lFieldsArray := FSavedRecords[lRow];
if <SomeCondition> then
put lClientDataSet field values into lFieldsArray
Since the condition is not always true, I end up with fewer than lCount(+1) elements in FSavedRecords.
I can count those of course (say: lNrOutput), but cannot do a SetLength(FSavedRecords,lNrOutput) ('Constant object cannot be passed as a var parameter').
If SetLength() cannot be used, I assume I can convert the variant array to a dynamic 'array of Variant' with DynArrayFromVariant and use SetLength on that, but this has the disadvantage of the extra copy operation. I would like to re-use the private FSavedRecords from an ancestor form, which is used in other places in the program for the same purpose.
Is there maybe a better way out?
SetLength is used to resize dynamic arrays and long strings. To resize a variant array you use VarArrayRedim.
Another option is to build your list of elements in a temporary container of type TList<T>. When you are finished, you can use the Count property of that container to size the variant array once and for all. And then you'd copy across the actual values.
I think it makes little difference which approach you use.

Resources