Variant records: Use components multiple times - ada

I created the following example code:
type Category is (A, B, C);
type My_Record (The_Category : Category) is record
case The_Category is
when A =>
A : Natural;
when B =>
B : Integer;
when C =>
A : Natural;
B : Integer;
end case;
end record;
The code is obviously not valid in Ada because I am getting two compiler errors: "A/B" conflicts with declaration at line ...
Why is such definition of a variant record not supported in Ada? I think it could be useful. Are there other ways to model this kind of data structure?

I ran into this problem recently. This is a case of Ada having your back. The Adaic (Pythonic equivalent?) way would be something like:
type Category is (A, B, C);
type A_Data is
record
A : Natural;
end record;
type B_Data is
record
B : Integer;
end record;
type C_Data is
record
A : Natural;
B : Integer;
end record;
type My_Record (The_Category : Category) is record
case The_Category is
when A =>
A : A_Data;
when B =>
B : B_Data;
when C =>
C : C_Data;
end case;
end record;
While verbose, this provides strong typing as to what you have access to. The usage is nearly identical, the main difference is that in the C category things look uniform:
case My_Object.The_Category is
when A =>
Use_Integer (My_Object.A.A);
when B =>
Use_Natural (My_Object.B.B);
when C =>
Use_Integer (My_Object.C.A);
Use_Natural (My_Object.C.B);
end case;
A lot of people take some time to get used to Ada's verbosity, but over time you learn to love it. This larger interface compiles to nearly identical code and is much more open to extensibility (what if category A items now also need a float? instead of changing all A category items, just change the A_Data type).

It is not supported because it might confuse a reader (or a developer).
The fields in the different branches are logically different fields, so using the same name would be confusing.
(This is my explanation. If there exists an official one, you can find it in the Ada 83 Rationale.)
I practice you do something like this:
type Categories is (A, B, C);
type Data (Category : Categories) is record
case Category is
when A =>
Identifier_1 : Natural;
when B =>
Identifier_2 : Integer;
when C =>
Identifier_3 : Natural;
Identifier_4 : Integer;
end case;
end record;
You have to come up with some actual, sensible naming yourself, as I don't know which problem you're trying to solve.

Related

Static Comparison of Record Layouts

I have two records, some fields of which need to be in the same positions within each record. Although this has been heavily commented in the code, it is possible that, in 10 years time, a programmer may change one of the records without changing the other and I would like to create a static check that this has not occurred.
I can create an "active" check in a procedure or function as follows:
procedure Main is
type SimpleRecord1 is record
FirstItem : Integer;
SecondItem : Boolean;
ThirdItem : Integer;
DoNotCare : Float;
end record;
type SimpleRecord2 is record
Foo : Integer;
Bar : Boolean;
Baz : Integer;
Rfc3092 : Boolean;
end record;
MyRecord1 : SimpleRecord1;
MyRecord2 : SimpleRecord2;
begin
Pragma Assert ((MyRecord1.FirstItem'Position = MyRecord2.Foo'Position) and
(MyRecord1.SecondItem'Position = MyRecord2.Bar'Position) and
(MyRecord1.ThirdItem'Position = MyRecord2.Baz'Position));
Put_Line ("The assert didn't fire");
end Main;
I am concerned that the first three vairiables have the same offsets within the two records. In the real code there are dozens of other variables within each record which are not the same between the records.
However, I would really like this to be a check, not on instances of the records (MyRecord1, MyRecord2), but on the records themselves (SimpleRecord1, SimpleRecord2). Then it could be placed in the .ads file where the records are defined.
SimpleRecord1.FirstItem'Position
is illegal. Is there a way to create a check without having to make instances and put the code into a function or procedure?
To make the last two comments (by Jere and Jim Rogers) more concrete, indeed the Ada way is to define the types of the list elements so that any kind of element can be placed in the same list, and accessed by the same kind of pointer, without any uncheckable conversions. In the OP's case, IMO the most appropriate method is to make all list elements be tagged records derived from the same abstract parent class where the parent contains the next, prev and priority components. For example like this:
type List_Element;
type List_Ptr is access List_Element'Class;
type List_Element is abstract tagged record
Next, Prev : List_Ptr;
Priority : Boolean;
end record;
type Simple_Record_1 is new List_Element with record
DoNotCare : Float;
end record;
type Simple_Record_2 is new List_Element with record
Rfc3092 : Boolean;
end record;
The SW that handles the linked list deals with List_Ptr values that point to List_Element'Class objects but with only the common components Next, Prev and Priority visible. When there is a need to execute some processing that depends on the actual type of the list element, you can use either a dynamically dispatching call, or a membership test followed by a type conversion, to get from a List_Ptr to the underlying Simple_Record_1, for example.
I would do this - especially if you are going to go with Address Overlays and/or Unchecked_Conversion
-----------------------------------------------------------------------------
type Header_Record is
record
First_Item : Integer;
Second_Item : Boolean;
Third_item : Integer;
end record
with Convention => C;
for Header_Record use
record
First_Item at 0 range 0 .. 31;
Second_Item at 0 range 32 .. 47;
Third_Item at 0 range 48 .. 79;
end record;
-----------------------------------------------------------------------------
type Item_Record_1 is
record
Header : Header_Record;
DoNotCare : Float;
end record
with Convention => C;
for Item_Record_1 use
record
Header at 0 range 0 .. 79;
DoNotCare at 0 range 80 .. 111;
end record;
--------------------------------------------------------------------------------
type Item_Record2 is
record
Header : Header_Record;
Rfc3092 : Boolean;
end record
with Convention => C;
for Item_Record2 use
record
Header at 0 range 0 .. 79;
Rfc3092 at 0 range 80 .. 95;
end record;
-----------------------------------------------------------------------------
Here, we are specifying the exact bit layout. It's a bit of pain that you'll need to use one level of indirection ie
Item_Record1.Header.First_Item
However, this should work. Also, always remember to use
Convention => C
Since Ada record layouts and C struct layouts may vary considerably - which is to be expected, given Ada's rich semantics.
EDIT: In response to portability issues. Although, the OP did not specify that it must be portable, it's still no problem ...
with Interfaces.C;
subtype int is Interfaces.C.int;
type Header_Record is
record
First_Item : Interfaces.C.int;
Second_Item : Interfaces.C.int; --bool?
Third_item : Interfaces.C.int;
end record
with Convention => C;
-- Exact values very likely to be different, for demo only
for Header_Record use
record
First_Item at 0 range 0 .. int'Size;
Second_Item at 0 range int'Size + 1 .. int'Size * 2;
Third_Item at 0 range int'Size * 2 + 1 .. int'Size * 3;
end record;

Can’t create access type to access an array of integer

Creating access type of array of integer gives an error. Below is my code, please help me to fix it.
procedure arry_point is
type arr is array(1..5) of integer;
obj:aliased arr;
type my_access1 is access all arr;
var1:my_access1:=obj'Access;-- this is good
ii: aliased array(1..5)of integer
type my_access is access all ii; --this is bad but how can i create access type for ii ?
var:my_access:=ii'access; ---?
begin
null;
end arry_point;
type My_Access is access ... what? The answer is, it has to be a type name (strictly a subtype_indication, see ARM 3.10(3).
When you say ii: aliased array(1..5) of integer you’re creating an array of an anonymous type; this means you can’t supply the type name to complete the access type definition.
You could imagine a language (C++?) in which you could say
type My_Access is access all Type_Of (II);
or, perhaps,
type My_Access is access all II'Type;
but neither of those is possible in Ada. I suspect the reason is that there wouldn’t be any point, because in Ada types are not equivalent even if they have the same structure:
1. procedure SG is
2. A : array (1 .. 5) of Integer := (others => 0);
3. B : array (1 .. 5) of Integer;
4. begin
5. B := A;
|
>>> expected type of B declared at line 3
>>> found type of A declared at line 2
6. end SG;

I need some answers for solving these problems.I am stuck in these errors whilw programming in Ada using records

I am new to Ada programming.This is my ADA CODE for a program which gives me a list of things when typed a Football legend name in execution time.But I am getting the following errors.Please help me out:
Some of the Errors found are:
1.Discriminants must have a discrete or Access type
2.components "FBClubs cannot be used before end of record declaration
3.discriminant in a variant part must be of discrete part
4."player" is undefined
5."pos" is undefined.
6.no candidate interpretations match the sctuals : = > in call to inherited operation "to_string" at line "type playernames..."
with Ada.Text_Io; use Ada.Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
Procedure football_record is
type Position is (Goalkeeper,Midfielder,Forward,Defender);
type playernames is new Unbounded_String;
type FBClubs is (ACMilan,Man_United,Aresnal,ParisSt.Germain,Real_Madrid,Liverpool,Chelsea,Man_City,Lille,
Tottenham,Ajax,Juventus,Dortmund,Roma,Santos,Roma,Bayern_Munich,Inter_Milan);
type countries is (England,Argentina,Brazil,France,Italy,Portugal,Spain,Germany,Iran,Japan);
type fbplayer(player:playernames) is
record
WCAppearances:Integer;
pos:Position;
country:countries;
fbclubs:FBClubs;
case player is
when "David Beckham" =>
country:countries:=England;
WCAppearances:Integer:=3;
pos:Position:=Midfielder;
fbclubs:FBClubs:=ACMilan &"+" & Man_United &"+" & Real_Madrid &"+"& ParisSt.Germain;
when "Lionel Messi" =>
country:countries:=Argentina;
WCAppearances:Integer:=1;
pos:Position:=Forward;
fbclubs:FBClubs:=Barcelona;
.....and some other 12 players(legends)..
when others =>
country:countries:=Nil;
WCAppearances:Integer:=Nil;
pos:Position:=Nil;
fbclubs:FBClubs:=Nil;
end case;
end record;
begin
Get(player);
Put_Line(To_String(Integer'Image(player)));
Put_Line(To_String(Integer'Image(FBClubs'Image(fbclubs)));
Put_Line(To_Unbounded_String(Position'Image(pos)));
end football_record;
The biggest problem is that you're mixing code in with a type declaration.
In Ada, putting a case within a record is only for variant records; these are records where some fields exist in certain cases but not others. For example:
type Employee_Type is (Regular, Manager);
type Employee (Emp_Type : Employee_Type) is record
Name : Unbounded_String;
case Emp_Type is
when Manager =>
Managerial_Level : Integer;
when Regular =>
null;
end case;
end record;
This is a variant record; we're assuming here that there are two kinds of employees, and the Managerial_Level field makes sense only for one of those kinds. Records whose type is Regular will not have a Managerial_Level field at all.
This syntax isn't what you would use to return different values of fields. Instead, you need to do this in statements, usually in a procedure or function (or package initialization, or some other places that allow statements).
And since you're not using the variant record syntax, you don't need to make player a discriminant. It doesn't work, anyway; in Ada, a "discriminant" (like Emp_Type in my example) has to be a discrete type like an integer or an enumeration type (Employee_Type is an enumeration type), or an access (access discriminants are an advanced concept). It can't be an Unbounded_String. So you'd want to make player a regular field:
type fbplayer is record
player : Unbounded_String;
pos : Position;
country : countries;
clubs : FBClubs; -- Note name change!
WCAppearances : Integer;
end record;
and create a procedure to fill in the fields:
procedure Fill_In_Player(P : in out fbplayer; Player : Playernames) is
begin
P.Player := Player;
if Player = "David Beckham" then
P.country := England;
P.WCAppearances := 3;
P.pos = Midfielder;
P.clubs := ??? -- see below
elsif Player = "Lionel Messi" then
------- etc.
elsif ------
end if;
end Fill_In_Player;
and then call Fill_In_Player when you have the Player and want to set up the record fields. You have to write statements to do this; you can't do it inside the declaration of a record.
Note that in Ada, case statements can only be used on integer or enumeration types. You can't use them to test for a string, as some other languages allow.
Ada does not treat lower-case and upper-case letters the same in identifiers or keywords. Therefore, fbclubs is the same name as FBClubs, and you can't declare the field
fbclubs : FBClubs;
because of the name conflict. I changed the name.
Finally, it looks like you want FBClubs to hold more than one club. But FBClubs is an enumeration type, and can therefore hold only one value at a time. If you want each player record to contain a list of clubs, you'll need to do something else, such as using one of Ada's container types (like Ada.Containers.Vectors.Vector) or something like
type Set_Of_Clubs is array(FBClubs) of Boolean;
where each array value is true if the player played for that club.
I'm not sure that will take care of all your errors, but it looks like you have a lot of work to do already.

Vector with tagged types

Can I have a vector filled with both, type A and B? I will fill it only with one type, so I am always sure, what I will get out of it. But it would make many things very easy for me, if I can define the vector once.
type T is tagged null record;
type A is new T with
record
x : Integer;
end record;
type B is new T with
record
y : Integer;
end record;
package Some_Vector is new Ada.Containers.Indefinite_Vectors (
Index_Type => Positive,
Element_Type => T <- ???
);
You can say:
package Some_Vector is new Ada.Containers.Indefinite_Vectors (
Index_Type => Positive,
Element_Type => T'Class
);
Some_Vector is now able to hold elements of type T or any type derived from it, including A or B. There's no requirement that all the elements have to be of the same type, as long as they're all derived from T; so if you don't really care whether this property is enforced, the above should work. If you really want the compiler to enforce that all elements are the same type, then you should simply declare two packages, A_Vector and B_Vector, for vectors of the two types; but then there's no way to write a "class-wide" type name that could refer to either an A_Vector or a B_Vector.
If you really want to combine both--have a vector type that could refer either to a vector of A or a vector of B, but still enforce that all elements of the vector have the same type, then I think this could be done if you define your own vector type and perform the needed check at run time, but it could get complicated. This compiles, but I haven't tested it:
generic
type Elem is new T with private;
package Sub_Vectors is
type Sub_Vector is new Some_Vector.Vector with null record;
overriding
procedure Insert (Container : in out Sub_Vector;
Before : in Some_Vector.Extended_Index;
New_Item : in T'Class;
Count : in Ada.Containers.Count_Type := 1)
with Pre => New_Item in Elem;
end Sub_Vectors;
package body Sub_Vectors is
procedure Insert (Container : in out Sub_Vector;
Before : in Some_Vector.Extended_Index;
New_Item : in T'Class;
Count : in Ada.Containers.Count_Type := 1) is
begin
Some_Vector.Insert
(Some_Vector.Vector(Container), Before, New_Item, Count);
end Insert;
end Sub_Vectors;
Unfortunately, you'd have to override every Insert and Replace_Element operation that could put an element into the vector. After you do all this, though, you can instantiate Sub_Vectors with Elem => A and with Elem => B, and the class Some_Vector.Vector'Class would be a class-wide type that would include both Sub_Vector types in the instance packages.
If you really want the compiler to enforce that all elements are the
same type, then you should simply declare two packages, A_Vector and
B_Vector, for vectors of the two types; but then there's no way to
write a "class-wide" type name that could refer to either an A_Vector
or a B_Vector.
You can, however, have a vector that points only to the A or B subtrees of the hierarchy:
type T is tagged null record;
type A is new T with null record;
type B is new T with null record;
type C is new T with null record;
type TAB is access all T'Class
with Dynamic_Predicate =>
TAB = null or else
(TAB.all in A'Class or TAB.all in B'Class);
Above yields the TAB Type which must be a [pointer to] an A'Class or B'Class, which you should be able to use in your vector. -- The only problem I've run into is you have to use GNAT's 'Unchecked_Access to get the access values of objects (due, I think, to my quick and dirty testing).

Array of variant records in Ada

I want to declare an array with element-type of a variant record.
Something like this:
type myStruct (theType : vehicleType) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
myArray : array (Positive range <>) of myStruct;
But in this case I get error.
It only allows this:
myArray : array (1.100) of myStruct(Car); //such as
So how to solve the index-problem and how to describe an array of a variant record's type without giving it's discriminant?
The example above will not compile. Here is a correct version (I changed mystruct to Integer for simplicity):
procedure test_array is
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Arr_T is array (Vehicle_Array_Index range <>) of Integer;
type Vehicle_Array (Size: Vehicle_Array_Index := 1) is record
Vehicles : Arr_T(1..Size);
end record;
begin
null;
end;
One of the errors was that you cannot have anonymous arrays inside records, and second, you should use the discriminant to constrain the array inside.
As mentioned above this is not a good solution for varying-length arrays as most likely you will get a maximum sized array anyway. If you want arrays with dynamically determined sizes you can use blocks for that.
declare
a: array(1..n) of integer; -- n can be a variable here
begin
--some code using a
end;
It also works in the declaration parts of procedures and functions where n can be a parameter passed to the subprogram (one of the advantages Ada has over C/C++). And of course you can just allocate arrays dynamically on the heap using allocators.
If you want to be able to create objects of a discriminated type and change (or figure out) what the type is at runtime, you have to give the discriminant a default value in the declaration.
The reason for this is that Ada doesn't want to have to worry about dealing with uninitialized discriminated objects that it can't even figure out the size and valid record fields of.
For this reason, and some reasons I go into a bit in the comments, Ada discriminated records aren't actually very useful in mixed language programming (eg: exactly duplicating a C union). They can be handy on their own terms though.
For the example you give, you'd do the following (warning: Not compiled):
type myStruct (theType : vehicleType := car) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
Then you could set one of the array values at runtime thusly:
myArray(20) := (theType => car,
field1 => myArray(20).field1,
... , --// Fill in the rest of the fields by name
numberOfWheels => 4);
As for this part:
myArray : array (Positive range <>) of myStruct;
You cannot declare actual array objects with an indeterminate range like this. You could declare a type that way, but an object has to have an actual size. If you want an array of varying length, you can once again use a variant record. (Again, not compiled)
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Vehicle_Array (Vehicle_Array_Index : Size := 1) is record
Vehicles : array (Vehicle_Array_Index range <>) of myStruct;
end record;
Oh, and one more thing. You aren't doing this in your example, but if you ever want to use your discriminant to size an array like above, beware. When you declare objects of that type (again, assuming you used a default for the discriminant), the compiler will try to reserve enough space for the largest possible size you could ever feed it a value for. That makes it a Very Bad Idea to make a discriminated array indexed by something with a huge range like Integer or Positive. I know computers are bigger these days, but still most folks don't have 4 gig to spare for one silly little array. So if you are using your discriminant as an array index, it would be a good idea to create a smaller subtype of Integer to use for the type of the discriminant.

Resources