/** Support for Definite Clause Grammars. A Prolog definite clause grammar (DCG) describes a sequence. Operationally, DCGs can be used to parse, generate, complete and check sequences manifested as lists. Check [The Power of Prolog chapter on DCGs](https://www.metalevel.at/prolog/dcg) to learn more about them. */ :- module(dcgs, [op(1105, xfy, '|'), phrase/2, phrase/3, phrase//2, phrase//3, seq//1, seqq//1, ... //0, (-->)/2 ]). :- use_module(library(error)). :- use_module(library(iso_ext)). :- use_module(library(lists), [append/3, member/2]). :- use_module(library(loader), [strip_module/3]). :- meta_predicate(phrase(2, ?)). :- meta_predicate(phrase(2, ?, ?)). :- meta_predicate(phrase(3, ?, ?, ?)). :- meta_predicate(phrase(4, ?, ?, ?, ?)). :- meta_predicate(','(2, 2, ?, ?)). :- meta_predicate(;(2, 2, ?, ?)). %% phrase(+Body, ?Ls). % % True iff Body describes the list Ls. Body must be a DCG body. % It is equivalent to `phrase(Body, Ls, [])`. % % Examples: % % ``` % as --> []. % as --> [a], as. % % ?- phrase(as, Ls). % Ls = [] % ; Ls = "a" % ; Ls = "aa" % ; Ls = "aaa" % ; ... . % % ?- phrase(as, "aaa"). % true. % ``` phrase(GRBody, S0) :- phrase(GRBody, S0, []). %% phrase(+Body, ?Ls, ?Ls0). % % True iff Body describes part of the list Ls and the rest of Ls is Ls0. % % Example: % % ``` % ?- phrase(seq(X), "aaa", Y). % X = [], Y = "aaa" % ; X = "a", Y = "aa" % ; X = "aa", Y = "a" % ; X = "aaa", Y = []. % ``` phrase(GRBody, S0, S) :- strip_module(GRBody, M, GRBody1), ( var(GRBody) -> instantiation_error(phrase/3) ; nonvar(GRBody1), dcg_constr(GRBody1), dcg_body(GRBody1, S0, S, GRBody2) -> call(M:GRBody2) ; call(M:GRBody1, S0, S) ). phrase(GRBody, Arg, S0, S) :- strip_module(GRBody, M, GRBody1), ( var(GRBody) -> instantiation_error(phrase/4) ; nonvar(GRBody1), GRBody1 =.. GRBodys1, append(GRBodys1, [Arg], GRBodys2), GRBody2 =.. GRBodys2, dcg_constr(GRBody2), dcg_body(GRBody2, S0, S, GRBody3) -> call(M:GRBody3) ; call(M:GRBody1, Arg, S0, S) ). phrase(GRBody, Arg1, Arg2, S0, S) :- strip_module(GRBody, M, GRBody1), ( var(GRBody) -> instantiation_error(phrase/5) ; nonvar(GRBody1), GRBody1 =.. GRBodys1, append(GRBodys1, [Arg1,Arg2], GRBodys2), GRBody2 =.. GRBodys2, dcg_constr(GRBody2), dcg_body(GRBody2, S0, S, GRBody3) -> call(M:GRBody3) ; call(M:GRBody1, Arg1, Arg2, S0, S) ). % The same version of the below two dcg_rule clauses, but with module scoping. dcg_rule(( M:NonTerminal, Terminals --> GRBody ), ( M:Head :- Body )) :- dcg_non_terminal(NonTerminal, S0, S, Head), dcg_body(GRBody, S0, S1, Goal1), dcg_terminals(Terminals, S, S1, Goal2), Body = ( Goal1, Goal2 ). dcg_rule(( M:NonTerminal --> GRBody ), ( M:Head :- Body )) :- NonTerminal \= ( _, _ ), dcg_non_terminal(NonTerminal, S0, S, Head), dcg_body(GRBody, S0, S, Body). % This program uses append/3 as defined in the Prolog prologue. % Expands a DCG rule into a Prolog rule, when no error condition applies. dcg_rule(( NonTerminal, Terminals --> GRBody ), ( Head :- Body )) :- dcg_non_terminal(NonTerminal, S0, S, Head), dcg_body(GRBody, S0, S1, Goal1), dcg_terminals(Terminals, S, S1, Goal2), Body = ( Goal1, Goal2 ). dcg_rule(( NonTerminal --> GRBody ), ( Head :- Body )) :- NonTerminal \= ( _, _ ), dcg_non_terminal(NonTerminal, S0, S, Head), dcg_body(GRBody, S0, S, Body). dcg_non_terminal(NonTerminal, S0, S, Goal) :- NonTerminal =.. NonTerminalUniv, append(NonTerminalUniv, [S0, S], GoalUniv), ( callable(NonTerminal) -> Goal =.. GoalUniv ; Goal = NonTerminal % let call/N throw an error instead of throwing one here. ). dcg_terminals(Terminals, S0, S, S0 = List) :- append(Terminals, S, List). dcg_body(Var, S0, S, Body) :- var(Var), Body = phrase(Var, S0, S). dcg_body(GRBody, S0, S, Body) :- nonvar(GRBody), dcg_constr(GRBody), dcg_cbody(GRBody, S0, S, Body). dcg_body(NonTerminal, S0, S, Goal1) :- nonvar(NonTerminal), \+ dcg_constr(NonTerminal), loader:strip_module(NonTerminal, M, NonTerminal0), dcg_non_terminal(NonTerminal0, S0, S, Goal0), ( functor(NonTerminal, (:), 2) -> Goal1 = M:Goal0 ; Goal1 = Goal0 ). % The following constructs in a grammar rule body % are defined in the corresponding subclauses. dcg_constr([]). % 7.14.1 dcg_constr([_|_]). % 7.14.2 - terminal sequence dcg_constr(( _, _ )). % 7.14.3 - concatenation dcg_constr(( _ ; _ )). % 7.14.4 - alternative dcg_constr(( _'|'_ )). % 7.14.6 - alternative dcg_constr({_}). % 7.14.7 dcg_constr(call(_)). % 7.14.8 dcg_constr(phrase(_)). % 7.14.9 dcg_constr(phrase(_,_)). % extension of 7.14.9 dcg_constr(phrase(_,_,_)). % extension of 7.14.9 dcg_constr(!). % 7.14.10 dcg_constr(\+ G_0) :- % 7.14.11 - not (existence implementation def.) throw(error(representation_error(dcg_body), [culprit- (\+ G_0)])). dcg_constr((If->Then)) :- % 7.14.12 - if-then (existence implementation def.) throw(error(representation_error(dcg_body), [culprit- (If->Then)])). % The principal functor of the first argument indicates % the construct to be expanded. dcg_cbody([], S0, S, S0 = S). dcg_cbody([T|Ts], S0, S, Goal) :- must_be(list, [T|Ts]), dcg_terminals([T|Ts], S0, S, Goal). dcg_cbody(( GRFirst, GRSecond ), S0, S, ( First, Second )) :- dcg_body(GRFirst, S0, S1, First), dcg_body(GRSecond, S1, S, Second). dcg_cbody(( GREither ; GROr ), S0, S, ( Either ; Or )) :- \+ subsumes_term(( _ -> _ ), GREither), dcg_body(GREither, S0, S, Either), dcg_body(GROr, S0, S, Or). dcg_cbody(( GRCond ; GRElse ), S0, S, ( Cond ; Else )) :- subsumes_term(( _GRIf -> _GRThen ), GRCond), dcg_cbody(GRCond, S0, S, Cond), dcg_body(GRElse, S0, S, Else). dcg_cbody(( GREither '|' GROr ), S0, S, ( Either ; Or )) :- dcg_body(GREither, S0, S, Either), dcg_body(GROr, S0, S, Or). dcg_cbody({Goal}, S0, S, ( Goal, S0 = S )). dcg_cbody(call(Cont), S0, S, call(Cont, S0, S)). dcg_cbody(phrase(Body), S0, S, phrase(Body, S0, S)). dcg_cbody(phrase(Body, Arg), S0, S, phrase(Body, Arg, S0, S)). dcg_cbody(phrase(Body, Arg1, Arg2), S0, S, phrase(Body, Arg1, Arg2, S0, S)). dcg_cbody(!, S0, S, ( !, S0 = S )). % dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )). dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then )) :- dcg_body(GRIf, S0, S1, If), dcg_body(GRThen, S1, S, Then). % When DCG expansion throws an exception – remove offending term and rethrow. user:term_expansion(throw_dcg_expansion_error(E), _) :- throw(E). user:term_expansion(Term0, Term) :- nonvar(Term0), catch(dcg_rule(Term0, Term), E, Term = throw_dcg_expansion_error(E)). %% seq(Seq)// % % Describes a sequence seq(Xs, Cs0,Cs) :- var(Xs), Cs0 == [], !, Xs = [], Cs0 = Cs. seq([]) --> []. seq([E|Es]) --> [E], seq(Es). %% seqq(SeqOfSeqs)// % % Describes a sequence of sequences seqq([]) --> []. seqq([Es|Ess]) --> seq(Es), seqq(Ess). %% ...// % % Describes an arbitrary number of elements ...(Cs0,Cs) :- Cs0 == [], !, Cs0 = Cs. ... --> [] | [_], ... . % defer instantiation errors until runtime. instantiations may be made % then. error_goal(error(instantiation_error, _Context), _). error_goal(error(E, must_be/2), error(E, must_be/2)). error_goal(error(E, (=..)/2), error(E, (=..)/2)). error_goal(error(representation_error(dcg_body), Context), error(representation_error(dcg_body), Context)). error_goal(E, _) :- throw(E). user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :- loader:strip_module(GRBody, M, GRBody0), nonvar(GRBody0), catch(dcgs:dcg_body(GRBody0, S, S0, GRBody1), E, dcgs:error_goal(E, GRBody1) ), ( E = error(instantiation_error, _), GRBody0 = [T|Ts] -> GRBody2 = (error:must_be(list, [T|Ts]), lists:append([T|Ts], S0, S)) ; GRBody = (_:_) -> GRBody2 = M:GRBody1 ; GRBody2 = GRBody1 ). user:goal_expansion(phrase(GRBody, S), phrase(GRBody, S, [])). % (-->)/2 behaves as if it didn't exist. We export (and define) it % only so that clauses for (-->)/2 cannot be asserted when % library(dcgs) is loaded. (_-->_) :- throw(error(existence_error(procedure,(-->)/2),(-->)/2)).