[mercury-users] Read term from a pipe?

Fergus Henderson fjh at cs.mu.OZ.AU
Fri Jun 5 18:29:14 AEST 1998

On 04-Jun-1998, tcklnbrg <at at ingenuity-sw.com> wrote:
> 1) pipes:  The ISO Prolog compliant predicate, "open(+SrcDest, +Mode,
> -Stream, +Options)", allows SrcDest to be
> a term of the form, "pipe(Command)".  This allows you to pipe a command
> to the OS and read the resulting output.

This is somewhat misleading.  According to the ISO Prolog standard, the
first argument to open/4 is "an implemented defined ground term".
It may well be that some Prolog systems support terms of the form
`pipe(Command)', but the ISO Prolog standard does not require this.

In fact, a brief glance at the SWI-Prolog 1.9.0 source code reveals that
SWI-Prolog only supports this command on some systems
(namely Unix and OS/2).  On other systems, you get a warning
"Pipes are not supported on this OS".

So if you thought this was portable, you were wrong ;-)

> Does Mercury support anything like this, ie., reading the
> result of a command that was piped to the OS or shell?

The Mercury standard library does not support this,
mainly because implementing it in a portable fashion is
a non-trivial exercise.  The obvious way of implementing it
would be to use the popen() function which is available on
many Unix systems, but this function is not included
in the ANSI C standard or even in the POSIX standard.

However, if you are willing to restrict yourself to
systems which have the popen() C function, it possible
to implement this in Mercury using Mercury's C interface.
See the `popen.m' module that I have enclosed as
an attachment. 

(Unfortunately, in order to implement this you do have to depend on
some of the implementation details of the Mercury library.  Really the
Mercury library ought to provide some appropriate hooks so that you can
create Mercury streams from C `FILE' pointers or POSIX file descriptors.
Then you could write popen() in a manner that was guaranteed to be
portable to future Mercury implementations.  Perhaps we will get around
to providing these hooks sometime in the not too distant future.)

> 2)reading terms:  I can successfully read in a term typed in at the
> keyboard
> using the term_io__read_term pred., and I note that the source for
> io__read
> calls term_io__read_term.  However, I can't figure out how to code
> io__read(io__read_result(T), io__state, io_state).  What does the 
> compiler want for the "io__read_result(T)" arg?.

The io__read_result(T) type is defined in library/io.m as

	:- type io__read_result(T)	--->	ok(T)
					;	eof
					;	error(string, int).
					% error message, line number

So we you read in a term with io__read, you need to handle each of
these three cases.  For example:

	main -->
			{ Result = ok(Value) },
			print("Read a value: "), io__write_int(Value), nl
			{ Result = eof },
			print("Got end-of-file"), nl
			{ Result = error(Message, Line) },
			print("Error at line "), print(Line),
			print(": "), print(Message), nl

Note that the semantics of io__read are a little bit tricky:
the type which it will try to read in is determined by the type
of the argument to io__read, which is in turn determined by
the context in which the call to io__read occurs.
In this case, the call to io__write_int(Value) implies that
Value has type int, which in turn (because of the unification
`Result = ok(Value)') implies that Result has type io__result(int).
This means that the call to io__read will try to read in an int.

Does that make it clear?


Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger fjh at        |     -- the last words of T. S. Garp.
-------------- next part --------------
:- module popen.
:- interface.
:- import_module io.

%	popen_input(Command, Result):
%		Attempts to open a command pipe for input.
%		Returns `Result = ok(Stream)' or `Result = error(_)'.
:- pred popen_input(string, io__res(io__input_stream), io__state, io__state).
:- mode popen_input(in, out, di, uo) is det.

%	pclose_input(Stream):
%		Closes a command pipe that was opened with popen_input.
:- pred pclose_input(io__input_stream, io__state, io__state).
:- mode pclose_input(in, di, uo) is det.

:- implementation.

popen_input(Command, Result) -->
	% call the C function that does the work
	do_popen_input(Command, ResultCode, NewStream, ErrorMsg),

	% convert the results into a discriminated union type
	( { ResultCode \= -1 } ->
		{ Result = ok(NewStream) }
		{ Result = error(ErrorMsg) }

:- pred do_popen_input(string, int, io__input_stream, io__error,
			io__state, io__state).
:- mode do_popen_input(in, out, out, out, di, uo) is det.
%	do_popen_input(Command, Stream, ResultCode, ErrorMsg, IO0, IO1).
%		Attempts to open a command pipe for input.
%		ResultCode is 0 for success, -1 for failure.

:- pragma c_code(do_popen_input(Command::in, ResultCode::out, Stream::out,
		Msg::out, _IO0::di, _IO::uo), will_not_call_mercury,
		"do_popen_input(Command, &ResultCode, &Stream, &Msg);").

:- pragma c_code(pclose_input(Stream::in, _IO0::di, _IO::uo),

% With the latest development version of Mercury, you can replace the above
% `pragma c_code' declarations with the following `pragma import' declarations:
:- pragma import(do_popen_input(in, out, out, di, uo), "do_popen_input").
:- pragma import(pclose_input(in, out, out, di, uo), "do_pclose").

:- pragma c_header_code("
	#include <stdio.h>

	/* Unfortunately, with Mercury 0.7.3,
	   we need to define this struct ourself.
	   With the latest development releases
	   we could just use the MercuryFile type
	   which is defined in runtime/mercury_types.h */

	typedef struct my_mercury_file {
		FILE *file;
		int line_number;
	} MyMercuryFile;

	void do_popen_input(String command, Integer *result_code_ptr,
			Word *mf_ptr, Word *error_msg_ptr);
	void do_pclose(Word mf);

:- pragma c_code("
	#include <errno.h>

	do_popen_input(String command, Integer *result_code_ptr,
			Word *mf_ptr, Word *error_msg_ptr)
		FILE *f;
		int result_code;
		MyMercuryFile *mf;
		const char *error_msg;
		f = popen(command, ""r"");
		if (!f) {
			result_code = -1;
			mf = NULL;
			error_msg = strerror(errno);
		} else {
			result_code = 0;
			mf = make(MyMercuryFile);
			mf->file = f;
			mf->line_number = 1;
			error_msg = NULL;
		*result_code_ptr = result_code;
		*mf_ptr = (Word) mf;
		*error_msg_ptr = (Word) error_msg;

	do_pclose(Word arg)
		MyMercuryFile *mf = (MyMercuryFile *) arg;

:- end_module popen.

More information about the users mailing list