3  File Input/Output

Simple file handling is the focus of this chapter. Reading existing files is at the core of many applications and is illustrated with several examples. All the output in this chapter is to the default standard output postponing creation of files to a later chapter.

Code organization is a key to building reliable applications. With this in mind, key ideas of this chapter are organized as a reusable toolkit. The applications built are then a rather thin layer that use the library. The library itself will be grown over the entire book.

3.1 Learning Objectives

  • Command Line processing

  • Reading text files

  • Simple string search

  • Binary File Reading

  • Simple Transformations of files

  • Code library to enable reuse

3.2 Projectlet - fileio

3.2.1 Sample Runs

This projectlet will leverage the command line to combine diverse functions into one executable.

~/bin/fileio
fileio
usage: fileio [lc|ls|find] file1 file2 ...

Line Count of a set of files

~/bin/fileio lc ../Prj/fileio/fileio.gpr ../Prj/fileio/src/fileio.adb
fileio
fileio.linecount
File ../Prj/fileio/fileio.gpr has  22 lines
File ../Prj/fileio/src/fileio.adb has  146 lines

List a small file.

~/bin/fileio list ../Prj/fileio/fileio.gpr
fileio
fileio.list
*****../Prj/fileio/fileio.gpr
 1      : with "config/fileio_config.gpr";
 2      : project Fileio is
 3      : 
 4      :    for Source_Dirs use ("src/", "config/");
 5      :    for Object_Dir use "obj/" & Fileio_Config.Build_Profile;
 6      :    for Create_Missing_Dirs use "True";
 7      :    for Exec_Dir use "../bin";
 8      :    for Main use ("fileio.adb");
 9      : 
 10     :    package Compiler is
 11     :       for Default_Switches ("Ada") use Fileio_Config.Ada_Compiler_Switches;
 12     :    end Compiler;
 13     : 
 14     :    package Binder is
 15     :       for Switches ("Ada") use ("-Es"); --  Symbolic traceback
 16     :    end Binder;
 17     : 
 18     :    package Install is
 19     :       for Artifacts (".") use ("share");
 20     :    end Install;
 21     : 
 22     : end Fileio;
***** End of File ******
~/bin/fileio find package ../Prj/fileio/fileio.gpr
fileio
fileio.search
*****../Prj/fileio/fileio.gpr
 10     :    package Compiler is
 14     :    package Binder is
 18     :    package Install is
***** Found  3 instances

3.2.2 Code Fragments

Predefined Language Environment

~/bin/codemd ../Prj/fileio/src/fileio.adb -x Library -l
0002 | with Ada.Text_IO;      use Ada.Text_IO;
0003 | with Ada.Command_Line; use Ada.Command_Line;
0004 | with Ada.Strings.Fixed;
0005 | 
0006 | with GNAT.Source_Info; use GNAT.Source_Info;

Command Line Processing

~/bin/codemd ../Prj/fileio/src/fileio.adb -x Command -l
0127 |    if Argument_Count >= 1 then
0128 |       declare
0129 |          operation : constant String := Argument (1);
0130 |       begin
0131 |          if operation = "lc" or operation = "linecount" then
0132 |             linecount;
0133 |          elsif operation = "ls" or operation = "list" then
0134 |             list;
0135 |          elsif operation = "find" or operation = "search" then
0136 |             search;
0137 |          else
0138 |             Put (operation);
0139 |             Put_Line (" is not a supported command");
0140 |          end if;
0141 |       end;

Line Oriented text file input

~/bin/codemd ../Prj/fileio/src/fileio.adb -x ReadLines -l
0092 |          while not End_Of_File (txtfile) loop
0093 |             Get_Line (txtfile, line, linelength);
0094 |             line_number := line_number + 1;
0095 |             argpos := Strings.Fixed.Index (line (1 .. linelength), arg, 1);
0096 |             if argpos > 0 then
0097 |                count := count + 1;
0098 |                Put (line_number'Image);
0099 |                Set_Col (8);
0100 |                Put (" : ");
0101 |                Put_Line (line (1 .. linelength));
0102 |             end if;
0103 |          end loop;

3.2.3 Sample Implementation

A sample implementation of the projectlets is available from:

https://gitlab.com/RajaSrinivasan/TechAdaBook.git
Directory: Prj/fileio

3.3 Projectlet - dump

This projectlet reads files as a binary stream without regard to any line breaks. The data is dump’ed in hexadecimal format.

3.3.1 Sample Runs

In the first example, the command line argument is dumped in hexadecimal form. The output is in 3 columns - the byte offset from the beginning of the string, a printout in ASCII followed by hexadecimal representation.

~/bin/dump "An arbitrary string to dump in hex"
      16#0# * An.arbitrary.str             * 416e2061726269747261727920737472 *
     16#10# * ing.to.dump.in.h             * 696e6720746f2064756d7020696e2068 *
     16#20# * ex                           * 6578                             *

A variation is to print out the contents of files.

~/bin/dumpf ~/profile.sh
Dump of /Users/rajasrinivasan/profile.sh Size :  283
      16#0# * ...bin.bash..exp             * 23212f62696e2f626173680a23657870 *
     16#10# * ort.PATH..HOME.t             * 6f727420504154483d24484f4d452f74 *
     16#20# * ools.bin.gnat.na             * 6f6f6c732f62696e2f676e61745f6e61 *
     16#30# * tive.11.2.3.f008             * 746976655f31312e322e335f66303038 *
     16#40# * a8a7.bin..HOME.t             * 613861372f62696e3a24484f4d452f74 *
     16#50# * ools.bin.gprbuil             * 6f6f6c732f62696e2f6770726275696c *
     16#60# * d.22.0.1.b1220e2             * 645f32322e302e315f62313232306532 *
     16#70# * b.bin..PATH.expo             * 622f62696e3a24504154480a6578706f *
     16#80# * rt.PATH..PATH..H             * 727420504154483d24504154483a2448 *
     16#90# * OME.bin..HOME.to             * 4f4d452f62696e3a24484f4d452f746f *
     16#A0# * ols.bin.export.G             * 6f6c732f62696e0a6578706f72742047 *
     16#B0# * PR.PROJECT.PATH.             * 50525f50524f4a4543545f504154483d *
     16#C0# * .GPR.PROJECT.PAT             * 244750525f50524f4a4543545f504154 *
     16#D0# * H..HOME.Prj.talk             * 483a24484f4d452f50726a2f74616c6b *
     16#E0# * .logging...Flutt             * 2f6c6f6767696e670a2320466c757474 *
     16#F0# * er.dev.export.PA             * 6572206465760a6578706f7274205041 *
    16#100# * TH..PATH..HOME.f             * 54483d24504154483a24484f4d452f66 *
    16#110# * lutter.bin.                  * 6c75747465722f62696e0a           *

3.3.2 Code Fragments

Considering that hexadecimal printouts are an important diagnostic tool for other applications, this is an opportunity to create a support library; finally wrapping the support into a cli tool for illustrative purposes.

The toolkit defines a set of packages hex and hex.dump. Command line tool dump to convert a string to hexadecimal and dumpf to dump an entire file are created. The applications themselves are almost trivial:

~/bin/codemd ~/Prj/GitLab/toolkit/examples/dump/src/dumpf.adb -x DumpFile -l
0002 | with Ada.Command_Line; use Ada.Command_Line ;
0003 | with hex.dump ;
0004 | procedure Dumpf is
0005 | begin
0006 |    hex.dump.Dump( Argument(1) );
0007 | end Dumpf ;

The core feature of this projectlet is the conversion of binary data block to a hexadecimal form. Basic building block:

~/bin/codemd ~/Prj/GitLab/toolkit/adalib/src/hex.adb -x BinHex -l
0090 |    function Image (bin : Interfaces.Unsigned_8) return Hexstring is
0091 |       use Interfaces;
0092 |       img     : Hexstring;
0093 |       Lnibble : constant Interfaces.Unsigned_8 := bin and 16#0f#;
0094 |       Hnibble : constant Interfaces.Unsigned_8 :=
0095 |         Interfaces.Shift_Right (bin and 16#f0#, 4);
0096 |    begin
0097 |       img (1) := Nibble_Hex (Integer (Hnibble) + 1);
0098 |       img (2) := Nibble_Hex (Integer (Lnibble) + 1);
0099 |       return img;
0100 |    end Image;

And the reverse - a hexadecimal string to a binary.

~/bin/codemd ~/Prj/GitLab/toolkit/adalib/src/hex.adb -x StrBin -l
0029 |    function Value (Hex : Hexstring) return Interfaces.Unsigned_8 is
0030 |       use Interfaces;
0031 |       Vhigh, Vlow : Interfaces.Unsigned_8;
0032 |    begin
0033 |       Vhigh := Value (Hex (1));
0034 |       Vlow  := Value (Hex (2));
0035 |       return Vhigh * 16 + Vlow;
0036 |    end Value;

These fragments can be integrated into a conversion of a block:

~/bin/codemd ~/Prj/GitLab/toolkit/adalib/src/hex.adb -x BlockHex -l
0130 |    function Image (binptr : System.Address; Length : Integer) return String is
0131 |       -- use Interfaces;
0132 |       img   : String (1 .. 2 * Length);
0133 |       bytes : array (1 .. Length) of Interfaces.Unsigned_8;
0134 |       for bytes'Address use binptr;
0135 |       hexstr : Hexstring;
0136 |    begin
0137 |       for binptr in 1 .. Length loop
0138 |          hexstr := Image (bytes (binptr));
0139 |          img (2 * (binptr - 1) + 1 .. 2 * (binptr - 1) + 2) := hexstr;
0140 |       end loop;
0141 |       return img;
0142 |    end Image;

And the reverse:

~/bin/codemd ~/Prj/GitLab/toolkit/adalib/src/hex.adb -x StrBlock -l
0075 |    function Value (Hex : String) return System.Storage_Elements.Storage_Array
0076 |    is
0077 |       use System.Storage_Elements;
0078 |       result : System.Storage_Elements.Storage_Array (1 .. Hex'Length / 2);
0079 |    begin
0080 |       for rptr in result'Range loop
0081 |          result (rptr) :=
0082 |            Value (Hex (2 * Integer (rptr) - 1 .. 2 * Integer (rptr)));
0083 |       end loop;
0084 |       --Put_Line (Hex);
0085 |       return result;
0086 |    end Value;

A file is then read as a block of binary data and converted into hex strings:

~/bin/codemd ~/Prj/GitLab/toolkit/adalib/src/hex-dump.adb -x DumpFile -l
0113 |       Ada.Streams.Stream_IO.Open
0114 |         (file, Ada.Streams.Stream_IO.In_File, filename);
0115 |       stream := Ada.Streams.Stream_IO.Stream (file);
0116 |       declare
0117 |          buffer    :
0118 |            Ada.Streams.Stream_Element_Array
0119 |              (1 .. Ada.Streams.Stream_Element_Offset (filesize));
0120 |          bufferlen : Ada.Streams.Stream_Element_Offset;
0121 |       begin
0122 |          stream.Read (buffer, bufferlen);
0123 |          if bufferlen > 0 then
0124 |             Hex.dump.Dump
0125 |               (buffer'Address, Integer (bufferlen), bare => bare,
0126 |                show_offset => show_offset, Blocklen => Blocklen,
0127 |                Outfile                                   => Outfile);
0128 |          end if;
0129 |       end;

3.4 Stretch

  1. Resource Packer: the goal is to enable executables to include resources such as graphics files or other files as resources instead of external files. A word puzzle application for example can then use the resource at runtime instead of having to be given a file to load. The output expected is a source file in a suitable language such as Ada that can be compiled as a part of the application. Reference Implementation

3.5 Sample Implementation

Repository: toolkit
Directory: examples/dump