[comp.sys.atari.st] OSS Personal Pascal tutorial #3

DAVIDLI@SIMVAX.BITNET (Dave Meile) (12/08/87)

This is another OSS Personal Pascal tutorial.  You can put it up on BBS
systems, on user disks, etc.  I just don't want to see them come out in
a book (without my name on it... :-)  )

Length c420 lines:
----------------------------------CUT HERE-------------------------------------


                                  Now What?
                OSS Personal Pascal and the beginner - part 3
                            written by David Meile

     [Copyright 1987 David Meile, all rights reserved.  Permission is
     given to Atari user groups to reprint this article, as long as
     this statement is included.  OSS Personal Pascal is a product of
     Optimized Systems Software, Inc.  I am not related to the company,
     I simply bought their compiler.]


                              Looking at files

        One of the most useful things about a computer is that  it
     can store and retrieve information on a more-or-less permanent
     basis.  Without this ability, you could not be reading this
     article in the first place ... I'm typing at my computer, I'll
     then STORE what I've typed onto a disk file, then I'll transfer
     the disk file electronically, where it will once more be stored
     as a disk file (on a somewhat more powerful computer, mind you,
     but the principle remains the same!).
        So, how do I get OSS Personal Pascal to store things?  And,
     more important, how can I retrieve what I've stored?  There are a
     small number of Pascal routines that will read and write things
     onto a disk drive.

                        A simple text file copy program
                        -------------------------------

     PROGRAM filexfer;

     { Take lines from an input file and place them in an output file }
     { Works on TEXT files with 80 characters or less per line        }

       CONST
         {$I gemconst.pas }

       TYPE
         {$I gemtype.pas }

       VAR
         ifile       :       file of TEXT;
         ofile       :       file of TEXT;
         istring     :       string;
         ipath       :       Path_Name;
         iname       :       Path_Name;
         oname       :       Path_Name;

       {$I gemsubs.pas }

     BEGIN
       IF Init_Gem >= 0 then
         BEGIN
           ipath := 'A:\*.*';
           iname := '';
           IF Get_In_File( ipath, iname ) THEN
             BEGIN
               Reset( ifile, iname );
               oname := '';
               IF Get_Out_File( 'Write to ...', oname ) THEN
                 BEGIN
                   Rewrite( ofile, oname );
                   WHILE NOT( eof( ifile )) DO
                     BEGIN
                       Readln( ifile, istring );
                       Writeln( ofile, istring );
                     END;
                   Close( ofile );
                   Close( ifile );
                 END;
             END;
           Exit_Gem;
         END;
     END.

                            What's going on?
                            ----------------

        The program above is an EXTREMELY simple copy program.  It
     reads in a line of text from a file and writes the line
     out to another file.  It also  makes use of the two ready-
     made dialog boxes that OSS Personal Pascal has provided.
        There are two text files, designated by 'ifile' and 'ofile'.
     One is our input file, the other will be our output file.  I'm
     assuming that the input file has lines of text 80 characters in
     length.  'Iname' and 'oname' are assigned the actual names of
     the files to be involved.
        After GEM gives us some space to work, the initial path name
     is set to 'A:\*.*' and the initial file name is cleared.  This
     is IMPORTANT, as the 'Get_In_File' procedure uses the initial
     path to search for a file, and strange things happen if the
     initial file name is left up to the whim of the machine.  In
     Pascal, you should set up the initial value of a variable,
     as you cannot depend on the compiler to do it for you.
        Get_In_File is a dialog box, thoughtfully provided for you by
     the people at OSS.  It should look familiar, as it is the "standard"
     GEM file-choice box, and works in the typical GEM manner.  If we
     don't select a valid file name, the program ends.
        Once we have a file name (and path), we can open the file and
     point to the the beginning of the text file using 'Reset'.  Now,
     whenever we want to refer to our text file, we use the variable
     'ifile'.  You can have several files open at one time, as long as
     you use different variable names for each file.
        'Oname' is cleared, and a call is made to the other dialog box
     that OSS provides.  'Get_Out_File' is simpler; a box with a
     message that you can provide (like 'Write to ...'), and space to
     type in a file name.  If you choose a file that already exists,
     you will get an alert box asking you to confirm your choice.  Neat!
     And you didn't even have to set it up...

        When you want to write to a file, you use the procedure
     'Rewrite'.  This actually creates the file, and sets a file
     pointer to the beginning of the file.  If you rewrite to an already
     existing file, you destroy its contents, so be careful.
        If you are at the end of a file, the value of 'eof( ifile )' is
     TRUE.  This is the basis of the WHILE loop in the program.  It
     means "Peek at the file.  If there is still more to read then do so,
     otherwise we're done".
        The 'readln' and 'writeln' work the same here as they do when
     you are typing away at the keyboard.  In fact, you are actually
     reading from a file called INPUT when typing at the keyboard, and
     writing to a file called OUTPUT when printing something on the
     screen.  These are "implicit" files, and save you a lot of typing
     when in the editor.  The full syntax is:

          READLN( input_file, var1, var2, ..., varx);
          WRITELN( output_file, var1, var2, ..., varx);

        Of course, you should also CLOSE a file once you are done with
     it, and that's what the 'Close' procedure does.

        That's it!  You should be able to use these examples to do
     things with any text file.  You can read and write to text files
     in exactly the same way you read and write from the computer's
     hardware.  You just have to set things up in advance.

                        Records and other file types
                        ----------------------------

        There are other types of files.  You can have files of integers,
     files of characters, files of records...  They all follow the same
     basic rules, which are somewhat different from those of text files.
     Here are two more short programs, one to get information INTO a file
     and another to read information back OUT of a file.

     ======================

     PROGRAM make_data;

     { Create a data file of 3 records }

       CONST
         {$I gemconst.pas}

       TYPE
         {$I gemtype.pas}

         rating = ( chief, cook, bottle_washer );

         info = record
                  name : string [30];
                  rank : rating;
                end; {info definition}

       VAR
         myfile : file of INFO;
         myrec  : info;
         iloop  : integer;

     {$I gemsubs.pas}

     BEGIN
       IF Init_Gem >= 0 THEN
         BEGIN
           Rewrite( myfile, 'A:\INFO.DAT' );
           FOR iloop := 1 to 3 DO
             BEGIN
               Write( 'Name: ');
               Readln( myrec.name );
               CASE iloop OF
                 1 : myrec.rank := chief;
                 2 : myrec.rank := cook;
                 3 : myrec.rank := bottle_washer;
               END; {case}
               myfile~ := myrec;
               Put( myfile );
             END;
           Close( myfile );
         END;
       Exit_Gem;
     END.

     =========================

     PROGRAM read_data;

     { Read from the file created by the program make_data }

       CONST
         {$I gemconst.pas}

       TYPE
         {$I gemtype.pas}

         rating = ( chief, cook, bottle_washer );

         info = record
                  name : string [30];
                  rank : rating;
                end; {info definition}

       VAR
         myfile : file of INFO;
         myrec  : info;

     {$I gemsubs.pas}

     BEGIN
       IF Init_Gem >= 0 THEN
         BEGIN
           Reset( myfile, 'A:\INFO.DAT' );
           WHILE NOT( EOF( myfile )) DO
             BEGIN
               myrec := myfile~;
               WITH myrec DO
                 BEGIN
                   Writeln( name );
                   CASE rank OF
                     chief        : Writeln( 'chief' );
                     cook         : Writeln( 'cook' );
                     bottle_washer: Writeln( 'bottle washer' );
                   END; {case}
                 END; {with}
               Get( myfile );
             END; {while}
           Close( myfile );
         END; {if}
       Exit_Gem;
     END.

     -----------------------------

                        And now ... an explanation
                        ---------------------------

        A lot of things are going on in these two short programs!  If
     you've never seen a record before, or an "enumerated ordinal type"
     pay close attention as we go through Program Make_Data.

        The first thing you see are several TYPE statements.  There is
     'rating', an "enumerated ordinal type".  That's a fancy way of
     saying that the words within the parentheses increase in value as
     you go from left to right.  (I.e. "chief" is actually a lower
     ordinal value than "bottle_washer"!)  Do not confuse this type for
     strings.  The string 'chief' is NOT equivalent to the ordinal
     value chief.
        Enumerated ordinal types can save a lot of space in files.  They
     don't take as much room as an equivalent string, and Pascal works
     with them quite easily.  They also have an advantage in that, while
     you can't use a string constant in a CASE statement, you can use an
     enumerated ordinal value.
        The 'info' type is a record.  Records are groups of variables
     that are related to each other in some way.  For example, you might
     want to keep track of your albums using a record (pun intended).
     You might define it thus:

          album = record
                    title      : string [30];
                    catalog_num: string[10];
                    year       : integer;
                    artist     : string [40];
                  end; {album record definition}

        All those variables relate to one album.  A file of records would
     be able to track many albums in a collection.  In the program
     make_data, we have a 'file of INFO', relating name and rank in
     each record.

        Again, we use 'rewrite' to let Pascal know that it should
     associate the file name 'A:\INFO.DAT' with the file variable
     'myfile'.  This also opens up a disk file called 'A:\INFO.DAT'.
        I ask for a name, and assign it to the record variable 'myrec'.
     Note, the record variable name is 'myrec'; the variable WITHIN the
     record is 'name'.  So, I use 'myrec.name' to refer to the name
     portion of the record 'myrec'.  Likewise for 'myrec.rank'.
        OK.  I've assigned values to both parts of a single record.  I
     want to write them to my file.  First, I need to 'point' to the
     record.  So, I assign 'myfile~' (that's a carat mark at the end)
     the value of myrec.  Myfile~ is a file pointer.  Think of the
     statement

        myfile~ := myrec;

     as putting the value of the record I want to store into the file
     output buffer (in this case).  When I 'Put( myfile )', I write the
     file output buffer (which contains the value for my record) to the
     disk.  Got it?
        Close( myfile ) wraps things up, and I'm done.

        Now I have a data file.  How do I read things back out?  It's
     fairly simmple, and we do it in the program Read_Data.
        Instead of rewrite, we use 'reset' to open a file for reading.
     Once the file is open, we go into a WHILE loop.  If there is
     information in the file, NOT( eof( myfile )) is TRUE and the WHILE
     loop is executed.
        Reset actually puts the first record into the file buffer
     'myfile'.  It's pointer is myfile~, and we can assign myrec the
     value of the file buffer using the reverse of the previous
     statement

        myrec := myfile~;

        It gets tiresome typing 'myrec.name' and 'myrec.rank', so Pascal
     provides a "shortcut", called the WITH statement.  When I write

        WITH myrec DO

     it's like saying "pretend there's a 'myrec.' in front of variable
     names."  So, 'name' is really 'myrec.name', etc.
        The last statement in the WHILE loop is 'Get( myfile )'.  It
     reads another record into the file buffer and, if it's NOT the
     end-of-file (eof) marker, then the WHILE loop gets executed once
     more.  When I reach the end of the file, I close it and end the
     program.

                    Some other file handling procedures
                    -----------------------------------

        Here are some short program fragments to explain some of the
     other file handling procedures...

        To rename a file, use the Rename procedure:

        ...
        Reset( ifile, 'A:\OLDFILE.DAT' );
        Reset( nfile, 'A:\NEWFILE.DAT' );
        Rename( ifile, nfile );
        ...

        To erase a file:

        ...
        Reset( ifile, 'A:\BYEBYE.DAT' );
        Erase( ifile );
        ...

        Standard Pascal files are SEQUENTIAL.  You read record 1, then
     record 2, then record 3, etc.  OSS Personal Pascal allows you to
     use RANDOM ACCESS files (but NOT for text files).  You use the
     Seek() procedure:

        ...
        Seek( ifile, position );
        Get( ifile );
        irec := ifile~;
        ...

        The program fragment means "Get the record indicated by the
     integer value 'position' in file 'ifile', read it into the file
     buffer, and assign the value there to the variable 'irec'."

        To use random access files properly, you need to keep track of
     where things are.  Think back to the golden days of BASIC for ideas
     on how to accomplish this.  (HINT:  you can SORT an ARRAY on some
     key RECORD VARIABLE and SAVE the SORTED ARRAY in another FILE...)

                                 Wrapping up
                                 -----------

        There's a LOT of information in this article.  It will take
     practice to use it well.  Files and records are some of the hardest
     parts of Pascal to use correctly and efficiently.  I hope I've set
     you well on your way to finding useful methods for implementing
     files in OSS Personal Pascal!

        Next time ... Remember the alert box with Now What?  Well, I've
     managed to turn it into a DESK ACCESSORY.  How?  Find out in part 4.

               David Meile
               Box 13038 - Dinkytown Station
               Minneapolis, MN  55414

                              Minor notes
                              -----------

        Were you disappointed to learn that the "special" characters
     are unavailable in OSS Personal Pascal?  According to the manual,
     the CHR() function accepts values from 1..127.  It will actually
     accept more [I assume from 1..255, but have not tested EVERY
     value].  For example:

        ...
        mystring := Concat( 'Copyright ', chr(189), ' 1987' );
        writeln( mystring );
        ...

        How do you tell what resolution your computer is in?  Use this
     function:

        FUNCTION getrez : integer;
          XBIOS( 4 );

     Then, call it like this:

        CASE getrez OF
          0 : Lo_res_stuff;
          1 : Med_res_stuff;
          2 : Hi_res_stuff;
        END; {case}