4. ATD grammar notation
Contents of ATD 0.1
ATD's grammar is written in ATD grammar notation, a subset of the grammar language of Lark (https://github.com/lark-parser/lark), stated here so that it can be implemented without Lark. The same notation is used by a binding that carries a grammar (section 11.9). grammar/notation.lark is the notation written as a grammar; every module of ATD's grammar reads under it.
| Admitted | Not admitted |
|---|---|
a rule name: expansion | expansion, and a terminal NAME: "x" | /re/ |
templates _rule{x} |
?rule (inlined when it has one child), _rule and _TERMINAL (filtered from the tree) |
rule priorities rule.2 |
x?, x*, x+, [x], ( … ) |
%declare, %override, %extend |
an alias -> name |
%import of anything outside the language's modules (in a binding, outside the binding's own files beside it) |
a terminal priority TERMINAL.2 |
the flags of regular expressions (/x/i), and every flag of a string but i |
a string matched in any case of its ASCII letters, "x"i, whose text is ASCII (section 3.6) |
|
%ignore TERMINAL |
terminals outside the subset of section 3.5 |
%import .module (names) |
keep-all-tokens rules !rule, ranges "a".."z", repetitions x ~ 3 |
Rule names are lowercase, terminal names uppercase. Comments are // to the end of the line. A definition starts with its name and colon and runs to the next definition; an alternative may continue on the next line after |.
- A rule's expansions are its alternatives; an expansion is a sequence of items; an item is a rule, a terminal, an anonymous string (a terminal whose pattern is that string), an anonymous regular expression, or a group.
x?and[x]arexor nothing;x*is any number ofx;x+one or more;( … )groups alternatives.- A terminal is a string, a string with
i, which matches its text in any case of its ASCII letters, a regular expression, or a sequence and choice of other terminals; it matches as section 3.6 says. Ingrammar/notation.larka string and itsiare one token,GRAMMAR_STRING, so that the flag is never read as a reference to a rulei. %ignore TmakesTa candidate in every state and drops what it matches.
The grammar of ATD 0.1 is the root module grammar/atd.lark and the modules it imports: core, cel, script, canvas, form, text, binding and notation. Every module's symbols share one grammar:
- In the root,
%import .m (a, b, …)makes the symbolsa,bof modulemthe root's own. - A module uses symbols of other modules by name, without an import of its own; the root's import list for that module names each such symbol, which then resolves to the root's symbol of that name. Every terminal a module defines is in the root's import list for it.
- Every other symbol of module
mis private to it, and is namedm__symbol(_m__symbolfor a symbol that starts with_) in the grammar the modules compose.
These are the rules of Lark's %import, so that Lark builds the grammar from atd.lark directly (contracts/grammar.md, rule 1).
An implementation builds its tables from the grammar after this expansion, which is Lark's, so that two implementations build the same automaton:
- A group of alternatives inside an expansion is distributed:
a (b | c) dbecomes the two expansionsa b danda c d. x?and[x]become two expansions, one withxand one without.x+becomes a new ruleH: x | H x, andxis replaced byH.x*becomes the same ruleH, and two expansions, one withHand one without.- Two repetitions of the same item share one rule
H, wherever they stand. - Duplicate expansions of a rule are kept once.
- The start symbol is
description. The automaton is augmented as an LR automaton is: the state the start state reaches ondescriptionaccepts at$END; the augmenting rule is not one of the grammar's rules.
A conforming parser need not build Lark's tree; ATD's own tree is the abstract tree of $defs/Tree, which an implementation lowers from the parse (section 5.5). For a grammar a binding carries, the tree is shaped as Lark shapes it, since a binding's selectors name its nodes (section 11.9): a node per rule, named by the rule or its alias; ?rule replaced by its child when it has one; _rule replaced by its children; _TERMINAL and anonymous strings dropped; and [x] that matched nothing kept as an empty child.
grammar/atd.flat.lark is the whole grammar in one file, generated by atd-grammar.py --flatten: the modules' definitions with the names of section 4.3, no %import, the root's %ignores, and the rule start: description that model services look for. It builds the same tables as the modules, and LLGuidance's Lark front end, the context-free engine behind grammar-constrained output in the major model services, accepts it (FR-090). It is the file to hand an agent (section 19.4).
grammar/atd.tables.json ($defs/ParseTables) is the LALR(1) automaton of the grammar, normalized so that two implementations can be compared byte for byte: terminals by name, an anonymous string terminal named by its quoted string, a string with i carrying "flags": "i"; the rules after section 4.4, sorted and numbered, the rule H of a repetition named (<alternatives>)+, its expansions that do not start with H, sorted and joined with |; and the states numbered breadth first from the start state, visiting the shifted symbols in sorted order, each with its actions on terminals (s<state>, r<rule>, accept) and its gotos. atd-grammar.py --tables writes it from Lark; atd tables writes it from another implementation. The grammar builds with no shift/reduce and no reduce/reduce conflict, and no two pattern terminals of one priority that a state accepts match the same text (FR-002, SC-001); a keyword and the pattern that spells it are rule 1 of section 3.6, not such a pair.