DCGs — Definite Clause Grammars
prolog2-4 ended with a promise: the difference-list threading built by hand there reappears, hidden, inside Prolog's own --> notation. This chapter delivers on that — DCGs are Prolog's built-in grammar-writing sugar, and no other language covered on this site has anything comparable baked directly into the language itself, not layered on as a separate parser-combinator library.
The --> Notation
A DCG rule looks like an ordinary Prolog clause, but with --> instead of :-, and its body written as a sequence of terminals — literal list elements in square brackets — to be matched against the input, left to right. phrase/2 is how you actually run one: phrase(NonTerminal, List) checks whether List can be fully consumed by the grammar rule NonTerminal.
What --> Is Actually Sugar Over
Every DCG rule is translated, automatically, into an ordinary Prolog clause with two extra hidden arguments — an input-so-far list and a remaining-list, threaded through exactly like prolog2-4's own List-Hole pair. Conceptually, greeting --> [hello], [world]. becomes:
Each terminal peels one element off the front of the "remaining input so far" list, threading the leftover tail into the next goal — S0 to S1 to S, the exact same open-list-with-a-hole pattern the previous chapter built by hand. phrase(greeting, [hello, world]) is really just calling greeting([hello, world], []) — the whole list in, nothing left over.
Nonterminals — Rules Calling Rules
A DCG rule body can reference other DCG rules, not just literal terminals — each nonterminal call receives whatever input is left after the rules before it, and passes along whatever remains after it consumes its own piece. This is exactly how a real grammar is meant to compose: small rules for individual pieces (noun, verb), combined into larger ones (noun_phrase, sentence) without any of them needing to know how the input-threading actually works underneath.
Embedding Ordinary Prolog Code — Curly Braces
Anything inside { } inside a DCG body is treated as an ordinary Prolog goal, run as-is rather than being interpreted as a grammar symbol to match against the input list — the escape hatch that lets a grammar rule check arithmetic, call another predicate, or do anything else Prolog can normally do, mid-parse.
A Practical Use — Summing an Expression List
A genuinely useful grammar, not just a toy: expr parses a term, then repeatedly looks for plus followed by another term, accumulating the running total with prolog1-7's own is/2 inside a curly-brace escape at each step, until expr_rest's empty-list base case is reached and the accumulated sum is unified with the final result.
| Underlying mechanism | What you write | |
|---|---|---|
| Hand-written difference lists | List-Hole pairs, threaded manually | every S0/S argument, explicitly |
| DCGs (--> notation) | the exact same List-Hole threading | grammar rules only — the threading is generated for you |
phrase(NonTerminal, List) handles that translation for you. phrase/3 additionally accepts a "leftover" argument, useful when the grammar is only meant to match a prefix of the input rather than the whole list.
N is Acc + T directly inside a DCG body — without wrapping it in { } — doesn't run it as Prolog code at all; it gets treated as a terminal, an attempt to match a literal grammar symbol against the input list, which either fails outright or raises a type error. Every ordinary Prolog goal inside a --> body needs its own { }, without exception.
Coding Challenges
Write the sentence/noun_phrase/verb_phrase grammar exactly as shown in the chapter, add a second noun (e.g. [mouse]) and a second verb (e.g. [saw]), and confirm phrase/2 accepts a new valid sentence combining them.
📄 View solutionWrite the expr/term/expr_rest grammar exactly as shown in the chapter, then use phrase/2 to sum a longer list such as [5, plus, 10, plus, 15, plus, 20], confirming the correct total.
📄 View solutionWrite a short comment demonstrating what happens if the { } braces around { Acc1 is Acc + T } in expr_rest/2 are accidentally removed, explaining specifically why Prolog then tries to treat it as a grammar terminal rather than running it as arithmetic.
📄 View solutionChapter 5 Quick Reference
- Rule --> Body. defines a grammar rule; terminals are written as literal lists (e.g. [hello])
- phrase(NonTerminal, List) runs a DCG rule against List — really calling the rule's own hidden two-argument compiled form
- Every --> rule compiles to a predicate threading an input-so-far/remaining-input pair — the exact same difference-list pattern from prolog2-4
- DCG rules can call other DCG rules as nonterminals, composing small grammar pieces into larger ones
- { Goal } runs Goal as ordinary Prolog code inside a DCG body, rather than treating it as a grammar terminal to match
- Forgetting { } around a Prolog goal inside a DCG body is a common, easy-to-miss source of failures or type errors