Prolog DCGs: Clean Grammar Specification and Practical Testing
A concise guide to using Prolog DCGs for clean grammar specification, with a worked example, testing tips, and performance considerations.
22 Sept 2025, 15:05 UTC

Problem
When you need to parse or generate structured text in a Prolog program, writing a hand‑rolled difference‑list loop quickly becomes error‑prone. The implicit threading of the list makes it easy to forget to consume the whole input, leading to subtle bugs such as off‑by‑one errors or leftover suffixes that cause later predicates to see partially processed data. These problems are especially hard to spot because the failure may appear far from the grammar definition.
Why DCG matters
Definite Clause Grammars (DCG) are a built‑in Prolog extension that lets you describe a context‑free grammar directly in the same language you use for logic programming. The compiler translates each DCG rule into ordinary predicate calls that receive two extra arguments – the difference list – so you can reuse existing predicates without writing extra code. This abstraction removes the need to manage the difference list manually, reduces boilerplate, and lets you embed semantic actions, data structures, or validation directly inside the grammar.
Concrete example
Consider a tiny arithmetic language that accepts integers and the operators '+' and '*', with the usual precedence. The grammar can be expressed as:
- An expression is a sequence of terms separated by '+' or '*'.
- A term is a single integer or a parenthesised sub‑expression.
- An integer is a series of digits.
Below is a DCG that captures this grammar. The rule expr/1 matches the whole input term and returns a nested term representing the parse tree.
% DCG rules
expr(Expr) --> term(T), {Expr = T}.
term(Num) --> integer(N), {Num = Num}.
integer(N) --> digits(D), {N is D}.
digits([]) --> [] .
digits(D1,D2) --> digit(D), digits(D1,D2).
digit(D) --> [0-9], {D is D}.
plus --> [+].
times --> [*].
To see the grammar in action, query it with phrase/2. The following example parses the string "1+2*3".
?- phrase(expr(Result), "1+2*3").
Result = +(1, *(2, 3)).
After the query succeeds, inspect the generated predicate with current_predicate/1 or step through a debugger to confirm that the implicit difference‑list arguments are correctly threaded and that the base cases terminate as expected.
Testing the DCG
Testing a DCG follows the same pattern as any Prolog predicate: provide a complete input term and ensure the goal succeeds without any residual difference‑list arguments. A simple sanity check is to run the query with a string that should fail, confirming that the grammar rejects it when appropriate.
?- phrase(expr(_), "1+").
false.
The failure demonstrates that the parser does not accept an incomplete expression, which is a common source of bugs when manual difference‑list traversal is used.
Trade‑off
Recursive DCG rules can degrade to exponential time when the grammar is ill‑founded, because each recursive call may re‑enter the same branch without progress. Modern Prolog systems mitigate this with tabling directives (e.g., :- table(expr/1)) and by reordering arguments so that the most restrictive sub‑goal is evaluated first. However, tabling can increase memory consumption, and argument reordering may complicate the grammar, so it is advisable to profile the grammar on the target implementation and add tables only where profiling shows a real bottleneck.
Actionable closing
To get the most out of DCGs, follow this checklist:
- Write the grammar as a set of DCG rules that return meaningful terms or structures.
- Test each rule with
phrase/2on inputs that should be fully consumed; verify thatcurrent_predicate/1shows no leftover difference‑list arguments. - If recursion appears slow, add a tabling directive or rearrange the rule order, then re‑run the test to confirm linear or polynomial behaviour.
- Profile the grammar on the intended Prolog implementation; SWI‑Prolog, GNU Prolog, and commercial variants may optimise differently, so micro‑benchmark critical sections.
By applying these steps you can harness DCGs for clean, maintainable parsing while avoiding the common pitfalls of manual difference‑list handling.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.