@@grammar :: TatSu
@@whitespace :: /(?m)\s+/
@@comments :: ?"(?sm)[(][*](?:.|\n)*?[*][)]"
@@eol_comments :: ?"(?m)#[^\n]*$"
@@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')
            ~
            ('::' ~ value:boolean|value:`True`)
        |
            name:('grammar')
            ~
            '::' ~ value:word
        |
            name:('namechars')
            ~
            '::' ~ value:string
    ) ~
    ;


keywords
    =
    { keywords }+
    ;


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


paramdef
    =
        (* no keyword params if ':: *)
        '::' ~ params:params
    |
        '('
        ~
        (
              kwparams:kwparams
            | params:params ',' ~ kwparams:kwparams
            | params:params
        )
        ')'
    ;


rule::Rule
    =
    decorators:{decorator}
    name:name
    ~
    [>paramdef]
    ['<' ~ base:known_name]
    '='
    ~
    exp:expre
    ';'
    ~
    ;


decorator
    =
    '@' !'@' ~ @:('override'|'name'|'nomemo')
    ;


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


first_param
    =
    | path
    | literal
    ;



kwparams
    =
    ','.{pair}+
    ;


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


expre
    =
    choice | sequence
    ;


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


option::Option
    =
    @:sequence
    ;



sequence::Sequence
    =
    sequence:{element}+
    ;


element
    =
    rule_include | named | override | term
    ;


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_single_deprecated
    ;


override_list::OverrideList
    =
    '@+:' ~ @:term
    ;


override_single::Override
    =
    '@:' ~ @:term
    ;


override_single_deprecated::Override
    =
    '@' ~ @:term
    ;


term
    =
    | void
    | gather
    | join
    | left_join
    | right_join
    | group
    | empty_closure
    | positive_closure
    | closure
    | optional
    | special
    | skip_to
    | lookahead
    | negative_lookahead
    | atom
    ;


group::Group
    =
    '(' ~ exp:expre ')' ~
    ;


gather
    =
    &(separator '.{') ~
    (
    | positive_gather
    | normal_gather
    )
    ;


positive_gather::PositiveGather
    =
    sep:separator '.{' exp:expre '}' ('+'|'-') ~
    ;


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


join
    =
    &(separator '%{') ~
    (
    | positive_join
    | normal_join
    )
    ;


positive_join::PositiveJoin
    =
    sep:separator '%{' exp:expre '}' ('+'|'-') ~
    ;


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


left_join::LeftJoin
    =
    sep:separator '<{' ~ exp:expre '}' ('+'|'-') ~
    ;


right_join::RightJoin
    =
    sep:separator '>{' ~ exp:expre '}' ('+'|'-') ~
    ;


separator
    =
    group | token | constant |  any | pattern
    ;


positive_closure::PositiveClosure
    =
    '{' @:expre '}' ('-' | '+') ~
    ;


closure::Closure
    =
    '{' @:expre '}' ['*'] ~
    ;


empty_closure::EmptyClosure
    =
    '{' @:() '}'
    ;


optional::Optional
    =
    '[' ~ @:expre ']' ~
    ;


special::Special
    =
    '?(' ~ @:/.*?(?!\)\?)/ ')?' ~
    ;


lookahead::Lookahead
    =
    '&' ~ @:term
    ;


negative_lookahead::NegativeLookahead
    =
    '!' ~ @:term
    ;


skip_to::SkipTo
    =
    '->' ~ @:term
    ;


atom
    =
    cut | cut_deprecated | token | alert | constant | call | pattern | eof
    ;


call::RuleRef
    =
    word
    ;


void::Void
    =
    '()' ~
    ;


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
    =
    string | raw_string | boolean | word | hex | float | int | null
    ;


string
    =
    STRING
    ;


raw_string
    =
    /r/ @:STRING
    ;


STRING
    =
    | @:/"((?:[^"\n]|\\"|\\\\)*?)"/ ~
    | @:/'((?:[^'\n]|\\'|\\\\)*?)'/ ~
    ;


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


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


int
    =
    /[-+]?\d+/
    ;


path
    =
    /(?!\d)\w+/ + /(?:::(?!\d)\w+)+/
    ;


word
    =
    /(?!\d)\w+/
    ;


any::Any
    =
    '/./'
    ;


pattern::Pattern
    =
    regexes
    ;


regexes
    =
    '+'.{regex}+
    ;



regex
    =
    | '/' ~ @:?"(?:[^/\\]|\\/|\\.)*" '/' ~
    | '?/' ~ @:?"(?:.|\n)*?(?=/\?)" ?"/\?+" ~
    | '?' @:STRING
    ;


boolean
    =
    'True' | 'False'
    ;


null
    =
    'None'
    ;


eof::EOF
    =
    '$' ~
    ;
