// See Extended Backus-Naur Form for more details.
start                   : comment_or_block+


// Contents may be either comment or block.
comment_or_block        : block
                        | block ";"

// Comments are signified by a hash followed by anything. Any line that follows
// a comment hash is thrown away.

?comment                : "#" COMMENT

// A block may consist of either an edge ("A -> B"), a constraint, or a "macro".
block                   : edge
                        | named_edge
                        | macro
                        | macro_call
                        | node_constraint
                        | automorphism_notation
                        | comment



// A macro can be considered a "function" definition that can be used in the
// rest of the file to define complex structure. For example,
//     foo(a, b) { a -> b }; foo(A, B);
// is the same as:
//     A -> B
// While this is a simple case, this is an immensely powerful tool.
macro                   : variable "(" arglist ")" "{" macro_rules "}"

// A series of arguments to a macro
?arglist                : variable ("," variable)*

macro_rules             : macro_block+

?macro_block            : edge_macro
                        | named_edge_macro
                        | macro_call_re
                        | comment
                        | macro_node_constraint

// A "hypothetical" edge that forms a subgraph structure.
edge_macro              : node_id relation node_id
                        | node_id relation node_id "[" macro_edge_clauses "]"


// A macro is called like a function: foo(args).
macro_call              : variable "(" arglist ")"
?macro_call_re          : variable "(" arglist ")"



// Edges are currently composed of a node, a relation, and a node. In other
// words, an arbitrary word, a relation between them, and then another node.
// Edges can also have optional edge attributes, delimited from the original
// structure with square brackets.
edge                    : node_id relation node_id
                        | node_id relation node_id "[" edge_clauses "]"

named_edge              : node_id relation node_id "as" edge_alias
                        | node_id relation node_id "[" edge_clauses "]" "as" edge_alias

named_edge_macro        : node_id relation node_id "as" edge_alias
                        | node_id relation node_id "[" edge_clauses "]" "as" edge_alias

// An edge alias is any variable-like token:
?edge_alias             : variable

// A Node ID is any contiguous (that is, no whitespace) word.
?node_id                : variable

// A relation is a bipartite: The first character is an indication of whether
// the relation exists or not. The following characters indicate if a relation
// has a type other than the default, positive, and negative types offered
// by default.
relation                : relation_exist relation_type

// A "-" means the relation exists; "~" means the relation does not exist.
relation_exist          : "-"                               -> rel_exist
                        | "~"                               -> rel_nexist
                        | "!"                               -> rel_nexist

// The valid types of relation are single-character, except for the custom
// relation type which is user-defined and lives inside square brackets.
relation_type           : ">"                               -> rel_def
                        | "+"                               -> rel_pos
                        | "-"                               -> rel_neg
                        | "|"                               -> rel_neg

// Edge attributes are separated from the main edge declaration with sqbrackets
edge_clauses            : edge_clause ("," edge_clause)*
macro_edge_clauses      : edge_clause ("," edge_clause)*

edge_clause             : value_or_quoted_value op value_or_quoted_value


// Node constraints:
                        // Dot Notation
node_constraint         : node_id "." key op value_or_quoted_value
                        | node_id "." key op node_id "." key
                        // Left-Hand Bracket Notation
                        | node_id "[" flex_key "]" op value_or_quoted_value
                        | node_id "['" flex_key "']" op value_or_quoted_value
                        | node_id "[\"" flex_key "\"]" op value_or_quoted_value
                        // LH and RH Bracket Notation
                        | node_id "[" flex_key "]" op node_id "[" flex_key "]"
                        | node_id "['" flex_key "']" op node_id "[" flex_key "]"
                        | node_id "[\"" flex_key "\"]" op node_id "[" flex_key "]"
                        | node_id "[" flex_key "]" op node_id "['" flex_key "']"
                        | node_id "['" flex_key "']" op node_id "['" flex_key "']"
                        | node_id "[\"" flex_key "\"]" op node_id "['" flex_key "']"
                        | node_id "[" flex_key "]" op node_id "[\"" flex_key "\"]"
                        | node_id "['" flex_key "']" op node_id "[\"" flex_key "\"]"
                        | node_id "[\"" flex_key "\"]" op node_id "[\"" flex_key "\"]"

                        // Dot Notation
macro_node_constraint   : node_id "." key op value_or_quoted_value
                        | node_id "." key op node_id "." key
                        // Left-Hand Bracket Notation
                        | node_id "[" flex_key "]" op value_or_quoted_value
                        | node_id "['" flex_key "']" op value_or_quoted_value
                        | node_id "[\"" flex_key "\"]" op value_or_quoted_value
                        // LH and RH Bracket Notation
                        | node_id "[" flex_key "]" op node_id "[" flex_key "]"
                        | node_id "['" flex_key "']" op node_id "[" flex_key "]"
                        | node_id "[\"" flex_key "\"]" op node_id "[" flex_key "]"
                        | node_id "[" flex_key "]" op node_id "['" flex_key "']"
                        | node_id "['" flex_key "']" op node_id "['" flex_key "']"
                        | node_id "[\"" flex_key "\"]" op node_id "['" flex_key "']"
                        | node_id "[" flex_key "]" op node_id "[\"" flex_key "\"]"
                        | node_id "['" flex_key "']" op node_id "[\"" flex_key "\"]"
                        | node_id "[\"" flex_key "\"]" op node_id "[\"" flex_key "\"]"


// Automorphism notation:
automorphism_notation: node_id "===" node_id

?value_or_quoted_value: WORD | NUMBER | DOUBLE_QUOTED_STRING


?key                    : WORD | variable
?flex_key               : WORD | variable | FLEXIBLE_KEY
?value                  : WORD | NUMBER
?op                     : OPERATOR | iter_ops


variable                : NAME

iter_ops                : "contains"                        -> iter_op_contains
                        | "in"                              -> iter_op_in
                        | "!contains"                       -> iter_op_not_contains
                        | "!in"                             -> iter_op_not_in

NAME                    : /[a-zA-Z_-]\w*/
FLEXIBLE_KEY            : "\"" /.+?/ /(?<!\\)(\\\\)*?/ "\"" | "'" /.+?/ /(?<!\\)(\\\\)*?/ "'"
OPERATOR                : /(?:[\!=\>\<][=]?)|(?:\<\>)/
VAR_SEP                 : /[\_\-]/
COMMENT                 : /#[^\n]+/
DOUBLE_QUOTED_STRING    : /"[^"]*"/
%ignore COMMENT

%import common.WORD -> WORD
%import common._STRING_ESC_INNER -> _STRING_ESC_INNER
%import common.SIGNED_NUMBER  -> NUMBER
%import common.WS
%ignore WS
