Prolog DCG Primer

Introduction

Head --> Body.

{ Goal }

A//N

A

N

//

a

as//0

as --> []. as --> [a], as.

a

a

phrase/2

phrase(Body, Ls)

Body

Ls

as//0

?- phrase(as, Ls). Ls = [] ; Ls = [a] ; Ls = [a, a] ; Ls = [a, a, a] ; Ls = [a, a, a, a] ; etc.

?- phrase(as, [a,a,a]). true. ?- phrase(as, [b,c,d]). false. ?- phrase(as, [a,X,a]). X = a.

Relating trees to lists

nil

node(Name, Left, Right)

Left

Right

tree_nodes(nil) --> []. tree_nodes(node(Name, Left, Right)) --> tree_nodes(Left), [Name], tree_nodes(Right).

?- phrase(tree_nodes(node(a, node(b, nil, node(c, nil, nil)), node(d, nil, nil))), Ns). Ns = [b, c, a, d].

[Name]

Left recursion

?- phrase(tree_nodes(Tree), [a,b,c,d]). Tree = node(a, nil, node(b, nil, node(c, nil, node(d, nil, nil)))) ; (nontermination)

tree_nodes//1

tree_nodes(nil, Ls, Ls) --> []. tree_nodes(node(Name, Left, Right), [_|Ls0], Ls) --> tree_nodes(Left, Ls0, Ls1), [Name], tree_nodes(Right, Ls1, Ls).

Ls0

Ls1

tree_nodes//3

Ls1

Ls

[_|Ls0]

Ls

Video:

[A,B,C]

[C]

[A,B]

[X,Y|Ls]

Ls

[X,Y]

Ls

Ls

[]

Ls0-Ls1

Ls0/Ls1

?- Ns = [a,b,c,d], phrase(tree_nodes(Tree, Ns, _), Ns). Ns = [a, b, c, d], Tree = node(a, nil, node(b, nil, node(c, nil, node(d, nil, nil)))) ; Ns = [a, b, c, d], Tree = node(a, nil, node(b, nil, node(d, node(c, nil, nil), nil))) ; Ns = [a, b, c, d], Tree = node(a, nil, node(c, node(b, nil, nil), node(d, nil, nil))) ; etc.

Semicontext notation

Head, [T 1 ,...,T n ] --> Body.

Body

T 1 , ..., T n

nt1, [b] --> [a]. nt2 --> [b].

nt1//0

a

nt1//0

a

b

nt2//0

b

nt2//0

b

nt1//0

?- phrase((nt1,nt2), [a]). true.

nt1//0

phrase/2

?- phrase(nt1, [a]). false.

phrase/3

nt1//0

?- phrase(nt1, [a], Rest). Rest = [b].

b

look_ahead(T), [T] --> [T].

?- phrase(look_ahead(T), [a], Rest). T = a, Rest = [a].

Implicitly passing states around

num_leaves(Tree, N) :- num_leaves_(Tree, 0, N). num_leaves_(nil, N0, N) :- N #= N0 + 1. num_leaves_(node(_,Left,Right), N0, N) :- num_leaves_(Left, N0, N1), num_leaves_(Right, N1, N).

num_leaves_/3

N0

N1

...

N

{}//1

{Goal}

Goal

num_leaves_//1

num_leaves(Tree, N) :- phrase(num_leaves_(Tree), [0], [N]). num_leaves_(nil), [N] --> [N0], { N #= N0 + 1 }. num_leaves_(node(_,Left,Right)) --> num_leaves_(Left), num_leaves_(Right).

num_leaves_//1

?- num_leaves(node(a,node(b,nil,nil), node(c,nil, node(d,nil,nil))), N). N = 5.

state(S), [S] --> [S]. state(S0, S), [S] --> [S0].

state(S)

S

state(S0, S)

S0

S

state//2

num_leaves_//1

num_leaves_(nil) --> state(N0, N), { N #= N0 + 1 }. num_leaves_(node(_,Left,Right)) --> num_leaves_(Left), num_leaves_(Right).

s(S1,S2,...,Sn)

Describing lists with list//1

list//1

list([]) --> []. list([L|Ls]) --> [L], list(Ls).

?- phrase((list([a,b]),list([c,d])), Ls). Ls = [a, b, c, d].

list//1

list//1

?- phrase(([a,b],[c,d]), Ls). Ls = [a, b, c, d].

list//1

concatenation([]) --> []. concatenation([List|Lists]) --> list(List), concatenation(Lists).

concat

?- phrase(concatenation([[a,b],[c,d],[e,f]]), Ls). Ls = [a, b, c, d, e, f].

list//1

?- phrase(([alpha],list(Mid),[omega]), [alpha,beta,gamma,omega]). Mid = [beta, gamma] ; false.

list//1

badlist(Ls) --> Ls.

?- phrase(badlist([a,b,c]), Ls). Ls = [a, b, c].

?- phrase(badlist(Ls), [a,b,c]). ERROR: call_dcg/3: Arguments are not sufficiently instantiated

{Goal}

?- phrase(badlist({halt}), _).

list//1

Reading from files

library(pio)

phrase/2

phrase_from_file/2

phrase/2

Video:

double_quotes

chars

?- set_prolog_flag(double_quotes, chars). true.

:- set_prolog_flag(double_quotes, chars).

list//1

dcg.html

?- phrase_from_file(list(Chars), 'dcg.html'). Chars = "<!DOCTYPE html>

<ht ..." ; false.

list//1

like(What) --> "I like ", list(What), ".", list(_).

?- phrase(like(What), "I like it. Anything can follow!"). What = "it" ; false.

What

i

t

library(pio)

phrase_from_file/2

like.txt

?- phrase_from_file(like(What), 'like.txt'). What = "it" ; false.

What

i

t

library(pio)

library(pio)

Parsing tricks and techniques

char_type/2

library(charsio)

ws --> [W], { char_type(W, whitespace) }, ws. ws --> [].

identifier([A|As]) --> [A], { char_type(A, alpha) }, symbol_r(As). symbol_r([A|As]) --> [A], { char_type(A, alnum) }, symbol_r(As). symbol_r([]) --> [].

...//0

list(_)

... --> [] | [_], ... .

('|')//2

(;)//2

(;)/2

('|')/2

(;)//2

...//0

?- phrase(("I like ",list(What),".",...), "I like it. Anything can follow!"). What = "it" ; false.

lines([]) --> call(eos), !. lines([L|Ls]) --> line(L), lines(Ls). line([]) --> ( "

" | call(eos) ), !. line([C|Cs]) --> [C], line(Cs). eos([], []).

call//1

!//0

?- phrase_from_file(lines(Ls), 'like.txt'). Ls = ["I like it. Anythi ..."].

eos/2

phrase/2

phrase_from_file/2

Describing output

library(format)

format_//2

format_//2

table --> row([this,is,a]), row([very,nice,table]). row(Ls) --> format_("~t~w~7+~t~w~7+~t~w~7+~n", Ls).

?- phrase(table, Ts). Ts = " this is ..."

format/2

?- phrase(table, Ts), format("~s", [Ts]). this is a very nice table Ts = " this is ..."

Implementation

listing/1

library(format)

num_leaves_//1

dynamic/1

?- listing(num_leaves_//1). num_leaves_(nil,A,B) :- state(C,D,A,E), D#=C+1, E=B. num_leaves_(node(A,B,C),D,E) :- num_leaves_(B,D,F), num_leaves_(C,F,E).

num_leaves_//1

term_expansion/2

phrase/[2,3]

Using DCGs

describing a list

reading from a file

passing around a state representation that only a few predicates actually use or modify.

include/3

regular Prolog Prolog DCG include(Goal, List, Included) :- include_(List, Goal, Included). include_([], _, []). include_([L|Ls], Goal, Included0) :- ( call(Goal, L) -> Included0 = [L|Included] ; Included0 = Included ), include_(Ls, Goal, Included). include(Goal, List, Included) :- phrase(include_(List, Goal), Included). include_([], _) --> []. include_([L|Ls], Goal) --> ( { call(Goal, L) } -> [L] ; [] ), include_(Ls, Goal).

?- include(integer, [a,b,c,1,2,e], Is). Is = [1, 2].

phrase/2 must of course be used to refer to a DCG nonterminal within a regular Prolog predicate

must of course be used to refer to a DCG nonterminal within a regular Prolog predicate conversely, {}//1 is used to call a regular Prolog goal within a DCG

is used to call a regular Prolog goal within a DCG the DCG version needs fewer explicit arguments, making it slightly easier to read and understand

although it is strongly related to the previous point, it is still worth mentioning explicitly that the DCG version also refers to fewer variables

using a DCG makes immediately clear that a list is being described.

markov.pl uses DCGs to describe output of a Markov chain

Lisprolog uses DCGs to parse Lisp expressions and to implicitly thread through the state of the environment

Tist uses DCGs for all of the above reasons throughout the document

The comp.lang.prolog FAQ are described with a DCG.