8000 minigraf/docs/GRAMMAR.md at v3 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
< 7FFF script type="application/json" data-target="react-partial.embeddedData">{"props":{"color_mode":"dark","logged_in":false,"marketing_page":false,"home_path":"/","login_path":"/login?return_to=https%3A%2F%2FGitHub.com%2Fproject-minigraf%2Fminigraf%2Fblob%2Fv3%2Fdocs%2FGRAMMAR.md","signup_path":"/signup?ref_cta=Sign+up\u0026ref_loc=header+logged+out\u0026ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow\u0026source=header-repo\u0026source_repo=project-minigraf%2Fminigraf","signup_enabled":true,"is_signup_controller":false,"show_search_and_nav":true,"hide_search":false,"private_mode_enabled":false,"should_use_dotcom_links":true,"auth_hydro_click":"{\"event_type\":\"authentication.click\",\"payload\":{\"location_in_page\":\"site header menu\",\"repository_id\":null,\"auth_type\":\"SIGN_UP\",\"originating_url\":\"https://GitHub.com/project-minigraf/minigraf/blob/v3/docs/GRAMMAR.md\",\"user_id\":null}}","auth_hydro_click_hmac":"cec3d29f4efdc274f9995e7d0af4092bb849afbe730f19ce74548c18dab972e8"}}

Latest commit

 

History

History
151 lines (133 loc) · 9.33 KB

GRAMMAR.md

File metadata and controls

151 lines (133 loc) · 9.33 KB

Datalog Grammar

This EBNF is the structural syntax of Minigraf's Datalog commands. It is generated from tests/grammar/grammar.pest, the grammar the parser is tested against: for every input up to a size bound, a test checks that the parser rejects what the grammar rejects, and accepts what it accepts unless a semantic rule rejects it. To change this page, edit the grammar and run UPDATE_GRAMMAR_DOC=1 cargo test --test grammar_doc.

Between tokens the parser skips spaces, tabs, newlines, carriage returns and commas. It also skips comments: ; and the rest of its line. Inside a rule marked (* token *) nothing is skipped, and the rules it uses are read the same way.

EBNF has no lookahead, so a pest lookahead appears as a comment: (* not followed by x *) means the next characters must not match x, and nothing is read. Literals have no escapes: "\" is one backslash and '"' one double quote.

(* ── WORD-BOUNDARY HELPER ── *)
word-cont             ::= letter | numeral | "?" | "_" | "-" | "/"
kw-find               ::= ":find" (* not followed by word-cont *)  (* token *)
kw-where              ::= ":where" (* not followed by word-cont *)  (* token *)
kw-with               ::= ":with" (* not followed by word-cont *)  (* token *)
kw-as-of              ::= ":as-of" (* not followed by word-cont *)  (* token *)
kw-valid-at           ::= ":valid-at" (* not followed by word-cont *)  (* token *)
kw-any-valid-time     ::= ":any-valid-time" (* not followed by word-cont *)  (* token *)
kw-max-derived-facts  ::= ":max-derived-facts" (* not followed by word-cont *)  (* token *)
kw-max-results        ::= ":max-results" (* not followed by word-cont *)  (* token *)
kw-query-memory-limit ::= ":query-memory-limit" (* not followed by word-cont *)  (* token *)
kw-max-iterations     ::= ":max-iterations" (* not followed by word-cont *)  (* token *)
kw-limit              ::= ":limit" (* not followed by word-cont *)  (* token *)
kw-offset             ::= ":offset" (* not followed by word-cont *)  (* token *)
kw-distinct           ::= ":distinct" (* not followed by word-cont *)  (* token *)
kw-over               ::= ":over" (* not followed by word-cont *)  (* token *)
kw-rows               ::= ":rows" (* not followed by word-cont *)  (* token *)
kw-partition-by       ::= ":partition-by" (* not followed by word-cont *)  (* token *)
kw-order-by           ::= ":order-by" (* not followed by word-cont *)  (* token *)
kw-desc               ::= ":desc" (* not followed by word-cont *)  (* token *)
kw-asc                ::= ":asc" (* not followed by word-cont *)  (* token *)
kw-valid-from         ::= ":valid-from" (* not followed by word-cont *)  (* token *)
kw-valid-to           ::= ":valid-to" (* not followed by word-cont *)  (* token *)
reserved-head         ::= ("not-join" | "not" | "or-join" | "or") (* not followed by word-cont *)  (* token *)
and-head              ::= "and" (* not followed by word-cont *)  (* token *)

(* ── EDN PRIMITIVES ── *)
boolean-lit ::= ("true" | "false") (* not followed by word-cont *)  (* token *)
nil-lit     ::= "nil" (* not followed by word-cont *)  (* token *)
integer-lit ::= "-"? digit+ (* not followed by "." digit *)  (* token *)
float-lit   ::= "-"? digit+ "." digit*  (* token *)
str-char    ::= "\" ("n" | "t" | "r" | '"' | "\" | any-char)
              | (* not followed by '"' | "\" *) any-char
string-lit  ::= '"' str-char* '"'  (* token *)
uuid-lit    ::= "#uuid" string-lit
set-elem    ::= uuid-lit | boolean-lit | nil-lit | float-lit | integer-lit | string-lit
              | keyword
set-lit     ::= "#{" set-elem* "}"
bind-slot   ::= "$" (letter | numeral | "_" | "-")+  (* token *)
keyword     ::= ":" word-cont+  (* token *)
variable    ::= "?" word-cont*  (* token *)
op-sym      ::= "<=" | ">=" | "!=" | "<" | ">" | "=" | "+" | "*" | "/"
              | "-" (* not followed by word-cont *)  (* token *)
plain-sym   ::= (* not followed by boolean-lit *) (* not followed by nil-lit *) (letter | "_") word-cont*
              | "-" (* not followed by numeral *) word-cont+  (* token *)

(* ── EDN VALUE (any EDN atom or container) ── *)
edn-value  ::= set-lit | uuid-lit | boolean-lit | nil-lit | float-lit | integer-lit
             | string-lit | bind-slot | keyword | op-sym | variable | plain-sym
             | edn-list | edn-vector | edn-map
edn-list   ::= "(" edn-value* ")"
edn-vector ::= "[" edn-value* "]"
edn-map    ::= "{" (edn-value edn-value)* "}"

(* ── TOP-LEVEL COMMAND ── *)
command ::= transact-cmd | retract-cmd | query-cmd | rule-cmd

(* ── TRANSACT ── *)
transact-cmd ::= "(" "transact" (valid-time-map fact-vector | fact-vector) ")"

(* ── RETRACT ── *)
retract-cmd     ::= "(" "retract" "[" retract-fact* "]" ")"
fact-vector     ::= "[" fact* "]"
fact            ::= "[" edn-value edn-value edn-value valid-time-map? "]"
retract-fact    ::= "[" edn-value edn-value edn-value "]"
valid-time-map  ::= "{" valid-time-pair* "}"
valid-time-pair ::= (kw-valid-from | kw-valid-to) string-lit

(* ── QUERY ── *)
query-cmd     ::= "(" "query" query-vector ")"
query-vector  ::= "[" query-section+ "]"
query-section ::= find-section | where-section | as-of-section | valid-at-section
                | any-valid-time-section | with-section | max-derived-facts-section
                | max-results-section | query-memory-limit-section
                | max-iterations-section | limit-section | offset-section
                | order-by-section | distinct-section

(* ── FIND SECTION ── *)
find-section      ::= kw-find find-spec+
find-spec         ::= window-expr | aggregate-expr | variable
aggregate-expr    ::= "(" (op-sym | plain-sym) variable ")"
window-expr       ::= "(" (op-sym | plain-sym) (variable kw-over | kw-over) over-clause ")"
over-clause       ::= "(" over-plain-option* (row-frame over-plain-option*)? ")"
over-plain-option ::= kw-partition-by variable | kw-order-by variable | kw-desc | kw-asc
row-frame         ::= kw-rows frame-width "preceding" (* not followed by word-cont *)
frame-width       ::= ("-" "0"+ | digit+) (* not followed by "." digit *)  (* token *)

(* ── WHERE SECTION ── *)
where-section   ::= kw-where where-clause+
where-clause    ::= expr-clause | not-clause | not-join-clause | or-clause
                  | or-join-clause | pattern-clause | rule-invocation
pattern-clause  ::= "[" edn-value edn-value edn-value "]"
expr-clause     ::= "[" (collect-set variable | expr variable?) "]"
not-clause      ::= "(" "not" where-clause+ ")"
not-join-clause ::= "(" "not-join" join-vars where-clause+ ")"
or-clause       ::= "(" "or" or-branch+ ")"
or-join-clause  ::= "(" "or-join" join-vars or-branch+ ")"
join-vars       ::= "[" variable* "]"
or-branch       ::= and-branch | (* not followed by "(" and-head *) where-clause
and-branch      ::= "(" "and" where-clause+ ")"
rule-invocation ::= "(" (* not followed by reserved-head *) plain-sym edn-value* ")"

(* ── EXPRESSIONS ── *)
expr             ::= "(" expr-body ")"
expr-body        ::= binary-form | unary-form
unary-form       ::= (* not followed by collect-set-head *) (unary-op | op-sym | plain-sym) expr-arg
binary-form      ::= binary-op expr-arg expr-arg
collect-set      ::= "(" collect-set-head variable keyword ")"
collect-set-head ::= "collect-set" (* not followed by word-cont *)  (* token *)
unary-op         ::= "string?" | "integer?" | "float?" | "boolean?" | "nil?"  (* token *)
binary-op        ::= "<=" | ">=" | "!=" | "starts-with?" | "ends-with?" | "contains?"
                   | "matches?" | "not-in?" | "any-in?" | "all-in?" | "none-in?" | "in?"
                   | "<" | ">" | "=" | "+" | "-" | "*" | "/"  (* token *)
expr-arg         ::= expr | set-lit | boolean-lit | nil-lit | float-lit | integer-lit
                   | string-lit | bind-slot | keyword | variable

(* ── TEMPORAL / WITH SECTIONS ── *)
as-of-section              ::= kw-as-of (integer-lit | string-lit | bind-slot)
valid-at-section           ::= kw-valid-at (string-lit | kw-any-valid-time | bind-slot)
any-valid-time-section     ::= kw-any-valid-time
with-section               ::= kw-with variable+
max-derived-facts-section  ::= kw-max-derived-facts (integer-lit | bind-slot)
max-results-section        ::= kw-max-results (integer-lit | bind-slot)
query-memory-limit-section ::= kw-query-memory-limit (integer-lit | bind-slot)
max-iterations-section     ::= kw-max-iterations (integer-lit | bind-slot)
limit-section              ::= kw-limit (integer-lit | bind-slot)
offset-section             ::= kw-offset (integer-lit | bind-slot)
distinct-section           ::= kw-distinct
order-by-section           ::= kw-order-by "[" order-entry+ "]"
order-entry                ::= find-spec (kw-asc | kw-desc)?

(* ── RULE ── *)
rule-cmd    ::= "(" "rule" rule-vector ")"
rule-vector ::= "[" rule-head where-clause* "]"
rule-head   ::= "(" plain-sym variable* ")"

(* letter = a character Unicode calls Alphabetic; numeral = a character in Unicode's number categories (Nd, Nl, No), digits included; digit = "0".."9"; any-char = any one character *)

Input that matches this grammar can still be rejected: the parser also enforces the rules in the Semantic Constraints section of the Datalog reference (variable safety, binding, aggregate and window compatibility, UUID and timestamp formats).

0