Chapter 14: The Computer reads its Files
1. Create (md) a sub-directory in the project folder. Give it the name Chapt14_readFile
2. Go (cd) to that new directory.
3. Using your text editor, create the following program file in that folder. Give the program file the name readFile.hla
4. Compile the program and run it. Study the comments that are in the hla program, as well as the output that appears on the screen when you run the program.
Notice that we are again starting out with the same program with which we ended in the previous chapter. For a simple start ... we will just add to it. We will add another procedure, inputBook(), to read these records into an array of records, when we first run our program, (if there is an existing file, that is ... with records to read.) Then we will just show them, with a little fancier formatting then before. (Check it out.)
BUT
if the records.dat file does NOT exist in the default directory, i.e. our working folder, we will just report that possibility and then ask if the user wishes to start a new one. If so, we can just call our previously defined procedure getBook() ... Lets give it a different name of newBook(). However we must not forget to then, right away, write these new records to the disk. To do so
we just call the procedure we made in the last chapter: fileBook( .. ). Of course, we must pass to it the number of elements to file. No problem. We just pass along the value in the ecx register from the previous function call of getBook(), which returned the number of contacts input from the keyboard in the ecx register.
NOTE: In our programs we have NOT been specifying any path to the file that we write to or read from the disk. Thus, the .exe file just then reads/writes its data files from/to the default working directory ... which is the same as the folder that we have just made ... the folder where we have been putting each chapters files.
Here then, is our next step in this mini data base type program:
program readFile;
#include( "stdlib.hhf" )
const
MaxCount: int32 := 100; // to allow for a sufficient? number of MyContacts
FNAME: text := """contacts.dat"""; // each """ includes one " in output
type
MyContacts: record
theName: string;
thePhone: string;
endrecord;
static
MyBBook: MyContacts[ MaxCount ]; // get array space for MaxCount MyContacts
// tot.num of contacts returned in ecx
procedure newBook( maxSize:int32 ); @nodisplay; @returns("ecx");
// nested procedures are allowed in HLA
procedure takeIn( message:string ); @nodisplay; @returns("eax");
var
s: string;
begin takeIn;
forever
stdout.put( message, ": " );
stdin.flushInput();
stdin.a_gets();
stdout.put("You input ", (type string eax), " ok (y/n) ? " );
mov( eax, s );
stdin.getc();
if( al == 'y' || al == 'Y' ) then
mov( s, eax );
break;
else str.free( s );
endif;
endfor;
end takeIn;
begin newBook;
mov( 0, ecx );
while( ecx < maxSize ) do
mov( @size( MyContacts ), ebx );
intmul( ecx, ebx ); // ebx := ecx*@size( MyContacts )
mov( takeIn( "Enter name " ), MyBBook.theName[ebx] );
mov( takeIn( "Enter phone " ), MyBBook.thePhone[ebx] );
inc( ecx );
stdout.puts( nl "More y/n ? " );
stdin.flushInput();
stdin.getc();
breakif( al == 'n' || al == 'N' );
endwhile;
end newBook;
// returns count of records written to file in ecx ...
procedure fileBook( numRecs:int32 ); @nodisplay; @returns( "ecx" );
var
outFileHandle: dword;
begin fileBook;
try
fileio.openNew( FNAME ); // overwrites any existing file
mov( eax, outFileHandle );
for( mov( 0, ecx ); ecx < numRecs; inc( ecx ) ) do
mov( @size( MyContacts ), ebx );
intmul( ecx, ebx ); // ebx := ecx*@size( MyContacts )
fileio.put( outFileHandle, MyBBook.theName[ebx], nl );
fileio.put( outFileHandle, MyBBook.thePhone[ebx], nl );
endfor;
fileio.close( outFileHandle );
exception( ex.FileOpenFailure )
stdout.put( "There was a problem opening file ", FNAME, " for output." nl );
endtry;
end fileBook;
// returns count of records read from file in ecx ...
procedure inputBook( maxSize:int32 ); @nodisplay; @returns( "ecx" );
var
inFileHandle: dword;
begin inputBook;
try
fileio.open( FNAME, fileio.r ); // open file for reading
mov( eax, inFileHandle );
mov( 0, ecx ); // initialize counter ...
while( !fileio.eof( inFileHandle) ) do
mov( @size( MyContacts ), ebx );
intmul( ecx, ebx ); // ebx := ecx*@size( MyContacts )
// allocate space for new strings and
// move pointers into array (of pointers)
fileio.a_gets( inFileHandle); // returns new string in eax
mov( eax, MyBBook.theName[ebx] ); // store in array of pointers
fileio.a_gets( inFileHandle);
mov( eax, MyBBook.thePhone[ebx] );
inc( ecx ); // increment contact count
endwhile;
fileio.close( inFileHandle );
exception( ex.FileOpenFailure )
stdout.puts
(
nl "There was some problem reading your file. "
"Perhaps it doesn't exist?"
nl "Do want to start a new contact book (y/n) ? "
);
stdin.getc();
if( al = 'y' || al = 'Y' ) then
newBook( maxSize ); // recall ... returns size in ecx
fileBook( ecx );
else
mov( 0, ecx );
endif;
endtry;
end inputBook;
procedure printBook( numRecs:int32 ); @nodisplay;
begin printBook;
// a little different formatting ...
stdout.puts
(
nl "Your 'book' : __________________________________________________" nl
);
for( mov( 0, ecx ); ecx < numRecs; nop() ) do // nop() as inc(ecx) inside loop
mov( @size( MyContacts ), ebx );
intmul( ecx, ebx ); // ebx := ecx*@size( MyContacts )\
inc( ecx );
stdout.puts( nl stdio.tab stdio.tab );
stdout.putu32Size( ecx, 3, '0' ); // 3 spaces and front pad with zeros
stdout.put( ": ", MyBBook.theName[ebx]:-20 );
stdout.puts( " ---> " );
stdout.put( MyBBook.thePhone[ebx] );
endfor;
end printBook;
begin readFile;
inputBook( MaxCount ); // recall ... returns the record count in ecx
printBook( ecx );
stdout.put( nl nl "Total contacts now in memory = ", (type int32 ecx),
". Press 'Enter' to exit ... " );
stdin.readLn();
end readFile;
Some things to note regarding the above program:
If you were to open up the contacts.dat file with a text editor like MS Notepad.exe, you would notice that each field is on a new line, since that is the way we asked the program to write each string to the file in our procedure fileBook( recCount )
as per the nl at the end of each fileio.put( outFileHandle, myBBook.thePhone[ebx], nl );
So when we read back from the file, we can read each field into a newly allocated string and then store the pointer to that new string, (the new string that on the fly was allocated with sufficient bytes to hold the characters in the field just read from the file), in the appropriate offset in our array of records, (which we, before compiling), set to a maximum of 100
// store next string from file-with-named-handle in newly allocated memory
fileio.a_gets( inFileHandle); // and return pointer to that memory in eax
// ebx holds previously calculated offset into next record in array myBBook
mov( eax, myBBook.theName[ebx] ); // store new address in array of addresses
So with the fileio.a_gets( inFileHandle) call, the strings are individually allocated in memory as input, and the new address to each new chunk of memory that holds the characters in that string, is then stored into the next spot in our array. Since presently, (in 2007), HLA addresses are 32 bits, (i.e. 4 bytes), all we need to reserve ahead of compile time, is 4 bytes for each string in our array, even though while the program is running, we could conceivable allocate 100s of bytes further to hold the characters of each string then input.
Thus, not so much computer memory space is wasted, just reserving 4 bytes for each pointer ahead of time, if we dont actually input all those strings allowed by our maxCount of 100 in our program.
We could also mem.alloc( numBytes) space for our array of records, (here, of pointers), on the fly, i.e. dynamically, while the program is executing.
Note: the older HLA style of mem.alloc was malloc, ... of str.alloc was stralloc and of mem.free( pointer ) was free( pointer ), ... of str.free( p ) was strfree( p)
Note that the older malloc and stralloc types are used in this link:
http://216.92.238.133/Webster/www.artofasm.com/Windows/HTML/ConstsVarsAndDataTypes2.html Copied here for reference ...(original link no longer available.)
16 The File I/O Module (fileio.hhf)
--------------------------------------------------------------------------------
The file I/O functions are quite similar to the file class functions except that you explicitly pass a file handle to these routines rather than invoking a method via a file class variable. In fact, the file class methods call the corresponding fileio functions whenever you invoke a file object's methods. Therefore, if you want your programs to be a little smaller, you should use the fileio functions rather than the file class package.
16.1 General File I/O Functions
Here are the file output routines provided by the HLA fileio unit:
fileio.open( FileName: string; Access:dword ); @returns( "eax" );
The fileio.open routine opens the file by the specified name. The Access parameter is one of the following:
fileio.r
fileio.w
fileio.rw
fileio.a
The fileio.r constant tells HLA to open the file for read-only access. The fileio.w constant tells HLA to open the file for writing. Using the fileio.rw constant tells fileio.open to open the file for reading and writing. The fileio.a option tells the fileio.open function to open the file for writing and append all written data to the end of the file.
This routine raise an exception if there is a problem opening the file (e.g., the file does not exist). If the file is successfully opened, this function returns the file handle in the EAX register.
fileio.openNew( FileName: string ); @returns( "eax" );
This function opens a new file for writing. The single parameter specifies the file's (path) name. This function raises an exception if there is an error opening the file. If the file is opened successfully, this function returns the file handle in the EAX register. If the file already exists, this function will successfully open the file and delete any existing data in the file.
fileio.close( Handle:dword );
14 Exceptions (excepts.hhf)
--------------------------------------------------------------------------------
The exceptions units contains several things of interest. First, it defines the ExceptionValues enumerated data type that lists out all the standard exceptions in the HLA Standard Library. The second thing provided in the excepts unit is the ex.PrintExceptionError procedure which prints a string associated with the exception number in EAX. Next, the excepts.hhf header file defines the "assert( expr )" macro. Finally,
14.1.7 ex.BadFileHandle (6)
The file class and fileio library modules raise this exception if you attempt to read from or write to a file with an illegal file handle (i.e., the file has not been opened or has already been closed).
14.1.8 ex.FileOpenFailure (7)
The HLA file open routines raise this error if there was a catastrophic error opening a file.
14.1.9 ex.FileCloseError ( 8 )
The HLA file close routines raise this error if there was an error closing a file.
14.1.10 ex.FileWriteError (9)
The HLA Standard Library file output routines raise this exception if there is an error while attempting to write data to a file. This is usually a catastrophic error such as file I/O or some hardware error.
14.1.11 ex.FileReadError (10)
The HLA Standard Library file output routines raise this exception if there is an error while attempting to read data from a file. This is usually a catastrophic error such as file I/O or some hardware error.
14.1.12 ex.DiskFullError (11)
The HLA Standard Library raises this exception if you attempt to write data to a disk that is full.
14.1.13 ex.EndOfFile (12)
The HLA Standard Library file I/O routines raise this exception if you attempt to read data from a file after you've reached the end of file. Note that HLA does not raise this exception upon reaching the EOF. You must actually attempt to read beyond the end of the file.
15 File Class (fileclass.hhf)
--------------------------------------------------------------------------------
The HLA Standard Library provides a file class that simplifies file I/O. The name of this class is file and you should declare all objects to be of this type or a pointer to this type, e.g.,
var
MyOuputFile: file;
filePtr: pointer to file;
Once you declare a file variable, you access the remaining methods, procedures, and fields in the file class by specifying the file variable name and a period as a prefix to the field name.
Note: HLA also provides a fileio library module that does file I/O using traditional procedures rather than class objects. If you're more comfortable using such a programming paradigm, or you prefer your code to be a bit more efficient, you should use the fileio module.
Warning: Don't forget that HLA objects modify the values in the ESI and EDI registers whenever you call a class procedure, method, or iterator. Do not leave any important values in either of these register when making calls to the following routines. If the use of ESI and EDI is a problem for you, you might consider using the fileio module that does not suffer from this problem.
Note: Although the file class is convenient to use and provides some nice features to object-oriented programming, the way that the classes work pretty much means that you will be linking in the entire file class if you use only a single method from the class. If you're trying to write a small program, you should use the fileio module rather than the file class.
15.1 General File Operations
filevar.create; @returns( "esi" );
file.create; @returns( "esi" ); [for dynamic objects]
The file class provides a file.create constructor which you should always call before making use of a file variable. For file variables (as opposed to file pointer variables), you should call this routine specifying the name of the file variable. For file pointer variables, you should call this routine using the class name and store the pointer returned in EAX into your file variable. For example, to initialize the two files in the previous example, you would use code like the following:
MyOutputFile.create();
file.create();
mov( eax, filePtr );
Note that the file.create constructor simply initializes the virtual method table pointer and does other necessary internal initialization. The constructor does not open a file or perform other file-related activities.
filevar.handle; @returns( "eax" );
This method returns the file handle in the EAX register. The returned value is invalid if you have not opened the file. You can pass this handle value to any of the Standard Library file routines (e.g., fileio.putc) that expect a handle. You may also pass this value to Windows or Linux API functions that expect a file handle.
filevar.open( filename:string; access:dword )
This method opens an existing file. The filename parameter is a string specifying the name of the file you wish to open. The access parameter is one of the following:
fileio.r
fileio.w
fileio.rw
fileio.a
The fileio.r constant tells filevar.open to open the file for read-only access. The fileio.w constant tells filevar.open to open the file for writing. Using the fileio.rw constant tells fileio.open to open the file for reading and writing. The fileio.a option tells the filevar.open function to open the file for writing and append all written data to the end of the file.
Before accessing the data in a file, you must open the file (which initializes the file handle). The filevar.open and filevar.openNew methods are excellent tools for this purpose. You may also open the file using direct calls to the Windows or Linux API, but you must initialize the filevar.fileHandle field of the class variable before making any other method calls in the file class.
filevar.openNew( filename:string )
This function opens a new file for writing (if the file already exists, it is first deleted and then a new file is opened for writing). The file is given the "normal" attribute.
Before accessing the data in a file, you must open the file (which initializes the file handle). The filevar.open and filevar.openNew methods are excellent tools for this purpose. You may also open the file using direct calls to the Windows or Linux API, but you must initialize the filevar.fileHandle field of the class variable before making any other method calls in the file class.
filevar.close;
This method closes a file opened via file.Open or file.OpenNew and flushes any buffered data to the disk.