Search code examples
inputioprologbufferedinputstreamgnu-prolog

How do I read a line of input from a user, until an EOF is hit, in GNU Prolog?


I have been reading the GNU Prolog documentation to figure out how to read a line of input until an end_of_file atom is reached. Here is my pseudocode for writing such a goal:

read_until_end(Chars, Out):
   if peek_char unifies with end_of_file, Out = Chars
   otherwise, get the current character, add it to a buffer, and keep reading

I implemented that like this:

read_until_end(Chars, Out) :-
    peek_char(end_of_file) -> Out = Chars;
    peek_char(C) -> read_until_end([C | Chars], Out).

prompt(Line) :-
    write('> '),
    read_until_end([], Line).

Here's what happens in the REPL:

| ?- prompt(Line).
> test

Fatal Error: global stack overflow (size: 32768 Kb, reached: 32765 Kb, environment variable used: GLOBALSZ)

If I print out C for the second branch of read_until_end, I can see that peek_char always gives me the same character, 'b'. I think that I need a way to progress some type of input character index or something like that, but I can't find a way to do so in the documentation. If I knew a way, I would probably have to use recursion to progress such a pointer, since I can't have any mutable state, but aside from that, I do not know what to do. Does anyone have any advice?


Solution

  • You are using peek_char/1 to get the next character, but that predicate does not consume the character from the stream (it just "peeks" the stream). Therefore an infinite recursion undergoes in your code that ends with a global stack overflow.

    You should use get_char/1 to read and consume the character from the stream, and reverse/2 the list of collected chars:

    read_until_end(Chars, Out) :-
        get_char(Char),
        (
         Char = end_of_file -> reverse(Chars, Out)
        ;
         read_until_end([Char | Chars], Out)
        ).
    

    To avoid the need to reverse the list you may slightly modify your procedures to build the list in order (without using an accumulator):

    read_until_end(Output) :-
        get_char(Char),
        (
         Char = end_of_file -> Output=[]
        ;
         (
           Output=[Char|NOutput],
           read_until_end(NOutput)
         )
        ).
    
    prompt(Line) :-
        write('> '),
        read_until_end(Line).