prologiso-prolog

at_end_of_stream on stdin in Prolog


I ask SICStus Prolog ?- at_end_of_stream. on the top-level and immediately I get: no.

GNU Prolog and Scryer Prolog do the same.

Traella Prolog and SWI-Prolog, however, choose to wait for input before answering, and rumor has it that behaving like this has been quite common among Prolog systems—especially in the past.

If I look at the ISO-Prolog standard, that behavior becomes spurious:

8.11.8.5 Bootstrapped built-in predicates
The built-in predicates at_end_of_stream/0 and at_the_of_stream/1 examine the single stream-property end_of_stream/1.

A goal at_end_of_stream is true iff the current input has a stream position end-of-stream or past-end-of-stream (7.10.2.9, 7.10.2.13).

A goal at_end_of_stream(S_or_a) is true iff the stream associated with stream or alias S_or_a has a stream position end-of-stream.

at_end_of_stream :-
  current_input(S),
  stream_property(S, end_of_stream(E)),
  !,
  (E = at ; E = past).

[The the sake of brevity, I omitted the code for at_end_of_stream/1.]

So it appears that the standard is quite clearly on the side of SICStus Prolog and GNU Prolog.

So my question boils down to this:

Is this "waiting" behavior simply non-conformance, a kind of anachronism justified on the basis of practicality / compatibility—or is there more to it?


Solution

  • Handling end of stream is hard, on the top-level it's even harder.

    A very simple and basic top-level is like sh (dash). Prolog top-level is a bit similar, it differs by having auto-completion/choice (like Scryer Prolog, SWI-Prolog), not accepting input with ctrl-D but it could be like sh.

    A query to test and better understand top-level is ?- get_char(C).%a (where C binds to %, source) and ?- get_char(C). (when submitting with enter, C binds to \n, if submitting with ctrl-D then waits or binds C to end_of_file).

    Why those query? The top-level can be model as read_term(user_input, Goal, []), call(Goal):

    Sadly there is a bit of twisting at the end.

    I might have missed/misread something if not then this undefinedness allows some optimization/simplification of the implementation, explains the difference between engine and also the actual top-level is more complicated that read_term(user_input, Goal, []), call(Goal) (even if it's the ideal one).

    When it comes to at_end_of_stream/0, it doesn't seem like there is a reason to wait.

    Digression

    If at_end_of_stream/0 is implemented with peek_char/1 without executing eof_action then that might explain the wait.

    On other operating system, it may be different. I use and test on Linux.

    On GNU Prolog (version 1.5.0), there is the issue that a stream with property eof_action(reset) is never at end of stream. This explains why at_end_of_stream/0 fails.

    $ echo -n | gprolog --init-goal "(at_end_of_stream, write(at), nl, halt ; peek_char(C), writeq(not(C)), nl, halt)"
    not(end_of_file)
    $ echo | gprolog --init-goal "(at_end_of_stream, writeq(at), nl, halt ; peek_char(C), writeq(not(C)), nl, halt)"
    not('\n')
    

    at_end_of_stream/0 fails even when the stdin is empty.

    On Trealla Prolog (version v2.1.11):

    $ echo -n | ./tpl -g "(at_end_of_stream, writeq(at), nl, halt ; peek_char(C), writeq(not(C)), nl, halt)"
    at
    $ echo | ./tpl -g "(at_end_of_stream, writeq(at), nl, halt ; peek_char(C), writeq(not(C)), nl, halt)"
    not('\n')
    $ echo -n | ./tpl -g "(stream_property(S, alias(user_input)), stream_property(S, end_of_stream(Eos)), at_end_of_stream(S), writeq(eos(Eos)), halt ; halt)"
    eos(not)
    

    The property end_of_stream doesn't agree with at_end_of_stream/1 (permuting stream_property/2 and at_end_of_stream/1 doesn't change the result).

    On Scryer Prolog (on master (6b8e6204957bfc3136ea39ec659d30627775260d) or rebis-dev (c1945caf11c0d202f4121de446f1694854dcba47)):

    $ echo -n | ./target/release/scryer-prolog -g "(at_end_of_stream, write(at), nl, halt ; peek_char(C), writeq(not(C)), nl, halt)"
    not('\x0\')
    $ echo | ./target/release/scryer-prolog -g "(at_end_of_stream, write(at), nl, halt ; peek_char(C), writeq(not(C)), nl, halt)"
    not('\n')
    

    at_end_of_stream/0 fails even when the stdin is empty.