In Ada, can we give a string less characters than initially specified ?
For example :
Text : String(1..5);
Text := "ada";
Is this code correct? Or are we obliged to give a 5 characters string?
Thank you.
Using the type String, you have to - like with other array types in Ada - fill all the positions in the array.
But there are a number of tricks:
Initialize the String (array) where you declare it, and you don't have to declare the range explicitly:
declare
Text : constant String := "Ada";
begin
...
end;
Select which slice of the String (array) you want to put something in:
declare
subtype Five_Characters is String (1 .. 5);
Text : Five_Characters := (others => ' ');
begin
Text (2 .. 4) := "Ada";
...
end;
Use Ada.Strings.Unbounded:
declare
use Ada.Strings.Unbounded;
Text : Unbounded_String;
begin
Text := To_Unbounded_String ("Ada");
...
end;
The code is not correct, you must either specify 5 characters, e.g.
declare
Text : String(1..5);
begin
Text := "ada ";
end;
or specify the range
declare
Text : String(1..5) := (others => ' '); -- Init to spaces
begin
Text(1..3) := "ada"; -- Text now contains "ada "
end;
or use one of the available string handling packages.
Yes, this is possible with Ada.
You can assign a string of smaller length to a fixed string of given length.
There is a procedure 'Move' defined under the 'Ada.Strings.Fixed' package.
With the help of this Move Procedure you can assign a smaller string to a given fixed string, pad the remaining indexes with spaces and Justify the copied string Left, Center or Right.
with Ada.Text_IO; use ADA.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Command_Line;
with Ada.Strings;
with Ada.Strings.Fixed;
with Ada.Strings.Bounded;
with Ada.Strings.Unbounded;
procedure fixed_string_ops is
str_1 : String(1 .. 5);
begin
Ada.Strings.Fixed.Move (Source => "Ada", Target => str_1, Drop => Ada.Strings.Right, Justify => Ada.Strings.Right, Pad => Ada.Strings.Space);
Put_Line(str_1);
Ada.Strings.Fixed.Move (Source => "Ada", Target => str_1, Drop => Ada.Strings.Right, Justify => Ada.Strings.Center, Pad => Ada.Strings.Space);
Put_Line(str_1);
Ada.Strings.Fixed.Move (Source => "Ada", Target => str_1, Drop => Ada.Strings.Right, Justify => Ada.Strings.Left,Pad => Ada.Strings.Space);
Put_Line(str_1);
end fixed_string_ops;
compile the above code with
> gnatmake fixed_string_ops.adb
And then run
> fixed_string_ops.exe
Ada
Ada
Ada
You can see the padding and Justification.
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;
In this code, I have built a list of three integers (5, 10, 15) and what I need help with is that I need to ask the user which of these elements he/she wants to remove and then only return the element/elements that are left. I need to write a subprogram for this and just by using recursion, I need to remove the elements that the user does not need.
Main program:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Linked_List; use Linked_List;
procedure Main_Remove is
I : Integer;
L : List_Type;
begin
Build_Test_List(L); -- builds a list of 3 integers (5 => 10 => 15)
Put(L);
Put("Which elements do you want to remove/delete ");
Get(I);
Remove(L, I);
Put(L);
end Main_Remove;
Package:
package Linked_List is
type List_Type is private;
procedure Put(Item : in List_Type);
procedure Build_Test_List(Item : out List_Type;
Base : in Integer := 5);
private
type E_Type;
type List_Type is access E_Type;
type E_Type is
record
Data : Integer;
Next : List_Type;
end record;
end Linked_List;
Pakage body:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Unchecked_Deallocation;
package body Linked_List is
procedure Put(Item : in List_Type) is
P : List_Type := Item;
begin
Put("Listan: ");
while P /= null loop
if P /= Item then
Put(" -> ");
end if;
Put(P.Data, Width => 0);
P := P.Next;
end loop;
New_Line;
end Put;
procedure Insert_First(L : in out List_Type;
D : in Integer) is
begin
L := new E_Type'(Data => D, Next => L);
end Insert_First;
procedure Build_Test_List(Item : out List_Type;
Base : in Integer := 5) is
begin
for I in reverse 1..3 loop
Insert_First(Item, Base * I);
end loop;
end Build_Test_List;
end Linked_List;
Something like this will do, with reservations: specifically, there’s a memory leak.
procedure Remove (L : in out List_Type; Item : Integer) is
begin
The recursion has to be stopped when the list is empty.
if L = null then
return;
end if;
The list isn’t empty. What to do next depends on whether the current list element contains the value we’re looking for, or not.
if L.Data = Item then
This item needs to be removed from the list. Do this by altering the original pointer (which came from the list head, or the previous element) to skip over this element, and then process that element.
This is the point at which the memory leak has occurred. Obviously the cell being pointed to by the initial L needs to be freed, but you’re going to have to be careful about the order of operations.
L := L.Next;
Remove (L, Item);
else
The item stays in the list, go on to the next element.
Remove (L.Next, Item);
end if;
end Remove;
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
I am a beginner in Ada programming and I have the following code :
PROCEDURE ask(variable: OUT myType) IS
BEGIN
Put("Enter : ");
Get(variable);
Skip_Line;
EXCEPTION
WHEN OTHERS => RAISE wrongInput;
END ask;
This procedure asks to enter something and put it in my "variable" variable (which type is myType containing characters from '1' to '3').
I raise an exception when the input is not correct.
Now I would like to do something else when the input is 'm' for example.
How could I do this?
Change the declaration of myType:
type myType is ('1', '2', '3', 'm');
This answer is based on the assumption that myType is declared as something like
subtype myType is Character range '1' .. '3';
You can write the output into an unconstraint Character variable, then check it:
PROCEDURE ask (variable : OUT myType) IS
Input : Character;
BEGIN
Put ("Enter : ");
Get (Input);
Skip_Line;
CASE Input IS
WHEN 'm' =>
RAISE gotM;
WHEN OTHERS =>
-- this will raise Constraint_Error if the value is not
-- in range '1' .. '3'
variable := Input;
END CASE;
EXCEPTION
WHEN OTHERS => RAISE wrongInput;
END ask;
I used case instead of a simple if because it sounds like there may be other input values added in the future. Now while this is technically possible, I consider it bad style, because it uses the exception gotM for implementing non-exceptional behavior. You should re-think your code layout so that you don't have a procedure ask that can only return '1' .. '3' but needs to handle other input, too.
Something like this, perhaps? (just guessing here, "I would like to do something else" is extremely vague).
procedure Ask (Variable : out My_Type) is
begin
loop
begin
Put ("Enter :");
Get (Variable);
Skip_Line;
return;
exception
when others =>
Skip_Line;
Put_Line ("invalid.");
end;
end loop;
end Ask;