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}