2.3 Input-Output

Standard input and output carried out by Card and Print can be redirected to other files by the means provided by the operating system. But often we wish to use one or more input-output files in addition to operations with the keyboard and screen. The built-in functions Get and Put can then be used in conjunction with the function Open .

A file can be used either in the read mode (when you input the information from it) or in the write mode (when you output information onto it). The same file can be used alternately in different modes. In order to use a file you must first open it by activating:

  <Open s.Mode s.Channel e.File-name> 

where s.Mode is either 'r' for the reading mode or 'w' for the writing mode; e.File-name is a string which represents the full name of a file; s.Channel is a whole number attached to the file you open. Channels are used to make the process of writing a program independent of the file names with which the program will be used. s.Channel must not exceed 19.

After a file has been written in, it must be closed; otherwise it will not be possible to read it in either the current program or afterwards (a file that has been opened for writing but never closed will be shown in the directory as empty). However, there is no Refal function for closing files; this is done automatically in two cases:

  1. When the execution of the Refal program is ended either by a normal stop or by the quitting command of the tracer.
  2. When a channel opened for writing is reopened for reading (either with the same file or with another).

To read a file that is open for reading, activate:

  <Get s.Channel>
The machine will read one line of characters from the file associated with s.Channel . If it has come to the End Of File, the result will be the number 0. Get is completely analogous to Card .

To write a line in a file that is open for writing, activate:

  <Put s.Channel e.Expression> 
Then e.Expression will be returned as the value and added as a line to the current contents of the file associated with s.Channel . Thus Put is analogous to Print . The function:
  <Putout s.Channel e.Expression> 
is analogous to Prout (returns empty). When Open is used to attach a channel to a file, s.Channel must not be 0. With s.Channel equal to 0, the functions Get , Put , and Putout use the terminal as the file (i.e., become equivalent to Card , Print , and Prout ); they are not, though, redirectable from the operating system).

Suppose we are writing a function which deals with three files in addition to operations at the terminal. We can assign channels 1, 2, and 3 to these files without committing ourselves to specific file names; only channels will show up in functions Get and Put . When the function definition is ready, we can either compile it as a separate module, or put it in a library. It may be used now with different main modules and different files. All we need to do is to attach channels to files by calling the function Open from the main module. We could not do this if we used file names right in the input-output functions.

The built-in input-output functions handle only one line per call and what they read or write is always a mere string of characters. But the Refal machine deals with expressions. Transformation of input strings into object expressions is, generally speaking, left to the programmer who writes each specific application of Refal. This is reasonable because the way the user wants to represent expressions by character strings may depend on the specifics of the application. The user must have the freedom to define input and output in the most convenient way. However, the need to transform input strings into expressions each time we write a Refal program is a definite nuisance. Therefore, we have defined a few standard general-purpose functions of input-output which are supplied with the Refal system (file reflib.ref , see Reference Section A).

To input Refal expressions, use Input . For this function we have adopted a representation of expressions which is basically the same as when we write a program in Refal. The only difference is a less stringent use of quotes which we can afford because we always read object expressions, not Refal programs. The only non-alphanumeric character-symbols that cannot represent themselves are round parentheses, blanks, and the single and double quotes. Some care is needed, though, in order to avoid confusing the character-symbol '-' with a hyphen in an identifier, and the symbols '+' and '-' with the sign of a real number. Thus, quotes or blanks must be used. Furthermore, the requirement that the first letter of an identifier must be capitalized is relaxed; it may also be lower case.

If you want to read more than one expression from the same file, separate them by empty lines. An empty line (an extra Carriage Return) will also terminate the input of an expression from the terminal. When

  <Input s.Channel> 
is activated, the Refal machine will read the file associated with s.Channel until it meets an empty line or the end of the file. When this call is activated again, it reads in the next expression, etc.

Function Input can also be used directly with the name of the file you want to read:

  <Input e.File-name> 

It will automatically find a free channel, associate it with e.File-name and call Open . Use <Input 0> or <Input> to read from the terminal.

While Input is convenient for the user-machine interface, it would not be the best choice for the exchange of information between the main storage and external memory (magnetic disks). We need a simpler code so as to perform the information exchange efficiently. For this purpose we are prepared to sacrifice the readability of the files (which is achieved with Input by the free use of blanks and line transfers). Therefore, our small Refal library reflib includes functions Xxin and Xxout (eXpression eXchange IN and OUT). You will also see there the function Xxinr (Xxin in Refal). Xxin and Xxinr have the same effect; the difference is that the former is mainly written in the C language and built into the system, while the latter is written in Refal for the purpose of a formal definition. Xxin is much faster than Xxinr .

The exchange functions single out # as the escape character to represent objects beyond characters. The code is as follows:

   in the view field              on the disk	

     s.Identifier            '#' e.Char-string ' '
      s.Number>             '#' e.Digit-string ' '
          (                          '('
          )                          ')'
         '('                         '#('
         ')'                         '#)'
         '#'                         '##'


Functions Xxin and Xxout are inverse to each other. Xxinr and Xxout can be, like Input , used with either channels or file names:

  <Xxinr s.Channel>
  <Xxin e.File-name>
  <Xxout s.Channel e.Expr> 
  <Xxout (e.File-name)e.Expr>

The efficient function Xxin can be used only with file names:

  <Xxin e.File-name>
Channel 0 corresponds to operations with the terminal. Line transfers do not break numbers and identifiers. Xxout cuts text into lines of length 75.

NOTE: Functions Input , Xxin and Xxout use their own table of free and taken channels which is buried under the number 153443950 (see programs) and has nothing to do with the system's table showing which channels are really used through the function Open . Thus one of these input-output functions may pick up a channel as free even though it has already been used in a call of Open . Therefore, we do not recommend using both channels and file names with the input-output functions in the same program because your choice of channels may come into conflict with the the automatic choices made by the functions. If you still want to use both methods, use small channels with Open : 1, 2, 3, etc., because the channels picked up automatically will be 19, 18, 17, etc.

A few examples of correct and incorrect usage of input-output functions are given below. In all examples the goal is to read the file xfile , write it in the exchange code into file yfile , then read it again and print out.

The simplest way is to use file names only:

  * Test of I/O functions.Example 1.
  * This works
  $ENTRY Go { =
      <Xxout ('yfile') <Input 'xfile'>>
      <Prout <Xxin 'yfile'>>; }
  $EXTRN Input,Xxin,Xxout;
In this program, Xxin closes yfile automatically. It knows that this file was opened for writing because Xxout did that by file name and not by channel.

The following example shows an incorrect use of channels:

  * Test of I/O functions. Example 2.
  * This does NOT work.
  $ENTRY Go { =
      <Open 'w' 19 'yfile'>
      <Xxout 19 <Input 'xfile'>>
      <Open 'r' 19 'yfile'>
      <Prout <Xxin 'yfile'>>; }
  $EXTRN Input,Xxin,Xxout

The error here is the explicit use of channel 19 together with Input . This input function reopens channel 19 for reading. When Xxout starts working, channel 19 is opened improperly. Let us replace channel 19 by channel 1:

  * Test of I/O functions. Example 3.
  * This  works.
  $ENTRY Go { =
      <Open 'w' 1 'yfile'>
      <Xxout 1 <Input 'xfile'>>
      <Open 'r' 1 'yfile'>
      <Prout <Xxin 'yfile'>>; }
  $EXTRN Input,Xxin,Xxout
This is a correct program. The second Open statement is necessary in order to close yfile which was opened initially for writing. If we do not do this, as in:
  * Test of I/O functions. Example 4.
  * This does NOT work.
  $ENTRY Go { =
      <Open 'w' 1 'yfile'>
      <Xxout 1 <Input 'xfile'>>
      <Prout <Xxin 'yfile'>>; }
  $EXTRN Input,Xxin,Xxout
the program will not work. When Xxin is executed, the file yfile is empty because it was not closed.

NOTE: Functions Print and Prout are meant primarily for printing strings of character-symbols. When their arguments include other Refal objects, namely identifiers, numbers and parentheses, these functions will print them out in an easily readable representation. An identifier is printed in capital letters and terminated by a blank; the decimal digits of a macrodigit are also terminated by a blank; parentheses are printed as the characters '(' and ')' . Thus it is impossible to know whether the argument really included non-character objects or whether it had strings of characters which look the same in print. This may be a nuisance in debugging; therefore, the tracer prints all expressions in the expression-exchange code which has a unique inverse mapping (see Reference Section D). If you have doubts about what the output of Prout means, use the tracer to find out.

Exercise 2.2 Let the character strings below be read by Input . Write the resulting expressions as they would appear in a program and in the expression-exchange code.
(a) sum-1
(b) sum - 1
(c) sum -1
(d) (95+25)'()'

Exercise 2.3 Function Input works much slower than Xxin but its format is more convenient when the file is written by a human. If the same file is to be read more than once, it may make sense to write it for Input and then convert into a file for Xxin . Write a program to do this conversion. It must be able to work with any file (use the built-in function Arg ).