Prolog DCGs in Practice: Efficient Parsing and DSL Building
Learn how Prolog's Definite Clause Grammars simplify parsing and DSL creation with a concrete example and practical tips.
24 Sept 2025, 08:46 UTC

Parsing user input in Prolog can be cumbersome, but Definite Clause Grammars turn the task into a concise specification.
The usual way to read a line of text, split it into tokens, and apply a grammar is to write a series of predicates that manually manage indices and temporary variables. This approach quickly becomes hard to read, easy to get wrong, and often forces you to rewrite code when the grammar changes.
Definite Clause Grammars (DCGs) are a Prolog extension that lets you describe a grammar directly in the same syntax you use for ordinary predicates. Under the hood, a DCG rule compiles to a regular predicate that receives an extra argument – the difference list – which tracks the remaining input. By calling the high‑level predicate phrase/2 instead of write/1, the framework automatically supplies and updates this difference list, so you can focus on the grammar itself.
What a DCG rule looks like
A DCG rule is written as a sequence of goals separated by -->. The final goal consumes the input and leaves the difference list in a state that matches the next rule’s expectations. For example, a rule that matches a single word might be written as:
word(w) --> [w].
The brackets indicate that the terminal w is matched against the current element of the difference list, and the list is then unified with the same list (no change) because the terminal consumes exactly one element.
From DCG syntax to ordinary predicates
When you load a DCG file into SWI‑Prolog, the compiler translates each DCG rule into a plain predicate that takes the input list, the output list, and an additional difference list argument. The transformation is deterministic, so you can inspect the generated code with listing/0 to see the exact predicate that will be executed.
Worked example: parsing a space‑separated list of words
- Define the DCG rules in a file named
parser.dcg: - Load the file and define a top‑level wrapper:
- Run a query to parse the string "a b c":
- Verify the transformation by viewing the generated predicate:
% parser.dcg
sentence(S) --> words(W), {S = W}.
words([]) --> [].
words([X|Xs]) --> word(X), words(Xs).
?- [parser]. % loads parser.dcg
true.
?- phrase(user_input, Sentence), listing(sentence/2).
The phrase/2 call supplies an empty difference list as the initial state and expects the same empty list after parsing, ensuring the whole input is consumed.
?- phrase( "a b c", Words ).
Words = [a, b, c].
?- listing(words/2).
% words/2
% (0+ charact.) = ...
You can see that the DCG rule words([]) --> [] became a clause that matches an empty list, while words([X|Xs]) --> word(X), words(Xs) was turned into a predicate that recursively processes the list.
Trade‑off: recursion depth and performance
DCGs are convenient, but naïve recursive definitions can hit Prolog’s maximum recursion depth when the input is large or the grammar is deeply nested. The difference list itself does not prevent stack overflow; it merely changes the way arguments are passed. To mitigate this, you can:
- Rewrite recursive rules using tail‑call patterns or accumulators.
- Switch to a difference‑list‑only style where the list is passed explicitly without the extra argument syntax.
- For heavy numeric or constraint‑based parsing, consider using CLP(FD) or other specialized libraries.
These adjustments are optional for small grammars but become essential when processing megabyte‑scale inputs or implementing complex domain‑specific languages.
Actionable closing
To try DCGs in your own project:
- Create a DCG file that describes the grammar you need, using
phrase/2as the entry point. - Load the file with
?- [your_file].and ensure you have read permission on the file. - Call
phrase/2with the input term (e.g., a string or list of characters) and a variable for the result. - Use
trace/0before the query to step through the generated predicate clauses and observe the difference‑list flow. - If you encounter recursion limits, refactor the rule to use an accumulator or a tail‑recursive pattern.
By following these steps you’ll replace a tangled set of manual token‑handling predicates with a clear, declarative grammar that is both easier to maintain and faster to execute for typical use cases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.