A Beginners Guide To Euphoria

Opening Files And Devices In Euphoria

The screen and keyboard are not the only things Euphoria programs can access on your computer. You can write programs that read and change file data on your floppy drive or hard drive, or create new files as a form of output. You can even access devices on your computer such as the printer and modem. However, for the sake of brevity, this chapter will focus more on using files on your computer with a passing reference or two to some devices.

A Euphoria program accesses a file or device for use by requesting the operating system to check if that file or device is free. If so, a buffer that will be used to hold data between the program and the file or device is created, and a number is returned to the program. This number is used by the program to reference the file or device. Every file accessed by a Euphoria program is assigned a unique number, so multiple files and devices can be handled without any confusion. Each file or device is also given its own data buffer. When a program outputs data to a file or device, it really goes to the buffer assigned to that file or device. When it is full, only then does it get sent to the file or device.

Any files or devices being used by a Euphoria program remains allocated until either the program stops running, or the program informs the operating system that it is finished with the device or file. When this occurs, any data that was wrtten to the buffer is sent to the file or device, and the file and device is then free for use by another program. Let's begin the process of accessing files and devices by first showing how a Euphoria program allocates, or "opens", a file or device for use.

   ri = open(s1,s2)

open() requests the operating system to allocate a file or device (s1) to be used in a certain mode (s2). If allowed, the number to reference the file or device by is stored in ri. If the file or device cannot be allocated, -1 is stored in ri instead. Accessing a file or device in a particular mode (s2) involves two parts. The first part is how you want to open the file or device. If you want to open a file or device only as a source of data for your program, and do not plan to send data back to it, then you want to open this file or device in "read" mode. This means the program can only accept data from a file or device, not send to it. In the case of a file, this means you cannot change the data inside the file. A file or device must exist in order to open it in "read" mode.

If your intention is to treat a file or device as a source of output, and do not intend to accept any data from it, then you want to open this file or device in "write" mode. This means the program can only send data to the file or device, not read from it. In the case of a file, opening in "write" mode will destroy any data stored in the file. If you wish to preserve the existing data in the file when outputting data to it, you can open the file in "append" mode, a variation of the "write" mode. "append" mode allows output data to be tagged at the end of the existing file data. While a device must exist in order to use "write" and "append", a file is created if it does not exist.

If you want to work with devices and files as both a source of data input and a place to send data as output, then you want to open in an "update" mode. This means programs can both send and receive data from files and devices. A file or device must exist in order to open it in "update" mode.

The second part of opening in a certain mode is how the data is to be treated. If you are treating the data as something to be used in text editors, then you want the data to be handled in text mode. Data outputted in text mode has carriage return codes (13 or '\r') added before any linefeed codes (10 or '\n'). Data inputted in text mode will have carriage return codes automatically removed. The ASCII value of 26 in text mode means the end of data to be read in the file.

If the data you are handling is more along the lines of digital pictures or compressed archive files like .ZIP, then you should handle the data in binary mode. This means the data is not altered in any way, and all ASCII values from 0 to 255 can be read or written unconditionally. By combining 4 open type and 2 data handling modes together, we produce the following 8 modes on the next page.

                          Read   Write   Append   Update
                          Data   Data     Data     Data
                          ====   =====   ======   ======
   Treat data as text      r       w       a        u
   Treat data as binary    rb      wb      ab       ub

Euphoria supports Windows 95's long file name format when using open() on any existing file in any of the modes listed above. However, if you try to open a new file using modes "wb", "w", "ab", or "a" under Windows 95, the name of the new file will be truncated to DOS's 8.3 format (an eight character filename, then a period, followed by a three character extension) if it is lengthy. The tutorial for the most part will use DOS's 8.3 format in fairness to all users with different operating systems, unless otherwise necessary. To open an existing file in the current directory you are in for reading, and to handle file data as text, you would type:

   integer file_id
   file_id = open("text.doc","r")

To open an existing file in another directory on drive C: for appending data to the end of the file, and to treat the data as binary, you type:

   integer file_id
   file_id = open("c:\\binary\\database.bin","ab")

Notice you have to use two slashes instead of one when defining the directory path. This is because \ is also a special character prefix. To create a new file in a different directory on drive F: for writing output data, and to handle data as text, you would type:

   integer file_id
   file_id = open("f:\\output\\write.dta","w")

To open an existing file on the C: drive for updating (using long file names) under the Windows95 operating system, and handling the data as text, you would type:

   integer file_id
   file_id = open("c:\\EuphoriaFiles\\tutorialfile.textfile","u")

You can use the open() library routine to open devices for use. The six allowed device names are listed below:

   CON - Console (screen)
   AUX - Auxiliary serial port
   COM1 - Serial port 1
   COM2 - Serial port 2
   PRN - Parallel port printer
   NUL - Non-existent device that discards accepted output

We will not place too much emphasis on working with devices in this tutorial, but here is how to open the printer, a device you'll use often:

   integer file_id
   file_id = open("PRN","w")

Notice that accessing the printer uses a mode of write text data. This makes sense, as printers cannot send data, and all printer line output (in text mode) is terminated with carriage and linefeed codes. After going through so much reading just to learn how to open a file or device for your program to use, you probably assume that the process of passing data between your program and the devices and files on your system is just as complex. That assumption couldn't be further from the truth. As a matter of fact, you have already learned about the library routines that handle data transfer between the program and any files or devices it works with. The next chapter will explain this in greater detail.

ToC

File And Device Data Handling

You'll recall earlier that Euphoria has a set of library routines that allow a program to accept data from the keyboard and send data to the screen. If the screen and keyboard can be used to access external data, this means they are just like files and other devices. If this is the case, then it would be better to have the library routines for screen and keyboard able to work with other devices and files, instead of designing a new set of library routines that does the same thing.

It makes sense. If Euphoria automatically assigns the number 0 for keyboard, and 1 and 2 for the screen, then replacing these numbers with a value returned by the open() library routine would have the input and output library routines handle data from other sources. This means you already know how to pass data between the program and other files and devices.

Let's expand this a little further, to help those that do not understand.

If i is a value returned by open(), then the following library routines can be used to send data to a file or device opened for data output:

   print(i,o)      - sends a Euphoria value to a file or device
   puts(i,o)       - sends an atom or a sequence as a character string to a
                     file or device
   printf(i,s,o)   - sends an atom or a sequence as part of an edited string to a
                     file or device

In short, all that was done was to replace 1 (screen) with the value returned by open(). The library routine still works the same way.

The same can also be done for library routines that handle input. If i is a value returned by open(), then the following library routines can be used to receive data from a file or device opened for data input:

  include get.e
  rs = get(i)     - retrieve a Euphoria data object from a file or device, and store
                    as a two element sequence
  ro = gets(o)    - retrieve a character string from a file or device up to and
                    including the '\n' code, or -1 if no data is available
 
wait_key() and get_key() only works with the keyboard. However, Euphoria has a counterpart for get_key(), called getc():

   ri = getc(i)

getc() retrieves a single byte from a file or device, defined as i, and stores the byte value into receiving variable ri. i is generated by the open() library routine when it successfully opens a file or device for input. If there is no data available to read, a value of -1 is returned.

While screen and keyboard come from distinctly separate sources (meaning screen and keyboard data are not sharable as a single source), it is possible, in the case of files, to retrieve information previously stored by your program or by another Euphoria program. For example, data stored in a file by a previous print() can be retrieved later by using get(). Data sent to a file by puts() or printf() can be retrieved as a string in one read ( gets() ) or character by character ( getc() ).

When a Euphoria program is finished using a file or device it can release (or close) it, so another program can use it, without having to stop running in order to do this.

   close(i)

close() closes a file or device, defined as i by open(). This will send any data still in the buffer to the file or device being closed.

To help you put this all together, a demo program will show how to open a file or device, send and receive data between files and devices, and close a file when not needed any more.

Demo program 42
include get.e
sequence input_data
integer file_id, byte

puts(1,"File And Device I/O Demo Program\n")
puts(1,"================================\n\n")
puts(1,"Creating a new file called demo.fle on your system......\n\n")
file_id = open("demo.fle","w")
if file_id != -1 then
     puts(1,"Successfully created file demo.fle on your system!\n\n")
     puts(1,"Writing a character string of \"Euphoria\" in demo.fle\n")
     puts(1,"using puts().....\n\n")
     puts(file_id,"Euphoria")
     puts(1,"Done....closing file demo.fle\n\n")
     close(file_id)
end if
puts(1,"Press any key to continue......\n\n")

while get_key() = -1 do
end while

puts(1,"-----------------------------------------------------------------\n")
puts(1,"Opening demo.fle on your system for reading......\n\n")
file_id = open("demo.fle","r")
if file_id != -1 then
     puts(1,"Successfully opened file demo.fle on your system!\n\n")
     puts(1,"Read character string from demo.fle\n")
     puts(1,"in one shot using gets().....\n\n")
     input_data = gets(file_id)
     if sequence(input_data) then
          printf(1,"The string read in is: %s\n\n", {input_data})
     else
          puts(1,"Error reading data from file!\n")
     end if
     puts(1,"Done....closing file demo.fle\n\n")
     close(file_id)
end if
puts(1,"Press any key to continue......\n\n")

while get_key() = -1 do
end while

puts(1,"-----------------------------------------------------------------\n")
puts(1,"Opening demo.fle on your system for reading......\n\n")
file_id = open("demo.fle","rb")
if file_id != -1 then
     puts(1,"Successfully opened file demo.fle on your system!\n\n")
     puts(1,"Read character string from demo.fle\n")
     puts(1,"one character at a time using getc().....\n\n")
     byte = getc(file_id)
     puts(1, "The character string read in is: ")
     while byte != -1 do
          puts(1,byte)
          byte = getc(file_id)
     end while
     puts(1,"\n\nDone....closing file demo.fle\n\n")
     close(file_id)
end if
puts(1,"Press any key to continue......\n\n")

while get_key() = -1 do
end while

puts(1,"-----------------------------------------------------------------\n")
puts(1,"Opening demo.fle on your system to clear data......\n\n")
file_id = open("demo.fle","w")
if file_id != -1 then
     puts(1,"Successfully cleared file demo.fle!\n\n")
     puts(1,"Writing a Euphoria data object of ")
     print(1,-36.5)
     puts(1," using print().....\n\n")
     print(file_id,-36.5)
     puts(1,"Done....closing file demo.fle\n\n")
     close(file_id)
end if
puts(1,"Press any key to continue......\n\n")

while get_key() = -1 do
end while

puts(1,"-----------------------------------------------------------------\n")
puts(1,"Opening demo.fle on your system for reading......\n\n")
file_id = open("demo.fle","r")
if file_id != -1 then
     puts(1,"Successfully opened file demo.fle on your system!\n\n")
     puts(1,"Read a Euphoria data object from demo.fle\n")
     puts(1,"using get().....\n\n")
     input_data = get(file_id)
     if input_data[1] = 0 then
          printf(1,"The value read in is: %.1f\n\n", {input_data[2]})
     else
          puts(1,"Error reading data from file!\n")
     end if
     puts(1,"Done....closing file demo.fle\n\n")
     close(file_id)
end if
puts(1,"Press any key to continue......\n\n")

while get_key() = -1 do
end while

puts(1,"-----------------------------------------------------------------\n")
puts(1,"Opening printer PRN......\n\n")
file_id = open("LPT1","w")
if file_id != -1 then
     puts(1,"Successfully opened printer for use!\n\n")
     puts(1,"Printing a line on your printer using puts()\n")
     puts(1,"Press 'y' to print or 'n' to skip\n\n")
     byte = get_key()
     while byte != 'n' do
          if byte = 'y' then
               puts(file_id,"*************************\n")
               puts(file_id,"* Euphoria in hardcopy! *\n")
               puts(file_id,"*************************\n")
          end if
          byte = get_key()
     end while
     puts(1,"Done....closing PRN\n\n")
     close(file_id)
end if

Whenever you open a file for use, there is a bookmark that determines where at the byte position the next read or write will occur in the file. When a file is opened for read, write and update (whether in text or binary handling), the byte position is 0, meaning the start of the file. For files open for append, the byte position is the last byte of the file. Any data output will change the byte position value.

It's possible for a Euphoria programmer to control where the next read or write will occur in the file by setting the current byte position to a new location. This is done by using the library routine seek():

    include file.e
    ri = seek(i1,i2)
 
seek() sets the next read or write in file i1 (returned by open()) to byte position i2. i2 is the number of bytes from the first byte in the file. For example, seek(0) would have the next read or write occur at the first byte. seek(2999) would have the next read or write occur at the 30,000th byte. A value of 0 is returned to receiving variable ri if seek() successfully changes the current byte position. If unsuccessful, seek() returns a non-zero value.

You can seek() to a position past the actual end of the file. If this happens, extra byte values of 0 will be added to the end of the file, making it long enough to match the byte position you are seek()-ing to.

A demo program is available to show how seek() is used to change specific byte locations in a file.

Demo program 43
include file.e
sequence string, seek_positions, vowels
object input_line
atom file_id, status

vowels = "aeiou"

string =
"The beauty of the seek() library routine is that you can control\n" &
"where the next write or read will occur. This will allow you to\n" &
"update any old information in your file with new data. This eliminates\n" &
"the need to maintain different versions of the same data in the\n" &
"file.\n\n"

seek_positions = {}
for element = 1 to length(string) do
     if find(string[element],vowels) then
          seek_positions = append(seek_positions,{element, string[element]})
          string[element] = ' '
     end if
end for
file_id = open("demo.fle","wb")
puts(file_id,string)
close(file_id)
puts(1,"The paragraph below has been written to a file named demo.fle,\n")
puts(1,"after the vowels were removed and stored in a sequence. This\n")
puts(1,"demo will use seek() to return the vowels back to the paragraph\n")
puts(1,"in the file\n\n")
puts(1,string)
file_id = open("demo.fle","ub")
for element = 1 to length(seek_positions) do
     status = seek(file_id,seek_positions[element][1]-1)
     if status = 0 then
          puts(file_id,seek_positions[element][2])
     end if
end for
close(file_id)
file_id = open("demo.fle","rb")
input_line = gets(file_id)
puts(1,"Adding vowels now.....\n\n")
while compare(input_line,-1) != 0 do
     puts(1,input_line)
     input_line = gets(file_id)
end while

If you want to know the current byte position in the file, you can find out using the where() library routine:

   include file.e
   ri = where(i)

where() is best used when you want to find out the current byte position where the next read or write will occur in file i. i is a value returned by the open() statement. A demo program is available showing where() in use, returning the current byte position at the time of opening a file, and after a few writes have been made to the file.

Demo program 44
include file.e
sequence list_of_words, input_string
integer file_id, current_location

list_of_words = {"Euphoria ","rocks"}

puts(1,"This demo program will show how the current byte position is\n")
puts(1,"updated with every I/O made to a file, courtesy of the where()\n")
puts(1,"library routine. Remember that the current byte position is\n")
puts(1,"the number of bytes from the first byte in the file!\n\n")

file_id = open("demo.fle","wb")

current_location = where(file_id)
printf(1,"Opening file in write mode, the current byte position is %d\n\n",
          current_location)

for word = 1 to length(list_of_words) do
     printf(1,"Writing \"%s\" to file....\n",{list_of_words[word]})
     puts(file_id,list_of_words[word])
     current_location = where(file_id)
     if word < 2 then
          printf(1,"The next write or read will occur at %d\n\n",
                   current_location)
     else
          puts(1,"Closing file\n\n")
     end if
end for

close(file_id)
file_id = open("demo.fle","ab")

current_location = where(file_id)
printf(1,"Opening file in append mode, the current byte position is %d\n\n",
          current_location)
printf(1,"Writing \"%s\" to file....\n",'!')
     puts(file_id,"!")
puts(1,"Closing file\n\n")

close(file_id)

puts(1,"Opening file in read mode now...\n\n")
file_id = open("demo.fle","rb")
input_string = gets(file_id)
printf(1,"The file contains the following string: %s\n",{input_string})

close(file_id)

The next few chapters will make learning Euphoria more fun, by writing programs that generate and manipulate colourful text and graphics.

ToC