next up previous contents index
Next: Input and Output Up: No Title Previous: Data Structures

Files

  One of the most frequently occurring cases of cooperation of agents is reading data written by another agent to a file, or writing data to a file needed by another agent. This section deals with how to read data from and write data to a file.

Basic Patterns of Use

The basic idea of how to use files in DFKI Oz is as follows. We provide a class Open.file. Creating an object by inheriting from this class we get a file object . Conceptually, its state consists of a string together with the current position in this string called seek pointer  . In the following, we will refer to this particular string as file . It is visible to other Unix processes via the Unix filesystem. The atoms used for labels and fields of methods of the class Open.file are chosen to coincide with the terminology in the Unix documentation. In the sequel, we will explain some of those operations.



Reading a file

Suppose we want to read data from a file a.txt. The content of this file is as followsgif:

The first step is to create a file object and associate it to the file a.txt. This is done by feeding

create F from Open.file
   with init(name:"a.txt" flags:[read])
end
  The list [read] used as value of the field flags means that we want to have read access to the file. After the file has been opened and associated to the file object F, we can read from it. To read the next five characters from the file we feed
{F read(list:{Browse} size:5)}
  The character string Hello appears in the browser window (the value 5 at the field size signals that we want to read five characters from the file.). Note, that if we had not switched the browser to the mode for displaying virtual strings (see Section gif), we would have seen the output [72 101 108 108 111] in the browser window. A common pattern of use is to read the entire file into an Oz string. This is especially supported. Feed:
{F read(list:{Browse} size:all)}
  You will see the rest of the file HelloOz is beautiful in the browser window. Up to now we have read the data in strict left to right fashion. Suppose we want to start reading at the eighth character of the file. Navigating to this character and reading the following five characters is done by:
{F [seek(whence:set offset:7)
    read(list:{Browse} size:5)]}
    You will see lloOz in the browser window. Internally, as already mentioned, each file object has a seek pointer . Each operation refers to its position: for instance, instead of ``reading five characters'' one should think of ``reading the next five characters after the seek pointer''. Note that in order to read the first character of a file, the seek pointer must be set to the offset zero. Furthermore, you get the current position of the seek pointer by:
{F tell(offset:{Browse})}
  The number 12 appears in the browser window.

Closing a File

After we have used the file object F, we have to close it. Using the ordinary close method takes care of all necessary actions. So, feeding

{F close}
  closes the file, as well as the file object: any further message sent to F is ignored.

Writing a File

As you already know, the first step is to create a file object.

create F from Open.file end
Suppose you want to create a file which does not exist by now with name ours.txt. Furthermore, this file should have read and write access rights for yourself and for each member of your group. You should feed
{F init(name: "ours.txt" 
        flags: [write 'create']
        mode: mode(owner:[read write] group:[read write]))}
  Afterwards you can write data to the file. The data expected by the file object are virtual strings. Thus, you can write the character sequence "Go ahead" by feeding:
{F write(vs:'Go ahead!')}
  Similarly, for writing the same data as a string to the file type
{F write(vs:"Go ahead!")}
  Even more complex virtual strings are handled this way. For example
{F write(vs:"This is "#1#' And '#("now a float: "#2.0)#"\n")}
  writes a nested virtual string containing integers, atoms, strings and floats to the file. Even the filenames of the init method of the class Open.file are allowed to be virtual strings. For more information on virtual strings see Section gif. If you type the Unix command cat ours.txt  to a shell, you will see the content of ours.txt printed to standard output. Thus, you can see that the file is visible to Unix agents, whereas the seek pointer is encapsulated as usual.


The Class Open.file

  The class Open.file   has no public attributes and the following public feature:

feat error: P
    P must be a ternary procedure which is called, when an error occurs. The first actual parameter is the object itself, the second the label of the method where the error occurred, and the third a virtual string, giving a short description of what has happened. The default error handler prints this error message together with the label of the method and the print name (see [1] for the concept of print name) of the object.

The class Open.file provides the following methods.

init(name:  +NameV
     flags: +FlagsAs <= [read]
     mode:  +ModeR   <= mode(owner:[write] all:[read]))
   Initializes the file object and associates it to a Unix file. NameV is either a valid Unix filename or one of the atoms stdin, stdout, and stderr. In this case, the standard input, standard output, or standard error stream is opened, respectively. The value of FlagsAs must be a list, with its elements chosen from the following atoms: read write append 'create' truncate exclude

For reading a file, the atom read must be included in FlagsAs. Similarly, the atom write must be included for writing. It is possible to include both atoms, giving both read and write access to the file. When a file object is opened, the seek pointer , pointing to the current position in the file, is initialized to point to the start of the file. Any subsequent read or write takes place at the position given by this pointer. The remaining atoms make sense only if the file is opened for writing. If the atom append is included, the seek pointer is moved to the end of the file prior to each attempt to write to the file. If the file to be opened already exists, the presence of the atom 'create' has no effect. Otherwise, the file is created. Including truncate resets the length of an existing file to zero and discards its previous content. An attempt to open an existing file fails, if exclude is contained. Thus, this flag grants exclusive access of the file object to the Unix file. If the file is opened for writing and the atom 'create' is included, the access rights are set as specified by ModeR. This must be a record with fields drawn from all, owner, group and others. Its subterms must be lists of the atoms read, write and execute. More detailed information can be found in open(2)  , chmod(2)  , and umask(2)  .

read(list: ?ListS  
     tail: TailX   <= nil  
     size: +SizeAI <= 1024  
     len:  ?LenI   <= _) 
   Reads data from a file. SizeAI specifies how much data should be read from the file. If the field len is present, its value LenI is constrained to the number of bytes actually read. LenI may be less than SizeAI. The atom all is also a legal value for SizeAI. In this case the entire file is read. The data read constrains ListS to a list whose elements are characters. The tail of the list may be given by TailX. The value for TailX defaults to nil, which means that in this case the list ListS is an ordinary string. See also read(2)  .
write(vs:  +V 
      len: ?I <= _)
   Writes the virtual string V to a file. Note that this method is not atomic: Even if V contains unconstrained variables, the part already constrained to a virtual string is written to the file, thus showing a side effect to the outside world. The method is sequential, i.e. only after the entire virtual string has been written, the object's state is released. See write(2)  .
seek(whence: +WhenceA <= set  
     offset: +OffsetI <= 0)
   Sets the seek pointer of the file object. Allowed values for WhenceA are the atoms set, current, or 'end'. In the case of set the position of the seek pointer is moved to the absolute position from the beginning of the file given by the value of OffsetI. In case of current the pointer is moved ahead by OffsetI. Notice, that the pointer can be moved backward by a negative OffsetI, and forward by a positive OffsetI. If 'end' is given, the pointer is moved by OffsetI with respect to the current end of the file. In particular, invoking seek with the default parameters moves the pointer to the beginning of the file. See lseek(2)  .
tell(offset: ?OffsetI)
    OffsetI is constrained to the current position of the seek pointer counting from the beginning of the file. See lseek(2)  .
close
   Closes the file object as well as the file. See close(2)  .

!!! Attention !!!

dOpen(+FileDescI)
   Instead of using the init  method, one can use this method to initialize the object. FileDescI must be an integer, a file descriptor which must be already opened (in the usually Unix meaning). Note, that this message should only be used for advanced purposes, like getting an already open file descriptor from a Unix process.

!!! Attention !!!

getDesc(?FileDescIB)
   This method gives access to the file descriptor the object uses internally. If the object is not yet initialized, FileDescIB will be constrained to False, otherwise to an integer giving the number of the file descriptor. Note, that this method as well is only for advanced purposes.

Example: Expanding TAB Characters

Suppose we want to read a file, expand all TAB characters to SPACE characters, and write the expanded lines to another file. The program which implements this task is shown in Program gif.  

local
   [SPACE TAB NL BS] = " \t\n\b"
   fun {Insert N Is}
      case N>0 then {Insert N-1 SPACE|Is} else Is end
   end
   fun {Scan Is TabStop N}
      case Is of nil then nil
      [] I|Ir then
         case I of !TAB then
            M=TabStop - (N mod TabStop) in
            {Insert M {Scan Ir TabStop M+N}}
         [] !NL then I|{Scan Ir TabStop 0}
         [] !BS then I|{Scan Ir TabStop {Max 0 N-1}}
         else I|{Scan Ir TabStop N+1}
         end
      end
   end
in
   proc {Expand TabStop InFile OutFile}
      Data in 
      create _ from Open.file 
         with [init(name:InFile) read(list:Data size:all) close]
      end
      create _ from Open.file 
         with [init(name:OutFile flags:[write 'create' truncate])
               write(vs:{Scan Data TabStop 0}) close]
      end
   end
end

The Expand procedure.

  The file with name InFile is opened for reading. Immediately after reading the entire file to the list Data the file and the associated object is closed. Remember that reading the entire file is obtained by giving all as the value for feature size. The expansion of TAB characters is done in the function Scan. It takes as input parameters the list of characters Is, the TabStop, and the current position N in the current line. The outer case of Scan  figures out whether there are characters to process. If the next character to process is a TAB character, enough SPACE characters to reach the next multiple of TabStop are inserted. This is performed by the self explanatory function Insert . A newline character resets the position N to zero. The position is decremented whenever a backspace character is encountered. Any other character increments the position. A second file is opened for writing (indicated by write). If a file with name OutFile does not exist, it is created (indicated by 'create'). Otherwise the already existing file is truncated to length zero (indicated by truncate) and rewritten. The expanded string is written to this file. The file and its associated file object is closed after writing the expanded list of characters to it.



next up previous contents index
Next: Input and Output Up: No Title Previous: Data Structures



Sven Schmeier
Tue Sep 5 10:43:51 MET DST 1995