I am still new to Ada and not very proficient in the way object orientation is handled in Ada. :(
I would like to know if it is possible to implement a builder like pattern in Ada? This pattern is quite common in the Java programming language.
A simple example: Let's say I want to model a person object. A person has the following attributes:
First name
Middle name (optional)
Last name
Date of birth
Place of birth (optional)
I could implement four (overloaded) Create functions to cover all possible combinations:
declare
Person_1 : Person;
Person_2 : Person;
Person_3 : Person;
Person_4 : Person;
begin
Person_1 := Create(First_Name => "John",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27");
Person_2 := Create(First_Name => "John",
Middle_Name => "Michael",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27");
Person_3 := Create(First_Name => "John",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27",
Place_Of_Birth => "New York");
Person_4 := Create(First_Name => "John",
Middle_Name => "Michael",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27",
Place_Of_Birth => "New York");
end;
Builder pattern like (don't know if this is possible in Ada):
declare
Person_1 : Person;
Person_2 : Person;
Person_3 : Person;
Person_4 : Person;
begin
Person_1 := Person.Builder.First_Name("John")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Build();
Person_2 := Person.Builder.First_Name("John")
.Middle_Name("Michael")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Build();
Person_3 := Person.Builder.First_Name("John")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Place_Of_Birth("New York")
.Build();
Person_4 := Person.Builder.First_Name("John")
.Middle_Name("Michael")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Place_Of_Birth("New York")
.Build();
end;
First question: How could this example be implemented in Ada?
The Build function could check (at runtime) if all required attributes where initialized by the belonging functions.
Second question: Could this check be delegated (in a magic way) to the compiler so the following example would not compile?
declare
Person : Person;
begin
-- Last_Name function not called
Person := Person.Builder.First_Name("John")
.Date_Of_Birth("1990-02-27")
.Build();
end;
One Ada way of supporting the problem as stated would be to use default values for the parameters whose values aren’t required:
function Create (First_Name : String;
Middle_Name : String := "";
Last_Name : String;
Date_Of_Birth : String;
Place_Of_Birth : String := "")
return Person;
which accepts all your examples.
So yes, this is possible.
I strongly recommend you do not go with this approach. It have very bad performance problems, not to mention is harder to maintain. That being said, it is in fact possible (and should be in any language with dispatching). It's accomplished by an extension to a fluent pattern, which uses an intermediary type, to keep the primary type readonly. Because Ada does not have readonly fields, you will also need to use a property pattern to expose the fields in a readonly way.
Here's the spec
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
package Persons is
type Person is tagged private;
function First_Name(Self : in Person) return String;
function Middle_Name(Self : in Person) return String;
function Last_Name(Self : in Person) return String;
function Date_of_Birth(Self : in Person) return String;
function Place_of_Birth(Self : in Person) return String;
type Person_Builder is tagged private;
function Builder return Person_Builder;
function First_Name(Self : in Person_Builder; Value : in String) return Person_Builder;
function Middle_Name(Self : in Person_Builder; Value : in String) return Person_Builder;
function Last_Name(Self : in Person_Builder; Value : in String) return Person_Builder;
function Date_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder;
function Place_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder;
function Build(Source : in Person_Builder'Class) return Person;
private
type Person is tagged record
First_Name : Unbounded_String;
Middle_Name : Unbounded_String;
Last_Name : Unbounded_String;
Date_of_Birth: Unbounded_String;
Place_of_Birth: Unbounded_String;
end record;
type Person_Builder is tagged record
First_Name : Unbounded_String;
Middle_Name : Unbounded_String;
Last_Name : Unbounded_String;
Date_of_Birth: Unbounded_String;
Place_of_Birth: Unbounded_String;
end record;
end Persons;
and the body
package body Persons is
function First_Name(Self : in Person) return String is (To_String(Self.First_Name));
function Middle_Name(Self : in Person) return String is (To_String(Self.Middle_Name));
function Last_Name(Self : in Person) return String is (To_String(Self.Last_Name));
function Date_of_Birth(Self : in Person) return String is (To_String(Self.Date_of_Birth));
function Place_of_Birth(Self : in Person) return String is (To_String(Self.Place_of_Birth));
function Builder return Person_Builder is
begin
return Person_Builder'(To_Unbounded_String(""), To_Unbounded_String(""), To_Unbounded_String(""), To_Unbounded_String(""), To_Unbounded_String(""));
end Builder;
function First_Name(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(To_Unbounded_String(Value), Self.Middle_Name, Self.Last_Name, Self.Date_of_Birth, Self.Place_of_Birth);
end First_Name;
function Middle_Name(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, To_Unbounded_String(Value), Self.Last_Name, Self.Date_of_Birth, Self.Place_of_Birth);
end Middle_Name;
function Last_Name(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, Self.Middle_Name, To_Unbounded_String(Value), Self.Date_of_Birth, Self.Place_of_Birth);
end Last_Name;
function Date_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, Self.Middle_Name, Self.Last_Name, To_Unbounded_String(Value), Self.Place_of_Birth);
end Date_of_Birth;
function Place_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, Self.Middle_Name, Self.Last_Name, Self.Date_of_Birth, To_Unbounded_String(Value));
end Place_of_Birth;
function Build(Source : in Person_Builder'Class) return Person is
begin
return Person'(Source.First_Name, Source.Middle_Name, Source.Last_Name, Source.Date_of_Birth, Source.Place_of_Birth);
end Build;
end Persons;
Then an example program using that package
with Ada.Text_IO, Persons;
use Ada.Text_IO, Persons;
procedure Proof is
P : Person;
begin
P := Builder
.First_Name("Bob")
.Last_Name("Saget")
.Place_of_Birth("Philadelphia, Pennsylvania")
.Build;
Put_Line("Hello, my name is " & P.First_Name & " " & P.Last_Name & " and I am from " & P.Place_of_Birth);
Put_Line("Middle Name: " & P.Middle_Name);
Put_Line("Date of Birth: " & P.Date_of_Birth);
end Proof;
And here's the command line output
Now let me explain. Your primary type is of course Person, with Person_Builder acting as the mutable form of it. Builder converts from Person to Person_Builder and Build converts from Person_Builder back to Person. Person only supports readonly access to the fields through a property pattern. Similarly, Person_Builder supports mutation but not through a property pattern, rather through a fluent pattern which returns the new instance each call. These modifications can then be chained as a result of fluent application.
I believe Java has the Builder pattern because it does not support parameters with defaults. The Builder pattern in Java creates a workaround for those who do not want to use function overloading.
Ada does have default parameters, so an Ada way to address this need, (without using overloadding) is to use default parameters, as was suggested by Simon Wright.
A benefit of this approach, is that this gives you compile time checking, whereas using the Builder pattern, apparently it is a run-time check. Using the Create function as suggested by Simon, one cannot create a Person that doesn't have a first name, for example.
So in Ada, I'd say there isn't a need to implement a Builder pattern, since better mechanisms are built into the syntax. However, if one did want to implement a builder pattern, my approach would be to use Ada streaming capabilities to build a stream of attribute objects that can be passed into a Build procedure which reads the stream, and builds an object. That is essentially what the Java Build pattern is doing. This however puts the error checking back in the run-time, rather than at compile time, as it does in Java.
From my point of view at first sight, this would require the compiler to know the content of your builder object at compilation time and so this is not possible but I may be wrong.
One solution though, which is not really a builder pattern, might be to declare intermediate types such as
type Person_with_name is tagged record
First_name : String(1..50);
end record;
type Person_with_last_name is new Person_With_First_Name with
record
Last_Name : String(1..50);
end record;
type Person_with_last_name is new Person_With_Birth with
record
Date_Of_Birth : Date;
end record;
And then each you would have in your Builder object, functions returning these types
function LastName(with_first : Person_With_First_Name, last_name : String(1..50)) return Person_With_Last_Name;
function Date_Of_Birth(with_last : Person_With_Last_Name, date_Of_Birth : Date) return Person_With_Birth;
And so on... But that's a bit ugly :D
Please keep in mind that I didn't compile such a code :)
On the other hand, by writing pre- and post- conditions, you could be able to check this property using Spark and then prove that, when calling Build on your Builder object, this latter is correctly initialized.
Related
I am trying to update an element in a vector. I have quite a few issues that I don't know how to solve. When I use Replace_Element, everything works, but I want to use the proper procedure.
This is my code:
with Ada.Containers.Vectors;
procedure Test_Update is
type Node is record
Parent : Integer := -1;
Size : Integer := -1;
end record;
function TestUpdate(n : Node; val : Integer) return Node is
begin
n.Size := n.Size + val;
return n;
end TestUpdate;
package NodeVector is new
Ada.Containers.Vectors
(
Index_Type => Natural,
Element_Type => Node
);
Nodes : NodeVector.vector;
Current_Node : Node;
begin
Current_Node.Size := 10;
Nodes.Append(Current_Node);
NodeVector.Update_Element(Nodes, 0, TestUpdate'Access(5));
--NodeVector.Update_Element(Nodes, 0, TestUpdate'Access);
end Test_Update;
These are the errors that I am getting, and I know what is causing them, but no idea how to fix them:
test_update.adb:11:09: error: assignment to "in" mode parameter not allowed
test_update.adb:28:59: error: unexpected argument for "Access" attribute
When I use the code in the comments, and remove the second parameter for the function, it still doesn't work.
You have a lot of things mixed up here. First if you look at Zerte's answer more carefully than before, you'll notice that your TestUpdate operation doesn't match that Process argument at all. It expects a procedure (you have a function) and the procedure's parameter is "in out" while you are using "in" and you have an additional parameter. You need to fix this first
If you want to use Update_Element (I don't recommend it for your specific case) then you need to look into "Nested Subprograms". Here is an example of how to change your TestUpdate operation to work with Update_Element:
procedure TestUpdate(V : in out NodeVector.Vector; val : Integer) is
procedure Actual_Update(N : in out Node) is
begin
n.Size := n.Size + val;
end Actual_Update;
begin
V.Update_Element(V.Last,Actual_Update'Access);
end TestUpdate;
Notice how the Actual_Update procedure actually matches the process argument of the Update_Element operation contract. Additionally, since Actual_Update is nested, it has access to the Val argument from TestUpdate.
Full example:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
procedure Test_Update is
type Node is record
Parent : Integer := -1;
Size : Integer := -1;
end record;
package NodeVector is new
Ada.Containers.Vectors
(
Index_Type => Natural,
Element_Type => Node
);
Nodes : NodeVector.vector;
Current_Node : Node;
procedure TestUpdate(V : in out NodeVector.Vector; val : Integer) is
procedure Actual_Update(N : in out Node) is
begin
n.Size := n.Size + val;
end Actual_Update;
begin
V.Update_Element(V.Last,Actual_Update'Access);
end TestUpdate;
begin
Current_Node.Size := 10;
Nodes.Append(Current_Node);
TestUpdate(Nodes, 5);
end Test_Update;
With Vectors, you can also just index things directly. After you append an item, you can use <vector_name>.Last to get the cursor (index) for the last element...the one you just added. Example:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
procedure Test_Update is
type Node is record
Parent : Integer := -1;
Size : Integer := -1;
end record;
package NodeVector is new
Ada.Containers.Vectors
(
Index_Type => Natural,
Element_Type => Node
);
Nodes : NodeVector.vector;
Current_Node : Node;
procedure Using_Cursors
(V : in out NodeVector.Vector;
N : Node;
Value : Integer)
is begin
Nodes.Append(N);
Nodes(Nodes.Last).Size := Nodes(Nodes.Last).Size + Value;
end Using_Cursors;
begin
Current_Node.Size := 10;
Using_Cursors(Nodes,Current_Node,5);
end Test_Update;
A quick Web search will lead you to...
A.18.2 The Package Containers.Vectors
(or, if you use GNAT Studio: right-click on "Vectors" in "Ada.Containers.Vectors", then choose Go To Declaration).
Then you will find this:
procedure Update_Element
(Container : in out Vector;
Index : in Index_Type;
Process : not null access procedure
(Element : in out Element_Type));
I would like to have a record with an integer and a variable-length string in it, something like this:
type Entry is
record
Value: Integer;
Label: String;
end record;
I ran into the issue that you can't put an unconstrained String in a record type, so following the advice at that link I tried
type Entry(Label_Length : Natural) is
record
Value: Integer;
Label: String(1..Label_Length);
end record;
But now the problem is, I want an array of these things:
Entries : Array(1..2) of Entry := (
(Label_Length => 0, Value => 1, Label => ""),
(Label_Length => 0, Value => 2, Label => "")
);
and I'm getting told
main.adb:17:28: unconstrained element type in array declaration
I just want to be able to declare a (constant) array of these things and type in the labels and values in an intuitive way (I already wasn't crazy about having to count string lengths and type in Label_Length by hand). How can I do this?
If you have no idea of the maximum size of the label field you can use Ada.Strings.Unbounded.
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
type Ent is record
Value : Integer;
Label : Unbounded_String;
end record;
type ent_array is array (1 .. 4) of Ent;
Foo : ent_array;
begin
for I of Foo loop
Put ("Enter a value: ");
Get (I.Value);
Skip_Line;
Put ("Enter a label: ");
I.Label := Get_Line;
New_Line;
end loop;
Put_Line ("Array Foo contents:");
for I of Foo loop
Put (I.Value'Image & " ");
Put_Line (I.Label);
end loop;
end Main;
[entry is a reserved word.]
If you want an array, all the entries have to be the same size. The size of your second record is Label_Length (4) + Value (4) + Label (Character (1) * Label_Length) i.e. anything between 8 and just over 2**31 bytes.
The trick is to fix the maximum size and give a default value:
subtype Ent_Label_Length is Natural range 0 .. 32;
type Ent (Label_Length : Ent_Label_Length := Ent_Label_Length'Last) is
record
Value : Integer;
Label : String (1 .. Label_Length);
end record;
You can save yourself the trouble of writing this (and working out the length of each string) by using Ada.Strings.Bounded (ARM A.4.4).
If you don't mind slightly different syntax, you can also consider using the Ada.Containers.Indefinite_Vectors package in place of arrays. Then each element can be a different size. And vectors can be used in for loops just like arrays can:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Indefinite_Vectors; use Ada.Containers;
procedure Main is
type Entry_Info(Label_Length : Natural) is
record
Value: Integer;
Label: String(1..Label_Length);
end record;
package Vectors is new Indefinite_Vectors
(Index_Type => Positive,
Element_Type => Entry_Info);
use type Vectors.Vector; -- so you can use the & operator
Entries : Vectors.Vector := Vectors.Empty_Vector
& (Label_Length => 0, Value => 1, Label => "")
& (Label_Length => 1, Value => 2, Label => "A");
begin
for Info of Entries loop
Put_Line(Info.Value'Image & " => " & Info.Label);
end loop;
end Main;
Yet another, but perhaps cruder, method is to put the strings on the heap and use access values:
type String_Ref is access String;
type Entry_T is record
Value: Integer;
Label: String_Ref;
end record;
To allocate the strings, use "new" with an initial value:
Entries : constant array(1..2) of Entry_T := (
(Value => 1, Label => new String'("First entry")),
(Value => 2, Label => new String'("Second entry"))
);
To get the value of a Label, deference with ".all":
for E of Entries loop
Ada.Text_IO.Put_Line (
"Value" & E.Value'Image
& ", label " & E.Label.all);
end loop;
If we're posting odd solutions, you can also use a holder:
package String_Holders is new Ada.Containers.Indefinite_Holders
(Element_Type => String);
type Entry_Is_Reserved is record
Value : Integer;
Label : String_Holders.Holder;
end record;
I was wondering if any of you could answer a quick question for me. I am currently working with records right now and in my program I need it to understand what the line of a file that i'm importing contains. My problem lies in the fact that I don't know how to "split" the line into actual variables. For example the line is
22134.09 Kia Bernice
How do I make the program know that the first part, 22134.09 is the variable price, Kia is the variable company and Bernice is the variable model, and then sort them all into a record?
Such as
type PriceCompModel is record
price : Float range 1.0..99999.99;
company : String (1..CompNameLength);
Model : String (1..ModelNameLength);
Thanks.
edited code
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure Testexercise is
type Inventory is Record
CarPrice : Float := 0.0;
CarType : String (1..40);
-- CarType will include both the model and company
end record;
InventoryItem : Inventory;
ImportedFile : File_Type;
FileName : String := "Cars.txt";
WordsFromFile : String(1..40);
LengthofWords : Integer ;
PriceofCar : Float := 0.0;
LoopCount : Integer := 1;
type Cars is array (1..12) of Inventory;
begin
Open(File => ImportedFile, Mode => In_File, Name => FileName);
--for I in 1..12 loop
while LoopCount /= 12 loop
Get(File => ImportedFile, Item => InventoryItem.CarPrice);
Get_Line(File => ImportedFile, Item => WordsFromFile, Last=> LengthofWords);
Put (Integer(InventoryItem.CarPrice),1);
Put (WordsFromFile(1..LengthofWords));
New_Line;
LoopCount := LoopCount + 1;
InventoryItem.CarType := WordsFromFile;
end loop;
close(ImportedFile);
end Testexercise;
So i tried doing this within the loop
for I in 1..12 loop
Cars := Inventory;
end loop;
This ended up working for me after i set
Car : Cars;
for I in 1..12 loop
Car(I) := Inventory;
end loop;
You have many factors to consider when defining the record to contain your information.
It will be useful to create a named subtype of float, or a named floating point type so that the I/O routines can check the input values for the price component.
Fields of type String must be constrained to a predefined size. This means that all "company" strings must be the same size, and all "model" strings must be the same size, although model strings may be a different length than company strings. If the names of companies and/or models may vary then you should consider using either bounded strings (Ada Language Reference Manual section A.4.4) or unbounded strings (Ada Language Reference Manual section A.4.5).
If the strings of the input file are fixed in size you can use Ada.Text_IO.Text_Streams (Ada Language Reference Manual section A.12.2) to read each field of the record. If the strings can be different sizes then you will need to read and parse each field manually using Ada.Text_IO.
-- Read record data from a file
with Ada.Text_Io; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
procedure read_record is
type Prices is delta 0.01 digits 7 range 0.0..99999.99;
type Auto_Inventory is record
Price : Prices := 0.0;
Company : Unbounded_String := Null_Unbounded_String;
Model : Unbounded_String := Null_Unbounded_String;
end record;
package AI_IO is new Ada.Text_IO.Decimal_IO(Prices);
use AI_IO;
Inventory_Item : Auto_Inventory;
The_File : File_Type;
File_Name : String := "inventory.txt";
Inpt_Str : String(1..1024);
Length : Natural;
Start, Finis : Positive;
begin
Open(File => The_File,
Mode => In_File,
Name => File_Name);
Get(File => The_File,
Item => Inventory_Item.Price);
Get_Line(File => The_File,
Item => Inpt_Str,
Last => Length);
Close(The_File);
Start := Index_Non_Blank(Source => Inpt_Str(1..Length));
Finis := Start;
while Finis < Length and then Inpt_Str(Finis) /= ' ' loop
Finis := Finis + 1;
end loop;
Inventory_Item.Company := To_Unbounded_String(Inpt_Str(Start..Finis));
Start := Index_Non_Blank(Inpt_Str(Finis + 1..Length));
Inventory_Item.Model := To_Unbounded_String(Inpt_Str(Start..Length));
Put_Line("Price: " & Prices'Image(Inventory_Item.Price));
Put_Line("Company: " & To_String(Inventory_Item.Company));
Put_Line("Model: " & To_String(Inventory_Item.Model));
end read_record;
If you want to read a file containing many records you need to collect the information in some kind of container. The following example uses a Vector from the generic package Ada.Containers.Vectors.
-- Auto Inventory Package specification
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
package Auto_Inventory is
type Prices is delta 0.01 digits 7 range 0.0..99999.99;
type Automobile is tagged private;
procedure Print(Item : Automobile);
function Set return Automobile;
function Get_Price(Item : Automobile) return Prices;
function Get_Company(Item : Automobile) return String;
function Get_Model(Item : Automobile) return String;
type Inventory is tagged private;
procedure Read(Item : out Inventory; File : File_Type) with
Pre => Mode(File) = In_File;
procedure Write(Item : in Inventory; File : File_type) with
Pre => Mode(File) = Out_File;
procedure Print(Item : Inventory);
private
type Automobile is tagged record
Price : Prices := 0.0;
Company : Unbounded_String := Null_Unbounded_String;
Model : Unbounded_String := Null_Unbounded_String;
end record;
package Auto_Vect is new
Ada.Containers.Vectors(Index_Type => Positive,
Element_Type => Automobile);
use Auto_Vect;
type Inventory is tagged record
List : Vector;
end record;
end Auto_Inventory;
The body for this package is:
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
package body Auto_Inventory is
package Prices_IO is new Ada.Text_IO.Decimal_IO(Prices);
use Prices_IO;
-----------
-- Print --
-----------
procedure Print (Item : Automobile) is
use Prices_Io;
begin
Put_Line("Price : " & Prices'Image(Item.Price));
Put_Line("Company: " & To_string(Item.Company));
Put_Line("Model : " & To_String(Item.Model));
New_Line;
end Print;
---------
-- Set --
---------
function Set return Automobile is
Temp : Automobile;
Inpt_Str : String(1..1024);
Length : Natural;
begin
Put("Enter the automobile price: ");
Get(Item => Temp.Price);
Put("Enter the automobile company: ");
Get_Line(Item => Inpt_Str, Last => Length);
Temp.Company := To_Unbounded_String(Inpt_Str(1..Length));
Put("Enter the automobile model: ");
Get_Line(Item => Inpt_Str, Last => Length);
Temp.Model := To_Unbounded_String(Inpt_Str(1..Length));
return Temp;
end Set;
---------------
-- Get_Price --
---------------
function Get_Price (Item : Automobile) return Prices is
begin
return Item.Price;
end Get_Price;
-----------------
-- Get_Company --
-----------------
function Get_Company (Item : Automobile) return String is
begin
return To_String(Item.Company);
end Get_Company;
---------------
-- Get_Model --
---------------
function Get_Model (Item : Automobile) return String is
begin
return To_String(Item.Model);
end Get_Model;
----------
-- Read --
----------
procedure Read (Item : out Inventory;
File : File_Type) is
Temp : Inventory;
Auto : Automobile;
Inpt_Str : String(1..1024);
Length : Natural;
Start, Finis : Positive;
begin
while not End_Of_File(File) loop
Get(File => File, Item => Auto.Price);
Get_Line(File => File, Item => Inpt_str, Last => Length);
Start := Index_Non_Blank(Inpt_Str(1..Length));
Finis := Start;
while Finis < Length and then Inpt_Str(Finis) /= ' ' loop
Finis := Finis + 1;
end loop;
Auto.Company := To_Unbounded_String(Inpt_Str(Start..Finis - 1));
Start := Index_Non_Blank(Inpt_Str(Finis..Length));
Auto.Model := To_Unbounded_String(Inpt_Str(Start..Length));
Temp.List.Append(Auto);
end loop;
Item := Temp;
end Read;
-----------
-- Write --
-----------
procedure Write (Item : in Inventory;
File : File_type) is
begin
for Element of Item.List loop
Put(File => File, Item => Prices'Image(Element.Price) &
" " & To_String(Element.Company) & " " &
To_String(Element.Model));
New_Line;
end loop;
end Write;
-----------
-- Print --
-----------
procedure Print (Item : Inventory) is
begin
for Element of Item.List loop
Element.Print;
end loop;
end Print;
end Auto_Inventory;
An example of a main procedure to exercise this package:
------------------------------------------------------------------
-- Read a file of many records --
------------------------------------------------------------------
with Auto_Inventory; use Auto_Inventory;
with Ada.Text_IO; use Ada.Text_IO;
procedure read_file is
The_Inventory : Inventory;
The_File : File_Type;
File_Name : String := "inventory.txt";
begin
Open(File => The_File,
Mode => In_File,
Name => File_Name);
The_Inventory.Read(The_File);
Close(The_File);
The_Inventory.Print;
end read_file;
An example input file for this program is:
22134.09 Kia Bernice
12201.15 Nissan Versa
22349.99 Chevrolet Cruse
It is not clear for me what language you are using .However the concept is to deal with each line from the file alone then process it with a function that do the token-zing or splitting depending on the language you use and save each token in a variable depending on how the function you are using will save the tokens
for example:
In java there is a class
StringTokenizer(String str, String delim)
StringTokenizer st = new StringTokenizer("this is a test", "$a; ");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
delim in your case is space so use the format
StringTokenizer st = new StringTokenizer("this is a test");
or
String line = reader.readLine();
String[] tokens = line.split("\\s");
note that you need to save the line you read in a string so you can use those functions in java then access each token from the array
String price = tokens[1] and so on
for other languages please find the following resources:
In c https://www.tutorialspoint.com/c_standard_library/c_function_strtok.htm
In pyhton https://www.tutorialspoint.com/python/string_split.htm
Suppose I have two records: Person and Animal. Each record is in a separate package.
Package persons:
with animals;
use animals;
package persons is
type person is record
...
animalref: animalPOINTER;
...
end record;
type personPOINTER is access person;
end persons;
Package animals:
with persons;
use persons;
package animals is
type animal is record
...
ownerref: personPOINTER;
...
end record;
type animalPOINTER is access animal;
end animals;
I have Circular Unit Dependency here, and compiler produces fatal error.
Does anyone have a pattern to address such issue ?
Thanks!
You need limited with, which was introduced to address exactly this problem. See the Rationale for Ada 2005, section 4.2.
Animals and Persons are symmetric (my editor has adjusted the layout and casing; I’ve added one record component to each so the demo program, below, can print something):
limited with Animals;
package Persons is
-- One of the few things you can do with an incomplete type, which
-- is what Animals.Animal is in the limited view of Animals, is to
-- declare an access to it.
type AnimalPOINTER is access Animals.Animal;
type Person is record
Name : Character;
Animalref : AnimalPOINTER;
end record;
end Persons;
limited with Persons;
package Animals is
type PersonPOINTER is access Persons.Person;
type Animal is record
Name : Character;
Ownerref : PersonPOINTER;
end record;
end Animals;
The demo program has the full view of Animals and Persons. This example is pretty clumsy; you may be able to organise things better by adding subprograms to Animals and Persons. Note that the body of Animals can (and must) with Persons; if it needs to use anything in Persons.
with Ada.Text_IO; use Ada.Text_IO;
with Animals;
with Persons;
procedure Animals_And_Persons is
A : Persons.animalPOINTER := new Animals.Animal;
P : Animals.PersonPOINTER := new Persons.Person;
begin
A.all := (Name => 'a', Ownerref => P);
P.all := (Name => 'p', Animalref => A);
Put_Line (P.Name & " owns " & P.Animalref.Name);
Put_Line (A.Name & " is owned by " & A.Ownerref.Name);
end Animals_And_Persons;
which when compiled and run gives
$ ./animals_and_persons
p owns a
a is owned by p
I am having trouble getting dynamic dispatching to work, even with this simple example. I believe the problem is in how i have set up the types and methods, but cannot see where!
with Ada.Text_Io;
procedure Simple is
type Animal_T is abstract tagged null record;
type Cow_T is new Animal_T with record
Dairy : Boolean;
end record;
procedure Go_To_Vet (A : in out Cow_T) is
begin
Ada.Text_Io.Put_Line ("Cow");
end Go_To_Vet;
type Cat_T is new Animal_T with record
Fur : Boolean;
end record;
procedure Go_To_Vet (A : in out Cat_T)
is
begin
Ada.Text_Io.Put_Line ("Cat");
end Go_To_Vet;
A_Cat : Cat_T := (Animal_T with Fur => True);
A_Cow : Cow_T := (Animal_T with Dairy => False);
Aa : Animal_T'Class := A_Cat;
begin
Go_To_Vet (Aa); -- ERROR This doesn't dynamically dispatch!
end Simple;
Two things:
The first is that you have to have an abstract specification of Go_To_Vet, so that delegation can take place (this has caught me a couple times as well :-):
procedure Go_To_Vet (A : in out Animal_T) is abstract;
And the second is that Ada requires the parent definition be in its own package:
package Animal is
type Animal_T is abstract tagged null record;
procedure Go_To_Vet (A : in out Animal_T) is abstract;
end Animal;
The type definitions in your Simple procedure then need to be adjusted accordingly (here I just withed and used the Animal package to keep it simple):
with Ada.Text_Io;
with Animal; use Animal;
procedure Simple is
type Cow_T is new Animal_T with record
Dairy : Boolean;
end record;
procedure Go_To_Vet (A : in out Cow_T) is
begin
Ada.Text_Io.Put_Line ("Cow");
end Go_To_Vet;
type Cat_T is new Animal_T with record
Fur : Boolean;
end record;
procedure Go_To_Vet (A : in out Cat_T)
is
begin
Ada.Text_Io.Put_Line ("Cat");
end Go_To_Vet;
A_Cat : Cat_T := (Animal_T with Fur => True);
A_Cow : Cow_T := (Animal_T with Dairy => False);
Aa : Animal_T'Class := A_Cat;
begin
Go_To_Vet (Aa); -- ERROR This doesn't dynamically dispatch! DOES NOW!! :-)
end Simple;
Compiling:
[17] Marc say: gnatmake -gnat05 simple
gcc -c -gnat05 simple.adb
gcc -c -gnat05 animal.ads
gnatbind -x simple.ali
gnatlink simple.ali
And finally:
[18] Marc say: ./simple
Cat
how to assign A_Cow to Aa ? (Aa := A_Cow; complains!)
You can't and shouldn't. Although they share a common base class, they are two different types. By comparison to Java, an attempt to convert a cat to a cow would cause a ClassCastException at run time. Ada precludes the problem at compile time, much as a Java generic declaration does.
I've expanded #Marc C's example to show how you can invoke base class subprograms. Note the use of prefixed notation in procedure Simple.
Addendum: As you mention class wide programming, I should add a few points related to the example below. In particular, class wide operations, such as Get_Weight and Set_Weight, are not inherited, but the prefixed notation makes them available. Also, these subprograms are rather contrived, as the tagged record components are accessible directly, e.g. Tabby.Weight.
package Animal is
type Animal_T is abstract tagged record
Weight : Integer := 0;
end record;
procedure Go_To_Vet (A : in out Animal_T) is abstract;
function Get_Weight (A : in Animal_T'Class) return Natural;
procedure Set_Weight (A : in out Animal_T'Class; W : in Natural);
end Animal;
package body Animal is
function Get_Weight (A : in Animal_T'Class) return Natural is
begin
return A.Weight;
end Get_Weight;
procedure Set_Weight (A : in out Animal_T'Class; W : in Natural) is
begin
A.Weight := W;
end Set_Weight;
end Animal;
with Ada.Text_IO; use Ada.Text_IO;
with Animal; use Animal;
procedure Simple is
type Cat_T is new Animal_T with record
Fur : Boolean;
end record;
procedure Go_To_Vet (A : in out Cat_T)
is
begin
Ada.Text_Io.Put_Line ("Cat");
end Go_To_Vet;
type Cow_T is new Animal_T with record
Dairy : Boolean;
end record;
procedure Go_To_Vet (A : in out Cow_T) is
begin
Ada.Text_Io.Put_Line ("Cow");
end Go_To_Vet;
A_Cat : Cat_T := (Weight => 5, Fur => True);
A_Cow : Cow_T := (Weight => 200, Dairy => False);
Tabby : Animal_T'Class := A_Cat;
Bossy : Animal_T'Class := A_Cow;
begin
Go_To_Vet (Tabby);
Put_Line (Tabby.Get_Weight'Img);
Go_To_Vet (Bossy);
Put_Line (Bossy.Get_Weight'Img);
-- feed Bossy
Bossy.Set_Weight (210);
Put_Line (Bossy.Get_Weight'Img);
end Simple;