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.

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

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.

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: