This infinite loop on invalid numeric input has existed since Ada 83. The description of Get is in ARM 10.8(7-10/2): “If the value of the parameter Width is zero, skips any leading blanks, line terminators, or page terminators, then reads a plus sign if present or (for a signed type only) a minus sign if present, then reads the longest possible sequence of characters matching the syntax of a numeric literal without a point.”
In your case that “longest possible sequence of characters” is presumably zero characters.
What’s important is what it doesn’t say: It doesn’t say that it reads any characters after that “longest possible sequence of characters”. Any characters at that point in the input are unread and remain for the next input operation.
What you are saying by adding a Skip_Line is that the program has to process complete lines of input at a time. This is better expressed by actually inputting complete lines and processing them. I use the convention that user input is always read a line at a time (using function Ada.Text_IO.Get_Line), followed by parsing that line.
Then there is the awkward construction
while not End_Program loop
Psychology tells us that negative logic (not End_Program
) is more difficult to understand than positive logic (End_Program
), and the the exit condition of a loop is more intuitive than the continuation condition. Like many while loops, this one uses both negative logic and the continuation condition, maximizing the difficulty of understanding it (admittedly not very bad in this case, but in real cases, often significant). Better would be
loop
exit when End_Program;
My convention is to use a plain loop with exit
when that results in positive logic, and a while loop (while Program_Running loop
) when that uses positive logic.
Finally, doing an Elements of Programming Style-like analysis of the whole loop, it loops until the user input causes a change to End_Program
, at which point it exits. This is a classic “loop and a half”, which are more clear using an exit statement than a Boolean variable, since the use of a variable separates the logic that results in the exit from the exit itself. Simply put, with an exit statement, the reader knows immediately that result of this input is exiting the loop; with the variable, that requires remembering that the variable controls the loop (again, not a big problem in this example, but can be for real cases).
Combining these observations results in
loop
declare
Line : constant String := Ada.Text_IO.Get_Line;
begin
exit when Command_String.To_String (Main_Menu (Integer'Value (Line) ) ) = "quit";
exception
when Constraint_Error => -- Data_Error is no longer possible
Ada.Text_IO.Put_Line (Item => "Invalid command");
end;
end loop;