Input and Output

There are a number of predicates that perform I/O. The general categories are:

Exactly where the I/O takes place is determined in two different ways.

I/O can be directed to/from the console, a file or a C/C++ function. There are predicates designed just for dealing with files, and predicates designed just for dealing with the console.

For example, write(hello), will write hello to the current output stream. It might be "stdout"or a file. write(ID, hello) will write hello to the file identified by the value of the variable ID no matter what the current output stream is.

The predicates which perform I/O succeed only once--they cannot be resatisfied by backtracking, since in general there is no way to "push" an arbitrary number of characters back into a file, or remove them from a file.

I/O by Ids

A device has to be opened for reading or writing before it can be used. The predicates which open the device return, in one of their arguments, a small integer, called a handle.

The handle can be used to identify the device for future I/O predicates. You can also simply use the device name to identify the device. The predicate handle_name/2 can be used to get the name of a device from its handle and the handle from the name of the device.

While always using the device name as an ID is probably the most straightforward approach, both types of ID are supported. So you can use either the filename or the handle as the ID.

All forms of reading and writing predicates that accept an ID, will decide whether the ID is a name or handle based on whether it is an atom or an integer.

There are 4 predefined IDs as follows:

ID Name Description
0 stdin standard input
1 stdout standard output
2 stderr standard error
3 function directs I/O through C/C++ functions defined via the Logic Server API

For more information on ID=3, see "Capturing Prolog I/O from C/C++" in the Logic Server section.

I/O by Streams

Prolog maintains two classes of streamsthe current streams and the "user" streams.

The I/O that is directed to the these streams is I/O from predicates that do not have a device ID associated with them. If an ID is specified, then the I/O from that predicate overrides the current stream.

Current Streams

The current streams define the stream I/O devices currently in effect, for input, output and errors. Current input and current output are separate entities. So, input could be coming from a file and output to the terminal, input could be coming from the keyboard and output to a file, or any other combination of the two.

Some predicates are designed to change both streams at the same time, such as seetell/1 which sets both current input and current output to its argument.

User Streams

The poorly named "user" streams actually define the default I/O devices, used when a current stream is closed via seen/1 or told/1. At start-up, "user input", "user output" and "user error" are set to "stdin", "stdout", and "stderr" and the current streams and user streams are the same.

Stream Management

The fundamental predicates for setting and retrieving stream IDs are current_streams/3 and user_stream/3. The other stream predicates are implemented using them. Because Prolog is more expressive at logic than English, the Technotes contains the source code used to implement the various stream predicates.

The stream I/O predicates generally expect the device to have already been opened. However, if it hasn't been they will attempt to open it. In this case they assume the device is a file. This means you don't have to necessarily open files before directing streams to them.

I/O by IDs and Streams

The following predicates are used for performing I/O with streams and IDs.

!EOF End of File

When you are reading from a file, it is very possible that you will eventually have read the whole file by making repeated calls to one or more of the reading predicates. In this case read, get and get0 will attempt to unify their argument with the special atom '!EOF'. Further attempts to read the file will result in this atom's being returned over and over. Thus it is the programmer's responsibility to look for this atom and take the appropriate action.

current_streams(Input_ID, Output_ID, Error_ID)

current_streams/3 sets or returns the IDs for each of the three streams, current_input, current_output, and current_error.

current_user(UserIn_ID, UserOut_ID, UserErr_ID)

current_user/3 sets or returns the IDs for each of the three streams, user_input, user_output, and user_error.

display(Term)

display/1 displays a Prolog term without expanding operators (useful for seeing precedence and associativity of operators)

flush_in and flush_out

These predicates flush the input and output streams. They are defined using fflush/1.

get(Char), get(ID, Char)

get/1 or get/2 succeeds if Char can be unified with the ASCII value of the next non-white space character read from the appropriate file. A character is white space if its ASCII value is less than or equal to 32.

get0(Char), get0(ID, Char)

get0/1 or get0/2 succeeds if Char can be unified with the integer ASCII value of the next character read in from the stream specified by ID. '!EOF' is returned if an end-of-file condition is met.

handle_name(Handle, DeviceName)

handle_name/2 will provide the handle for the named device, or the name of a device associated with a handle. One of the arguments must be a variable.

nl, nl(ID)

nl/0 or nl/1 prints out a newline to the current output stream or to the device specified by ID.

put(Char), put(ID, Char)

put/1 or put/2 prints a character. Char should be bound to an integer which is a valid ASCII value. The corresponding character is printed at the current output stream or device identified by ID.

read(Term), read(ID, Term)

read/1 or read/2 succeeds if Term can be unified with the next term read from current input (or device identified by ID). Fails if Term does not unify with the term. Raise an error condition if a term cannot be read (or fail, depending on fileerrors mode). The built-in predicate read/1 is the main predicate for reading a term from current input. If current input is the terminal the predicate waits for a term to be entered at the terminal (terminated with a ".'). The goal succeeds if its argument can be unified with the term read in. For example:

After the initial goal was typed in, Prolog waited until a term was entered (here "foo(bar)') and then Prolog tried to prove "read(X)", unifying X with "foo(bar)".

If the current input is a file then Prolog expects to be able to read a Term from the file exactly as it would from the terminala stream of ASCII characters which represent a Prolog term terminated with a period.

If the argument cannot be unified with the input term then read fails. If backtracking passes over read then there is no way that its proof can be totally undonethis would require that the term typed in during the previous proof is somehow pushed back into the terminal. This does not happen. Any bindings caused by the read predicate are discarded on backtracking.

If the end-of-file is reached (or [Ctrl-Z] is read from the terminal) then read behaves as though the atom '!EOF' had been read. Successive reads will cause rereadings of the '!EOF' atom. Thus it is up to your program to detect the end-of-file condition.

read_string(String), read_string(ID, String)

read_string/1 or read_string/2 succeeds if String can be unified with the next string read from current input (or device identified by ID). Fails if String does not unify with the string. Raise an error condition if a term cannot be read (or fail, depending on fileerrors mode).

A string is a series of ASCII characters ending with a newline (\n). The string has a maximum length of 255 characters.

If the end-of-file is reached (or [Ctrl-Z] is read from the terminal on the IBM-PC) then read behaves as though the atom '!EOF' had been read. Successive reads will cause rereadings of the '!EOF' atom. Thus it is up to your program to detect the end-of-file condition.

see(StreamA)

see/1 opens the device named StreamA as the current input stream. StreamA must be instantiated to an atom naming a file or window, which must exist.

If StreamA is not an atom, or the named file does not exist then an error is raised (or the predicate may failsee fileerrors and nofileerrors).

The initial current input stream defaults to your terminal. This is represented by the "special" file whose name is "user".

seen

The current input stream is closed and is redirected to "user.'

seeing(SName)

seeing/1 returns the name of the current input stream. SName must be a variable.

seetell(StreamA)

seetell/1 does a simultaneous see and tell, which is useful for windows when you want to direct both input and output streams to the window.

seentold

The current input and output streams are closed and redirected to "user.'

set_errors(StreamA)

set_errors/1 sets the standard error stream to the window or file specified by the atom, StreamA.

skip(Char), skip(ID, Char)

skip/1 or skip/2 keeps reading characters from the appropriate stream until one is read whose ASCII value matches Char, then succeeds. Fails if end-of-file condition is reached.

stream_type(ID, Type)

stream_type identifies the type of stream represented by ID. ID must be bound to a stream name. Type is instantiated to:

0
for stdin or stdout
1
for a file
2
for a window
3
if I/O for the stream is redirected through a function set-up using the Logic Server API
-1
if the I/O stream is not defined

The -1 check is useful for Logic Server API applications running under Windows that do not redefine the I/O streams. In those cases normal Prolog reads and writes will generate errors, so this check can be used for applications where the Prolog code might run in different environments.

tab(N), tab(ID, N)

tab(N) or tab(ID, N) prints out N spaces to the current output or the file identified by ID. An error is generated if N is not bound to an integer.

tell(StreamA)

tell opens device StreamA as the current output device. If StreamA does not exist then a file with its name is created. If it cannot be created or StreamA is not an atom then an error is raised or the predicate fails.

It should be noted that all output (except error messages) generated by a Prolog stream write predicate goes to the current output stream. This includes all the prompts generated by the system (e.g. ?-, | ).

told

Closes the current output file, flushing any buffers to the file. Redirects current output to "user".

telling(SName)

telling/1 returns the name of the current output stream. SName must be a variable.

write(Term), write(ID, Term)

write/1 and write/2 write out term Term to the current output stream or the device identified by ID. Prolog does not keep the names of variables, but will produce its own internal names when writing out any unbound variables.

The atom 'XYZ' (notice the quotes), prints as XYZ (no quotes). Thus files written using write are not guaranteed to be readable by read and retain their original Prolog meaning. (XYZ would be read back as a variable above).

writeq(Term), writeq(ID, Term)

writeq/1 or writeq/2 is similar to write (see the section on Streams) except that quoted atoms are quoted on output. This makes it possible to read back in the term that was written.

File-Only I/O

This section describes the predicates for working directly with files. Like the streams described above, ID in the predicates below can either be the file handle obtained from fopen, or the file name passed to fopen.

Binary files have an associated file pointer. This is a pointer into the file which indicates where the next byte will be written to or read from. It is normally adjusted after each read or write so that bytes are read or written consecutively.

fopen(ID, File, Mode)

fopen opens the file with filename File in a specific Mode and returns its ID.

File can be an atom, a string, or a character list. Mode should be one of r (read), w (write), a (append), rb (read binary), wb (write binary) or ab (append binary). The Mode can include a u to indicate the file to be opened is a Unicode file, instead of an ASCII file.

If the binary option is not used then the file system may do some strange things to carriage-return / line-feed pairs of characters. As long as ASCII text is being read (e.g. in a .pro file) then this manipulation is beneficial.

For text files that are open in read mode, fopen/3 determines the type of file from the data in the file (Unicode or ASCII). For text files to be written with Unicode characters, you must add a 'u' to the Mode (the default is ASCII):

If File cannot be opened then an error is raised (or failure occurs, depending on the file_errors mode).

fclose(ID)

fclose closes the file corresponding to ID. It raises an error if the file could not be closed. If the file was opened for writing it is flushed before being closed.

fflush(ID)

fflush flushes pending writes to disk for the file identified by ID, if it has been opened for writing.

fread(ID, Value, Type)

fread reads data from a file. It is the same as fwrite except Value is unified with the value read from the disk file. If end-of-file is met then fread fails, rather than returning an error condition.

fseek(ID, Offset, Method, NewOff)

fseek repositions the pointer of the file associated with ID. Offset is a number representing the number of byte positions to be added to the starting point, which is determined by the value of Method as follows:

0
offset from 0
1
offset from the current file position
2
offset from the end of file

NewOff is bound to the new position, and the file is repositioned to point to it. For example, to get the current position of a file:

fwrite(ID, Value, Type)

fwrite is for random file I/O. Value must be instantiated to a value which is written to the file ID at the current file position. Either one byte is written (Type = 0) or one 16 bit word (Type = 1) or one 32 bit floating point number (Type = 2) or one 32-bit integer (Type = 3).

file_exists(File), file_exists(File, Type)

file_exists/1 succeeds if the File exists. File can be an atom, a string, or a character list specifying the path name of a file.

file_exists/2 is similar to file_exists/1 above. It succeeds if File exists, unifying Type with 1 if File is a Prolog Object file or with 0 if Type is an ASCII file. Note that file_exists does not examine the whole file before deciding on the type of the file, and in fact will classify any file into one of two types. However if the file is either source or compiled code then Type will accurately indicate which it is.

Keyboard Input

These predicates read or simulate reading the keyboard.

get1(Key)

get1/1 gets a single character response key for environments that don't support keyb/1. Unlike keyb/1 and friends, get1/1 does not respond until after the [Enter] key is struck. See also respkey/1.

keyb(Ascii)

keyb/1 returns the ASCII character code of the next key struck without echoing to the screen. keyb/1 is not available in all environments. It is implemented in DOS, and is available for Windows programs running in the IDE environment, but not for Windows programs running in other environments.

respkey(Char)

respkey/1 gets a single character response key by calling keyb/1 predicate if it exists, and get1/1 if not. Here is its definition:

Logging

The log file capability allows you to record a transcript of a Prolog session. This is especially useful in recording long traces during debugging.

Logging can be controlled from a listener, or within a Prolog program through the use of built-in predicates.

openlog(Fname)

This opens the file Fname and sets a flag letting Prolog know it is logging. The file overwrites any previous file of the same name. Fname must be an atom, such as "temp.log" or simply log.

closelog

This closes the log file and stops the logging process.

writelog(X)

Writes X just to the log file.

nllog

Writes a newline to the log file.

Logging can also be turned on for an application by specifying a log file in the application's .cfg file.

Copyright ©1987-2000 Amzi! inc. All Rights Reserved.