Filtered streams via open_prolog_stream/4 possible?

I want to use SWI-Prolog open_prolog_stream/4 to realize
the object oriented pattern of filtered streams. The idea is
that a new stream is created piggybacking on an

existing stream. But I don’t find these options:

  • open option data/1: A wildcard option data/1 where I
    can assign any data during open to a stream. This will be
    used to provide the existing piggybacked stream.

  • stream property data/1: This is to access the data/1 option
    from a stream that was created with the data/1 option. In case of a filtered
    character-output stream this is the underlying character-output stream.

Alternatively maybe I could just create the stream, and
use assertz/1 and some dynamic fact to create an associate
between the stream and the piggybacked stream.

And the close callback would remove this association. A further
alternative would be to make the callbacks all closures that
record the underlying character-output stream. But it

seems open_prolog_stream/4 only supports predefined
predicate names in a given module, and not arbitrary closures as
callbacks? So maybe a stream handle has already such a data

field, and it would be only a matter of making this field accessible.

The stream hooks are associated to a module. It is completely agnostic as to where the data comes from, goes to or is represented. The callbacks get the stream handle passed, so you can associate the data any way you want to these stream handles (they can be used in dynamic facts).

They have been added to facilitate I/O of pengines, connecting regular read/write to Pengine events. Tat is just one of the scenarios where there is no need for an explicit data storage.

You got in typedef struct io_stream of SWI-Stream.h:

  intptr_t		reserved[4];	/* reserved for extension */

Or maybe there is another field. A field where some client data
can be stored, where the client of a stream is free to store some
data, any data he likes? But maybe assertz/1 is the better

solution because of garbage collection. If there where such a
client data field it would possibly need the attention of garbage
collection, otherwise a stream might be erroreously seen

as unused and purged? Don’t know, are streams garbage
collected if not closed and not referenced anymore?

Edit 01.12.2023
Or do filtered streams already somehow exist? In some other
library, in some other corner of SWI-Prolog? There is a field
downstream in io_stream. Ok it says in pl-stream.c:

		 /*******************************
		 *	   FILTER STREAMS	        *
		 *******************************/

But this is used for websocket, zlib, etc… etc… And it
seems its not a contructed to provide Prolog written filters?
But somehow downstream is the field I am looking for.

Reserved is there to allow extending the structure while remaining backward compatible. The way to add context from the C API is to use handle. For normal file streams that is a file handle, but C defined user streams can put anything they like there as this is the thing that is passed to read(), etc.

As is, not. I think that could be provided. Streams can be chained using upstream/downstream links. This connection is used by the stream administration to avoid that a connected stream vanishes without other members of the chain knowing about it.

As is, such streams are used to implements protocols like TLS, HTTP chunked streams, zlib, websockets, etc. I don’t know how useful it is to support that actively from Prolog. The way the low level stream API is designed makes it rather easy to deadlock or create unbounded recursion over C, typically crashing the system.

Ok, it wurks, example of a filtered output stream:

?- current_output(S), dom_writer_new(S,W), set_output(W).
S = <stream>(00007ff94e60f660),
W = <stream>(000002be04413b30).

?- write('foo<bar>baz'), flush_output.
foo&lt;bar&gt;baz
true.

?- tag('<h1>'), write('foo<bar>baz'), tag('</h1>'), flush_output.
<h1>foo&lt;bar&gt;baz</h1>
true.

The set_output/1 is not for answers (bug or feature?):

?- X = 'foo<bar>baz'.
X = 'foo<bar>baz'

Source code:

piggy.p.log (1,9 KB)

The REPL loop reads/writes to user_input and user_output rather than current_input and current_output. You can replaces these using set_prolog_IO/3. Normally used by e.g., the libssh pack which provides login to Prolog using SSH. So, this creates a thread and uses set_prolog_IO/3 to bind the Prolog standard streams. Mostly used for managing and debugging services :slight_smile: