Quintus
Prolog Manual
This chapter describes how Prolog streams are represented as C data structure how streams can be configured to handle various file formats and how to create a customized Prolog stream in C.
The Quintus Prolog input/output system is designed to handle various file formats, device-dependent I/O, and in particular, it enables you to create customized Prolog streams in C. File-related input/output operations of a Prolog program can be coded to be portable among different operating systems and the underlying formats of files. A Prolog stream is an object storing the information about how to complete input/output operations to a file, device or other form of I/O channel. All Prolog input/output operations are performed through Prolog streams. The embedding ("bottom") layer of the Quintus Prolog system provides a set of default functions for handling normal Prolog streams. However, user defined streams can be defined at runtime in such a way that Prolog built-in I/O operations work on other types of Prolog stream. Examples of user-defined streams include:
There are three streams opened by the embedding layer I/O initialization functions when a Prolog process starts:
Prolog also keeps track of two current streams,
A Prolog input/output built-in predicate or function which takes no stream argument is performed on the current input or current output stream. In this Chapter, we outline the Quintus Prolog input/output model and describ the Prolog stream structure, defining the different formats and options that can be associated with a stream. Then we discuss the method of creating a user-defined stream. We also list a number of functions which enable Prolog streams to be manipulated in foreign code. Finally we discuss some compatability issues with the I/O system in previous versions of Quintus Prolog.
Note: The terms 'record' and 'line' have the same meaning in this chapter. A line terminated with either a new line or linefeed character is just a type of record. However, a record (line) is not always terminated by a new line character or line feed character.
There are three layers of input/output operations visible in the Prolog system. The top layer is character based. It supports reading a character, writing a character and testing the state of a Prolog stream. get0/[1,2] and put/[1,2] are examples of the first layer operation. The middle layer is record based. Its primary function is to keep the integrity of a record, through such operations as trimming a record, padding a record and handling output overflow. This layer is not visible to the user and cannot be changed by the user. The bottom layer is buffer based. It performs the actual input from or output to the underlying device of a Prolog stream. The bottom layer is a collection of five functions associated with a stream: read, write, flush, seek and close functions. Typically the read function reads data from the underlying device into a buffer that it maintains and then passes this data up to the middle layer a record at a time. The write function provides buffer space for a record to be received from the middle layer and writes out the buffer to the underlying device. The embedding open function QU_open() assigns the appropriate bottom layer functions for a stream created by open/[3,4]. A user-defined Prolog stream must supply its own bottom layer functions for the stream. Bottom layer functions are described in {manual(i-5-6)}.
Writing to a linefeed (newline) character record file stream demonstrates how the three layers work together. Each put/2 call on the stream simply stores a character in the record buffer of the stream. When the top layer predicate, nl/1, is called on the stream, the middle laye called. The middle layer function stores a linefeed character in the record buffer and updates some counters for the stream. It then calls the bottom layer function of the stream. The bottom layer writes out the record to the output file.
Note: In addition, the top layer contains predicates and C functions to perform seek, flush output and close operations on a stream. There are no middle layer functions for these operations.
The Prolog representation of a Prolog stream provides a way of retrieving inf structure. It is currently represented as a Prolog term in the form of '$stream'(N) where N is an integer to identify the stream. However, a user application should not explicitly create such a Prolog term as a Prolog stream, it should only be obtained through open/[3,4], open_null_stream/1, or stream_code/2. In C code, a Prolog stream is represented as a pointer to structure of type QP_stream. The formatting information along with bottom layer functions of a stream is stored in its QP_stream structure. A stream behavior depends on the values of fields in its QP_stream structure. The options specified in open/4 are converted and stored in the QP_stream structure created for the stream. Selected fields in QP_stream() which can be accessed and modified by an application are described in the remainder of the section. Most of these fields are option fields which can be specified in open/4.
open/[3,4]: 1st argument QP_stream: char *filename
The filename of a stream is recorded in the stream structure. If a stream does not have a filename, (e.g., a data communication channel), the filename field should be an empty string. After a stream is created, the Prolog system accesses this field only to get the corresponding filename of a stream.
open/[3,4]: 2nd argument QP_stream: unsigned char mode
This field indicates whether the stream is created for input or output mode. The mode field is QP_READ for an input (read only) stream, QP_WRITE or QP_APPEND for an output stream. This field should not be changed for the lifetime of a stream.
open/4 option: format(Format) QP_stream: unsigned char format
This field indicates the format of a stream. The format tells the middle layer functions how to wrap (unwrap) a record. Possible formats are:
For a delimited record format stream, the middle layer input function removes the delimiting character at the end of each record and the line border code for the stream is returned to a top layer input predicate (function) when the end of a record is reached. The middle output layer output function adds the delimited character at the end of each record before the record is passed to the bottom layer write function. The format field may be set to QP_FMT_UNKNOWN when a stream structure is created if the format to be used is not known yet, for example, because the underlying device is not yet opened. This format field must be set to a proper format before any I/O takes place on the stream. An example of this is when opening a Prolog stream through open/[3,4] or QP_fopen() without specifying the format. The embedding open function, QU_open() is given the stream format QP_FMT_UNKNOWN and thus chooses an appropriate format for the stream based on the filename and options of the stream. Depending on the host operating system, some formats may be used more frequently than others. QP_DELIM_LF and QP_DELIM_TTY are the most frequently used formats for a Prolog system running under Unix. QP_VAR_LEN is more frequently used under VMS.
open/4 option: record(MaxRecLen) QP_stream: long int max_reclen
This field indicates the maximum record length of the stream. It is usually also the buffer length of the stream. The value in this field is not changed once the stream is created. The maximum record length can be bigger than the value store in the max_reclen field for some operating systems allowing reading or writing partial records, such as the Unix operating system. To create an unbuffered output stream, the value in max_reclen must be set to 0. The bottom layer write function is then called for each character placed in the output stream. To create an unbuffered input stream, the value in max_reclen can be either 0 or 1.
open/4 option: end_of_line(LineBorder) QP_stream: long int line_border
The line_border field can be any integer which is greater than or equal to QP_NOLB (-1). If the value is QP_NOLB, there is no line border code for the stream. For an input stream, the line border code is the value to be returned in getting a character when a stream is positioned at the end of a record. (Notice that if the stream is a delimited record format stream, the delimited character has already been removed.) If there is no line border code, the first character in the next record is returned at the end of a record. Writing the line border code (i.e. with put/[1,2]) to an output stream signals the end of the current record. Instead of writing out the line border code, the record is appropriately wrapped up based on the format of the stream, and the wrapped record is handed to the bottom layer write function of the stream. This is just like a new line operation on the stream (e.g. nl/[0,1] or QP_newln()).
open/4 option: end_of_file(FileBorder) QP_stream: long int file_border
This is the code to be returned on reading at the end of file for an input stream. This field is not used for an output stream. The value in file_border field can be any integer which is greater than or equal to QP_NOFB (-2). If the value is QP_NOFB, there is no file border code for the stream. If the file_border field is QP_NOFB, reading at the end of file is the same as reading past the end of file.
open/4 option: eof_action(Action) QP_stream: unsigned char peof_act
This field is only used for an input stream. There are three states for an input stream, normal, end of file, and past end of file. An input stream is in normal state until it reaches end of file, where the state is switched to end of file state. If there is no file border code or the file border code is consumed, the input stream is switched to past end of file state. The field peof_act specifies which action to take for reading from a stream at past end of file state. The value of peof_act can be one of the following.
Once an input stream reaches end of file state, its bottom layer read functio will not be called unless the peof_act field is QP_PASTEOF_RESET.
QP_stream: char *prompt
A prompt string is a character string that will be written out when a tty stream (a stream with QP_DELIM_TTY format) reads input at the beginning of a line. The prompt string is stored in this field. This field is only used for an input tty stream. The prompt string will not be written out if there are no output tty streams connected to the same tty as the input tty stream ({manual(i-5-4)}). A prompt string of a stream may be changed after the stream is created through prompt/[2,3] or by assigning a new character string to this field directly.
open/4 option: trim QP_stream: unsigned char trim
A non-zero value in trim field indicates trimming should be performed on each record of an input stream with either QP_FIX_LEN or QP_VAR_LEN format. The trailing blank characters in each of the records are removed in the trimming operation. This field has no impact on an input stream with other formats or for any output stream.
open/4 option: seek(SeekType) QP_stream: unsigned char seek_type
This field specifies which type of seeking can be performed on the stream. The possible values for seek_type are:
The seek function must be supplied for a user-defined stream with seek_type set to any value other than QP_SEEK_ERROR.
open/4 option: flush(FlushType) QP_stream: unsigned char flush_type
This field specifies whether or not the characters buffered in an output stream can be written out immediately as a partial record. It is not used for an input stream. Characters written to an output stream are buffered until the current record is terminated or the output buffer overflows. When an output record is terminated, it is passed to the bottom layer of the write function of the stream. The completed record is either written to the associated device of the stream or further buffered by the bottom layer write function. If the output buffer overflows, or flush_output/1 is called, buffered characters may be forced out through flushing the output stream. The possible values for flush_type are:
open/4 option: overflow(OverFlowAction) QP_stream: unsigned char overflow
Written characters are stored in the buffer of an output stream by the middle layer output function until the current record is terminated, through a newline operation (such as nl/[0,1] or QP_newln()) or by writing the line border code of the stream. If a character is written when the buffer of an output stream is full, it overflows the output buffer. The overflow field specifies the action that the middle layer function should take if this happens. The possible values for overflow are:
Note that if an output stream is unbuffered (i.e. max_reclen is 0) then the middle layer function ignores the meta(overflow) field and calls the meta(write) function for each character written to the stream.
QP_stream: int errno;
An error code is stored in the errno field of a stream structure when an error is detected in any of the three layers of input/output functions. The top layer QP functions set the error code to QP_errno when an error occurs in the call to the functions. The error code stored in this field may not last more than two QP input/output functions calls since middle layer functions and some QP functions clear out this field before they call the bottom layer function. If an error is detected in the bottom layer function, the errno field should be given an appropriate error code before the function returns. The error code stored in errno field can be any of the host operating system error numbers, QP error numbers or user-defined error numbers.
QP_stream: union QP_cookie magic;
The system-dependent address of the current position in a stream is stored in the magic field of the stream structure. It is only used when there is any kind of seek to be performed on the stream. This field is a C type union cookie, which is defined in quintus.h as follows:
union QP_cookie {
struct RFA {
long BlockNumber;
short Offset;
} vms_rfa;
long mvs_rrn;
int cms_recno;
long byteno;
long user_defined[2];
} magic;
Depending on the host operating system, different field names of union cookie are used to store the position address of the stream depending on the host operating system. On Unix magic.byteno is used to record the current location of the file pointer as an absolute byte offset from the beginning of the stream; magic.vms_rfa is used on VMS; magic.mvs_rrn is used on MVS; magic.cms_recno is used on CMS. magic.user_defined is used for a user's specific method of recording the current location of a stream. These values must be maintained in the bottom layer functions of a stream with seek permission.
QP_stream: int (*read)(), (*write)(), (*flush)(),
(*seek)(), (*close)();
These fields store the address of the corresponding bottom layer functions of the stream. See {manual(i-5-6)} for description about how to define these functions.
A Prolog stream is a tty stream if the format of the stream is QP_DELIM_TTY. A tty stream is normally associated with a terminal device, a pseudo-terminal device or a terminal emulator. A set of tty streams can be grouped together through a distinct character string key for each group of tty streams. All the tty streams from the same tty device (emulator) should normally be grouped together. A tty stream registers itself to a tty stream group by calling QP_add_tty() with the specific character string key for the group. There are two services provided automatically by Prolog I/O system to each tty stream group. When a tty stream is closed, it is automatically removed from its tty group.
A sample Prolog session demonstrates the special services performed for tty streams. The default open/[3,4] automatically registers tty streams to the tty group using filename as the key. After writing "write\n" to Output1, the counts for Output1 and Output2 are brought up to date. The counts in Input1 is not changed since counts in input stream are only updated when reading from the input stream. After reading from Input1, the counts for all the three streams are updated. The prompt "INPUT>> " is written out either through Output1 or Output2, so it is included in the counts. The count in Input1 is different from Output1 and Output2 since only character 'r' is consumed in the input of "read\n".
| ?- compile(user).
| write_count(Input, Output1, Output2) :-
character_count(Input, C0), line_count(Input, L0),
line_position(Input, P0),
character_count(Output1, C1), line_count(Output1, L1),
line_position(Output1, P1),
character_count(Output2, C2), line_count(Output2, L2),
line_position(Output2, P2),
format('input : ~d, ~d, ~d~n', [C0, L0, P0]),
format('output1 : ~d, ~d, ~d~n', [C1, L1, P1]),
format('output2 : ~d, ~d, ~d~n', [C2, L2, P2]).
| ^D
% user compiled in module user, 0.216 sec 384 bytes
yes
| ?- open('/dev/tty', read, Input), prompt(Input, _, 'INPUT>> '),
open('/dev/tty', write, Output1),
open('/dev/tty', write, Output2),
format(Output1, 'write~n', []),
write_count(Input, Output1, Output2),
get0(Input, _), write_count(Input, Output1, Output2).
write
input : 0, 1, 0
output1 : 6, 2, 0
output2 : 6, 2, 0
INPUT>> read
input : 15, 2, 9
output1 : 19, 3, 0
output2 : 19, 3, 0
Notice that the I/O in the user_input and user_output are not included in the counts although both streams are connected to the same tty. The three default streams (user_input, user_output and user_error) are put into a different tty group in the embedding initialization function, QU_initio().
In addition to calling default open predicates or functions -- such as open/[3,4] or QP_fopen() to create a stream, a user can define a stream through the method described in this section. A common reason to do this is to create a stream which is not supported directly by the Prolog input/output system, such as a stream for inter-process communication. The created stream can be passed as the stream argument to all the input/output related Prolog predicates and functions. This section presupposes {manual(i-5-3)} on stream structure.
The following steps are required to create a user-defined stream in foreign code, such as C. The stream is represented in C as a pointer to a QP_stream structure. It can then be converted back to Prolog stream representation through stream_code/2. The predicate stream_code/2 converts, in either direction, between Prolog and C representations of a stream.
These steps are described in more detail in the remainder of this section. An example of creating a stream for a binary file in one of read, write or append modes on Unix is discussed. The example is written in C although it can also be written in other languages, such as Pascal or Fortran. The example opens a file as a binary stream. The characters input from or output to the stream are exactly the same as stored in file. Seeking to a random byte position and flushing output are permitted in the stream. The first example lists complete source code ({manual(i-5-7)}). Note that binary streams are in fact supported in the system.
The first field of the user-defined stream structure must be of type QP_stream. Other fields in the user-defined stream structure can be anything that is required to operate on the user-defined stream. The Prolog input/output system passes a QP_stream pointer as the first argument to the bottom layer functions; casting this to the user-defined stream structure enables other fields in the user-defined stream to be accessed. The example below declares a binary stream structure as:
typedef struct
{
QP_stream qpinfo;
int fd; /* Unix file descriptor */
int last_rdsize; /* size of last returned record */
unsigned char buffer[Buffer_Size]; /* I/O buffer */
} BinStream;
#define CoerceBinStream(x) ((BinStream *)(x))
The field qpinfo stores information about the binary stream known to the Prolog input/output system. There is a buffer field in the structure since the I/O buffer is allocated by the user. The macro CoerceBinStream is used to convert a pointer to QP_stream into a pointer to BinStream. We use this macro to convert the pointer so that fields in the BinStream structure can be accessed.
Depending on the specific user-defined stream, there are different operations needed for the stream. A stream which operates on a file needs to open the file; a stream which operates for inter-process communication needs to build the connection to different process. Our example stream operates on files, so we just open the file to get file descriptor. The parameters of our function and local variables in the function are also listed.
QP_stream *
open_bin(filename, modename, error_num)
char *filename, *modename;
int *error_num;
{
BinStream *stream;
QP_stream *option;
int fd, mode;
switch (*modename) {
case 'r': mode = QP_READ;
fd = open(filename, O_RDONLY, 0000);
break;
case 'w': mode = QP_APPEND;
fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC,
0666);
break;
case 'a': mode = QP_APPEND;
fd = open(filename, O_WRONLY|O_CREAT, 0666);
break;
default: *error_num = QP_E_BAD_MODE;
return QP_NULL_STREAM;
}
if (fd < 0) {
*error_num = errno;
return QP_NULL_STREAM;
}
...... allocate space and set user-stream fields .......
...... setup QP_stream structure fields ......
...... register the created QP_stream ......
return &stream->qpinfo;
}
This function can be called from Prolog using the Prolog calling C interface described in {manual(i-3)}. The address returned by this function is converted into the Prolog representation of a stream using stream_code/2.
The memory space for a user-defined stream structure and its buffer are controlled by the user application. It is recommended to use QP_malloc() to allocate the space for more efficient memory utilization in the Prolog system. In our example, the buffer is a field in the BinStream structure so that only one QP_malloc() call allocates both buffer and stream space. After memory is allocated, the fields in the stream structure are set appropriately. The fields of QP_stream part in the stream structure is set up in the next step.
if ((stream = (BinStream *) QP_malloc(sizeof(*stream)))
== ((BinStream *) 0) ) {
(void) close(fd);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
stream->fd = fd;
stream->last_rdsize = 0;
The default values in the fields of QP_stream part of the user-defined stream are set through the QU_stream_param() function. The declaration of QU_stream_param() is given as:
void QU_stream_param(filename, mode, format, option)
char *filename;
int mode;
int format;
QP_stream *option;
If the stream does not have a filename, the empty string "" should be used. The parameter 'mode' can be one of QP_READ, QP_WRITE or QP_APPEND. The parameter 'format' can be one of the format types listed in {manual(i-5-3-3)}. The default version of QU_stream_param() source code is shipped with Quintus Prolog ({manual(m-3)} also lists the source). The fields in the QP_stream structure can then be modified based on the desired features of the user-defined stream. All the fields described in the Stream Structure section can be modified ({manual(i-5-3)}), but often the only modified fields are max_reclen, seek_type and bottom layer function fields. In our example, the format QP_VAR_LEN is chosen for non-tty files, and the line_border field is reset so that the middle layer functions do not alter any of the input/output records. The fields max_reclen and seek_type are set to the right values for our stream. The bottom layer function fields are set based on the mode and the seek_type of the stream. If the stream is opened for append, the file pointer of the stream is moved to the end of file and the magic field is updated (magic.byteno is used since it is a Unix file).
option = &stream->qpinfo;
QU_stream_param(filename, mode, QP_VAR_LEN, option);
option->max_reclen = Buffer_Size;
option->line_border = QP_NOLB;
if (isatty(fd)) {
option->format = QP_DELIM_TTY;
option->seek_type = QP_SEEK_ERROR;
} else {
option->seek_type = QP_SEEK_BYTE;
option->seek = bin_seek;
}
if (mode != QP_READ) {
option->write = bin_write;
option->flush = bin_write;
} else
option->read = bin_read;
if (option->mode == QP_APPEND &&
option->format != QP_DELIM_TTY) {
if ((option->magic.byteno=lseek(fd,0L,L_XTND)) < 0) {
(void) close(fd);
*error_num = errno;
return QP_NULL_STREAM;
}
}
option->close = bin_close;
The fields of QP_stream structure used internally by the Prolog system are initialized through QP_prepare_stream(). It should be called after other fields in QP_stream are properly set up. QP_prepare_stream() takes a pointer to QP_stream as its first parameter and the address of the input/output buffer for the stream as its second parameter. QP_register_stream() is then called to register the user-defined stream so that the stream can be used in Prolog code. In our example, if the registration fails the bottom layer function is used to close the opened file and deallocate the memory space for the created stream and a null stream is returned.
QP_prepare_stream(&stream->qpinfo, stream->buffer);
if (QP_register_stream(&stream->qpinfo) == QP_ERROR)
{ (void) stream->qpinfo.close(&stream->qpinfo);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
This is an optional step only for tty streams. A tty stream needs to register to its group for special tty service (see {manual(i-5-4)}). Finally the pointer to the created stream is returned to Prolog, and converted to Prolog stream representation through stream_code/2. In our example, we use the file name of the stream as the key to register into its group.
if (option->format == QP_DELIM_TTY)
(void) QP_add_tty(&stream->qpinfo, filename);
return &stream->qpinfo;
There are five bottom layer functions for a stream: read, write, flush, seek and close. However, not all of these functions are needed for every stream:
These functions usually only operate on the specific fields of a particular user-defined stream. (i.e. Fields other than the first field in a user-defined stream structure.) The errno field and magic field in QP_stream part may also be maintained by the bottom layer functions. The mode field and max_reclen field are also typically accessed by the bottom layer functions. Except for the read function, all the bottom layer functions return QP_SUCCESS upon success, and return QP_ERROR and assign an error code to the errno field upon failure. These functions are described further in subsequent sub-sections.
int <read function>(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
Return Values: QP_FULL : a complete record is read
QP_PART : a partial record is read
QP_EOF : end of file is reached
QP_ERROR : a partial record is read
The bottom layer read function returns a record of input to its caller. The returned record is buffered. The buffer address is returned through *bufptr parameter and the size of the returned record is stored in *sizeptr parameter. The magic field in qpstream should be updated to the system-dependent file address ({manual(i-5-3-14)}) for the beginning of the returned record. If there is no seek permission for the stream, the magic field may be ignored. The errno field in QP_stream stores the error code if an error is detected in the function. In our example, the read function does not return QP_PART since any length of input is chosen as a complete record.
static int
bin_read(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
int n;
register BinStream *stream = CoerceBinStream(qpstream);
qpstream->magic.byteno += stream->last_rdsize;
stream->last_rdsize = 0;
n = read(stream->fd, (char*) stream->buffer,
(int) qpstream->max_reclen);
if (n > 0) {
*bufptr = stream->buffer;
*sizeptr = n;
stream->last_rdsize = n;
return QP_FULL;
} else if (n == 0) {
*sizeptr = 0;
return QP_EOF;
} else {
qpstream->errno = errno;
return QP_ERROR;
}
}
int <write function>(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
Return Values: QP_SUCCESS
QP_ERROR
The bottom layer write function writes out a record from buffer address stored in *bufptr and the size of the record stored in *sizeptr. Upon successful return, *sizeptr stores the maximum record size and *bufptr stores the address of the beginning of the buffer for the next output record. The magic field in qpstream should be updated to the system-dependent file address ({manual(i-5-3-14)}) for the beginning of the next output record. If there is no seek permission for the stream, the magic field may be ignored. The errno field in QP_stream stores the error code if an error is detected in the function and QP_ERROR is returned. The output record passed into the write function may be a partial record if output record overflows the output buffer for a stream which permits overflow.
static int
bin_write(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
BinStream *stream = CoerceBinStream(qpstream);
int n, len=(int) *sizeptr;
char *buf = (char *) *bufptr;
while ((n = write(stream->fd, buf, len)) > 0 && n < len) {
buf += n;
len -= n;
}
if (n >= 0) {
qpstream->magic.byteno += *sizeptr;
*sizeptr = qpstream->max_reclen;
*bufptr = stream->buffer;
return QP_SUCCESS;
} else {
qpstream->errno = errno;
return QP_ERROR;
}
}
int <flush function>(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
Return Values: QP_SUCCESS
QP_ERROR
The parameters and the return values of the flush function have the same syntax and the same meaning as the write function. The write function may buffer the output record without writing the record out. The flush function should write out the output record immediately when it is called. The middle layer function will not call the write function with an empty record (*sizeptr is zero), but the flush function may be called with an empty record passed in. In general, the flush function can be the same as write function unless the write function buffers output records. An output stream needs a bottom layer flush function only if flush_type of the stream is not FLUSH_ERROR. In our example, the bottom layer write function does not buffer output record and it can also handle writing an empty record, so the bottom layer flush function is the same as the write function.
int <seek function>(qpstream, qpmagic, whence, bufptr, sizeptr)
QP_stream *qpstream;
union QP_cookie *qpmagic;
int whence;
unsigned char **bufptr;
long int *sizeptr;
Return Values: QP_SUCCESS
QP_ERROR
The bottom layer seek function sets the stream qpstream to a new position based on the method whence and the system-dependent file address qpmagic specified in the parameters. The output parameter *bufptr stores the beginning of the buffer and *sizeptr stores the size of record. When the bottom layer seek function is called, the magic field of qpstream is the current stream position known to the user of the stream. (It does not include the unconsumed characters in the buffer.) Upon success, the seek function should return QP_SUCCESS and the magic field of qpstream should be updated to the new position. Upon failure, it returns QP_ERROR and suitable error code should be assigned to error field of qpstream. The stream is set to a new position based on the whence value and qpmagic values. ({manual(i-5-3-14)})
Due to the buffering mechanism of a stream, the magic field in qpstream might be different from the actual position for the file (or other devices) associated with the stream. For example, if the current record of an input stream has a size of 10 and there are only 5 characters consumed, the magic field indicates the position at the sixth character rather than the 11th character of the current record. In short, the value in magic field of qpstream does not count any characters in the buffer which are not consumed in an input stream. For an output stream, the caller of the bottom layer seek function will call flush function to flush output prior to calling this function if qpstream permits flushing (flush_type is not QP_FLUSH_ERROR ). The caller of this function does not permit any seeking in an output stream with no flush permission if there are characters in the current record (line position is not zero in the output stream). However, if the bottom layer of an output stream without flushing permission buffers more than one output record, it is possible for the bottom layer seek function to be called with records in the buffer. (This would be the case that there are no characters in the current record.) The bottom layer seek function should write out the records in the buffer for this case. One effect of seeking is to clear out the buffer of a stream. This should be adhered to in implementing the bottom layer seek function. If qpstream is an input stream, bufptr and sizeptr have the same meaning as in the bottom layer read function. If qpstream is an output stream, bufptr and sizeptr have the same meaning as in the bottom layer write function. So for most of cases, *bufptr should be set to the beginning of the buffer of the stream and *sizeptr should be set as follows:
*sizeptr = (qpstream->mode == QP_READ) ? 0 : qpstream->max_reclen;
The bottom layer seek function for our example stream:
static int
bin_seek(qpstream, qpmagic, whence, bufptr, sizeptr)
QP_stream *qpstream;
union QP_cookie *qpmagic;
int whence;
unsigned char **bufptr;
long int *sizeptr;
{
BinStream *stream = CoerceBinStream(qpstream);
long int new_offset;
switch (whence) {
case QP_BEGINNING:
new_offset = lseek(stream->fd,qpmagic->byteno,L_SET);
break;
case QP_CURRENT:
/* The current location of file pointer is different
from what the user thinks it is due to buffering.
The magic field has been brought up to date by the
caller of this function already, so just seek to
that position first. */
if (lseek(stream->fd, qpstream->magic.byteno, L_SET)
== -1) {
qpstream->errno = errno;
return QP_ERROR;
}
new_offset = lseek(stream->fd,qpmagic->byteno,L_INCR);
break;
case QP_END:
new_offset = lseek(stream->fd,qpmagic->byteno,L_XTND);
break;
default:
qpstream->errno = QP_E_INVAL;
return QP_ERROR;
}
if (new_offset == -1) { /* error in seeking */
qpstream->errno = errno;
return QP_ERROR;
}
bufptr = stream->buffer;
*sizeptr = (qpstream->mode == QP_READ) ? 0
: qpstream->max_reclen;
stream->last_rdsize = 0;
return QP_SUCCESS;
}
int <close function>(qpstream)
QP_stream *qpstream;
Return Value: QP_SUCCESS
QP_ERROR
The bottom layer close function performs the specific close operation of a user-defined stream and deallocates the memory space for the stream. It returns QP_ERROR and assigns an appropriate error code to the errno field of QP_stream if an error occurs in the function. In our example, we use QP_free() to deallocate memory space since the memory is allocated by QP_malloc().
static int
bin_close(qpstream)
QP_stream *qpstream;
{
BinStream *stream = CoerceBinStream(qpstream);
int fd = stream->fd;
if (close(fd) < 0) {
qpstream->errno = errno;
return QP_ERROR;
}
(void) QP_free(qpstream);
return QP_SUCCESS;
}
Three examples of creating a user-defined stream are listed in this section. The foreign code examples are written in C language under Unix operating system.
This example creates a binary stream. All the characters read from the stream are exactly the same as the characters stored in the file of the stream. All the characters stored in the file of the stream are the same as the characters written to the stream. The stream permits flushing output and random seek to arbitrary byte position in the file. By choosing QP_VAR_LEN as the format of the stream and using the full buffer as a record to communicate between middle layer and bottom layer functions, a line is actually a full buffer of the stream. A newline operation does not output a linefeed character either so that the line count of the stream is not based on the number of linefeed (newline) characters,
example1% cat bin.pl
foreign(open_bin, c, open_bin(+string, +string, -integer,
[-address])).
foreign_file('bin.o', [open_bin]).
:- load_foreign_files(['bin.o'],[]).
open_bin_file(FileName, ModeName, Stream) :-
open_bin(FileName, ModeName, ErrorNum, CStream),
( CStream =:= 0 ->
raise_exception(existence_error(
open_bin_file(FileName, ModeName, Stream),
1, file, FileName, errno(ErrorNum)))
; stream_code(Stream, CStream)
).
example1% cat bin.c
#include <fcntl.h>
#include <errno.h>
#include <sys/file.h> /* for seek */
#ifndef L_SET
#define L_SET 0
#endif
#ifndef L_INCR
#define L_INCR 1
#endif
#ifndef L_XTND
#define L_XTND 2
#endif
#include "quintus.h"
extern char *QP_malloc();
/* The following three functions support Unix I/O on files
without breaking things into records. All the characters
read from or written to the file are kept exactly the same.
*/
#define Buffer_Size 8192
typedef struct
{
QP_stream qpinfo;
int fd; /* Unix file descriptor */
int last_rdsize; /* size of last returned line */
unsigned char buffer[Buffer_Size]; /* I/O buffer */
} BinStream;
#define CoerceBinStream(x) ((BinStream *)(x))
static int
bin_read(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
int n;
register BinStream *stream = CoerceBinStream(qpstream);
qpstream->magic.byteno += stream->last_rdsize;
stream->last_rdsize = 0;
n = read(stream->fd, (char*)stream->buffer,
(int) qpstream->max_reclen);
if (n > 0) {
*bufptr = stream->buffer;
*sizeptr = n;
stream->last_rdsize = n;
return QP_FULL;
} else if (n == 0) {
*sizeptr = 0;
return QP_EOF;
} else {
qpstream->errno = errno;
return QP_ERROR;
}
}
static int
bin_write(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
BinStream *stream = CoerceBinStream(qpstream);
int n, len=(int) *sizeptr;
char *buf = (char *) *bufptr;
while ((n = write(stream->fd, buf, len)) > 0 && n < len) {
buf += n;
len -= n;
}
if (n >= 0) {
qpstream->magic.byteno += *sizeptr;
*sizeptr = qpstream->max_reclen;
*bufptr = stream->buffer;
return QP_SUCCESS;
} else {
qpstream->errno = errno;
return QP_ERROR;
}
}
static int
bin_seek(qpstream, qpmagic, whence, bufptr, sizeptr)
QP_stream *qpstream;
union QP_cookie *qpmagic;
int whence;
unsigned char **bufptr;
long int *sizeptr;
{
BinStream *stream = CoerceBinStream(qpstream);
long int new_offset;
switch (whence) {
case QP_BEGINNING:
new_offset = lseek(stream->fd,qpmagic->byteno,L_SET);
break;
case QP_CURRENT:
/* The current location of file pointer is different from
what the user thinks it is due to buffering. The magic
field has been brought up to date by the caller of this
function, so just seek to that position first. */
if (lseek(stream->fd, qpstream->magic.byteno, L_SET)
== -1) {
qpstream->errno = errno;
return QP_ERROR;
}
new_offset = lseek(stream->fd,qpmagic->byteno,L_INCR);
break;
case QP_END:
new_offset = lseek(stream->fd,qpmagic->byteno,L_XTND);
break;
default:
qpstream->errno = QP_E_INVAL;
return QP_ERROR;
}
if (new_offset == -1) { /* error in seeking */
qpstream->errno = errno;
return QP_ERROR;
}
qpstream->magic.byteno = new_offset;
*bufptr = stream->buffer;
*sizeptr = (qpstream->mode == QP_READ) ? 0
: qpstream->max_reclen;
stream->last_rdsize = 0;
return QP_SUCCESS;
}
static int
bin_close(qpstream)
QP_stream *qpstream;
{
BinStream *stream = CoerceBinStream(qpstream);
int fd = stream->fd;
if (close(fd) < 0) {
qpstream->errno = errno;
return QP_ERROR;
}
(void) QP_free(qpstream);
return QP_SUCCESS;
}
QP_stream *
open_bin(filename, modename, error_num)
char *filename, *modename;
int *error_num;
{
BinStream *stream;
QP_stream *option;
int fd, mode;
switch (*modename) {
case 'r': mode = QP_READ;
fd = open(filename, O_RDONLY, 0000);
break;
case 'w': mode = QP_APPEND;
fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC,
0666);
break;
case 'a': mode = QP_APPEND;
fd = open(filename, O_WRONLY|O_CREAT, 0666);
break;
default: *error_num = QP_E_BAD_MODE;
return QP_NULL_STREAM;
}
if (fd < 0) {
*error_num = errno;
return QP_NULL_STREAM;
}
if ((stream = (BinStream *) QP_malloc(sizeof(*stream)))
== ((BinStream *) 0) ) {
(void) close(fd);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
stream->fd = fd;
stream->last_rdsize = 0;
/* obtain default values in QP_stream structure */
/* and modified fields for this stream */
option = &stream->qpinfo;
QU_stream_param(filename, mode, QP_VAR_LEN, option);
option->max_reclen = Buffer_Size;
option->line_border = QP_NOLB;
if (isatty(fd)) {
option->format = QP_DELIM_TTY;
option->seek_type = QP_SEEK_ERROR;
} else {
option->seek_type = QP_SEEK_BYTE;
option->seek = bin_seek;
}
if (mode != QP_READ) {
option->write = bin_write;
option->flush = bin_write;
} else
option->read = bin_read;
if (option->mode == QP_APPEND
&& option->format != QP_DELIM_TTY) {
if ((option->magic.byteno=lseek(fd,0L,L_XTND)) < 0) {
(void) close(fd);
*error_num = errno;
return QP_NULL_STREAM;
}
}
option->close = bin_close;
QP_prepare_stream(&stream->qpinfo, stream->buffer);
if (QP_register_stream(&stream->qpinfo) == QP_ERROR) {
(void) stream->qpinfo.close(&stream->qpinfo);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
/* Use filename to register tty stream to its group */
if (option->format == QP_DELIM_TTY)
(void) QP_add_tty(&stream->qpinfo, filename);
return &stream->qpinfo;
}
This example creates an input stream to read from a file encrypted using a simple encryption algorithm. The key is stored in the first byte of the file. A character code stored in the file is the result of a logical exclusive-or operation of the output character and the key. The decryption of the input file is done in the bottom layer read function. The input stream created only permits seeking to a previous read position. Notice the bottom layer read function defined (decrypt_read()) buffers more than one record. By doing this, the Prolog input/output system will maintain a correct line count and line position based on the new line character ('\n'). There are also two user-defined error numbers used in this example (DECRYPT_NO_KEY and DECRYPT_TTY_FILE).
example2% cat decrypt.pl
foreign(open_decrypt, c, open_decrypt(+string, -integer,
[-address])).
foreign_file('decrypt.o', [open_decrypt]).
:- load_foreign_files(['decrypt.o'],['-lc']).
open_decrypt_stream(FileName, PrologStream) :-
open_decrypt(FileName, ErrorNum, CStream),
( CStream =:= 0 ->
raise_exception(existence_error(
open_decrypt_stream(FileName, PrologStream),
1, file, FileName, errno(ErrorNum)))
; stream_code(PrologStream, CStream)
).
example2% cat decrypt.c
#include <fcntl.h>
#include "quintus.h"
extern int errno;
#define Buffer_Size 8192
typedef unsigned char Key_Type;
typedef struct
{
QP_stream qpinfo;
int fd; /* file descriptor */
int last_rdsize; /* size of last record */
int left_size; /* char. left unread */
unsigned char *left_ptr; /* pointer to the unread */
unsigned char buffer[Buffer_Size+3];
Key_Type key; /* decryption key */
} DecryptStream;
#define CoerceDecryptStream(qpstream) \
((DecryptStream *)(qpstream))
/* define user-defined error number */
#define DECRYPT_NO_KEY QP_END_ECODE+1
#define DECRYPT_TTY_FILE QP_END_ECODE+2
/*
To enable the Prolog system to maintain correct line count
and line position, a whole buffer is read but only a line
in the buffer is returned every time.
The characters in the buffer are decrypted at once.
The buffer is maintained as follows:
<- left_size ->
+---------------+-------------+--+-------+
| has been read | to be read |\n| empty |
+---------------+-------------+--+-------+
^ left_ptr ^ <pad '\n' character>
*/
static int
decrypt_read(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
register DecryptStream *stream =
CoerceDecryptStream(qpstream);
register int n;
register unsigned char *s, *s1;
/* magic is the beginning byte offset of return record */
qpstream->magic.byteno += stream->last_rdsize;
if (stream->left_size <= 0) {
register Key_Type *kp, *kq, key;
/* read a new buffer of input and decrypt characters*/
n = read(stream->fd, (char *) stream->buffer,
Buffer_Size);
if (n > 0) {
kp=(Key_Type *) stream->buffer;
kq=(Key_Type *) &stream->buffer[n];
for (key = stream->key; kp < kq ; ) /* decrypt */
*kp++ ^= key;
stream->left_size = n;
stream->left_ptr = stream->buffer;
} else if (n == 0) {
stream->last_rdsize = stream->left_size = 0;
*bufptr = stream->left_ptr = stream->buffer;
*sizeptr = stream->last_rdsize = 0;
return QP_EOF;
} else {
qpstream->errno = errno;
stream->last_rdsize = stream->left_size = 0;
return QP_ERROR;
}
}
/* make next line of data available */
s = stream->left_ptr;
se = s + stream->left_size;
while (s < se) {
if (*s++ == '\n') { /* found end of record */
break;
}
}
*bufptr = stream->left_ptr;
*sizeptr = stream->last_rdsize = s - stream->left_ptr;
stream->left_ptr = s;
stream->left_size = se - s;
return (*--s == '\n') ? QP_FULL : QP_PART;
}
/* Only QP_SEEK_PREVIOUS is allowed for the file, so 'whence'
specified can only be QP_BEGINNING. '*sizeptr' should always
be set to 0 since there is only input stream.
*/
static int
decrypt_seek(qpstream, qpmagic, whence, bufptr, sizeptr)
QP_stream *qpstream;
union QP_cookie *qpmagic;
int whence;
unsigned char **bufptr;
long int *sizeptr;
{
DecryptStream *stream = CoerceDecryptStream(qpstream);
long int offset;
switch (whence) {
case QP_BEGINNING:
if ((offset = lseek(stream->fd,qpmagic->byteno,L_SET))
== -1) {
qpstream->errno = errno;
return QP_ERROR;
}
qpstream->magic.byteno = offset;
*bufptr = stream->buffer;
*sizeptr = 0;
stream->left_ptr = stream->buffer;
stream->left_size = stream->last_rdsize = 0;
return QP_SUCCESS;
case QP_CURRENT:
case QP_END:
default:
qpstream->errno = QP_E_INVAL;
return QP_ERROR;
}
}
static int
decrypt_close(qpstream)
QP_stream *qpstream;
{
DecryptStream *stream = CoerceDecryptStream(qpstream);
int fd = stream->fd;
QP_free((char *)stream);
if (close(fd) < 0) {
qpstream->errno = errno;
return QP_ERROR;
}
return QP_SUCCESS;
}
/* open_crypt_stream: open the specified non-tty 'filename' for
reading. The file is a simple crypted file with the first
byte as the key. It is crypted by logical exclusive-or
operation of the key with every character in the file.
Upon success, the opened stream is returned.
Upon failure, QP_NULL_STREAM is returned and the error code
is stored in the parameter 'error_num'.
*/
QP_stream *
open_decrypt(filename, error_num)
char *filename;
int *error_num;
{
int fd;
Key_Type key;
DecryptStream *stream;
QP_stream *option;
if ((fd = open(filename, O_RDONLY)) < 0) {
*error_num = errno;
return QP_NULL_STREAM;
}
if (isatty(fd)) { /* tty file is not accepted */
(void) close(fd);
*error_num = DECRYPT_TTY_FILE;
}
switch (read(fd, (char *) &key, sizeof(key)) ) {
case sizeof(key):
break;
case 0:
*error_num = DECRYPT_NO_KEY;
(void) close(fd);
return QP_NULL_STREAM;;
default:
*error_num = errno;
(void) close(fd);
return QP_NULL_STREAM;
}
if (! (stream = (DecryptStream *)
QP_malloc(sizeof(*stream))) ) {
(void) close(fd);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
stream->fd = fd;
stream->last_rdsize = 0;
stream->left_size = 0;
stream->key = key;
option = &stream->qpinfo;
QU_stream_param(filename, QP_READ, QP_DELIM_LF, option);
option->max_reclen = Buffer_Size;
/* Record the current byte offset in the file */
option->magic.byteno = sizeof(key);
option->read = decrypt_read;
option->seek = decrypt_seek;
option->close = decrypt_close;
QP_prepare_stream(&stream->qpinfo, stream->buffer);
if (QP_register_stream(&stream->qpinfo) == QP_ERROR) {
(void) stream->qpinfo.close(&stream->qpinfo);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
return (QP_stream *) stream;
}
This example demonstrates creating a stream based on standard I/O library package. The stream is created as unbuffered for the Prolog I/O system (It is still buffered in the standard I/O package). By making the stream unbuffered, mixed I/O operations between Prolog code and C code using standard I/O library functions will work appropriately. In this case, line counts and character counts will be maintained for Prolog I/O predicates and QP functions only.
example3% cat stdio.pl
foreign(open_stdio, c, open_stdio(+string, +string, -integer,
[-address])).
foreign_file('stdio.o', [open_stdio]).
:- load_foreign_files(['stdio.o'],['-lc']).
open_stdio_file(FileName, ModeName, Stream) :-
valid_open_mode(ModeName, Mode),
open_stdio(FileName, Mode, ErrorNum, CStream),
( CStream =:= 0 ->
raise_exception(existence_error(
open_stdio_file(FileName, ModeName, Stream),
1, file, FileName, errno(ErrorNum)))
; stream_code(Stream, CStream)
).
valid_open_mode(read, r).
valid_open_mode(write, w).
valid_open_mode(append, a).
example3% cat stdio.c
#include <stdio.h>
#include "quintus.h"
/* Create a stream based on Unix standard I/O library.
This stream is created as an unbuffered stream so that
mixed calls of Quintus Prolog I/O predicates (functions)
and standard I/O on the stream will read/write the same
sequence of bytes of the stream */
typedef struct
{
QP_stream qpinfo;
FILE *fp;
unsigned char buffer[4];
} StdioStream;
#define CoerceStdioStream(stream) ((StdioStream *) stream)
extern int errno;
static int
stdio_read(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
StdioStream *stream = CoerceStdioStream(qpstream);
register int c;
if ((c = getc(stream->fp)) < 0)
return QP_EOF;
stream->buffer[0] = (unsigned char) c;
*bufptr = stream->buffer;
*sizeptr = 1;
/* '-1' because the magic field stores the beginning
address of the returned buffer */
qpstream->magic.byteno = ftell(stream->fp)-1;
return (c == '\n') ? QP_FULL : QP_PART;
}
static int
stdio_write(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
StdioStream *stream = CoerceStdioStream(qpstream);
if (*sizeptr == 0) {
*bufptr = stream->buffer;
*sizeptr = 0;
return QP_SUCCESS;
}
errno = 0;
if (putc((char) stream->buffer[0], stream->fp) < 0) {
qpstream->errno = (errno) ? errno : QP_E_CANT_WRITE;
return QP_ERROR;
}
qpstream->magic.byteno = ftell(stream->fp);
*bufptr = stream->buffer;
*sizeptr = 0; /* use 0 for unbuffered write */
return QP_SUCCESS;
}
static int
stdio_flush(qpstream, bufptr, sizeptr)
QP_stream *qpstream;
unsigned char **bufptr;
long int *sizeptr;
{
StdioStream *stream = CoerceStdioStream(qpstream);
/* The stream is unbuffered so that there is no character
in the buffer of stream->buffer */
errno = 0;
if (fflush(stream->fp) < 0) {
qpstream->errno = (errno) ? errno : QP_E_CANT_FLUSH;
return QP_ERROR;
}
qpstream->magic.byteno = ftell(stream->fp);
*bufptr = stream->buffer;
*sizeptr = 0;
return QP_SUCCESS;
}
static int
stdio_seek(qpstream, qpmagic, whence, bufptr, sizeptr)
QP_stream *qpstream;
union QP_cookie *qpmagic;
int whence;
unsigned char **bufptr;
long int *sizeptr;
{
StdioStream *stream = CoerceStdioStream(qpstream);
int rtn;
errno = 0;
/* fseek() should normally flush out the buffered input
for stream->fp. Use fflush() just to be safe */
if (qpstream->mode != QP_READ)
(void) fflush(stream->fp);
switch (whence) {
case QP_BEGINNING:
rtn = fseek(stream->fp, qpmagic->byteno, 0);
break;
case QP_CURRENT:
rtn = fseek(stream->fp, qpmagic->byteno, 1);
break;
case QP_END:
rtn = fseek(stream->fp, qpmagic->byteno, 2);
break;
default:
qpstream->errno = QP_E_INVAL;
return QP_ERROR;
}
if (rtn == -1) {
qpstream->errno = (errno) ? errno : QP_E_CANT_SEEK;
return QP_ERROR;
}
qpstream->magic.byteno = ftell(stream->fp);
*bufptr = stream->buffer;
*sizeptr = (qpstream->mode == QP_READ) ? 0
: qpstream->max_reclen;
return QP_SUCCESS;
}
static int
stdio_close(qpstream)
QP_stream *qpstream;
{
StdioStream *stream = CoerceStdioStream(qpstream);
/* characters in fp buffer is flushed by fclose() */
if (fclose(stream->fp) < 0) {
qpstream->errno = errno;
return QP_ERROR;
}
QP_free((char *) stream);
return QP_SUCCESS;
}
/* open_stdio() creates an instance of standard input/output
based stream. The function creates a file stream based
on the 'filename' and 'modename' parameter.
It returns the pointer to the created QP_stream structure
upon success. It returns QP_NULL_STREAM and sets
error code in 'error_num' upon failure.
*/
QP_stream *
open_stdio(filename, modename, error_num)
char *filename, *modename;
int *error_num;
{
QP_stream *option;
FILE *fp;
StdioStream *stream;
int mode, stdio_read(), stdio_write(),
stdio_flush(), stdio_seek(), stdio_close();
switch (*modename) {
case 'r': mode = QP_READ; break;
case 'w': mode = QP_WRITE; break;
case 'a': mode = QP_APPEND; break;
default: *error_num = QP_E_BAD_MODE;
return QP_NULL_STREAM;
}
if ((fp = fopen(filename, modename)) == NULL) {
*error_num = errno;
return QP_NULL_STREAM;
}
/* allocate space for the stream */
stream = (StdioStream *) QP_malloc(sizeof(*stream));
/* set values in the stream */
stream->fp = fp;
/* obtain default values in QP_stream structure */
/* and modified fields for this stream */
option = &stream->qpinfo;
QU_stream_param(filename, mode, QP_DELIM_LF, option);
if (isatty(fileno(fp))) {
option->format = QP_DELIM_TTY;
option->seek_type = QP_SEEK_ERROR;
} else {
option->seek_type = QP_SEEK_BYTE;
}
option->max_reclen = (mode == QP_READ) ? 1 : 0;
if (mode != QP_READ) {
option->write = stdio_write;
option->flush = stdio_flush;
} else {
option->read = stdio_read;
option->peof_act = QP_PASTEOF_EOFCODE;
}
option->seek = stdio_seek;
option->close = stdio_close;
/* sets correct value in magic field */
if (option->mode != QP_APPEND)
option->magic.byteno = 0;
else
option->magic.byteno = ftell(fp);
/* set internal fields and register stream */
QP_prepare_stream(&stream->qpinfo, stream->buffer);
if (QP_register_stream(&stream->qpinfo) == QP_ERROR) {
(void) stream->qpinfo.close(&stream->qpinfo);
*error_num = QP_errno;
return QP_NULL_STREAM;
}
/* register tty stream to its group */
if (option->format == QP_DELIM_TTY)
(void) QP_add_tty(&stream->qpinfo, filename);
return (QP_stream *) stream;
}
Several builtin functions and macros are defined to enable Prolog streams to be manipulated in foreign code. This section lists each of these functions. In this list, the character '#' is used to denote a C macro which is defined in the file quintus.h. Full descriptions of each of these functions can be found in the individual reference pages.
Finally, there are five global stream variables accessible in foreign code. These are streams, not file descriptors. It makes no sense to pass these to UNIX system calls that expect file descriptors. The values in these variables should not be changed by an assignment statement. These variables are:
The Quintus Prolog input/output system is redesigned in Release 3. C code written for Quintus Prolog application prior to Release 3.0 should also work on Release 3 since the new design also maintains backward compatibility.
However, while the old Prolog I/O system is based on the C standard I/O library, the new Prolog I/O system is not. If an application performs mixed I/O operation in Prolog and foreign code on the three default Prolog streams, it might not work appropriately under the new I/O due to incompatibilities between the buffering mechanism in the C standard I/O stream and the Quintus Prolog stream. Let's look at an example of a mixed output operation on a Prolog session under Unix. Both the C standard output stream and the Prolog user_output stream write output to the same Unix file descriptor, 1, which is a tty.
|- ? set_output(user_output), write(first), c_printf('FIRST'),
write(second), c_printf('SECOND'), nl, c_nl.
The predicate c_printf/1 and c_nl/0 calls the following C functions:
void c_printf(atom)
char *atom;
{ printf("%s", atom); }
void c_nl() { putchar('\n'); }
The query yields the following output as expected prior to Quintus Prolog Release 3.
firstFIRSTsecondSECOND
However it yields the following output on Quintus Prolog Release 3 since each stream has its own buffer and no characters are actually written to the file descriptor 1 until new line operation is called.
firstsecond FIRSTSECOND
This problem can be solved by supplying a different embedding QU_initio() function at the time of the installation of Quintus Prolog (or at the time of creating a statically linked Prolog system) to create the three default streams based on C standard I/O streams. How to create an unbuffered Prolog stream based on a C standard I/O stream has already been shown in the third example of creating customized Prolog streams ({manual(i-5-7-3)})
In Quintus Prolog releases prior to 3.0, QP_make_stream() was the function used to create a user-defined stream. Quintus Prolog 3.1 users sho use the method described in {manual(i-5-5)}. QP_make_stream() creates an unbuffered Prolog stream. This is not very efficient. QP_make_stream() can still be used in Release 3, but may not be supported in the future. Other old QP I/O functions which may not be available in future release are:
QP_sprintf() QP_getc() QP_sgetc()
QP_putc() QP_sputc() QP_sputs()
The naming convention of these functions does not match well with their counterparts on C standard I/O library. For instance, QP_sprintf() performs formatted output on a Prolog stream as the same operation for fprintf() on a C standard I/O stream. It is therefore renamed to be QP_fprintf() in Release 3. For the same reason, QP_sgetc() is renamed as QP_fgetc(); QP_sputc() is renamed as QP_fputc(). QP_getc() and QP_putc() are now actually macros defined in quintus.h. However, all these functions are still available in Release 3. If a user's foreign code calls either QP_getc() or QP_putc() without including quintus.h, the old version of the function will be called. If quintus.h is included, the call is expanded to another function since both QP_getc() and QP_putc() are macros in quintus.h.
j: Inter-Process Communication
contact: product
support sales information