use Rainbow::Token;
#use Grammar::Tracer;
#use Grammar::Profiler::Simple;

my %open-close-punct =
    "\x0028", "\x0029", "\x003c", "\x003e", "\x005b", "\x005d",
    "\x007b", "\x007d", "\x00ab", "\x00bb", "\x0f3a", "\x0f3b",
    "\x0f3c", "\x0f3d", "\x169b", "\x169c", "\x2018", "\x2019",
    "\x201a", "\x2019", "\x201b", "\x2019", "\x201c", "\x201d",
    "\x201e", "\x201d", "\x201f", "\x201d", "\x2039", "\x203a",
    "\x2045", "\x2046", "\x207d", "\x207e", "\x208d", "\x208e",
    "\x2208", "\x220b", "\x2209", "\x220c", "\x220a", "\x220d",
    "\x2215", "\x29f5", "\x223c", "\x223d", "\x2243", "\x22cd",
    "\x2252", "\x2253", "\x2254", "\x2255", "\x2264", "\x2265",
    "\x2266", "\x2267", "\x2268", "\x2269", "\x226a", "\x226b",
    "\x226e", "\x226f", "\x2270", "\x2271", "\x2272", "\x2273",
    "\x2274", "\x2275", "\x2276", "\x2277", "\x2278", "\x2279",
    "\x227a", "\x227b", "\x227c", "\x227d", "\x227e", "\x227f",
    "\x2280", "\x2281", "\x2282", "\x2283", "\x2284", "\x2285",
    "\x2286", "\x2287", "\x2288", "\x2289", "\x228a", "\x228b",
    "\x228f", "\x2290", "\x2291", "\x2292", "\x2298", "\x29b8",
    "\x22a2", "\x22a3", "\x22a6", "\x2ade", "\x22a8", "\x2ae4",
    "\x22a9", "\x2ae3", "\x22ab", "\x2ae5", "\x22b0", "\x22b1",
    "\x22b2", "\x22b3", "\x22b4", "\x22b5", "\x22b6", "\x22b7",
    "\x22c9", "\x22ca", "\x22cb", "\x22cc", "\x22d0", "\x22d1",
    "\x22d6", "\x22d7", "\x22d8", "\x22d9", "\x22da", "\x22db",
    "\x22dc", "\x22dd", "\x22de", "\x22df", "\x22e0", "\x22e1",
    "\x22e2", "\x22e3", "\x22e4", "\x22e5", "\x22e6", "\x22e7",
    "\x22e8", "\x22e9", "\x22ea", "\x22eb", "\x22ec", "\x22ed",
    "\x22f0", "\x22f1", "\x22f2", "\x22fa", "\x22f3", "\x22fb",
    "\x22f4", "\x22fc", "\x22f6", "\x22fd", "\x22f7", "\x22fe",
    "\x2308", "\x2309", "\x230a", "\x230b", "\x2329", "\x232a",
    "\x23b4", "\x23b5", "\x2768", "\x2769", "\x276a", "\x276b",
    "\x276c", "\x276d", "\x276e", "\x276f", "\x2770", "\x2771",
    "\x2772", "\x2773", "\x2774", "\x2775", "\x27c3", "\x27c4",
    "\x27c5", "\x27c6", "\x27d5", "\x27d6", "\x27dd", "\x27de",
    "\x27e2", "\x27e3", "\x27e4", "\x27e5", "\x27e6", "\x27e7",
    "\x27e8", "\x27e9", "\x27ea", "\x27eb", "\x2983", "\x2984",
    "\x2985", "\x2986", "\x2987", "\x2988", "\x2989", "\x298a",
    "\x298b", "\x298c", "\x298d", "\x298e", "\x298f", "\x2990",
    "\x2991", "\x2992", "\x2993", "\x2994", "\x2995", "\x2996",
    "\x2997", "\x2998", "\x29c0", "\x29c1", "\x29c4", "\x29c5",
    "\x29cf", "\x29d0", "\x29d1", "\x29d2", "\x29d4", "\x29d5",
    "\x29d8", "\x29d9", "\x29da", "\x29db", "\x29f8", "\x29f9",
    "\x29fc", "\x29fd", "\x2a2b", "\x2a2c", "\x2a2d", "\x2a2e",
    "\x2a34", "\x2a35", "\x2a3c", "\x2a3d", "\x2a64", "\x2a65",
    "\x2a79", "\x2a7a", "\x2a7d", "\x2a7e", "\x2a7f", "\x2a80",
    "\x2a81", "\x2a82", "\x2a83", "\x2a84", "\x2a8b", "\x2a8c",
    "\x2a91", "\x2a92", "\x2a93", "\x2a94", "\x2a95", "\x2a96",
    "\x2a97", "\x2a98", "\x2a99", "\x2a9a", "\x2a9b", "\x2a9c",
    "\x2aa1", "\x2aa2", "\x2aa6", "\x2aa7", "\x2aa8", "\x2aa9",
    "\x2aaa", "\x2aab", "\x2aac", "\x2aad", "\x2aaf", "\x2ab0",
    "\x2ab3", "\x2ab4", "\x2abb", "\x2abc", "\x2abd", "\x2abe",
    "\x2abf", "\x2ac0", "\x2ac1", "\x2ac2", "\x2ac3", "\x2ac4",
    "\x2ac5", "\x2ac6", "\x2acd", "\x2ace", "\x2acf", "\x2ad0",
    "\x2ad1", "\x2ad2", "\x2ad3", "\x2ad4", "\x2ad5", "\x2ad6",
    "\x2aec", "\x2aed", "\x2af7", "\x2af8", "\x2af9", "\x2afa",
    "\x2e02", "\x2e03", "\x2e04", "\x2e05", "\x2e09", "\x2e0a",
    "\x2e0c", "\x2e0d", "\x2e1c", "\x2e1d", "\x2e20", "\x2e21",
    "\x3008", "\x3009", "\x300a", "\x300b", "\x300c", "\x300d",
    "\x300e", "\x300f", "\x3010", "\x3011", "\x3014", "\x3015",
    "\x3016", "\x3017", "\x3018", "\x3019", "\x301a", "\x301b",
    "\x301d", "\x301e", "\xfd3e", "\xfd3f", "\xfe17", "\xfe18",
    "\xfe35", "\xfe36", "\xfe37", "\xfe38", "\xfe39", "\xfe3a",
    "\xfe3b", "\xfe3c", "\xfe3d", "\xfe3e", "\xfe3f", "\xfe40",
    "\xfe41", "\xfe42", "\xfe43", "\xfe44", "\xfe47", "\xfe48",
    "\xfe59", "\xfe5a", "\xfe5b", "\xfe5c", "\xfe5d", "\xfe5e",
    "\xff08", "\xff09", "\xff1c", "\xff1e", "\xff3b", "\xff3d",
    "\xff5b", "\xff5d", "\xff5f", "\xff60", "\xff62", "\xff63"
;

my %quote-pairs =
    "'", "'", '"', '"', "<", ">", "<<", ">>", "«", "»",
    |%open-close-punct;

class QuoteMode {
    has $.q is rw;
    has $.s is rw;
    has $.a is rw;
    has $.h is rw;
    has $.f is rw;
    has $.c is rw;
    has $.b is rw;
    has $.closer is rw;
    has $.heredoc-marker is rw;
}

grammar Rainbow::RakuGrammar {
    token TOP {
        <raku-TOP>
    }

# Raku Code ====================================================================

    token raku-TOP {
        :my @*upcoming-heredocs;
        :my $*scope-opener-count = 0;
        :my $*token-seen = False;
        (
            || <name-scalar>    { $*token-seen = True }
            || <name-array>     { $*token-seen = True }
            || <name-hash>      { $*token-seen = True }
            || <name-code>      { $*token-seen = True }
            || <array-access>
            || <number-literal> { $*token-seen = True }
            || <regex>          { $*token-seen = True }
            || <rakudoc-block>
            || <enum-decl>      { $*token-seen = False }
            || <keyword>        { $*token-seen = False }
            || <string>         { $*token-seen = True }
            || <q-string>       { $*token-seen = True }
            || <operator>       { $*token-seen = False }
            || <type>           { $*token-seen = True }
            || <method-call>
            || <sub-call>
            || <builtin-routine> { $*token-seen = False }
            || <scope>          { $*token-seen = True }
            || <heredoc>
            || <comment>
            || <name-unsigiled> { $*token-seen = True }
            || <text>
        )*
    }

    token name-unsigiled {
        $<name>=[
            <identifier>
            <!ww>
        ]
        <hash-access>?
    }
    token sporco { # space-or-comment
        (
            | $<space>=[\s+]
            | <comment>
        )+
    }
    token number-literal {
        | '0'  '_'? <[ 0..7 ]>+ [ '_' <[ 0..7 ]>+ ]*                                # Octal
        | '0x' <[ 0..9 A..F a..f ]>+ [ '_' <[ 0..9 A..F a..f ]>+ ]*                     # Hex
        | '0b' <[ 0 1 ]>+ [ '_' <[ 0 1 ]>+ ]*                                     # Bin
        | 'i'? [ \d+ [ '_' \d+ ]* ]* '.' \d+ [ '_' \d+ ]* [ 'e' <[ + - ]>? \d+ ]? # Float
        | 'i'? \d+ [ '_' \d* ]* 'e' <[ + - ]>? \d+ [ '_' \d* ]*                   # Float
        # | (?<=\d+)i                                                             # NameConstant ? What is this?
        | \d+ [ '_' \d+ ]*                                                        # Integer
    }
    token text {
        <!before
            <?{ $*scope-closer }>
            <?{ $*scope-opener-count == 0 }>
            $*scope-closer
        >
        .
        {
            my $match = $/;
            if $*scope-opener {
                $*scope-opener-count++ if $match eq $*scope-opener;
                $*scope-opener-count-- if $match eq $*scope-closer;
            }
            if $match ~~ / \s / {
                # Don't change token-seen state.
            }
            elsif $match eq ')' | ']' { # Routine call / grouping, list access. Scopes ('}') are tracked separately.
                $*token-seen = True;
            }
            else {
                $*token-seen = False;
            }
        }
    }
    token scope {
        $<opener>='{'
        :my $*scope-opener;
        :my $*scope-closer;
        {
            $*scope-opener = '{';
            $*scope-closer = '}';
        }
        { $*token-seen = False }
        <raku-TOP>
        $<closer>=[ '}' | $ ]
    }
    token array-access {
        || '[' { $*token-seen = False }
        || ']' { $*token-seen = True }
    }
    token parens-scope {
        $<opener>='('
        :my $*scope-opener;
        :my $*scope-closer;
        {
            $*scope-opener = '(';
            $*scope-closer = ')';
        }
        { $*token-seen = False }
        <raku-TOP>
        $<closer>=[ ')' | $ ]
        { $*token-seen = True }
    }
    token method-call {
        $<dot>='.'
        $<sporco1>=<sporco>?
        <identifier>
        { $*token-seen = True }
        $<sporco2>=<sporco>?
        [
            | $<colon>=':' { $*token-seen = False }
            | <parens-scope>
        ]?
    }
    token sub-call {
        <identifier>
        <sporco>?
        <parens-scope>
    }
    token enum-decl {
        $<enum>='enum'
        <sporco>
        <identifier>
    }
    token name-scalar {
        $<name>=[
            '$'
            [
            ||
                [ '*' | '?' | '.' | '!' ]?
                <identifier>
                <!ww>
            ||
                # Pre-defined lexical variables
                <[ / _ ! ¢ ]>
            ]?
            # nothing after the $ is an anon state variable
        ]
        <hash-access>?
    }
    token name-array {
        '@'
        [
            [ '*' | '?' | '.' | '!' ]?
            <identifier>
            <!ww>
        ]?
        # nothing after the @ is an anon state variable
    }
    token name-hash {
        $<name>=[
            '%'
            [
                [ '*' | '?' | '.' | '!' ]?
                <identifier>
                <!ww>
            ]?
            # nothing after the % is an anon state variable
        ]
        <hash-access>?
    }
    token name-code {
        '&'
        [ '*' | '?' | '.' | '!' ]?
        <identifier>
        <!ww>
    }
    token hash-access {
        || <hash-string-access>
        || <hash-normal-access>
    }
    token hash-normal-access {
        $<opener>=  '{'
        :my $*scope-opener;
        :my $*scope-closer;
        {
            $*scope-opener = '{';
            $*scope-closer = '}';
        }
        { $*token-seen = False }
        <raku-TOP>
        $<closer>=[ '}' | $ ]
    }
    token hash-string-access {
        $<opener>=  '<'
        $<content>= <-[ > ]>+
        $<closer>=  '>'
    }

# Comments =====================================================================

    token comment {
        || <delimited-comment>
        || <rakudoc-comment>
        || <simple-comment>
    }
    token simple-comment {
        '#'
        <-[\n]>*
        [ \n | $ ]
    }
    token delimited-comment {
        :my $*comment-opener-char;
        :my $*comment-closer-char;
        :my $*comment-closer;
        :my $*comment-opener-count = 0;
        :my $*rakudoc-mode;
        $<pre>=[
            [
                | '#`'
                | '#=' { $*rakudoc-mode = True; }
                | '#|' { $*rakudoc-mode = True; }
            ]
            $<opener>=[
                $<first-opener>=@(%open-close-punct.keys)
                $<first-opener>*
            ]
        ]
        {
            $<opener>.trim-leading.comb.map({
                $*comment-opener-char = $_;
                $*comment-closer-char = %open-close-punct{$_};
                $*comment-closer ~= %open-close-punct{$_};
            }).sink;
        }
        [
            || <?{ $*rakudoc-mode }> <delimited-rakudoc-comment-content>
            || <delimited-comment-content-char>*
        ]
        $<closer>=[ $*comment-closer | $ ]
    }
    token delimited-comment-content-char {
        <!before
            <?{ $*comment-opener-count == 0 }>
            $*comment-closer
        >
        $<char>=.
        {
            $*comment-opener-count++ if $/ eq $*comment-opener-char;
            $*comment-opener-count-- if $/ eq $*comment-closer-char && $*comment-opener-count > 0;
        }
    }

# Regexes ======================================================================

    token regex {
        :my $*perl5-mode;
        :my $*regex-closer;
        [
            || <regex-m>
            || <regex-s>
            || <regex-routine>
        ]
    }
    token regex-TOP {
        (
            || <regex-variable-declaration>
            || <regex-named-capture>
            || <regex-regex-variable-interpolation>
            || <regex-literal-closure-interpolation>
            || <regex-regex-closure-interpolation>
            || <regex-code>
            || <name-scalar>
            || <name-array>
            || <regex-quote>
            || <regex-before-after>
            || <regex-escape-literal>
            || <regex-escape-special>
            || <comment>
            || <regex-angled-bracket-special>
            || <regex-literal>
            || <regex-special>
        )*
    }

# Starters --------------------------------------------------------------------

    token regex-m {
        <!ww>
        $<begin>=[
            ||
                [ m | rx ]
                <regex-adverb>*
                <regex-opener>
                {
                    with $<regex-opener>.Str.trim-leading {
                        $*regex-closer = %quote-pairs{$_} // $_;
                    }
                }
            ||
                <!{$*token-seen}>
                '/'
                { $*regex-closer = '/' }
        ]
        <regex-TOP>
        <regex-closer>
    }
    token regex-s {
        :my $*assignment-form;
        <!ww>
        $<begin>=[
            [ s | S ]
            <regex-adverb>*
            <regex-opener>
            {
                with $<regex-opener>.Str.trim-leading {
                    $*assignment-form = %quote-pairs{$_}:exists;
                    $*regex-closer = %quote-pairs{$_} // $_;
                }
            }
        ]
        <regex-TOP>
        $<end1>=<regex-closer>
        [
            <!{ $*assignment-form }>
            <subst>
            $<end2>=<regex-closer>
        ]?
    }
    token regex-routine {
        <!ww>
        $<type>=[
            | regex
            | rule
            | token
        ]
        $<name>=[
            [\s+]
            [
                <-[{\s]>+
                [\s+]
            ]*
        ]
        $<begin>='{'
        { $*regex-closer = '}' }
        <regex-TOP>
        $<end>=[ '}' | $ ]
    }
    token regex-adverb {
        ':'
        [
            || P[erl]?5
               { $*perl5-mode = True; }
            || <alnum>+
        ]
    }
    token regex-opener {
        | <regex-opener-immediate>
        | <regex-opener-spaced>
    }
    token regex-opener-immediate {
        <:!L - [ \# \: \s \d \( \) ]>
    }
    token regex-opener-spaced {
        $<sp>=[' '+]
        <:!L - [ \# \: \s \d ]>
    }
    token regex-closer {
        || $*regex-closer
        || $
    }
    token subst {
        # TODO
        <-[/]>*
    }

# Content ---------------------------------------------------------------------

    token regex-literal {
        \w
    }
    token regex-special {
        <!before
            <?{ $*regex-closer }>
            $*regex-closer
        >
        <-[\w]>
    }
    token regex-escape-literal {
        $<escaper>=\\
        $<literal>=<-[\w]>
    }
    token regex-escape-special {
        $<escaper>=\\
        $<special>=\w
    }
    token regex-quote {
        $<pre>=\'
        (
            || $<esc-close>=[\\ \']
            || $<esc-esc>=[\\\\]
            || $<literal>=<-[']>
        )*
        $<post>=[ \' | $ ]
    }
    token regex-variable-declaration {
        $<pre>=':'
        $<keyword>=[ my | our ]
        $<ws1>=\s*
        [
            | <name-scalar>
            | <name-array>
            | <name-hash>
            | <name-code>
        ]
        $<ws2>=\s*
        [
            $<eq>='='
            $<ws3>=\s*
            # TODO: Maybe we can do better here.
            $<val>=<-[;]>+
        ]?
        $<post>=';'
    }
    token regex-named-capture {
        '$<'
        <identifier>
        '>'
        \s*
        '='
    }
    token regex-regex-variable-interpolation {
        $<pre>='<'
        [
            | <name-scalar>
            | <name-array>
        ]
        $<post>='>'
    }
    token regex-literal-closure-interpolation {
        $<pre>='$('
        :my $*scope-opener;
        :my $*scope-closer;
        {
            $*scope-opener = '(';
            $*scope-closer = ')';
        }
        <raku-TOP>
        $<post>=[ ')' | $ ]
    }
    token regex-regex-closure-interpolation {
        $<pre>='<{'
        :my $*scope-closer;
        {
            $*scope-closer = '}>';
        }
        <raku-TOP>
        $<post>=[ '}>' | $ ]
    }
    token regex-code {
        $<pre>='{'
        :my $*scope-opener;
        :my $*scope-closer;
        {
            $*scope-opener = '{';
            $*scope-closer = '}';
        }
        <raku-TOP>
        $<post>=[ '}' | $ ]
    }
    token regex-before-after {
        '<'
        [ \? | \! ]
        [ 'before' | 'after' ]
        <!ww>
    }
    token regex-angled-bracket-special {
        '<' <-[ > \s ]>+ '>'
    }

# Strings ======================================================================

    token string-TOP {
        (
            || <q-interpolation>
            || <b-interpolation>
            || <s-interpolation>
            || <a-interpolation>
            || <h-interpolation>
            || <f-interpolation>
            || <c-interpolation>
            || <quote-content>
        )*
    }

# Starters --------------------------------------------------------------------

# String ----------------------------------------------------------------------
    token string {
        <quote-opener>
        :my $*quote-mode = QuoteMode.new;
        {
            $*quote-mode.closer = %quote-pairs{$<quote-opener>};
            given $<quote-opener> {
                when "'" {
                    $*quote-mode.q = True;
                }
                when '"' | '<<' | '«' {
                    $*quote-mode.s = True;
                    $*quote-mode.a = True;
                    $*quote-mode.h = True;
                    $*quote-mode.f = True;
                    $*quote-mode.c = True;
                    $*quote-mode.b = True;
                }
            }
        }
        <string-TOP>
        <quote-closer>
    }
    token quote-opener {
        | '｢'
        | "'"
        | '"'
        | <!{$*token-seen}>
        [
            | "<<"
            | "<"
            | "«"
        ]
    }
    token quote-closer {
        | <?{ $*quote-mode.closer }>
          $( $*quote-mode.closer )

        | $ # Prevent excessive backtracking.
    }

# q-String --------------------------------------------------------------------

    token q-string {
        :my $*quote-mode = QuoteMode.new;
        <q-letter>
        <q-adverb>*
        {
            given $<q-letter> {
                when "q" { $*quote-mode.q = True; }
                when "qq" | "qqw" | "qqww" | "qqx" {
                    $*quote-mode.s = True;
                    $*quote-mode.a = True;
                    $*quote-mode.h = True;
                    $*quote-mode.f = True;
                    $*quote-mode.c = True;
                    $*quote-mode.b = True;
                }
            }
            for $<q-adverb>.list {
                given $_ {
                    when ":q" { $*quote-mode.q = True; }
                    when ":qq" {
                        $*quote-mode.s = True;
                        $*quote-mode.a = True;
                        $*quote-mode.h = True;
                        $*quote-mode.f = True;
                        $*quote-mode.c = True;
                        $*quote-mode.b = True;
                    }
                    when ":s" { $*quote-mode.s = True; }
                    when ":a" { $*quote-mode.a = True; }
                    when ":h" { $*quote-mode.h = True; }
                    when ":f" { $*quote-mode.f = True; }
                    when ":c" { $*quote-mode.c = True; }
                    when ":b" { $*quote-mode.b = True; }
                }
            }
        }

        [
            | <?{ $*quote-mode.heredoc-marker }>
              { @*upcoming-heredocs.push: $*quote-mode; }

            | <q-quote-opener>
              {
                  $<q-quote-opener>.trim-leading.comb.map({
                      $*quote-mode.closer ~= %quote-pairs{$_} // $_;
                  }).sink
              }
              <string-TOP>
              <quote-closer>
        ]
    }
    token q-letter {
        | Q
        | q
        | qq
        | qw
        | qww
        | qqw
        | qqww
        | qx
        | qqx
    }
    token q-adverb {
        \:
        (
            | <heredoc-adverb>
            | \w*
        )
    }
    token q-quote-opener {
        [
            | <q-quote-opener-immediate>
            | <q-quote-opener-spaced>
        ]
        <free-quote-opener>*
    }
    token q-quote-opener-immediate {
        <:!L - [ \s \( \) ]>
    }
    token q-quote-opener-spaced {
        $<sp>=[' '+]
        <free-quote-opener>
    }

# Heredoc ---------------------------------------------------------------------

    token heredoc {
        <?{ @*upcoming-heredocs.elems }>
        $<nl>=[\n]
        :my $*quote-mode;
        {
            $*quote-mode = @*upcoming-heredocs.shift;
        }
        <string-TOP>
        <heredoc-closer>
    }
    token heredoc-adverb {
        :my $*heredoc-marker-delim := '';
        'to'
        <q-quote-opener>
        {
            $<q-quote-opener>.trim-leading.comb.map({
                $*quote-mode.closer ~= %quote-pairs{$_} // $_;
            }).sink
        }
        $<heredoc-marker>=<string-TOP>
        <quote-closer>
        { $*quote-mode.heredoc-marker = $<heredoc-marker> }
    }
    token heredoc-marker-char {
        .
        <!{ $/ eq $*heredoc-marker-delim }>
    }
    token heredoc-closer {
        \n
        \h*
        $( $*quote-mode.heredoc-marker )
        \h*
        <?before \n>
    }

# Interpolation ---------------------------------------------------------------

    token backslash-quote-interpolation {
        $<bs>=['\\']
        <q-string>
    }
    token q-interpolation {
        <?{ $*quote-mode.q }>

        (
            || <backslash-quote-interpolation>
            || <q-escape>
        )
    }
    token b-interpolation {
        <?{ $*quote-mode.b }>
        '\\'
        .
    }
    token s-interpolation {
        <?{ $*quote-mode.s }>
        <name-scalar>
    }
    token a-interpolation {
        <?{ $*quote-mode.a }>
        <name-array>
    }
    token h-interpolation {
        <?{ $*quote-mode.h }>
        <name-hash>
    }
    token f-interpolation {
        <?{ $*quote-mode.f }>
        <name-code>
    }
    token c-interpolation {
        <?{ $*quote-mode.c }>
        $<opener>='{'
        :my $*scope-opener;
        :my $*scope-closer;
        {
            $*scope-opener = '{';
            $*scope-closer = '}';
        }
        <raku-TOP>
        $<closer>=[ '}' | $ ]
    }

# Content ---------------------------------------------------------------------

    token quote-content {
        <!before
            || <?{ $*quote-mode.closer }>
               $($*quote-mode.closer)

            || <?{ $*quote-mode.heredoc-marker }>
               <heredoc-closer>
        >
        .
    }
    token q-escape {
        || '\\\\'

        || <?{ $*quote-mode.closer }>
           '\\' $($*quote-mode.closer)
    }

# RakuDoc =====================================================================

    token rakudoc-TOP {
        (
            || <?{ $*rakudoc-block-can-nest }> <rakudoc-block>
            # TODO
            # || <rakudoc-implicit-code-block>
            || <rakudoc-markup>
            || <rakudoc-text>
        )*
    }

# Starters --------------------------------------------------------------------

    token rakudoc-block {
        :my $*rakudoc-closer-regex;
        :my $*rakudoc-block-can-nest;
        :my $*rakudoc-block-lang = 'raku';
        :my @*rakudoc-block-allow;

        <?after \n | ^ >
        $<start>=[\h* '=']
        [
            || $<type>=[begin \h+]
               $<name>=\w+
               { $*rakudoc-closer-regex = '<?after \n \h* > \=end \h+ \w+ \n'; $*rakudoc-block-can-nest = $<name> ne 'code'; }
               <rakudoc-block-configs>?

            || $<type>=[for   \h+]
               $<name>=\w+
               { $*rakudoc-closer-regex = '<?after \n \h* \n > || <?after \n > <?before \h* \= \w >'; }
               <rakudoc-block-configs>?

            || $<name>=[<!before end | begin | for> \w+]
               { $*rakudoc-closer-regex = '<?after \n \h* \n > || <?after \n > <?before \h* \= \w >'; }
        ]
        $<nl>=[\h* \n]
        <rakudoc-TOP>
        $<end>=[
            | <$*rakudoc-closer-regex>
            | $
        ]
    }

    token rakudoc-implicit-code-block {
        # TODO: Indentation stuff
        <rakudoc-code-block>
    }

    token rakudoc-block-configs {
        $<pre-space>=\h+
        <rakudoc-block-config-line>+ % $<sep>=[\n '=' \h+]
    }

    token rakudoc-block-config-line {
        <rakudoc-block-config>* %% $<sep>=\h+
    }

    token rakudoc-block-config {
        $<starter>=':'
        $<neg>='!'?
        $<name>=<identifier>
        :my $*rakudoc-block-config-set-allow;
        :my $*rakudoc-block-config-set-lang;
        {
            if $<name> eq 'allow' {
                $*rakudoc-block-config-set-allow = True;
            }
            elsif $<name> eq 'lang' {
                $*rakudoc-block-config-set-lang = True;
            }
        }
        <rakudoc-block-config-value>
    }

    token rakudoc-block-config-value {
        :my @*rakudoc-block-config-value-closers;
        # The $ at the end is meant to prevent excessive backtracking.
        | $<opener>='('
          { @*rakudoc-block-config-value-closers = ')', ','; }
          $<s1>=\h*
          <rakudoc-block-config-value-literal>* %% $<sep>=[\h* ',' \h*]
          $<s2>=\h*
          $<closer>=[ ')' | $ ]

        | $<opener>='{'
          { @*rakudoc-block-config-value-closers = '}', ','; }
          $<s1>=\h*
          <rakudoc-block-config-value-pair>* %% $<sep>=[\h* ',' \h*]
          $<s2>=\h*
          $<closer>=[ '}' | $ ]

        | $<opener>='<'
          { @*rakudoc-block-config-value-closers = '>', ' '; }
          $<s1>=\h*
          <rakudoc-block-config-value-literal>* %% $<sep>=\h+
          $<s2>=\h*
          $<closer>=[ '>' | $ ]

        | ''
    }

    token rakudoc-block-config-value-word-list-entry {
        [ <!before @*rakudoc-block-config-value-closers> . ]*
    }

    token rakudoc-block-config-value-literal {
        $<value>=[
            | <string>
            | $<stuff>=[ <!before @*rakudoc-block-config-value-closers> . ]*
        ]
        {
            if $*rakudoc-block-config-set-allow {
                @*rakudoc-block-allow.push: $<value>.Str;
            }
            elsif $*rakudoc-block-config-set-lang {
                $*rakudoc-block-lang = $<value>;
            }
        }
    }

    token rakudoc-block-config-value-pair {
        # Colon pairs (including without a value) or => arrows
        | $<starter>=':'
          $<neg>='!'?
          $<name>=<identifier>
          $<s1>=\h*
          <rakudoc-block-config-value>

        | [ <string> | $<no-string-key>=<-[\h=]> ]
          $<between>=[\h* '=>' \h*]
          <rakudoc-block-config-value-literal>

        | $<unknown>=[ <!before @*rakudoc-block-config-value-closers> . ]* # Escape hatch, so we don't run-away.
    }

    token rakudoc-comment {
        $<pre>=[ '#=' | '#|' ]
        :my $*rakudoc-closer-literal = "\n";
        :my $*rakudoc-block-can-nest = False;
        <rakudoc-TOP>
        $<post>=[ \n | $ ]
    }
    token delimited-rakudoc-comment-content {
        :my $*rakudoc-opener-char;
        :my $*rakudoc-closer-char;
        :my $*rakudoc-closer-literal;
        :my $*rakudoc-opener-count = 0;
        :my $*rakudoc-block-can-nest = True;
        {
            $*rakudoc-opener-char = $*comment-opener-char;
            $*rakudoc-closer-char = $*comment-closer-char;
            $*rakudoc-closer-literal = $*comment-closer;
        }
        <rakudoc-TOP>
    }

# Content ---------------------------------------------------------------------

    token rakudoc-markup {
        $<pre>=[ <:Lu> ]
        <?{
            !@*rakudoc-block-allow.defined ||
            !@*rakudoc-block-allow.elems ||
            $<pre> (elem) @*rakudoc-block-allow
        }>
        $<opener>=[ \<+ | « ]
        :my $*rakudoc-closer-literal;
        :my $*rakudoc-block-can-nest = False;
        :my @*rakudoc-forbidden;
        {
            $*rakudoc-closer-literal = $<opener>.Str.subst(/\</, '>').subst(/«/, '»');
            @*rakudoc-forbidden = $*rakudoc-closer-literal, '|';
        }
        <rakudoc-TOP>
        [
            $<pipe>='|'
            <rakudoc-meta>?
            <rakudoc-more-meta>*
        ]?
        $<closer>=$*rakudoc-closer-literal
    }
    token rakudoc-meta {
        [
            <!before $*rakudoc-closer-literal>
            <!before ',' | ';'>
            .
        ]+
    }
    token rakudoc-more-meta {
        $<meta-sep>=[ ',' | ';' ]
        <rakudoc-meta>?
    }
    token rakudoc-text {
        <!before
            <?{ !$*rakudoc-opener-count.defined || $*rakudoc-opener-count == 0 }>
            [
                ||
                    <?{ $*rakudoc-closer-regex.defined }>
                    <$*rakudoc-closer-regex>

                ||
                    <?{ $*rakudoc-closer-literal.defined }>
                    $*rakudoc-closer-literal

                || <?{ @*rakudoc-forbidden.defined }>
                   @*rakudoc-forbidden
            ]
        >
        $<char>=.
        {
            # TODO copy guarding solution
            if $*rakudoc-opener-count.defined {
                $*rakudoc-opener-count++ if $/ eq $*rakudoc-opener-char;
                $*rakudoc-opener-count-- if $/ eq $*rakudoc-closer-char;
            }
        }
    }

# Generic =====================================================================

    token identifier-char-normal {
        \w
    }

    token identifier-char-special {
        <[ - ' ]>
    }

    token identifier-char {
        | <identifier-char-normal>
        | <identifier-char-special>
        | ':'
    }

    token identifier {
        '::'?
        <identifier-char-normal>
        [
            || <identifier-char-special>+ <identifier-char-normal>
            || '::' <identifier-char-normal>
            || <identifier-char-normal>
        ]*
    }

# Put at the end, because it's long ===========================================

    token keyword {
        <!after <identifier-char> >
        [
        'BEGIN' | 'CATCH' | 'CHECK' | 'CLOSE' | 'COMPOSE' | 'CONTROL' | 'DOC' | 'END' | 'ENTER' | 'FIRST' | 'INIT' |
        'KEEP' | 'LAST' | 'LEAVE' | 'NEXT' | 'POST' | 'PRE' | 'QUIT' | 'UNDO' | 'anon' | 'augment' | 'but' |
        'class' | 'constant' | 'default' | 'does' | 'else' | 'elsif' | 'enum' | 'for' | 'gather' | 'given' |
        'grammar' | 'has' | 'if' | 'import' | 'is' | 'of' | 'let' | 'loop' | 'made' | 'make' | 'method' |
        'module' | 'multi' | 'my' | 'need' | 'orwith' | 'our' | 'proceed' | 'proto' | 'repeat' | 'require' |
        'where' | 'return' | 'return-rw' | 'returns' | 'role' | 'state' | 'sub' | 'no' |
        'submethod' | 'subset' | 'succeed' | 'supersede' | 'try' | 'unit' | 'unless' | 'until' |
        'use' | 'when' | 'while' | 'with' | 'without' | 'export' | 'native' | 'repr' | 'required' | 'rw' |
        'symbol' | 'default' | 'cached' | 'DEPRECATED' | 'dynamic' | 'hidden-from-backtrace' | 'nodal' |
        'pure' | 'raw' | 'start' | 'react' | 'supply' | 'whenever' | 'also' | 'rule' | 'token' | 'regex' |
        'dynamic-scope' | 'built' | 'temp' | '->' | '-->'
        ]
        <!before <identifier-char> >
    }
    token operator {
        [
        '++' | '--' | '**' | '!' | '+' | '~' | '?' | '+^' | '~^' | '?^' | '^' | '*' | '/' | '%' | '%%' | '+&' |
        '+<' | '+>' | '~&' | '~<' | '~>' | '?&' | '+|' | '+^' | '~|' | '~^' | '?' | '?|' | '?^' | '&' | '^' |
        '<=>' | '^…^' | '^…' | '…^' | '…' | '...' | '...^' | '^...' | '^...^' | '..' | '..^' | '^..' | '^..^' |
        '::=' | ':=' | '!=' | '==' | '<>' | '<=' | '<' | '>=' | '>' | '~~' | '===' | '&&' | '||' | '|' | '^^' | '//' |
        '??' | '!!' | '^fff^' | '^ff^' | '<==' | '==>' | '<<==' | '==>>' | '=>' | '=' | '<<' | '«' | '>>' | '»' |
        ',' | '>>.' | '».' | '.&' | '.=' | '.^' | '.?' | '.+' | '.*' | '∘' | '∩' | '⊍' | '∪' | '⊎' | '∖' |
        '⊖' | '≠' | '≤' | '≥' | '=:=' | '=~=' | '≅' | '∈' | '∉' | '≡' | '≢' | '∋' | '∌' | '⊂' | '⊄' | '⊆' | '⊈' |
        '⊃' | '⊅' | '⊇' | '⊉' | '!!!' | '???' | '¯' | '×' | '÷' | '⁺' | '⁻' | '(&)' | '(.)' | '(|)' | '(+)' | '(-)' | '(^)' | '(elem)' | '(==)' |
        '(cont)' | '(<)' | '(<=)' | '(>)' | '(>=)' | ','
        ]
        |
        # Word Operators
        <!after <identifier-char> >
        [
        'X' | 'Z' | 'R' | 'after' | 'and' | 'andthen' | 'before' | 'cmp' | 'div' | 'eq' | 'eqv' | 'extra' | 'ge' |
        'gt' | 'le' | 'leg' | 'lt' | 'mod' | 'ne' | 'or' | 'orelse' | 'x' | 'xor' | 'xx' | 'gcd' | 'lcm' |
        'but' | 'min' | 'max' | '^fff' | 'fff^' | 'fff' | '^ff' | 'ff^' | 'ff' | 'so' | 'not' | 'unicmp' |
        'TR' | 'o' | 'coll' | 'minmax' | 'notandthen' | 'S' | '−' | '-' | ':'
        ]
        <!before <identifier-char> >
    }

    token type {
        <!after <identifier-char> >
        [
        'False' | 'True' | 'Order' | 'More' | 'Less' | 'Same' | 'Any' | 'Array' | 'Associative' | 'AST' |
        'atomicint' | 'Attribute' | 'Backtrace' | 'Backtrace::Frame' | 'Bag' | 'Baggy' | 'BagHash' |
        'Blob' | 'Block' | 'Bool' | 'Buf' | 'Callable' | 'CallFrame' | 'Cancellation' | 'Capture' |
        'CArray' | 'Channel' | 'Code' | 'compiler' | 'Complex' | 'ComplexStr' | 'CompUnit' |
        'CompUnit::PrecompilationRepository' | 'CompUnit::Repository' | 'Empty' |
        'CompUnit::Repository::FileSystem' | 'CompUnit::Repository::Installation' | 'Cool' |
        'CurrentThreadScheduler' | 'CX::Warn' | 'CX::Take' | 'CX::Succeed' | 'CX::Return' | 'CX::Redo' |
        'CX::Proceed' | 'CX::Next' | 'CX::Last' | 'CX::Emit' | 'CX::Done' | 'Cursor' | 'Date' | 'Dateish' |
        'DateTime' | 'Distribution' | 'Distribution::Hash' | 'Distribution::Locally' |
        'Distribution::Path' | 'Distribution::Resource' | 'Distro' | 'Duration' | 'Encoding' |
        'Encoding::GlobalLexerRegistry' | 'Endian' | 'Enumeration' | 'Exception' | 'Failure' | 'FatRat' | 'Grammar' |
        'Hash' | 'HyperWhatever' | 'Instant' | 'Int' | 'int' | 'int16' | 'int32' | 'int64' | 'int8' | 'str' |
        'IntStr' | 'IO' | 'IO::ArgFiles' | 'IO::CatHandle' | 'IO::Handle' | 'IO::Notification' |
        'IO::Notification::Change' | 'IO::Path' | 'IO::Path::Cygwin' | 'IO::Path::Parts' |
        'IO::Path::QNX' | 'IO::Path::Unix' | 'IO::Path::Win32' | 'IO::Pipe' | 'IO::Socket' |
        'IO::Socket::Async' | 'IO::Socket::Async::ListenSocket' | 'IO::Socket::INET' | 'IO::Spec' |
        'IO::Spec::Cygwin' | 'IO::Spec::QNX' | 'IO::Spec::Unix' | 'IO::Spec::Win32' | 'IO::Special' |
        'Iterable' | 'Iterator' | 'Junction' | 'Kernel' | 'Label' | 'List' | 'Lock' | 'Lock::Async' |
        'Lock::ConditionVariable' | 'long' | 'longlong' | 'Macro' | 'Map' | 'Match' |
        'Metamodel::AttributeContainer' | 'Metamodel::C3MRO' | 'Metamodel::ClassHOW' |
        'Metamodel::ConcreteRoleHOW' | 'Metamodel::CurriedRoleHOW' | 'Metamodel::DefiniteHOW' |
        'Metamodel::Documenting' | 'Metamodel::EnumHOW' | 'Metamodel::Finalization' |
        'Metamodel::MethodContainer' | 'Metamodel::Mixins' | 'Metamodel::MROBasedMethodDispatch' |
        'Metamodel::MultipleInheritance' | 'Metamodel::Naming' | 'Metamodel::Primitives' |
        'Metamodel::PrivateMethodContainer' | 'Metamodel::RoleContainer' | 'Metamodel::RolePunning' |
        'Metamodel::Stashing' | 'Metamodel::Trusting' | 'Metamodel::Versioning' | 'Method' | 'Mix' |
        'MixHash' | 'Mixy' | 'Mu' | 'NFC' | 'NFD' | 'NFKC' | 'NFKD' | 'Nil' | 'Num' | 'num32' | 'num64' |
        'Numeric' | 'NumStr' | 'ObjAt' | 'Order' | 'Pair' | 'Parameter' | 'Perl' | 'Pod::Block' |
        'Pod::Block::Code' | 'Pod::Block::Comment' | 'Pod::Block::Declarator' | 'Pod::Block::Named' |
        'Pod::Block::Para' | 'Pod::Block::Table' | 'Pod::Heading' | 'Pod::Item' | 'Pointer' |
        'Positional' | 'PositionalBindFailover' | 'Proc' | 'Proc::Async' | 'Promise' | 'Proxy' |
        'PseudoStash' | 'QuantHash' | 'RaceSeq' | 'Raku' | 'Range' | 'Rat' | 'Rational' | 'RatStr' |
        'Real' | 'Regex' | 'Routine' | 'Routine::WrapHandle' | 'Scalar' | 'Scheduler' | 'Semaphore' |
        'Seq' | 'Sequence' | 'Set' | 'SetHash' | 'Setty' | 'Signature' | 'size_t' | 'Slip' | 'Stash' |
        'Str' | 'StrDistance' | 'Stringy' | 'Sub' | 'Submethod' | 'Supplier' | 'Supplier::Preserving' |
        'Supply' | 'Systemic' | 'Tap' | 'Telemetry' | 'Telemetry::Instrument::Thread' |
        'Telemetry::Instrument::ThreadPool' | 'Telemetry::Instrument::Usage' | 'Telemetry::Period' |
        'Telemetry::Sampler' | 'Thread' | 'Test' | 'ThreadPoolScheduler' | 'UInt' | 'uint16' | 'uint32' |
        'uint64' | 'uint8' | 'Uni' | 'utf8' | 'ValueObjAt' | 'Variable' | 'Version' | 'VM' | 'Whatever' |
        'WhateverCode' | 'WrapHandle' | 'NativeCall' |
        # Pragmas
        'precompilation' | 'experimental' | 'worries' | 'MONKEY-TYPING' | 'MONKEY-SEE-NO-EVAL' |
        'MONKEY-GUTS' | 'fatal' | 'lib' | 'isms' | 'newline' | 'nqp' | 'soft' |
        'strict' | 'trace' | 'variables'
        ]
        <!before <identifier-char> >
    }
    token builtin-routine {
        <!after <identifier-char> >
        [
        'ACCEPTS' | 'abs' | 'abs2rel' | 'absolute' | 'accept' | 'accepts_type' | 'accessed' | 'acos' |
        'acosec' | 'acosech' | 'acosh' | 'acotan' | 'acotanh' | 'acquire' | 'act' | 'action' | 'actions' |
        'add' | 'add_attribute' | 'add_enum_value' | 'add_fallback' | 'add_method' | 'add_parent' |
        'add_private_method' | 'add_role' | 'add_stash' | 'add_trustee' | 'addendum' | 'adverb' | 'after' |
        'all' | 'allocate' | 'allof' | 'allowed' | 'alternative-names' | 'annotations' | 'antipair' |
        'antipairs' | 'any' | 'anyof' | 'api' | 'app_lifetime' | 'append' | 'arch' | 'archetypes' |
        'archname' | 'args' | 'ARGS-TO-CAPTURE' | 'arity' | 'Array' | 'asec' | 'asech' | 'asin' | 'asinh' |
        'ASSIGN-KEY' | 'ASSIGN-POS' | 'assuming' | 'ast' | 'at' | 'atan' | 'atan2' | 'atanh' | 'AT-KEY' |
        'atomic-assign' | 'atomic-dec-fetch' | 'atomic-fetch' | 'atomic-fetch-add' | 'atomic-fetch-dec' |
        'atomic-fetch-inc' | 'atomic-fetch-sub' | 'atomic-inc-fetch' | 'AT-POS' | 'attributes' | 'auth' |
        'await' | 'backend' | 'backtrace' | 'Bag' | 'bag' | 'Baggy' | 'BagHash' | 'bail-out' | 'base' |
        'basename' | 'base-repeating' | 'base_type' | 'batch' | 'BIND-KEY' | 'BIND-POS' | 'bind-stderr' |
        'bind-stdin' | 'bind-stdout' | 'bind-udp' | 'bits' | 'bless' | 'block' | 'Bool' | 'bool-only' |
        'bounds' | 'break' | 'Bridge' | 'broken' | 'BUILD' | 'TWEAK' | 'build-date' | 'bytes' | 'cache' |
        'callframe' | 'calling-package' | 'CALL-ME' | 'callsame' | 'callwith' | 'can' | 'cancel' |
        'candidates' | 'cando' | 'can-ok' | 'canonpath' | 'caps' | 'caption' | 'Capture' | 'capture' |
        'cas' | 'catdir' | 'categorize' | 'categorize-list' | 'catfile' | 'catpath' | 'cause' | 'ceiling' |
        'cglobal' | 'changed' | 'Channel' | 'channel' | 'chars' | 'chdir' | 'child' | 'child-name' |
        'child-typename' | 'chmod' | 'chomp' | 'chop' | 'chr' | 'chrs' | 'chunks' | 'cis' | 'classify' |
        'classify-list' | 'cleanup' | 'clone' | 'close' | 'closed' | 'close-stdin' | 'cmp-ok' | 'code' |
        'codename' | 'codes' | 'coerce_type' | 'coll' | 'collate' | 'column' | 'comb' | 'combinations' |
        'command' | 'comment' | 'compiler' | 'Complex' | 'compose' | 'composalizer' | 'compose_type' |
        'compose_values' | 'composer' | 'compute_mro' | 'condition' | 'config' | 'configure_destroy' |
        'configure_type_checking' | 'conj' | 'connect' | 'constraints' | 'construct' | 'contains' |
        'content' | 'contents' | 'copy' | 'cos' | 'cosec' | 'cosech' | 'cosh' | 'cotan' | 'cotanh' | 'count' |
        'count-only' | 'cpu-cores' | 'cpu-usage' | 'CREATE' | 'create_type' | 'cross' | 'cue' | 'curdir' |
        'curupdir' | 'd' | 'Date' | 'DateTime' | 'day' | 'daycount' | 'day-of-month' | 'day-of-week' |
        'day-of-year' | 'days-in-month' | 'dd-mm-yyyy' | 'declaration' | 'decode' | 'decoder' | 'deepmap' |
        'default' | 'defined' | 'DEFINITE' | 'definite' | 'delayed' | 'delete' | 'delete-by-compiler' |
        'DELETE-KEY' | 'DELETE-POS' | 'denominator' | 'desc' | 'DESTROY' | 'destroyers' | 'devnull' |
        'diag' | 'did-you-mean' | 'die' | 'dies-ok' | 'dir' | 'dirname' | 'distribution' | 'dir-sep' |
        'DISTROnames' | 'do' | 'does' | 'does-ok' | 'done' | 'done-testing' | 'duckmap' | 'dynamic' | 'e' |
        'eager' | 'earlier' | 'elems' | 'emit' | 'enclosing' | 'encode' | 'encoder' | 'encoding' | 'end' |
        'endian' | 'ends-with' | 'enum_from_value' | 'enum_value_list' | 'enum_values' | 'enums' | 'EOF' |
        'eof' | 'EVAL' | 'eval-dies-ok' | 'EVALFILE' | 'eval-lives-ok' | 'event' | 'exception' |
        'excludes-max' | 'excludes-min' | 'EXISTS-KEY' | 'EXISTS-POS' | 'exit' | 'exitcode' | 'exp' |
        'expected' | 'explicitly-manage' | 'expmod' | 'export_callback' | 'extension' | 'f' | 'fail' |
        'FALLBACK' | 'fails-like' | 'fc' | 'feature' | 'file' | 'filename' | 'files' | 'find' |
        'find_method' | 'find_method_qualified' | 'finish' | 'first' | 'flat' | 'first-date-in-month' |
        'flatmap' | 'flip' | 'floor' | 'flunk' | 'flush' | 'flush_cache' | 'fmt' | 'format' | 'formatter' |
        'free-memory' | 'freeze' | 'from' | 'from-list' | 'from-loop' | 'from-posix' | 'from-slurpy' |
        'full' | 'full-barrier' | 'GENERATE-USAGE' | 'generate_mixin' | 'get' | 'get_value' | 'getc' |
        'gist' | 'got' | 'grab' | 'grabpairs' | 'grep' | 'handle' | 'handled' | 'handles' | 'hardware' |
        'has_accessor' | 'Hash' | 'hash' | 'head' | 'headers' | 'hh-mm-ss' | 'hidden' | 'hides' | 'hostname' |
        'hour' | 'how' | 'hyper' | 'id' | 'illegal' | 'im' | 'in' | 'in-timezone' | 'indent' | 'index' |
        'indices' | 'indir' | 'infinite' | 'infix' | 'postcirumfix' | 'cicumfix' | 'install' |
        'install_method_cache' | 'Instant' | 'instead' | 'Int' | 'int-bounds' | 'interval' | 'in-timezone' |
        'invalid-str' | 'invert' | 'invocant' | 'IO' | 'IO::Notification.watch-path' | 'is_trusted' |
        'is_type' | 'isa' | 'is-absolute' | 'isa-ok' | 'is-approx' | 'is-deeply' | 'is-hidden' |
        'is-initial-thread' | 'is-int' | 'is-lazy' | 'is-leap-year' | 'isNaN' | 'isnt' | 'is-prime' |
        'is-relative' | 'is-routine' | 'is-setting' | 'is-win' | 'item' | 'iterator' | 'join' | 'keep' |
        'kept' | 'KERNELnames' | 'key' | 'keyof' | 'keys' | 'kill' | 'kv' | 'kxxv' | 'l' | 'lang' | 'last' |
        'lastcall' | 'later' | 'lazy' | 'lc' | 'leading' | 'level' | 'like' | 'line' | 'lines' | 'link' |
        'List' | 'list' | 'listen' | 'live' | 'lives-ok' | 'load' | 'load-repo-id' | 'load-unit' | 'loaded' |
        'loads' | 'local' | 'lock' | 'log' | 'log10' | 'lookup' | 'lsb' | 'made' | 'MAIN' | 'make' | 'Map' |
        'map' | 'match' | 'max' | 'maxpairs' | 'merge' | 'message' | 'method' | 'meta' | 'method_table' |
        'methods' | 'migrate' | 'min' | 'minmax' | 'minpairs' | 'minute' | 'misplaced' | 'Mix' | 'mix' |
        'MixHash' | 'mixin' | 'mixin_attribute' | 'Mixy' | 'mkdir' | 'mode' | 'modified' | 'month' | 'move' |
        'mro' | 'msb' | 'multi' | 'multiness' | 'name' | 'named' | 'named_names' | 'narrow' |
        'nativecast' | 'native-descriptor' | 'nativesizeof' | 'need' | 'new' | 'new_type' |
        'new-from-daycount' | 'new-from-pairs' | 'next' | 'nextcallee' | 'next-handle' | 'nextsame' |
        'nextwith' | 'next-interesting-index' | 'NFC' | 'NFD' | 'NFKC' | 'NFKD' | 'nice' | 'nl-in' |
        'nl-out' | 'nodemap' | 'nok' | 'normalize' | 'none' | 'norm' | 'not' | 'note' | 'now' | 'nude' |
        'Num' | 'numerator' | 'Numeric' | 'of' | 'offset' | 'offset-in-hours' | 'offset-in-minutes' |
        'ok' | 'old' | 'on-close' | 'one' | 'on-switch' | 'open' | 'opened' | 'operation' | 'optional' |
        'ord' | 'ords' | 'orig' | 'os-error' | 'osname' | 'out-buffer' | 'pack' | 'package' | 'package-kind' |
        'package-name' | 'packages' | 'Pair' | 'pair' | 'pairs' | 'pairup' | 'parameter' | 'params' |
        'parent' | 'parent-name' | 'parents' | 'parse' | 'parse-base' | 'parsefile' | 'parse-names' |
        'parts' | 'pass' | 'path' | 'path-sep' | 'payload' | 'peer-host' | 'peer-port' | 'periods' | 'perl' |
        'permutations' | 'phaser' | 'pick' | 'pickpairs' | 'pid' | 'placeholder' | 'plan' | 'plus' |
        'polar' | 'poll' | 'polymod' | 'pop' | 'pos' | 'positional' | 'posix' | 'postfix' | 'postmatch' |
        'precomp-ext' | 'precomp-target' | 'precompiled' | 'pred' | 'prefix' | 'prematch' | 'prepend' |
        'primary' | 'print' | 'printf' | 'print-nl' | 'print-to' | 'private' | 'private_method_names' |
        'private_method_table' | 'proc' | 'produce' | 'Promise' | 'promise' | 'prompt' | 'protect' |
        'protect-or-queue-on-recursion' | 'publish_method_cache' | 'pull-one' | 'push' | 'push-all' |
        'push-at-least' | 'push-exactly' | 'push-until-lazy' | 'put' | 'qualifier-type' | 'quaternary' |
        'quit' | 'r' | 'race' | 'radix' | 'raku' | 'rand' | 'Range' | 'range' | 'Rat' | 'raw' | 're' | 'read' |
        'read-bits' | 'read-int128' | 'read-int16' | 'read-int32' | 'read-int64' | 'read-int8' |
        'read-num32' | 'read-num64' | 'read-ubits' | 'read-uint128' | 'read-uint16' | 'read-uint32' |
        'read-uint64' | 'read-uint8' | 'readchars' | 'readonly' | 'ready' | 'Real' | 'reallocate' |
        'reals' | 'reason' | 'rebless' | 'receive' | 'recv' | 'redispatcher' | 'redo' | 'reduce' |
        'rel2abs' | 'relative' | 'release' | 'remove' | 'rename' | 'repeated' | 'replacement' |
        'replace-with' | 'repo' | 'repo-id' | 'report' | 'required' | 'reserved' | 'resolve' | 'restore' |
        'result' | 'resume' | 'rethrow' | 'return' | 'return-rw' | 'returns' | 'reverse' | 'right' |
        'rindex' | 'rmdir' | 'role' | 'roles_to_compose' | 'rolish' | 'roll' | 'rootdir' | 'roots' |
        'rotate' | 'rotor' | 'round' | 'roundrobin' | 'routine-type' | 'run' | 'RUN-MAIN' | 'rw' | 'rwx' |
        'samecase' | 'samemark' | 'samewith' | 'say' | 'schedule-on' | 'scheduler' | 'scope' | 'sec' |
        'sech' | 'second' | 'secondary' | 'seek' | 'self' | 'send' | 'Seq' | 'Set' | 'set' | 'serial' |
        'set_hidden' | 'set_name' | 'set_package' | 'set_rw' | 'set_value' | 'set_api' | 'set_auth' |
        'set_composalizer' | 'set_export_callback' | 'set_is_mixin' | 'set_mixin_attribute' |
        'set_package' | 'set_ver' | 'set_why' | 'SetHash' | 'Setty' | 'set-instruments' |
        'setup_finalization' | 'setup_mixin_cache' | 'shape' | 'share' | 'shell' | 'short-id' |
        'short-name' | 'shortname' | 'shift' | 'sibling' | 'sigil' | 'sign' | 'signal' | 'signals' |
        'signature' | 'sin' | 'sinh' | 'sink' | 'sink-all' | 'skip' | 'skip-at-least' |
        'skip-at-least-pull-one' | 'skip-one' | 'skip-rest' | 'sleep' | 'sleep-timer' | 'sleep-until' |
        'Slip' | 'slip' | 'slurp' | 'slurp-rest' | 'slurpy' | 'snap' | 'snapper' | 'so' | 'socket-host' |
        'socket-port' | 'sort' | 'source' | 'source-package' | 'spawn' | 'SPEC' | 'splice' | 'split' |
        'splitdir' | 'splitpath' | 'sprintf' | 'spurt' | 'sqrt' | 'squish' | 'srand' | 'stable' | 'start' |
        'started' | 'starts-with' | 'status' | 'stderr' | 'stdout' | 'STORE' | 'store-file' |
        'store-repo-id' | 'store-unit' | 'Str' | 'Stringy' | 'sub_signature' | 'subbuf' | 'subbuf-rw' |
        'subname' | 'subparse' | 'subst' | 'subst-mutate' | 'substr' | 'substr-eq' | 'substr-rw' |
        'subtest' | 'succ' | 'sum' | 'suffix' | 'summary' | 'Supply' | 'symlink' | 'T' | 't' | 'tail' |
        'take' | 'take-rw' | 'tan' | 'tanh' | 'tap' | 'target' | 'target-name' | 'tc' | 'tclc' | 'tell' |
        'term' | 'tertiary' | 'then' | 'throttle' | 'throw' | 'throws-like' | 'time' | 'timezone' |
        'tmpdir' | 'to' | 'today' | 'todo' | 'toggle' | 'to-posix' | 'total' | 'total-memory' | 'trailing' |
        'trans' | 'tree' | 'trim' | 'trim-leading' | 'trim-trailing' | 'truncate' | 'truncated-to' |
        'trusts' | 'try_acquire' | 'trying' | 'twigil' | 'type' | 'type_captures' | 'type_check' |
        'typename' | 'uc' | 'udp' | 'uncaught_handler' | 'undefine' | 'unimatch' | 'unicmp' | 'uniname' |
        'uninames' | 'uninstall' | 'uniparse' | 'uniprop' | 'uniprops' | 'unique' | 'unival' | 'univals' |
        'unlike' | 'unlink' | 'unlock' | 'unpack' | 'unpolar' | 'unset' | 'unshift' | 'unwrap' | 'updir' |
        'USAGE' | 'usage-name' | 'use-ok' | 'utc' | 'val' | 'value' | 'values' | 'VAR' | 'variable' | 'ver' |
        'verbose-config' | 'Version' | 'version' | 'VMnames' | 'volume' | 'vow' | 'w' | 'wait' | 'warn' |
        'watch' | 'watch-path' | 'week' | 'weekday-of-month' | 'week-number' | 'week-year' | 'WHAT' |
        'what' | 'when' | 'WHERE' | 'WHEREFORE' | 'WHICH' | 'WHO' | 'whole-second' | 'WHY' | 'why' |
        'with-lock-hidden-from-recursion-check' | 'wordcase' | 'words' | 'workaround' | 'wrap' |
        'write' | 'write-bits' | 'write-int128' | 'write-int16' | 'write-int32' | 'write-int64' |
        'write-int8' | 'write-num32' | 'write-num64' | 'write-ubits' | 'write-uint128' | 'write-uint16' |
        'write-uint32' | 'write-uint64' | 'write-uint8' | 'write-to' | 'x' | 'yada' | 'year' | 'yield' |
        'yyyy-mm-dd' | 'z' | 'zip' | 'zip-latest' | 'HOW' | 's' | 'DEPRECATED' | 'trait_mod'
        ]
        <!before <identifier-char> >
    }

    token free-quote-opener {
        "'" | '"' | "<" | "«" | "<<" |
        "\x0028" | "\x003c" | "\x005b" |
        "\x007b" | "\x00ab" | "\x0f3a" |
        "\x0f3c" | "\x169b" | "\x2018" |
        "\x201a" | "\x201b" | "\x201c" |
        "\x201e" | "\x201f" | "\x2039" |
        "\x2045" | "\x207d" | "\x208d" |
        "\x2208" | "\x2209" | "\x220a" |
        "\x2215" | "\x223c" | "\x2243" |
        "\x2252" | "\x2254" | "\x2264" |
        "\x2266" | "\x2268" | "\x226a" |
        "\x226e" | "\x2270" | "\x2272" |
        "\x2274" | "\x2276" | "\x2278" |
        "\x227a" | "\x227c" | "\x227e" |
        "\x2280" | "\x2282" | "\x2284" |
        "\x2286" | "\x2288" | "\x228a" |
        "\x228f" | "\x2291" | "\x2298" |
        "\x22a2" | "\x22a6" | "\x22a8" |
        "\x22a9" | "\x22ab" | "\x22b0" |
        "\x22b2" | "\x22b4" | "\x22b6" |
        "\x22c9" | "\x22cb" | "\x22d0" |
        "\x22d6" | "\x22d8" | "\x22da" |
        "\x22dc" | "\x22de" | "\x22e0" |
        "\x22e2" | "\x22e4" | "\x22e6" |
        "\x22e8" | "\x22ea" | "\x22ec" |
        "\x22f0" | "\x22f2" | "\x22f3" |
        "\x22f4" | "\x22f6" | "\x22f7" |
        "\x2308" | "\x230a" | "\x2329" |
        "\x23b4" | "\x2768" | "\x276a" |
        "\x276c" | "\x276e" | "\x2770" |
        "\x2772" | "\x2774" | "\x27c3" |
        "\x27c5" | "\x27d5" | "\x27dd" |
        "\x27e2" | "\x27e4" | "\x27e6" |
        "\x27e8" | "\x27ea" | "\x2983" |
        "\x2985" | "\x2987" | "\x2989" |
        "\x298b" | "\x298d" | "\x298f" |
        "\x2991" | "\x2993" | "\x2995" |
        "\x2997" | "\x29c0" | "\x29c4" |
        "\x29cf" | "\x29d1" | "\x29d4" |
        "\x29d8" | "\x29da" | "\x29f8" |
        "\x29fc" | "\x2a2b" | "\x2a2d" |
        "\x2a34" | "\x2a3c" | "\x2a64" |
        "\x2a79" | "\x2a7d" | "\x2a7f" |
        "\x2a81" | "\x2a83" | "\x2a8b" |
        "\x2a91" | "\x2a93" | "\x2a95" |
        "\x2a97" | "\x2a99" | "\x2a9b" |
        "\x2aa1" | "\x2aa6" | "\x2aa8" |
        "\x2aaa" | "\x2aac" | "\x2aaf" |
        "\x2ab3" | "\x2abb" | "\x2abd" |
        "\x2abf" | "\x2ac1" | "\x2ac3" |
        "\x2ac5" | "\x2acd" | "\x2acf" |
        "\x2ad1" | "\x2ad3" | "\x2ad5" |
        "\x2aec" | "\x2af7" | "\x2af9" |
        "\x2e02" | "\x2e04" | "\x2e09" |
        "\x2e0c" | "\x2e1c" | "\x2e20" |
        "\x3008" | "\x300a" | "\x300c" |
        "\x300e" | "\x3010" | "\x3014" |
        "\x3016" | "\x3018" | "\x301a" |
        "\x301d" | "\xfd3e" | "\xfe17" |
        "\xfe35" | "\xfe37" | "\xfe39" |
        "\xfe3b" | "\xfe3d" | "\xfe3f" |
        "\xfe41" | "\xfe43" | "\xfe47" |
        "\xfe59" | "\xfe5b" | "\xfe5d" |
        "\xff08" | "\xff1c" | "\xff3b" |
        "\xff5b" | "\xff5f" | "\xff62"
    }
}
