@@grammar :: TatSu
@@comments :: ?"(?ms)[(][*]\s*(.*?)\s*[*][)]|/[*]\s*(.*?)\s*[*]/"
@@eol_comments :: ?"(?ms)(?:#|//)(.*?)$"
@@parseinfo :: True
@@left_recursion :: False

start: grammar

grammar[Grammar]:
    title=`TATSU`
    {directives+=directive | keywords+=keyword}
    rules+=rule
    {rules+=rule | keywords+=keyword}
    $

directive:
    '@@'
    !'keyword'
    ~
    (
        | name=('comments' | 'eol_comments') ~ '::' ~ value=regex
        | name='whitespace'
        ~
        '::'
        ~
        value=(regex | string | 'None' | 'False' | `None`)
        | name=(
            | 'nameguard'
            | 'ignorecase'
            | 'left_recursion'
            | 'parseinfo'
            | 'memoization'
        )
        ~
        ('::' ~ value=boolean | value=`True`)
        | name='grammar' ~ '::' ~ value=word
        | name='namechars' ~ '::' ~ value=string
    )
    ~

keywords: {keyword}+

keyword: '@@keyword' ~ '::'
    (
        | '(' ~ ={word|string}+ ')'
        | {+=(word | string) !(':' | '=')}+
    )

params: +=first_param {',' +=literal !'=' ~}

first_param: path | literal

kwparams: ','.{pair}+

the_params_at_last:
    | kwparams=kwparams
    | params=params ',' ~ kwparams=kwparams
    | params=params

paramdef:
    | '[' ~ >the_params_at_last ']'
    | '(' ~ >the_params_at_last ')'
    | '::' ~ params=params

rule[Rule]:
    decorators={decorator}
    name=@name
    ~
    [>paramdef]
    ['<' ~ base=known_name]
    ()
    /=|::=|:=?/
    ~
    exp=expre
    ENDRULE
    ~

ENDRULE: DEDENT | BLANK | ';' | $

DEDENT: EOL &/^\S/

BLANK: EOL EOL

# BLANK: $-> $-> ;


EOL: /(?m)[ \t]*$/ /(?m)(?:\r?\n|\r)/

decorator: '@' !'@' ~ =/(override|name|isname|nomemo|nostak)\b/

pair: +=word '=' ~ +=literal

expre: choice | sequence

choice[Choice]: ['|' ~] +=option {'|' ~ +=option}+

option[Option]: sequence

sequence[Sequence]:
    | &(element ',') ','.{element}+
    | {!ENDRULE element}+

element: override | meta | named | term | rule_include

rule_include[RuleInclude]: '>' ~ =known_name

named: named_list | named_single

named_list[NamedList]: name=@name /\+[:=]/ ~ exp=term

named_single[Named]: name=@name /[:=]/ ~ exp=term

override:
    | override_list
    | override_single

override_list[OverrideList]: /\+=|@\+:/ ~ =term

override_single[Override]: /=|@:/ ~ =term

term:
    | gather
    | join
    | left_join
    | right_join
    | empty_closure
    | positive_closure
    | closure
    | optional
    | atom
    | void
    | skip_to
    | lookahead
    | negative_lookahead
    | cut
    | cut_deprecated

group[Group]: !'(?:' '(' ~ =expre ')' ~

skip[SkipGroup]: '(?:' ~ =expre ')' ~

gather: &(atom '.{') ~ (positive_gather | normal_gather)

positive_gather[PositiveGather]: sep=atom '.{' exp=expre '}' !/\+=/ /[+-]/ ~

normal_gather[Gather]: sep=atom '.{' ~ exp=expre '}' ['*' ~] ~

join: &(atom '%{') ~ (positive_join | normal_join)

positive_join[PositiveJoin]: sep=atom '%{' exp=expre '}' !/\+=/ /[+-]/ ~

normal_join[Join]: sep=atom '%{' ~ exp=expre '}' ['*' ~] ~

left_join[LeftJoin]: sep=atom '<{' ~ exp=expre '}' !/\+=/ /[+-]/ ~

right_join[RightJoin]: sep=atom '>{' ~ exp=expre '}' !/\+=/ /[+-]/ ~

positive_closure[PositiveClosure]:
    | '{' =expre '}' !/\+=/ /[+-]/ ~
    | =atom !/\+=/ /[+]/ ~

closure[Closure]: '{' =expre '}' ['*'] ~ | =atom '*' ~

empty_closure[EmptyClosure]: '{}' ~ =()

optional[Optional]:
    | '[' ~ =expre ']' ~
    | =atom !('?"' | "?'" | '?/') '?' ~

lookahead[Lookahead]: '&' ~ =term

negative_lookahead[NegativeLookahead]: '!' ~ =term

skip_to[SkipTo]: '->' ~ =term

atom:
    | meta
    | token
    | call
    | dot
    | pattern
    | skip
    | group
    | eol
    | eof
    | alert
    | constant

meta[Meta]: /@(name|int|uint|float|bool)\b/

call[Call]: @name

void[Void]: '()' ~

fail[Fail]: '!()' ~

cut[Cut]: '~' ~

cut_deprecated[Cut]: '>>' ~

known_name: @name ~

name: word

constant[Constant]:
    &'`'
    (
        | /(?ms)```((?:.|\n)*?)```/
        | '`' =literal '`'
        | /`(.*?)`/
    )

alert[Alert]: level=/\^+/ message=constant

token[Token]: string | raw_string

literal:
    | raw_string
    | value
    | boolean
    | none
    | word
    | hex
    | float
    | int

string: &('"'|"'") (multiline_string | singlequoted | doublequoted)

singlequoted: SINGLEQUOTED

doublequoted: DOUBLEQUOTED

raw_string: &/r["']/ /r/ =STRING

STRING: SINGLEQUOTED | DOUBLEQUOTED

SINGLEQUOTED: /'((?:[^'\n]|\\'|\\\\)*?)'/ ~

DOUBLEQUOTED: /"((?:[^"\n]|\\"|\\\\)*?)"/ ~

multiline_string:
    | /(?ms)'''((?:\\\\|\\.|.)*?)'''/ ~
    | /(?ms)"""((?:\\\\|\\.|.)*?)"""/ ~

hex: /0[xX](?:\d|[a-fA-F])+/

float: /[-+]?(?:\d+\.\d*|\d*\.\d+)(?:[Ee][-+]?\d+)?/

int: /[-+]?\d+/

path: /[_\w][_\w\d]*(?:::[_\w][_\w\d]*)+/

word: /(?ms)\s*([_\w][_\w\d]*)\b/

dot[Dot]: '/./'

pattern[Pattern]: regex

regex: deprecated_regex | !'?/' ( REGEX | '?' =STRING)

REGEX: &'/' ?"(?ms)/((?:[^/\\]|\\/|\\.)*)/" ~

deprecated_regex: '?/' ~ =?"(?ms)((?:[^/\\]|\\/|\\.)*)" ~ '/?'

boolean: 'True' | 'False'

none: 'None'

eof[EOF]: '$' ~

eol[EOL]: '$->'

value: string | number | true | false | null

# NOTE: weird indentation is to pass pretty-print roundtrip test
number:
    /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/

true: 'true'

false: 'false'

null: 'null'
