I am stumped. The stripped-down example contains a package with a function “get_strings” that reads the lines of “readme.txt” into an array of bounded strings. The “run_test” will compile and run without “Put_Line(To_String…)”. But with that line we get compile error shown in comments in run_test.adb below.
q_pkg.ads contents:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Bounded;
package Q_pkg is
package B_str is new Ada.Strings.Bounded.Generic_Bounded_Length (Max => 100); use B_Str;
type B_array is array (Natural range <>) of B_str.Bounded_String;
function get_strings return B_array;
end Q_pkg;
q_pkg.adb contents:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Bounded;
package body Q_pkg is
function get_strings return B_Array is
Dummy : B_str.Bounded_String;
N_lines : Integer := 0;
F_In : File_Type;
Input_File_Name : constant String := "readme.txt";
begin
Open (F_In, In_File, "readme.txt");
while not End_Of_File (F_In) loop
Dummy := To_Bounded_String(Get_Line(F_In));
N_Lines := N_Lines+1;
end loop;
Reset (F_In);
declare
A1 : B_array (1 .. N_Lines);
begin
for I in A1'Range loop
A1(I) := To_Bounded_String(Get_Line(F_In));
end loop;
return A1;
end;
end get_strings;
end Q_pkg;
and run_test.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Bounded;
with Q_pkg; use Q_pkg;
procedure run_test is
begin
declare
A : B_Array := get_strings;
begin
for I in A'Range loop
Put_Line(Integer'Image(I));
-- Put_Line(To_String(A(I)));
-- uncommenting line above gives:
-- run_test.adb:16:16: error: "To_String" is not visible
-- run_test.adb:16:16: error: non-visible declaration from "b_str" at a-strbou.ads:130, instance at q_pkg.ads:7
end loop;
end;
end run_test;
readme.txt:
1
2
3
I must be missing something about bounded strings, etc. Can anybody see the issue?
To_String is declared in an instance of Ada.Strings.Bounded, which is Q_Pkg.B_Str. Thus its fully qualified name is Q_Pkg.B_Str.To_String. Add use B_Str to make it visible.
function To_String (Source : B_Str.Bounded_String) return String
renames B_Str.To_String;
You should read files once because there exist files that do not allow repositioning.
Instead of testing for file end simply read it in an infinite loop and catch Use End_Error exception. The patter is:
begin
loop
... -- read file
end loop;
exception
when End_Error =>
null;
end;
Note that you selected bounded string which means that it is likely be allocated out of the pool. Therefore you will get all on the stack and therefore you should expect stack overflow coming quite quickly. If not, you can write a function that utilize stack making recursive calls to get array in chunks. No extra pass of lines counting is needed.
The general rule is that bounded strings are basically useless. You either go full pool e.g. doing Unbounded_String + Vector. Or else you use an arena to allocate strings there if you want stay with stack keeping it as tight as possible. Arena can sit in a pool too, but allocated just once.
There is also middle way like having an array of access to string automatically freeing strings. It has the advantage that strings will be accessible directly like in case of arena. Unbounded_String is quite clumsy and inefficient to access.
Direct_IO has the function Size for the purpose. Sequential_IO faces same problem as Text_IO. An implementation of End_Of_File requires read-ahead and buffering. Which precludes in-place I/O semantics (OK, UNIX never cared of) and causes unnecessary blocking when working with pipes, terminals, network.
P.S. Thanks to AI (sorry, SI ) hype memory becomes an issue again. Good chance for Ada and solid engineering.
End_of_File works fine for files written with Ada.Text_IO because closing a file (also implicitly) adds an end marker (RM A.10.2(3)) which then is read by look-ahead (note: Ada is well defined). Files written by other means might not do this, so End_of_File is doomed to fail. End_Error always works.
with Ada.Text_IO;
procedure EOF_Broken is
Name : constant String := "eof_broken.txt";
File : Ada.Text_IO.File_Type;
Num : Natural := 0;
begin -- EOF_Broken
Ada.Text_IO.Create (File => File, Name => Name);
Ada.Text_IO.Put_Line (File => File, Item => "Line 1");
Ada.Text_IO.Put_Line (File => File, Item => "Line 2");
Ada.Text_IO.New_Line (File => File); -- Line 3
Ada.Text_IO.Close (File => File);
Ada.Text_IO.Open (File => File, Mode => Ada.Text_IO.In_File, Name => Name);
Count : loop
exit Count when Ada.Text_IO.End_Of_File (File);
Ada.Text_IO.skip_line (File => File);
Num := Num + 1;
end loop Count;
Ada.Text_IO.Close (File => File);
Ada.Text_IO.Put_Line (Item => Num'Image);
end EOF_Broken;
This creates a file with 3 lines, then counts and outputs the number of lines in the file using End_Of_File. It outputs 2 because End_Of_File is broken and returns True after the second line is read.
This behavior is required by the ARM, which means that if reading all the lines in a text file is required, End_Of_File should not be used.