Can I use with_output_to/2 with a foreign function that calls Sprintf()?

I have a foreign predicate written in C that calls Sprintf(). For tests, I’d like to capture its output. I tried wrapping with with_output_to/2 but that didn’t work.

I can modify the predicate to write to “current output”, except I can’t figure out how to specify “current output” … as far as I can tell, I can only specify Soutput or Serror … or do I need to pass in the current_output/1 stream as an argument to the predicate?

You can use with_output_to/3. It is probably more elegant to pass the stream or even better return the output. If you try to test the Sprintf() family, I’d use Ssprintf() and return the output as a string (AFAIK it is nowadays UTF-8 encoded). In the end they all call the same routine to deal with the format string and arguments, so testing one tests all of them. If you are testing something else I’d also just return a string.

What am I doing wrong? The foreign predicate is pl_write_atoms/1 from foreign.doc … the results below show that with_output_to/2 doesn’t work when used with write/2 with output to user_output:

?- with_output_to(string(S), pl_write_atoms([a,b])).
a
b
S = "".

 ?- with_output_to(string(S), write(hello)).
S = "hello".

?- with_output_to(string(S), write(current_output, hello)).
S = "hello".

?- with_output_to(string(S), write(user_output, hello)).
hello
S = "".

with_output_to/2 captures current_output. You can capture user_output using

 ?- with_output_to(string(S), write(user_output, hello),
                   [capture([user_output])]).
 S = "hello".

This was introduced with the new testing framework implementation to suppress output of succeeded tests and ensure that output from concurrent failed tests is not interleaved. It is a bit of an emergency that I would only use when really needed. I think it should work when nested inside a test, but I’m not sure.

capture([user_output]) doesn’t work with a foreign predicate, it seems. (Also, it would be nice if this option were documented …)

?- with_output_to(string(S), write(user_output, hello), [capture([user_output])]).
S = "hello".

?- with_output_to(string(S), pl_write_atoms([a,b]), [capture([user_output])]).
a
b
S = "".

An additional question is: how would I specify current_output to Sfprintf()? As far as I can tell, there are predefined streams only for user_output and user_error.

See streams:with_output_to/3

A little search says there is Sinput, Soutput and Serror. these are hard bound to the streams that are the initial stdin/stdout/stderr of the process. SWI-Prolog.h however also defines

PL_EXPORT(IOSTREAM *)*_PL_streams(void);	/* base of streams */
#ifndef PL_KERNEL
#define Suser_input     (_PL_streams()[0])
#define Suser_output    (_PL_streams()[1])
#define Suser_error     (_PL_streams()[2])
#define Scurrent_input  (_PL_streams()[3])
#define Scurrent_output (_PL_streams()[4])
#endif

So, I think Sfprintf(Scurrent_output, ...) should do the job. And yes, I fear this is not documented. At least, git grep in the man directory remains silent :frowning:

capture([current_output]) throws an error:

?- with_output_to(string(S), write([a,b]), [capture([current_output])]).
ERROR: Type error: `oneof([user_output,user_error])' expected, found `current_output' (an atom)
ERROR: In:
ERROR:   [17] throw(error(type_error(...,current_output),_8194))
ERROR:   [10] streams:with_output_to(string(_8238),user:write(...),[capture(...)]) at /home/peter/src/swipl-devel/build/home/library/streams.pl:87
ERROR:    [9] toplevel_call(user:user: ...) at /home/peter/src/swipl-devel/build/home/boot/toplevel.pl:1173
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

Docs:

As current_output is always captured, the only two values are user_output and user_error

Sfprintf(Suser_output, ...) works as expected with with_output_to/3.

However Sprintf(...) and Sprintf(Suser, ...) don’t work with with_output_to/3.

Sprintf(Suser, is invalid Suser does not exist and Sprintf() wants a format as first argument. Sprintf() is defined to print to Soutput that is the stream bound to the OS file handle 1. Whether that is a good idea or not, I don’t know. I can think of arguments either way. As is, pl-stream.c started life completely independent from SWI-Prolog. Later some Prolog specific things were added (notably to improve error handling).

I don’t think I ever use it. Debugging uses Sdprintf() and just about any other case you have a concrete stream handle to write to.

Sorry - typo - I used Sfprintf(Suser, ...). Suser is defined in SWI-Streams.h, which is why I didn’t notice the other definitions in SWI-Prolog.h. I’ll update the documentation and the examples in foreign.doc.

1 Like

??

 grep Suser os/SWI-Stream.h
 <nothing>

user is, AFAIK, only accessible as alias at the Prolog level for backward compatibility, where it means either user_input or user_output, depending on whether we want an input or output stream. I hope it is deprecated :slight_smile:

I really shouldn’t post things before coffee. :slight_smile:

I meant Soutput and Serror.