On this page
.prime DSL grammar
A small, hand-written grammar. 28 keywords (one per kind), 14 verb names as field
identifiers, 6 punctuation classes. Comments use //.
Lexical structure
- Keywords — the 28 atom kinds. Reserved.
- Identifiers — atom names (
PascalCase) and field names (kebab-case). - Atom IDs —
@scope/kind-kebab-name. Cross-Prime refs allowed. - Strings — double-quoted,
\\-escapes for",n,t. - Numbers — integers and decimals.
- Booleans —
true,false. - Punctuation —
{}[]:, - Comments —
// to end of line.
BNF (sketch)
prime-file ::= atom-decl*
atom-decl ::= kind-keyword name "{" field* "}"
kind-keyword ::= "fact" | "term" | "rule" | "method" | ... (28 kinds)
name ::= [A-Z][A-Za-z0-9]*
field ::= field-name ":" value
field-name ::= [a-z][a-z0-9-]* // kebab-case
value ::= string | number | bool
| atom-id
| list
| object
string ::= '"' (escaped-char | non-quote)* '"'
number ::= integer | decimal
bool ::= "true" | "false"
atom-id ::= "@" scope "/" name-kebab
scope ::= [a-z][a-z0-9-]*
name-kebab ::= [a-z][a-z0-9-]*
list ::= "[" (value ("," value)* ","?)? "]"
object ::= "{" (field ("," field)* ","?)? "}" Trailing commas
Allowed in lists and objects. The parser silently accepts [a, b, c,]
so reordering edge lists doesn't churn diffs.
Field types per value
| Field shape | Examples |
|---|---|
| string | statement: "..." |
| number | confidence: 0.99 |
| bool | verified: true |
| atom id | requires: [@my/term-x] |
| list of strings | tags: ["fintech", "button"] |
| list of objects | steps: [{ action: "..." }] |
| nested object | contract: { must-include: [...] } |
Edge fields
The 14 edge verbs are recognised as field names. Their values are always lists of atom IDs.
requires: [@my/term-x, @my/term-y]
validates-with: [@my/source-rfc-1234]
contradicts: [@my/anti-pattern-y]
see-also: [@my/pattern-z]
Edge cases
- Duplicate fields — last wins; L1 emits a warning.
- Empty list — equivalent to omitting the field.
- Empty object — invalid for kind-required fields.
- Self-reference — an atom that references itself in
requires is an L3 cycle.
Why a DSL?
YAML or JSON would have worked. We picked a thin DSL because:
- YAML's whitespace + 9 ways to write a string makes parser errors fragile.
- Atom IDs (
@scope/name) get a dedicated lexical class — better error messages on typos. - Reserved kind keywords are clearer than YAML's
kind: rule with arbitrary other fields.