The EAPI Grammar
The Gentoo Package Manager Specification (PMS) defines a dependency language that is easy to underestimate on first reading. A single atom can carry a version comparator, a category and package name, a Gentoo version string, slot and sub-slot operators, USE restrictions, and—wrapped around lists of atoms—USE conditionals, choice groups, and blockers. Traditional Portage implements this surface syntax with an ad hoc parser written in Python.
portage-ng uses Prolog’s built-in Definite Clause Grammar (DCG)
notation to encode the same language directly
(Source/Domain/Gentoo/eapi.pl). The insight is simple: PMS dependency
syntax is a grammar, so it should be expressed as one. DCG rules
describe that grammar directly; the parser is the grammar, not a separate
layer that tries to stay in sync with a prose spec.
What PMS says and what the executable parser accepts are one artifact—the rules
in eapi.pl—rather than a specification document drifting away from a pile of
regexes and special cases.
The grammar fully implements PMS 9 / EAPI 9.
What gets parsed
The EAPI grammar is exercised whenever md5-cache metadata is loaded. Each file
under the Portage tree’s metadata/md5-cache/ directory holds one ebuild’s
worth of metadata as a flat list of lines, each line a single KEY=VALUE pair
(PMS 9, §14.3). A typical fragment looks like this:
BDEPEND=>=dev-build/cmake-3.16
DEFINED_PHASES=compile configure install prepare test
DEPEND=dev-libs/openssl:= dev-libs/libffi:=
EAPI=8
IUSE=debug doc test
KEYWORDS=~amd64 ~arm64
RDEPEND=dev-libs/openssl:= >=dev-lang/python-3.10[ssl,threads]
REQUIRED_USE=|| ( python_targets_python3_11 python_targets_python3_12 )
SLOT=0
The DCG is responsible for turning the values of dependency-related keys into
structured terms: dependency strings (DEPEND, BDEPEND, RDEPEND,
PDEPEND, IDEPEND), USE-conditional groups, version operators, slot
operators, USE dependencies, and REQUIRED_USE constraints. Other keys (EAPI,
SLOT, KEYWORDS, DESCRIPTION, …) use smaller, dedicated value rules in the
same module—still DCG-driven, but without the full dependency expression
machinery.
A worked example: one dependency atom
Consider a single atom as it might appear in RDEPEND or DEPEND:
>=dev-libs/openssl-3.0:0/3=[ssl,-test]
The core DCG rule for a package atom is eapi:package_dependency/3 in
eapi.pl. Conceptually it composes the way PMS §8.3 suggests reading the text:
optional blocker, optional comparator, category/package, optional version,
optional slot restriction, optional USE dependency list, with a small helper to
merge “= + wildcard” into the dedicated wildcard operator:
eapi:package_dependency(T, _R://_E, Output) -->
eapi:blocking(B), % optional
eapi:operator(O), % optional
eapi:category(C), eapi:separator, !, eapi:package(P), % required
eapi:version0(V, W), % optional
eapi:slot_restriction(S), % optional
eapi:use_dependencies(U), % optional
{ eapi:select_operator(O, W, Op),
Output = package_dependency(T, B, C, P, Op, V, S, U) }.
Matching each piece
blocking— No!or!!prefix, so this clause leaves the blocking marker as “none” (noin the concrete term).operator— The leading>=matches thegreaterequaloperator.category,/,package— Consumesdev-libs, the slash, andopenssl.version0— After the hyphen, parses3.0into aversion/7term (and records that there is no=*-style wildcard suffix on this atom).slot_restriction— The:0/3=fragment becomes a list describing the main slot, sub-slot, and trailing=(rebuild-on-slot change semantics).use_dependencies— Bracket contents parse as a comma-separated list:sslas an enable requirement,-testas a disable requirement.select_operator— With no wildcard, the final operator remainsgreaterequal.
Intermediate state and difference lists
Operationally, each DCG goal is expanded into an ordinary Prolog predicate with two extra arguments: the current suffix of the input code list and the remaining suffix after the rule succeeds. That is the standard DCG difference-list threading: parsing advances by shortening the difference between “input seen so far” and “still to read.”
You can read the parse as a sequence of remaining input snapshots (conceptual, not a separate data structure the code prints):
| After this part succeeds | Remaining input (conceptually) |
|---|---|
| (start) | >=dev-libs/openssl-3.0:0/3=[ssl,-test] |
operator |
dev-libs/openssl-3.0:0/3=[ssl,-test] |
category + / + package |
-3.0:0/3=[ssl,-test] |
version0 |
:0/3=[ssl,-test] |
slot_restriction |
[ssl,-test] |
use_dependencies |
(empty — parse succeeds) |
Calling phrase(Rule, Codes) wraps that pattern: it requires the rule to
consume all of Codes (or you supply an explicit remainder). There is no
hand-maintained cursor variable in application code—the DCG expansion supplies
it.
Final term
For the install-time dependency role (package_d in the grammar), parsing the
string above yields a package_dependency/8 term of this shape (as produced by
the running grammar; minor formatting may vary):
package_dependency(
install,
no,
'dev-libs',
openssl,
greaterequal,
version([3, 0], '', 4, 0, [], 0, '3.0'),
[slot('0'), subslot('3'), equal],
[use(enable(ssl), none), use(disable(test), none)])
The first argument (install / run / compile) records which PMS
dependency class is being parsed, so the same DCG surface syntax feeds
slightly different typing in the abstract syntax: install for
DEPEND/BDEPEND/IDEPEND, run for RDEPEND/PDEPEND (PDEPEND is re-tagged
pdepend at the query layer), and compile for CDEPEND.
Why DCG instead of regex or ad hoc code?
The dependency language defined by PMS is recursive: USE conditionals contain dependency lists, which themselves contain atoms, which may carry nested USE restrictions. A regex or hand-coded string scanner can handle flat patterns, but recursive structure calls for a recursive formalism. Prolog’s DCG notation is exactly that formalism, and it brings several practical advantages.
Composition. The package-atom rule is assembled from small,
self-contained nonterminals — blocking, operator, category,
separator, package, version0, slot_restriction, and
use_dependencies — each of which can be understood and tested in
isolation:
dep_atom --> blocking, operator, category, '/', package, version_suffix, slot_suffix, use_deps.
Local testing. Every nonterminal is an ordinary Prolog predicate,
so you can call phrase/2 on a single rule without loading the cache
or the full pipeline.
Free recursion. USE conditionals and nested choice groups use the
same mechanism as every other rule: eapi:dependencies//3 recurses
through lists of eapi:dependency//3 alternatives, so nested
flag? ( … ) structures need no separate stack machine.
Failure locality. When parsing fails, the failure occurs at a named nonterminal — a clear indication of which part of the input was unexpected, rather than a terse “pattern did not match” from a regex engine.
Graceful evolution. New PMS features tend to introduce new
alternatives or new rules (additional operators, value forms, EAPI-9
extensions), not a rewrite of central control flow. Adding a rule to
a DCG is a one-clause change; the equivalent in a Python parser is
typically a new branch in an if eapi >= … ladder spread across
several functions.
DCG grammar design
The grammar is implemented in Source/Domain/Gentoo/eapi.pl as a set of DCG
rules. DCGs are a natural fit for dependency specifications because:
- Dependency atoms have recursive structure (USE conditionals nest).
- The grammar is context-free at the level PMS defines.
- DCG rules compose as Prolog predicates, so the parser constructs Prolog terms while it reads text.
Dependency atoms
The table below summarizes how the surface syntax maps into fields of the abstract atom (illustrated with the same running example):
| Component | Example | Parsed as |
|---|---|---|
| Version operator | >= |
Comparator atom (e.g. greaterequal) |
| Category | dev-libs |
Atom |
| Name | openssl |
Atom |
| Version | 3.0 |
version/7 term |
| Slot operator | :0/3= |
Slot + sub-slot + rebuild flag |
| USE deps | [ssl,-test] |
Enable/disable (and related) wrappers |
USE conditionals
USE-conditional dependency groups use the syntax:
flag? ( deps... ) — include deps if flag is enabled
!flag? ( deps... ) — include deps if flag is disabled
These are parsed into conditional terms that the rules layer evaluates against the USE model during proof construction.
Choice groups
PMS defines three choice operators for REQUIRED_USE and dependency specs:
||(any-of) —|| ( a b c )— at least one of the listed items must be satisfied^^(exactly-one-of) —^^ ( a b c )— exactly one must be satisfied??(at-most-one-of) —?? ( a b c )— at most one may be satisfied
Reader/parser pipeline
Loading md5-cache is a small pipeline with clear separation of concerns.
The repository side (Source/Knowledge/repository.pl) knows where the cache
lives: under each tree’s metadata/md5-cache/ directory, with one file per
category/package-version entry (repository:get_cache_file/2 resolves entry
→ path). Sync and incremental updates decide which entries need work; for
each entry that must be read, the repository opens the flat cache file.
Source/Pipeline/reader.pl does one job: given a path (or stream), it reads
the file line by line into a list of strings—each string is still a raw
KEY=VALUE line, unchanged.
Source/Pipeline/parser.pl walks that list. For each line it converts the
string to character codes and runs phrase(eapi:keyvalue(metadata, …), Codes).
That single DCG entry point dispatches on the key: dependency keys delegate to
the full dependency grammar (DEPEND, BDEPEND, RDEPEND, PDEPEND,
IDEPEND, REQUIRED_USE, …); non-dependency keys use lighter value rules
(EAPI, SLOT, KEYWORDS, IUSE, …).
So the data flow is:
md5-cache files → reader.pl (lines) → parser.pl → eapi.pl (DCG) → cache predicates
reader.plreads each md5-cache file into a list of lines (one key/value pair per line).parser.plparses every line througheapi:keyvalue/3, which routes values to the appropriate DCG subtree.eapi.plbuilds structured Prolog terms (e.g.depend(D),rdepend(D),slot(S),eapi(E)).The results are asserted as
cache:entry/5and related predicates, populating the knowledge base.
The reader supports incremental loading — only new or changed files need to be
re-parsed when using --regen.
Parsed output
After parsing, each ebuild is represented by a set of cache predicates. The
dependency model for an ebuild is a list of package_dependency/8 terms:
package_dependency(DepType, Blocking, Category, Name, Operator, Version,
SlotInfo, UseInfo)
These terms are consumed by the rules layer during proof construction. The EAPI grammar handles all PMS 9 / EAPI 9 constructs:
- Version operators:
=,>=,<=,>,<,~,=*(wildcard) - Slot operators:
:SLOT,:SLOT/SUBSLOT,:*,:= - USE dependencies:
[flag],[-flag],[flag=],[!flag=],[flag(+)],[flag(-)] - Blockers:
!cat/pkg(weak),!!cat/pkg(strong) - All-of groups (implicit conjunction)
- Any-of groups (
|| ( ... )) - USE conditionals (
flag? ( ... ),!flag? ( ... ))
Further reading
- Chapter 6: Knowledge Base and Cache — how parsed data is stored
- Chapter 12: Resolution — Configuration as Proofs — how dependency terms are consumed during proof construction
- Chapter 24: Dependency Ordering — PMS dependency type semantics