SWI-Prolog predicate for reading in lines from input file
Asked Answered
O

2

6

I'm trying to write a predicate to accept a line from an input file. Every time it's used, it should give the next line, until it reaches the end of the file, at which point it should return false. Something like this:

database :-
    see('blah.txt'),
    loop,
    seen.

loop :-
    accept_line(Line),
    write('I found a line.\n'),
    loop.

accept_line([Char | Rest]) :-
    get0(Char),
    C =\= "\n", 
    !,
    accept_line(Rest).
accept_line([]).

Obviously this doesn't work. It works for the first line of the input file and then loops endlessly. I can see that I need to have some line like "C =\= -1" in there somewhere to check for the end of the file, but I can't see where it'd go.

So an example input and output could be...

INPUT
this is
an example

OUTPUT
I found a line.
I found a line.

Or am I doing this completely wrong? Maybe there's a built in rule that does this simply?

Ordinand answered 16/7, 2015 at 20:39 Comment(0)
S
9

In SWI-Prolog, the most elegant way to do this is to first use a DCG to describe what a "line" means, and then use library(pio) to apply the DCG to a file.

An important advantage of this is that you can then easily apply the same DCG also on queries on the toplevel with phrase/2 and do not need to create a file to test the predicate.

There is a DCG tutorial that explains this approach, and you can easily adapt it to your use case.

For example:

:- use_module(library(pio)).

:- set_prolog_flag(double_quotes, codes).

lines --> call(eos), !.
lines --> line, { writeln('I found a line.') }, lines.

line --> ( "\n" ; call(eos) ), !.
line --> [_], line.

eos([], []).

Example usage:

?- phrase_from_file(lines, 'blah.txt').
I found a line.
I found a line.
true.

Example usage, using the same DCG to parse directly from character codes without using a file:

?- phrase(lines, "test1\ntest2").
I found a line.
I found a line.
true.

This approach can be very easily extended to parse more complex file contents as well.

Sisile answered 17/7, 2015 at 6:13 Comment(5)
A good suggestion (+1). The only problem that I still have with library(pio) for SWI-Prolog is that it still does not implement reading from a non-repositioning stream. So, I cannot use it for stdin, and have to use read_line_to_codes or read_pending_input or read_string+string_codes before I can use a DCG. Since I seem to be the only person who needs this functionality, instead of complaining I could maybe do it myself....Etom
@Boris: library(pio) is lacking a lot of things: It should also work for double_quotes set to chars, be more generic like this, etc.etc. It's more a political issue than anything else. The blessings of kind-of free software.Pecten
@Pecten I am not able to follow exactly. What exactly is the technical reason why the library cannot work with SWI7?Etom
@Boris: Like: There is no support for chars in SWI7 as there is support for codes.Pecten
@Boris: Like: Change of basic assumptions that all these mechanisms rely on (see link above). You can't win against a single-person system that changes randomly.Pecten
E
3

If you want to read into code lists, see library(readutil), in particular read_line_to_codes/2 which does exactly what you need.

You can of course use the character I/O primitives, but at least use the ISO predicates. "Edinburgh-style" I/O is deprecated, at least for SWI-Prolog. Then:

get_line(L) :-
    get_code(C),
    get_line_1(C, L).

get_line_1(-1, []) :- !. % EOF
get_line_1(0'\n, []) :- !. % EOL
get_line_1(C, [C|Cs]) :-
    get_code(C1),
    get_line_1(C1, Cs).

This is of course a lot of unnecessary code; just use read_line_to_codes/2 and the other predicates in library(readutil).

Since strings were introduced to Prolog, there are some new nifty ways of reading. For example, to read all input and split it to lines, you can do:

read_string(user_input, _, S),
split_string(S, "\n", "", Lines)

See the examples in read_string/5 for reading linewise.

PS. Drop the see and seen etc. Instead:

setup_call_cleanup(open(Filename, read, In),
        read_string(In, N, S), % or whatever reading you need to do
        close(In))
Etom answered 17/7, 2015 at 3:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.