# EBNF-Grammar in EBNF

@ comment    = /(?!#x[A-Fa-f0-9])#.*(?:\n|$)|\/\*(?:.|\n)*?\*\/|\(\*(?:.|\n)*?\*\)/
    # comments can be either C-Style: /* ... */
    # or pascal/modula/oberon-style: (* ... *)
    # or python-style: # ... \n, excluding, however, character markers: #x20
@ whitespace = /\s*/                            # whitespace includes linefeed
@ literalws  = right                            # trailing whitespace of literals will be ignored tacitly
@ disposable = pure_elem, FOLLOW_UP, SYM_REGEX, EOF
@ drop       = whitespace, EOF                  # do not include these even in the concrete syntax tree
@ RNG_BRACE_filter = matching_bracket()         # filter or transform content of RNG_BRACE on retrieve


# re-entry-rules for resuming after parsing-error

@ definition_resume = /\n\s*(?=@|\w+\w*\s*=)/
@ directive_resume  = /\n\s*(?=@|\w+\w*\s*=)/


# specialized error messages for certain cases

@ definition_error  = /,/, 'Delimiter "," not expected in definition!\nEither this was meant to '
                           'be a directive and the directive symbol @ is missing\nor the error is '
                           'due to inconsistent use of the comma as a delimiter\nfor the elements '
                           'of a sequence.'


#: top-level

syntax     = [~//] { definition | directive } EOF
definition = symbol §:DEF~ expression :ENDL~ & FOLLOW_UP

directive  = "@" §symbol "=" (regexp | literals | procedure | symbol)
             { "," (regexp | literals | procedure | symbol) } & FOLLOW_UP
literals   = { literal }+                       # string chaining, only allowed in directives!
procedure  = SYM_REGEX "()"                     # procedure name, only allowed in directives!

FOLLOW_UP  = `@` | symbol | EOF


#: components

expression = sequence { :OR~ sequence }
sequence   = ["§"] ( interleave | lookaround )  # "§" means all following terms mandatory
             { :AND~ ["§"] ( interleave | lookaround ) }
interleave = difference { "°" ["§"] difference }
lookaround = flowmarker § (oneormore | pure_elem)
difference = term ["-" § (oneormore | pure_elem)]
term       = oneormore | counted | repetition | option | pure_elem


#: elements

pure_elem  = element § !/[?*+]/                 # element strictly without a suffix
element    = [retrieveop] symbol !DEF           # negative lookahead to be sure it's not a definition
           | literal
           | plaintext
           | regexp
           | char_range
           | character
           | whitespace
           | group


#: flow-operators

flowmarker = "!"  | "&"                         # '!' negative lookahead, '&' positive lookahead
           | "<-!" | "<-&"                      # '<-' negative lookbehind, '<-&' positive lookbehind
retrieveop = "::" | ":?" | ":"                  # '::' pop, ':?' optional pop, ':' retrieve


#: groups

group      = "(" no_range §expression ")"
oneormore  = "{" no_range expression "}+" | element "+"
repetition = "{" no_range §expression "}" | element "*" no_range
option     = !char_range "[" §expression "]" | element "?"
counted    = element range | element :TIMES~ multiplier | multiplier :TIMES~ §element

range      = RNG_BRACE~ multiplier [:RNG_DELIM~ multiplier] ::RNG_BRACE~
no_range   = !multiplier | &multiplier :TIMES
multiplier = /\d+/~


#: leaf-elements

symbol     = SYM_REGEX ~                        # e.g. expression, term, parameter_list
literal    = /"(?:(?<!\\)\\"|[^"])*?"/~         # e.g. "(", '+', 'while'
           | /'(?:(?<!\\)\\'|[^'])*?'/~         # whitespace following literals will be ignored tacitly.
plaintext  = /`(?:(?<!\\)\\`|[^`])*?`/~         # like literal but does not eat whitespace
regexp     = /\/(?:(?<!\\)\\(?:\/)|[^\/])*?\//~     # e.g. /\w+/, ~/#.*(?:\n|$)/~
char_range = '[' (character | free_char) '-' (character | free_char) ']'
character  = :CH_LEADIN HEXCODE | '[' free_char ']'
free_char  = /[0-9]/ | [^\w\n(){}\[\]/\\`'"] | `\` /[\w(){}\[\]/\\`'"]/
whitespace = /~/~                               # insignificant whitespace

SYM_REGEX  = /(?!\d)\w+/                        # regular expression for symbols
HEXCODE    = /[A-Fa-f0-9]/+

#: delimiters

DEF        = `=` | `:=` | `::=` | `<-`
OR         = `|` | `/` &` ` no_regex_heuristics
AND        = `,` | ``
ENDL       = `;` | ``

RNG_BRACE  = :BRACE_SIGN
BRACE_SIGN = `{` | `(`
RNG_DELIM  = `,`
TIMES      = `*`

RE_LEADIN  = `/` | `%/`
RE_LEADOUT = `/`

CH_LEADIN  = `0x` | `#x`

no_regex_heuristics = !regexp

EOF = !/./ [:?DEF] [:?OR] [:?AND] [:?ENDL]      # [:?DEF], [:?OR], ... clear stack by eating stored value
           [:?RNG_DELIM] [:?BRACE_SIGN] [:?CH_LEADIN] [:?TIMES] [:?RE_LEADIN] [:?RE_LEADOUT]

