Input and Output

Amzi! supports a variety of predicates that perform I/O on streams, which can be standard input/output, files, strings (limited), or host application defined.

Predicates for accessing a machine's directory structure, deleting files, changing directories etc. are not part of the standard Prolog built-in predicates, but are provided for most platforms as extended predicates implemented in a Logic Server Extension.

General Principles

Stream IDs

All I/O is directed to and from streams. Most of the I/O predicates have two forms, one with a stream ID, the other without. When the stream ID is specified, that stream is used for the I/O. When the stream ID is NOT specified, the current default input or output stream is used. See example below.

Backtracking and I/O

The I/O predicates perform I/O services when called. On backtracking, they do nothing and fail through to the previous goal.

If you want an I/O predicate to keep repeating its behavior, for example to read all the clauses in a file, you can use the repeat/0 predicate before the I/O predicate. See example below.

End of File

Some streams, notably files, have ends. When an I/O predicate attempts to read at or beyond the end of a stream, the atom end_of_file is unified with the predicate's argument that contains the 'read' term or item.

When reading to the end of a file, the programmer must test for this atom. This, and the two topic above are illustrated in this example that reads and echos all the clauses (Prolog terms) in a file:

Streams

Prolog I/O is done via streams. The streams can be:

Streams are identified by a stream ID, which is a small integer in Amzi! Prolog. The stream ID can be optionally specified in all I/O predicates. If the stream ID is not specified, then a default stream is used. See redirecting streams for details.

For simple stand-alone Prolog programs, run at a console or window in the Amzi! IDE, the programmer does not need to know about streams, and the read/1 and write/1 predicates will read and write as expected.

Each stream can also have an alias, which is an atom. The alias can be used instead of the stream ID in the I/O predicates.

Standard Streams

There are four standard streams. Their aliases and IDs (in brackets) are:

NOTE ==> The Amzi! IDE is a special application that makes use of the user_function [3] stream to allow Prolog to read and write from a window. Default I/O is redirected to the user_function [3] stream in the IDE. This is why it appears that the Prolog standard input/output appears in the IDE windows.

Typically, you will never need to use either the alias name or the stream ID for any of the standard streams. They will be set up as the current input/output streams.

Stream Properties

Each stream has a number of properties associated with it. For file streams, many are set when you open the file. For standard streams, they are predefined.

The properties are:

stream_property(STREAM, PROPERTY)

stream_property/2 can be used to query stream properties. It returns, on backtracking, all the properties of a given stream. If STREAM is an unbound variable, it returns the properties of all current streams.

String I/O

String I/O is also carried out via Prolog streams, but the interface is hidden from the user. The predicates string_term/2 and string_termq/2 are used to read and write single Prolog terms to and from strings. Because they work with single terms, the terms, unlike for other I/O predicates, do not need an ending period(.).

The same internal Prolog read/write routines used for other I/O is used for strings, so syntax errors will be reported/thrown just as from other I/O predicates.

Open and Closing Files

File streams can be directly opened and closed.

open(FILE, MODE, ID, OPTIONS)

open/4 opens a file stream. The arguments are:

Example:

open/4,3 throws an error when an attempt is made to open, for reading, a file that does not exist. You might want to use catch/3 to handle this error in your program, say by printing a message and failing as in this example:

open(FILE, MODE, ID)

open/3 can be used when the default options are to be used. These are type(text) and alias(FILE).

Example:

close(ID)

close/1 is used to close a stream.

Reading and Writing Prolog Terms

read(TERM), read(ID, TERM)

read/1,2 behaves as follows:

Each term read must be terminated with a period(.). This is true even for terms entered at the console. Input terms can cover multiple lines, which means if you forget to enter the period(.), you can enter it on the next line.

If the current input is a file then Prolog expects to be able to read a Term from the file. This query can be used to read all of the clauses from a Prolog source file:

write(TERM), write(ID, TERM)

write/1,2 write Prolog terms to stream ID, or the current output stream. Variables names are not preserved, but are regenerated. Atoms, strings, and other optionally quoted primitives are written without their quotes. This means terms written using write/1,2 may not be readable by read/1,2. Use write quoted, writeq/1,2 (see below), for terms that are intended to be read back.

Examples:

writeq(Term), writeq(ID, Term)

Write quoted, writeq/1,2, writes Prolog terms to stream ID, or the current output stream. Variables names are not preserved, but are regenerated. Atoms, strings, and other optionally quoted primitives are quoted if necessary. Terms written with writeq/1,2 can be read by read/1,2 without loss of meaning.

Examples:

Note that neither writeq/1,2 or write/1,2 writes the ending period required for input. That must be explicitly written as well if the term is to be later read. You probably want to write a newline (\n) character as well. Here is one way to generate a file to be later read (see listing below for a better way).

The example also illustrates writing a full Prolog rule, which requires parentheses around the rule to prevent the neck (:-) and comma (,) operators from swallowing the surrounding Prolog text.

listing, listing(PRED), listing(ID, PRED)

listing/0,1,2 uses pp/1 (see below) to write dynamic clauses from the logicbase to either current output or the stream ID specified. Using listing from the listener, is a good way to see the dynamic clauses in the logicbase.

Using listing with a stream ID is a good way to write out clauses that are intended to be later read. This is because pp/1 uses writeq and puts on the ending period(.) and newline indicator.

The PRED argument can be:

listing/2 can be used to save dynamic predicates that represent the state of an application. They can then be re-read using consult.

For example, in a game where the state of the game is kept in a predicate location/2, the following predicates implement a save/restore capability for the game.

pp(TERM), pp(ID, TERM)

pp/1,2 (pretty print) write terms with indentation and terminating period(.) and new line indication. Variable names are not preserved, and generated variable names are used instead. If you implement your own version of pp/1 and call it user_pp/1, then pp will use your version instead. This means that listing will use your version as well.

consult(FILE), reconsult(FILE)

(re)consult/1 reads all of the clauses from an input Prolog source file, or list of files, and asserts them as dynamic clauses in the logicbase. It also applies any directives that are part of that file(s). It is often used from the listener, but can be used directly by an application to dynamically read a source file, as in the save/restore example above.

reconsult/1 differs from consult/1, in that it retracts any previous clauses for a predicate that is about to be reconsulted.

If a path name is used in FILE, it can use either forward or backward slashes to separate directories. If backslashes are chosen, then two are usually required, unless string_esc is off.

display(Term)

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

Reading and Writing Strings and Characters

get(CODE), get(ID, CODE)

get/1,2 succeeds if CODE can be unified with the code value of the next non-white space character read from the appropriate file, or end_of_file.

get0(CODE), get0(ID, CODE)

get0/1,2 succeeds if CODE can be unified with the code value of the next character read in from the stream specified by ID, or end_of_file.

put(CODE), put(ID, CODE)

put/1,2 writes a character corresponding to the CODE value.

read_string(STRING), read_string(ID, STRING)

read_string/1,2 succeeds if STRING can be unified with the next line of characters read from the stream. The end-of-line indicator is not included as part of the string.

STRING is unified with the atom end_of_file at the end of a file.

read_string can be used with string_term to read Prolog terms without requiring the ending period, as the following get_input predicate illustrates.

You can also add your own error handling:

Trying it:

Special Purpose I/O Predicates

at_end_of_stream(ID)

Succeds if the file position is at the end of the specified stream.

flush_output, flush_output(ID)

These predicates flush either current output or the specified output stream.

nl, nl(ID)

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

skip(CODE), skip(ID, CODE)

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

tab(N), tab(ID, N)

tab/1,2 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.

respkey(CHAR)

respkey/1 gets a single character response key from the keyboard. This means it doesn't require an enter as get/1,2 does. It is useful for applications that want an immediate response from a user. An example of its use is the Amzi! debugger in console mode, where single letter keys trigger debugger actions.

Repositioning Streams

Certain streams, in particular files, can be repositioned. This is most often the case with binary files, but text files can be repositioned as well.

To get the current position of a file, use the stream_property/2 predicate as follows:

stream_property(ID, position(Position))

To set the position, use set_stream_position/2.

set_stream_position(ID, POSITION)

You can use the first call above, to get a position, and the second call to reset the stream to that position, without having to know the details of how POSITION is represented.

For example, this series of goals saves a position and then goes back to it:

Using it on this file, duck.pro:

produces these results:

Repositioning can be used to restart reading in a file, or in read/write files to read elements written previously, or to insert elements in a file, such as adding a length at the beginning of the file, which was only determined after writing the file. Repositioning is probably of the most use with binary files. (For example, it is used heavily in the Amzi! compiler, written in Prolog, which generates the binary output format .plm files.)

The POSITION argument is a structure, defining the state of the stream. It allows for the specification of both a read and a write position. BUT, this feature is only supported if the underlying tools used to build the Amzi! runtime engine support dual positions. On most platforms, both are the same, and setting one sets the other.

The POSITION argument is different for text and binary streams. For binary streams, the POSITION structure is relatively straight forward:

binary(ORIGIN, READ_POSITION, WRITE_POSITION).

Here is an example of reading the above sample file as a binary file, reading a character at a time:

For text streams, the POSITION structure is more complex for the read position. This is because input streams are read a line at a time, and the line is converted from possible multi-byte (variable length) characters in the stream to wide characters internally. It's form is:

text(ORIGIN, READ_LINE, READ_COLUMN, READ_OFFSET, WRITE_POSITION).

You should never attempt to manually set the arguments of the text position. You can get position information, save it, and then reuse it, but attempts to generate text positions by hand can lead to unpredictable results.

Redirecting Streams

There are two sets of predicates for redirecting streams. The predicates with input/output in their names are part of the ISO standard for Prolog. The see/tell family of predicates are from the older defacto Edinburgh standard for Prolog.

In either case, the predicates are used to examine or to change the current (default) input/output streams. The current input/output streams are the ones used by I/O predicates that do not have stream ID specified.

For example, listing output is redirected to a file during this listener session:

(The fact that COUT = 3 in the example, is due to the example being run in the Amzi! Windows IDE. It would have been 1 in a console application.)

NOTE ==> It is important to always first save the current input/output stream before changing the stream. This allows you to reset the stream to its previous value.

current_input(ID)

current_input/1 unifies ID with the current input stream.

current_output(ID)

current_output/1 unifies ID with the current output stream.

set_input(ID)

set_input/1 sets the current input stream to ID.

set_output(ID)

set_output/1 sets the current output stream to ID.

see(ID), see(FILENAME), see(user)

see/1 redirects the current input to ID. Unlike set_input/1, the argument to see/1 does not have to be a valid stream ID. If its not, see/1 will assume the argument is a file name and open it for input. The special atom user indicates that current input should be directed to the normal user input stream. This would be 0 for console applications and 3 for Amzi! Windows IDE applications.

seen

The current input stream is closed and is redirected to user, see above.

seeing(ID)

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

tell(ID), tell(FILENAME), tell(user)

tell/1 redirects the current output to ID. Unlike set_output/1, the argument to tell/1 does not have to be a valid stream ID. If its not, tell/1 will assume the argument is a file name and open it for output. The special atom user indicates that current output should be directed to the normal user output stream. This would be 1 for console applications and 3 for Amzi! Windows IDE applications.

told

The current output stream is closed and is redirected to user, see above.

telling(ID)

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

Binary I/O

The binary I/O predicates view a stream as a stream of bytes, without recognition of end-of-line indicators. The predicates read/write various types of data, such as integers, floats and characters. The DATA_TYPE in the two predicates can be one of the following atom values:

read_binary(ID, DATA_TYPE, DATA)

read_binary/3 reads a binary quantity, of type DATA_TYPE, from stream ID and unifies the read quantity with DATA.

write_binary(ID, DATA_TYPE, DATA)

write_binary/3 writes a binary quantity, DATA, of type DATA_TYPE, to stream ID.

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-2011 Amzi! inc. All Rights Reserved. Amzi! is a registered trademark and Logic Server is a trademark of Amzi! inc.