0
votes

I'm trying to write a DCG for a command interface. The idea is to read a string of input, split it on spaces, and hand the resulting list of tokens to a DCG to parse it into a command and arguments. The result of parsing should be a list of terms which I can use with =.. to construct a goal to call. However, I've become really confused by the string type situation in SWI-Prolog (ver. 7.2.3). SWI-Prolog includes a library of basic DCG functionality, including a goal integer//1 which is supposed to parse an integer. It fails due to a type error, but the bigger problem is that I can't figure out how to make a DCG work nicely in SWI-Prolog with "lists of tokens".

Here's what I'm trying to do:

:- use_module(library(dcg/basics)).

% integer//1 is from the dcg/basics lib
amount(X) --> integer(X), { X > 0 }.
cmd([show,all]) --> ["show"],["all"].
cmd([show,false]) --> ["show"].
cmd([skip,X]) --> ["skip"], amount(X).

% now in the interpreter:
?- phrase(cmd(L), ["show","all"]).
L = [show, all].
% what is the problem with this next query?
?- phrase(cmd(L), ["skip", "50"]).
ERROR: code_type/2: Type error: `character' expected, found `"50"' (a string)

I have read Section 5.2 of the SWI manual, but it didn't quite answer my questions:

  1. What type is expected by integer//1 in the dcg/basics library? The error message says "character", but I can't find any useful reference as to what exactly this means and how to provide it with "proper" input.
  2. How do I pass a list of strings (tokens) to phrase/2 such that I can use integer//1 to parse a token as an integer?
  3. If there's no way to use the integer//1 primitive to parse a string of digits into an integer, how should I accomplish this?

I did quite a bit of expermenting with using different values for the double_quote flag in SWI-Prolog, plus different input formats, such as using a list of atoms, using a single string as the input, i.e. "skip 50" rather than ["skip", "50"], and so on, but I feel like there are assumptions about how DCGs work that I don't understand.

I have studied these three pages as well, which have lots of examples but none quite address my issues (some links omitted since I don't have enough reputation to post all of them):

  1. The tutorial "Using Definite Clause Grammars in SWI-Prolog" by Anne Ogborn
  2. A tutorial from Amzi! Prolog about writing command interfaces as DCGs.
  3. Section 7.3 of J. R. Fisher's Prolog tutorial

A third, more broad question is how to generate an error message if an integer is expected but cannot be parsed as one, something like this:

% the user types:
> skip 50x
I didn't understand that number.

One approach is to set the variable X in the DCG above to some kind of error value and then check for that later (like in the hypothetical skip/1 goal that is supposed to get called by the command), but perhaps there's a better/more idiomatic way? Most of my experience in writing parsers comes from using Haskell's Parsec and Attoparsec libraries, which are fairly declarative but work somewhat differently, especially as regards error handling.

1
How is integer defined in your DCG?lurker
It's part of a library that ships with SWI-Prolog, documented here. I'll edit the post with this information.DCE
That predicate (integer(X)) requires an integer argument, according to the documentation, but you are trying to parse an atom ('50') with it (I assume your SWI prolog is configured to interpret "50" as the atom '50').lurker

1 Answers

0
votes

Prolog doesn't have strings. The traditional representation of a double quoted character sequence is a list of codes (integers). For efficiency reasons, SWI-Prolog ver. >= 7 introduced strings as new atomic data type:

?- atomic("a string").
true.

and backquoted literals have now the role previously held by strings:

?- X=`123`.
X = [49, 50, 51].

Needless to say, this caused some confusion, also given the weakly typed nature of Prolog...

Anyway, a DCG still works on (difference) lists of character codes, just the translator has been extended to accept strings as terminals. Your code could be

cmd([show,all]) --> whites,"show",whites,"all",blanks_to_nl.
cmd([show,false]) --> whites,"show",blanks_to_nl.
cmd([skip,X]) --> whites,"skip",whites,amount(X),blanks_to_nl.

and can be called like

?- phrase(cmd(C), ` skip 2300 `).
C = [skip, 2300].

edit

how to generate an error message if an integer is expected

I would try:

...
cmd([skip,X]) --> whites,"skip",whites,amount(X).

% integer//1 is from the dcg/basics lib
amount(X) --> integer(X), { X > 0 }, blanks_to_nl, !.
amount(unknown) --> string(S), eos, {print_message(error, invalid_int_arg(S))}.

prolog:message(invalid_int_arg(_)) --> ['I didn\'t understand that number.'].

test:

?- phrase(cmd(C), ` skip 2300x `).
ERROR: I didn't understand that number.
C = [skip, unknown] ;
false.