Looking for an example of a REPL

I want to wrap a simple REPL around my app. It should:

  • Read lines from the terminal
  • Parse the lines using a DCG
  • Process the resulting requests and display the results
  • Have command-line editing and persistent history

All the bits appear to be in the standard libraries but I’m struggling to find a worked example of how to glue them together. Does anyone have a pointer? Thanks!

Where would I find that?

Sorry, I don’t see what any of that has to do with the question I asked.

(Nothing of importance)

Why are you posting Rust code as the solution to a SWI Prolog question?

(post deleted by author)

(Nothing of importance)

I have a sort of terminal emulator thing here that might be of some use to you. See loop/3 executing.pl for the entry point to the “REPL”.

Except for the command line editing, this is all pretty specific to your application. Best SWI-Prolog option for commandline editing is library(editline), providing el_wrap/4 and friends. Normally, the terminal is already wrapped though. Then use read_line_to_codes/3 to read lines, add your DCG and process the output thereof.

1 Like

I have tried to make my own REPL more than once and it turned out every time that I should be using the top level instead. There must be a very good reason not to; for example if the language is syntactically incompatible with (SWI-)Prolog.

1 Like

Maybe make 4 different posts on SWI-Prolog discourse
for the 4 different questions in the bullet list below:

For example “Read lines from the terminal” has
basically two solutions, solution I with tail recursion,
solution II with repeat/0:

Solution I:

/* tail recursive */
loop :-
    write('> '), flush_output,
    read(X),
    (X == end_of_file -> true;
        loop).

Solution II:

/* failure driven */
loop :-
    repeat,
    write('> '), flush_output,
    read(X),
    (X == end_of_file -> !, true;
        fail).

Solution II is based on the fact that the cut (!) affects
the whole loop clause including the choice point of
repeat/0, and not only the if-then-else. This semantic

goes by name cut transparency of if-then-else. In
particular according to ISO core standard the then part
and the else part are supposed to be cut transparent,

concerning cuts that are present at compile time.

Edit 04.06.2025
There is a small sublety in SWI-Prolog, you have to switch
off the input prompt with prompt/2:

Thanks, I found read_line_to_codes, have tinkered with it and I noticed that it seemed to do editing and history, but I wasn’t sure if that was something I could rely on. What’s the relationship between editline and readline? I see mentions of them both in the docs.

I would like to do “backslash continuation”, i.e. if the last char of a line before the CR is \ then read another line. I couldn’t find a way of doing that with the difflist version of read_line_to_codes and strings seem easier to munge than codes, so I was thinking of using read_line to build up a list of strings, collapsing it down to a single string, converting it to codes and then passing it to my DCG.

There is a good reason, it’s that the users are syntactically incompatible with Prolog :laughing: The language is trivial, just a list of field specifiers and search terms.

Well thats a late minute requirement that wasn’t specified
in your original post bullet list. Do you have more such surprises?
In the worst case this is simply a loop inside a loop. It is nowhere

forbidden to apply programming patterns more than once:

/* tail recursive */
loop :-
    write('> '), flush_output,
    loop2(X),
    (X == end_of_file -> true;
        write(X), nl,
        loop).

/* tail recursive 2 */
loop2(R) :-
    current_input(S),
    read_line_to_string(S, X),
    (sub_string(X, _, _, 0, '\\') ->
         sub_string(X, 0, _, 1, Y),
         loop2(L),
         (L == end_of_file ->
             throw(error(syntax_error(continuation_missing),_));
             string_concat(Y, L, R));
         R = X).

It is based on two data types, atom and string. Thats very
SWI-Prolog specific. So a test X == end_of_file is not the
same as a test X == "end_of_file". Also a little SWI-Prolog

The sub_atom/5, atom_concat/3 and double_quotes flag are
from the ISO core standard, originally for atoms only. I
have used the new sub_string/5 and string_concat/3.

It seems to work as expected:

/* SWI-Prolog 9.3.24 */
?- prompt(_,'').
true.

?- loop.
> foo
foo
> foo\
bar
foobar
> 
true.

Using strings, you wont polute the SWI-Prolog atom table.
But beware, read line history works worst with line
continuations. I haven’t seen many good solutions that

could recall a block instead only a line. Also extremly
annoying in many Prolog systems REPLs if you spread a
Prolog clause or query along multiple lines.

Actually I can’t rely on it, all that’s in the command history is whatever was typed into the prolog top level. A call to el_add_history addresses that, thanks for the pointer to editline, I don’t think I would have found it otherwise.

Thanks. I was wondering how to do that.

Bash glues continuation lines into one long line in its history, the SWI top-level also accepts \ continuation lines but preserves the \ + linebreak, That’s ideal but looks like it might be a bit of a faff to do so I’ve just done “glue all the lines together in the history” as it’s trivial to do. Here’s what I ended up with:

repl(Action) :-
  prompt(P, 'p2k> '),
  readContLines([], RevLines),
  prompt(_, P),
  foldl(string_concat, RevLines, "", Line),
  el_add_history(user_input, Line),
  call(Action, Line),
  repl(Action)
.

readContLines(PrevLines, Lines) :-
  read_line_to_string(user_input, Line),
  Line \= end_of_file, Line \= "exit",
  (
  sub_string(Line, Pos, 1, 0, "\\")
  ->
    sub_string(Line, 0, Pos, 1, L1),
    readContLines([L1 | PrevLines], Lines)
  ;
    Lines = [Line | PrevLines]
  )
.

They are two line editing libraries. Originally, SWI-Prolog used GNU libreadline, but the license change to BSD makes this a bit cumbersome (you effectively get GPL), so editline was added as alternative. As is, the editline has several advantages, such as a single process can have multiple iistances of editline, but not of readline. This is handy for e.g., the SSH server that is in an extension package.

1 Like

I don’t expect there to be more than 2-3 lines of input and it’s for interactive use, so I won’t be worrying about any quadratic behaviour in this case.

The last time I touched Prolog was when I was working on the Alvey Project. :older_person:

(post deleted by author)