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}