Late evaluation of function argument to formatted_string - ada

Let's consider this MWE:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with GNAT.Strings; use GNAT.Strings;
with GNAT.Command_Line; use GNAT.Command_Line;
procedure Verbose_And_Image is
type T_Foo is tagged record
Member_1 : Natural := 13;
Member_2 : Float := 1.414;
Member_3 : Boolean := False;
Member_4 : Unbounded_String := To_Unbounded_String ("Foo");
Member_5 : Natural := 42;
end record;
function Image (Foo : in T_Foo) return String is
Message : Unbounded_String :=
"M1: " & Foo.Member_1'Image & " " &
"M2: " & Foo.Member_2'Image & " " &
"M3: " & Foo.Member_3'Image & " " &
"M4: " & Foo.Member_4 & " " &
"M5: " & Foo.Member_5'Image;
begin
return To_String(Message);
end Image;
Global_Foo : T_Foo;
Config : Command_Line_Configuration;
Verbose : aliased Boolean := False;
N_Elem : aliased Integer := 0;
procedure Debug (Message : in String) is
begin
if Verbose then
Put_Line (Message);
else
null; -- the program is quiet.
end if;
end Debug;
procedure Bar (I : in Integer; Foo : in out T_Foo) is
begin
Foo.Member_5 := I;
Debug ("I is: " & Image (Foo));
end Bar;
begin
Define_Switch (Config, Verbose'Access, "-v", Help => "Verbose");
Define_Switch (Config, N_Elem'Access, "-n:", Help => "Number of tries");
Getopt (Config);
Put_Line ("File argument was " & Verbose'Image);
for I in 1..N_Elem loop
Bar (I, Global_Foo);
end loop;
end Verbose_And_Image;
Compiling this example with gnatmake, we may "profile" our program with:
gnatmake -O3 verbose_and_image.adb
gcc -c -O3 verbose_and_image.adb
gnatbind -x verbose_and_image.ali
gnatlink verbose_and_image.ali -O3
$time ./verbose_and_image -n 19999999 > /dev/null # In "quiet"
real 0m8.282s
user 0m7.946s
sys 0m0.005s
$time ./verbose_and_image -v -n 19999999 > /dev/null # In verbose
real 0m19.481s
user 0m11.946s
sys 0m6.756s
In many cases, to "optimize" the code, some developers wrote everywhere:
procedure Bar (I : in Integer; Foo : in out T_Foo) is
begin
Foo.Member_5 := I;
if Verbose then
Debug ("I is: " & Image (Foo));
end if;
end Bar;
Which is indeed order of magnitude faster (though my example is not really precise):
$time ./verbose_and_image -v -n 19999999 > /dev/null
real 0m19.585s
user 0m12.146s
sys 0m6.655s
$time ./verbose_and_image -n 19999999 > /dev/null
real 0m0.027s
user 0m0.023s
sys 0m0.003s
Writing a dedicated Debug (Message : in String; Foo : in T_Foo) function leads to comparable performances.
My concern with this later form is that the thousands of if Level make the code horribly difficult to read (increasing the cyclomatic complexity) and most of them do not even make sense (no image evaluation inside).
It is also not acceptable to write those dedicated functions each time you want to print a message (plus the debugging is handled by a dedicated package).
Is there a way to offer a procedure based on Formatted_String (for instance) to allow a late evaluation of these Images when needed. Something like:
procedure Debug(format: in out Formatted_String, ...) is
begin
if Verbose then
for argument of arguments loop
format := format & argument'Image;
end loop;
Put_Line (-format);
end if;
end Debug;
So that, at any point, one may do:
Debug ( +"Debug %s", Foo);
Delaying the evaluation of the images to the Debug function and without the need to declare functions any time you want to print some debug message.

I would recommend against using "formatting strings", if possible.
They're too easy to screw-up and the compiler generally can't confirm that you're not making some mistake the way it can with Text_IO.Put_Line( "First-part " & Image(Object) & " second part.") or Text_IO.Put("First-part ")/Enumeration_IO.put(Object)/Text_IO.Put_Line(" second part.").
The way to handle the need for variable length inputs is typically via unconstrained arrays or Ada containers (esp vectors).

Related

Using multidimensional array in Ada

In this code, I need help writing a multidimensional array with a range between 2020-01-01 to 2119-12-31.
My code works but as you see there are no arrays in it. How can I write this code with only arrays?
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
Procedure Date is
type date_type is record
Str : string (1..8);
Length : Natural := 0; end record;
A: date_type;
begin
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Get_Line (A.Str, A.Length);
exit when A.Length = 8;
Put_Line ("Wrong input. Try again.");
end loop;
Put_Line (A.Str (1 .. 4) & "-" & A.Str (5 .. 6) & "-" & A.Str (7 .. 8));
end Date;
Perhaps, rather than a multi-dimensional array you should consider using a record such as
type Year_Number is range 1900..3000;
type Month_Number is range 1..12;
type Day_Number is range 1..31;
type Date_Rec is record
Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
end record;
subtype Year_String is string (1..4);
subtype Month_String is string (1..2);
subtype Day_String is string (1..2);
function To_Date (Yr : Year_String; Mnth : Month_String; Dy : Day_String)
return Date_Rec is
Result : Date_Rec;
begin
Result.Year := Year_Number'Value (Yr);
Result.Month := Month_Number'Value (Mnth);
Result.Day := Day_Number'Value (Dy);
return Result;
end To_Date;
You can now pass around instances of Date_
Rec doing whatever you want with the date.
If you go this far then you might want to consider using the Time type described in Ada Language Reference Manual sections 9.6 and 9.6.1.
You haven't asked a reasonable question here because "I want to use arrays" is not a good reason to use an array.
"This problem can be best solved with an array ... but how do I deal with ... ?" would be a reasonable question, but you haven't stated a problem,let alone one that needs an array.
This is important because "using an array" is thinking in the solution domain, like "using a chisel". It's not the way to think about programming in Ada, (or IMO in any language).
Try thinking in the problem domain first : instead of "I want to use a chisel", I think "I want to recess this hinge so the door fits precisely" and a chisel is the neatest way of doing the job.
Then "How can I best validate a date?" would be one reasonable question, or "how can I store events that happen on each day for 100 years?"
The answer to the first question is probably in the Ada.Calendar package. Possibly the Value function in Ada.Calendar.Formatting, with an exception handler to catch incomprehensible strings and make the user try again.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar;
with Ada.Calendar.Formatting;
procedure date is
Date : Ada.Calendar.Time;
Done : Boolean;
begin
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Done := TRUE;
declare
A : String := Get_Line & " 12:00:00";
begin
Date := Ada.Calendar.Formatting.Value(A); -- validate it's a date
Done := Ada.Calendar.Year(Date) >= 2020
and Ada.Calendar.Year(Date) < 2120; -- validate correct range
exception
when Constraint_Error => Done := False; -- Formatting.Value failed
end;
exit when Done;
Put("Try Again : ");
end loop;
end date;
The answer to the second is probably a 1-dimensional array indexed by Day_Count from Ada.Calendar.Arithmetic but let's use the wrong tool : a 3D array indexed by your range of years, Month_Number and Day_Number from the Ada.Calendar base package.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
with Ada.Calendar.Formatting;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
procedure date is
Date : Ada.Calendar.Time;
Done : Boolean := TRUE;
Event_Array : array(2020 .. 2119,
Ada.Calendar.Month_Number,
Ada.Calendar.Day_Number) of Unbounded_String;
begin
Event_Array := (others => (others => (others => Null_Unbounded_String)));
Event_Array(2020,11,3) := To_Unbounded_String("nothing much");
loop
Put ("Enter a date between 2020-01-01 to 2119-12-31 : ");
Done := TRUE;
declare
A : String := Get_Line & " 12:00:00";
begin
Date := Ada.Calendar.Formatting.Value(A);
Done := Ada.Calendar.Year(Date) >= 2020
and Ada.Calendar.Year(Date) < 2120;
exception
when Constraint_Error => Done := False;
end;
exit when Done;
Put("Try Again : ");
end loop;
Put_Line("Today " & Ada.Calendar.Formatting.Image(Date) & " : " &
To_String(Event_Array(Year(Date), Month(Date), Day(Date))) & " happened");
end date;
Test with the string 2020-11-03

Ada error: invalid use of subtype mark in expression or call

I'm stuck here with an error in my Ada program. There is a lot of code and I don't want to copy all of it here, so I hope that the part that I'm sharing is the part from where the problem comes.
task type Producent is
entry Start(Jedzenie: in Typ_Jedzenia; Czas_Produkcji: in Integer);
end Producent;
task type Buffer is
entry Zamow(Jedzenie: in Typ_Jedzenia; Numer: in Integer; Czy_Zatwierdzono: out Boolean);
entry Dostarcz(Zamowienie: in Typ_Zestawu; Numer: out Integer);
end Buffer;
task body Producent is
package Losowa_Produkcja is new
Ada.Numerics.Discrete_Random(Zakres_Czasu_Produkcji);
Generator: Losowa_Produkcja.Generator;
Index_Jedzenia: Integer;
Nr_Produkcji_Jedzenia: Integer := 1;
Produkcja: Integer;
Zatwierdzono: Boolean := False;
begin
accept Start (Jedzenie : in Typ_Jedzenia; Czas_Produkcji : in Integer) do
Losowa_Produkcja.Reset(Generator);
Index_Jedzenia := Jedzenie;
Produkcja := Czas_Produkcji;
end Start;
loop
delay Duration(Losowa_Produkcja.Random(Generator));
Put_Line("Przygotowano " & Nazwa_Jedzenia(Index_Jedzenia) & " numer " & Integer'Image(Nr_Produkcji_Jedzenia));
loop
Buffer.Zamow(Index_Jedzenia, Nr_Produkcji_Jedzenia, Zatwierdzono); <-------- ERROR
if Zatwierdzono = False then
Put_Line("Brak miejsca w kuchni dla " & Nazwa_Jedzenia(Index_Jedzenia) & ". Wstrzymanie");
delay Duration(3.0);
else
Nr_Produkcji_Jedzenia := Nr_Produkcji_Jedzenia + 1;
end if;
exit;
end loop;
end loop;
end Producent;
task body Buffer is
begin
Put_Line("Jestesmy u Buffera");
loop
select
accept Zamow(Jedzenie: in Typ_Jedzenia; Numer: in Integer; Czy_Zatwierdzono: out Boolean) do
Put_Line("Trwa zamawianie...");
end Zamow;
end select;
end loop;
end Buffer;
From my attempts I understand that when I want to call entry Buffer.Zamow(Index_Jedzenia, Nr_Produkcji_Jedzenia, Zatwierdzono); (which is in task Producent) there is an error with 'Zatwierdzono' argument. When I removed this argument from declarations and definitions Zamow() entry worked.
Full error: invalid use of subtype mark in expression or call
What should I change or where is the problem with this boolean Zatwierdzono variable?
Zatwierdzono means Accepted in this case.
Thanks for any ideas.
You have two problems:
Index_Jedzenia := Jedzenie;
In your Start entry is trying to implicitly convert Jedzenie from its type, Typ_Jedzenia, to Integer, the type of Index_Jedzenia. You need some way to convert this.
Additionally on the line you are seeing the error on, the first parameter of that entry is of type Typ_Jedzenia but you are passing in an Integer (Index_Jedzenia is an integer). Again, you can't implicitly convert types like that.
If Typ_Jedzenia is actually an integer, you can explicitly convert them. Otherwise you need to make a conversion function of some type and use that before passing in or assigning to different types.

Ada - How do you read an array from a single line of input?

My question is pretty simple, I have input that looks like this...
0 0 0 1 1 1 -1 -1 -1 1
And I need to store these values into an array but I can't figure it out. This is what I have so far...
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type arr is array(1..10) of Integer;
Data : arr;
begin
for I in 1..arr'Length loop
Data(I) := Integer'Value(Get_Line);
end loop;
end Main;
I know this wrong and it's pretty obvious why this isn't working. I'm trying to store multiple values into a single integer, I need a way to iterate over the input or load all the values at once. How would you do this in Ada?
You can use Get_Line to get the whole line as a string and then Ada.Integer_Text_IO to parse the string:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Hello is
Line : String := Get_Line;
Value : Integer;
Last : Positive := 1;
begin
while Last < Line'Last loop
Get(Line(Last..Line'Last),Value,Last);
Put_Line(Value'Image); -- Save the value to an array here instead
Last := Last + 1; -- Needed to move to the next part of the string
end loop;
end Hello;
After that, you can load the values into an array in the loop or however you like.
Example output:
$gnatmake -o hello *.adb
gcc -c hello.adb
gnatbind -x hello.ali
gnatlink hello.ali -o hello
$hello
0
0
0
1
1
1
-1
-1
-1
1
EDIT: Adding a recursive option that is more general. This will read a line from STDIN and recursively concatenate the values into an array. It uses the secondary stack to do so in GNAT.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_Io;
procedure Hello is
-- Need an array return type
type Integer_Array is array (Positive range <>) of Integer;
-- Recursive function
function Get_Ints return Integer_Array is
Value : Integer;
begin
-- Read from STDIN using Integer_Text_IO;
Get(Value);
-- Concatinate recursively
return Integer_Array'(1 => Value) & Get_Ints;
exception
-- I found different exceptions with different versions
-- of GNAT, so using "others" to cover all versions
when others =>
-- Using Ada2012 syntax here. If not using Ada2012
-- then just declare the Empty variable somewhere
-- and then return it here
return Empty : Integer_Array(1..0);
end Get_Ints;
Result : Integer_Array := Get_Ints;
begin
Put_Line("Hello, world!");
Put_Line(Integer'Image(Result'Length));
for E of Result loop
Put(Integer'Image(E) & " ");
end loop;
end Hello;
If you know that you have 10 elements to read, it can be done a little more simply like this:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Hello is
A: array (1..10) of Integer;
begin
for V of A loop
Get(V);
end loop;
for I in A'Range loop
Put(I, 5);
Put(": ");
Put(A(I), 5);
New_Line;
end loop;
end Hello;
If you don't actually know how many elements to read in advance, please update the question.
Even though this already was answered, I'd like to add a couple improvements to Jere's answer.
It is more Ada-like to terminate the recursion using End_Of_File rather than an exception. Plus, it makes the program clearer.
Also, using tail-call recursion instead of normal recursion allows the compiler to perform some optimization.
function Get_Ints(input : in File_Type) return Integer_Array is
function Get_Ints_Rec(accumulator : in Integer_Array) return Integer_Array is
value : Integer;
begin
if End_Of_File(input) then
return accumulator;
else
begin
Get(input, value);
exception
when Data_Error => -- problem when reading
if not End_Of_Line(input) then
Skip_Line(input);
end if;
return Get_Ints_Rec(acc);
end;
return Get_Ints_Rec(accumulator & (1 => value));
end if;
end Get_Ints_Rec;
acc : constant Integer_Array(1 .. 0) := (others => 0);
begin
return Get_Ints_Rec(acc);
end Get_Ints;

Ada, raised CONSTRAINT_ERROR : bad input for 'Value: "well."

I am unable to get the following script to return my input value; I've looked up ARM as well as John Barnes book but to no avail. In theory it should work.
Anyone know why? I'm a newby so the Barnes book and the ARM are probably too advanced for me.
with Ada.Text_IO;
use Ada.Text_IO;
procedure ron is
A : Character;
begin
Put_Line ("Hi Ron, how are you?");
A := Character'Value (Get_Line);
Put_Line ("So you feel" &
Character'Image (A));
end ron;
--TERMINAL OUTPUT
--ronhans#amante ~/Desktop $ gnatmake -gnat2012 ron.adb
--gcc-4.8 -c -gnat2012 ron.adb
--gnatbind -x ron.ali
--gnatlink ron.ali
--ronhans#amante ~/Desktop $ ./ron
--Hi Ron, how are you?
--well.
--raised CONSTRAINT_ERROR : bad input for 'Value: "well."
If you look in the LRM, you will see that Ada.Text_IO.Get_Line returns a String:
with Ada.Text_IO;
procedure Ron is
begin
Ada.Text_IO.Put_Line ("Hi Ron, how are you?");
declare
Reply : constant String := Ada.Text_IO.Get_Line;
begin
Ada.Text_IO.Put_Line ("So you feel " & Reply & "?");
end;
end Ron;
The problem with you program is that you try to put an array of characters into a single character. Instead of of using A : Character, try to define an array type something like
type Character_Array_T (1 .. 10) of Character;
...
A : Character_Array_T;
or use
with Ada.Strings.Unbounded;
...
A : Ada.Strings.Unbounded.Unbounded_String;
I suggest the use of an unbounded string, so that the input is not bounded to some specific string length, if your intention is to read out an input several times. Ada type string requires you to specify the string length and this length is exactly the number of characters this string should contain.
See Wiki, unbounded strings and Unbounded string handling for the reference.

Function reading from standard input without any "in" parameters

Perhaps this is simple, and I am just missing some basic information, but I can't seem to find the answer anywhere.
I'm writing a Get_Word function for class, here is the relevant section of the spec file my prof wrote:
function Get_Word return Ustring;
-- return a space-separated word from standard input
procedure Fill_Word_List(Wl : in out Ustring_Vector);
-- read a text file from standard in and add all
-- space-separated words to the word list wl
I've written the Get_Word function, and am trying to test it out with this code:
with Ada.Text_IO; use Ada.Text_Io;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure ngramtest is
Name : String(1..80);
File : File_Type;
Size : Natural;
function Get_Word return String is
-- I'm using a strings instead of Unbounded_Strings for testing purposes.
Word : String(1..80) := (others => ' ');
Char : Character;
File : File_Type;
Eol : Boolean;
I : Integer := 1;
begin
--this code below, when uncommented reveals whether or not the file is open.
--if Is_Open(File) then
-- Word := (1..80 => 'y');
--else
-- Word := (1..80 => 'n');
--end if;
loop
Look_Ahead(File, Char, Eol);
if Eol then
exit;
elsif Char = ' ' then
exit;
else
Get (File, Char);
Word(I) := Char;
I := I + 1;
end if;
end loop;
return Word(1..Word'Last);
end Get_Word;
begin
Put ("Enter filename: ");
Get_Line (Name, Size);
Open (File, Mode => In_File, Name => Name(1..Size));
Put (Get_Word);
Close(File);
end ngramtest;
It compiles, but at runtime I get an exception telling me that the file isn't open, and the commented out section returns "nnnnnn..." meaning that the file is not open within the function.
My question is how am I to read from standard input if i'm not allowed to use in parameters in my function? Without them the function won't be able to access files.
Essentially, how can I "Get_Word"?
Sorry if this is simple, but I'm completely lost.
You need to set your "File" variable to standard input:
File : File_Type := Ada.Text_IO.Standard_Input;

Resources