Syntax Example for: Perl6Grammar.nqp
use QRegex;
use NQPP6QRegex;
use NQPP5QRegex;
use Perl6::Actions;
use Perl6::World;
use Perl6::Pod;
 
role startstop[$start$stop] {
    token starter { $start }
    token stopper { $stop }
}
 
role stop[$stop] {
    token starter { <!> }
    token stopper { $stop }
}
 
# This role captures things that STD factors out from any individual grammar,
# but that don't make sense to go in HLL::Grammar.
role STD {
    token 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
        ]>
    }
    
    method balanced($start$stop) {
        self.HOW.mixin(self, startstop.HOW.curry(startstop, $start$stop));
    }
    method unbalanced($stop) {
        self.HOW.mixin(self, stop.HOW.curry(stop, $stop));
    }
    
    token starter { <!> }
    token stopper { <!> }
    
    my %quote_lang_cache;
    method quote_lang($l$start$stop@base_tweaks?, @extra_tweaks?) {
        sub lang_key() {
            my @keybits := [$l.HOW.name($l), $start$stop];
            for @base_tweaks {
                @keybits.push($_);
            }
            for @extra_tweaks {
                if $_[0] eq 'to' {
                    return 'NOCACHE';
                }
                @keybits.push($_[0] ~ '=' ~ $_[1]);
            }
            nqp::join("\0"@keybits)
        }
        sub con_lang() {
            my $lang := $l;
            for @base_tweaks {
                $lang := $lang."tweak_$_"(1);
            }
            for @extra_tweaks {
                my $t := $_[0];
                if nqp::can($lang"tweak_$t") {
                    $lang := $lang."tweak_$t"($_[1]);
                }
                else {
                    self.sorry("Unrecognized adverb: :$t");
                }
            }
            $start ne $stop ?? $lang.balanced($start$stop)
                            !! $lang.unbalanced($stop);
        }
 
        # Get language from cache or derive it.
        my $key := lang_key();
        nqp::ifnull(%quote_lang_cache%quote_lang_cache := nqp::hash());
        nqp::existskey(%quote_lang_cache$key) && $key ne 'NOCACHE'
            ?? %quote_lang_cache{$key}
            !! (%quote_lang_cache{$key} := con_lang());
    }
    
    token babble($l@base_tweaks?) {
        :my @extra_tweaks;
 
        <.ws>
        [ <quotepair> <.ws>
            {
                my $kv := $<quotepair>[-1].ast;
                my $k  := $kv.named;
                if nqp::istype($kv, QAST::Stmts) || nqp::istype($kv, QAST::Stmt) && +@($kv) == 1 {
                    $kv := $kv[0];
                }
                my $v := nqp::istype($kv, QAST::IVal)
                    ?? $kv.value
                    !! $kv.has_compile_time_value
                        ?? $kv.compile_time_value
                        !! self.panic("Invalid adverb value for " ~ $<quotepair>[-1].Str);
                nqp::push(@extra_tweaks, [$k$v]);
            }
        ]*
 
        $<B>=[<?>]
        {
            # Work out the delimeters.
            my $c := $/.CURSOR;
            my @delims := $c.peek_delimiters($c.target, $c.pos);
            my $start := @delims[0];
            my $stop  := @delims[1];
            
            # Get the language.
            my $lang := self.quote_lang($l$start$stop@base_tweaks@extra_tweaks);
            $<B>.'!make'([$lang$start$stop]);
        }
    }
    
    my @herestub_queue;
 
    my class Herestub {
        has $!delim;
        has $!orignode;
        has $!lang;
        method delim() { $!delim }
        method orignode() { $!orignode }
        method lang() { $!lang }
    }
 
    role herestop {
        token starter { <!> }
        token stopper { ^^ {} $<ws>=(\h*) $*DELIM \h* $$ [\r\n | \v]? }
    }
 
    method heredoc () {
        if @herestub_queue {
            my $here := self.'!cursor_start_cur'();
            $here.'!cursor_pos'(self.pos);
            while @herestub_queue {
                my $herestub := nqp::shift(@herestub_queue);
                my $*DELIM := $herestub.delim;
                my $lang := $herestub.lang.HOW.mixin($herestub.lang, herestop);
                my $doc := $here.nibble($lang);
                if $doc {
                    # Match stopper.
                    my $stop := $lang.'!cursor_init'(self.orig(), :p($doc.pos), :shared(self.'!shared'())).stopper();
                    unless $stop {
                        self.panic("Ending delimiter $*DELIM not found");
                    }
                    $here.'!cursor_pos'($stop.pos);
                    
                    # Get it trimmed and AST updated.
                    $*ACTIONS.trim_heredoc($doc$stop$herestub.orignode.MATCH.ast);
                }
                else {
                    self.panic("Ending delimiter $*DELIM not found");
                }
            }
            $here.'!cursor_pass'($here.pos);
            $here
        }
        else {
            self
        }
    }
 
    method queue_heredoc($delim$lang) {
        nqp::ifnull(@herestub_queue@herestub_queue := []);
        nqp::push(@herestub_queue, Herestub.new(:$delim, :$lang, :orignode(self)));
        return self;
    }
 
    token quibble($l, *@base_tweaks) {
        :my $lang;
        :my $start;
        :my $stop;
        <babble($l@base_tweaks)>
        { my $B := $<babble><B>.ast; $lang := $B[0]; $start := $B[1]; $stop := $B[2]; }
 
        $start <nibble($lang)> [ $stop || { $/.CURSOR.panic("Couldn't find terminator $stop") } ]
 
        {
            nqp::can($lang'herelang') && self.queue_heredoc(
                $*W.nibble_to_str($/, $<nibble>.ast[1], -> { "Stopper '" ~ $<nibble> ~ "' too complex for heredoc" }),
                $lang.herelang)
        }
    }
 
    method nibble($lang) {
        my $lang_cursor := $lang.'!cursor_init'(self.orig(), :p(self.pos()), :shared(self.'!shared'()));
        my $*ACTIONS;
        for %*LANG {
            if nqp::istype($lang$_.value) {
                $*ACTIONS := %*LANG{$_.key ~ '-actions'};
                last;
            }
        }
        $lang_cursor.nibbler();
    }
    
    method panic(*@args) {
        self.typed_panic('X::Comp::AdHoc', payload => nqp::join(''@args))
    }
    method sorry(*@args) {
        self.typed_sorry('X::Comp::AdHoc', payload => nqp::join(''@args))
    }
    method worry(*@args) {
        self.typed_worry('X::Comp::AdHoc', payload => nqp::join(''@args))
    }
 
    method typed_panic($type_str, *%opts) {
        $*W.throw(self.MATCH(), nqp::split('::'$type_str), |%opts);
    }
    method typed_sorry($type_str, *%opts) {
        if +@*SORROWS + 1 == $*SORRY_LIMIT {
            $*W.throw(self.MATCH(), nqp::split('::'$type_str), |%opts);
        }
        else {
            @*SORROWS.push($*W.typed_exception(self.MATCH(), nqp::split('::'$type_str), |%opts));
        }
        self
    }
    method typed_worry($type_str, *%opts) {
        @*WORRIES.push($*W.typed_exception(self.MATCH(), nqp::split('::'$type_str), |%opts));
        self
    }
    
    method malformed($what) {
        self.typed_panic('X::Syntax::Malformed', :$what);
    }
    method missing($what) {
        self.typed_panic('X::Syntax::Missing', :$what);
    }
    method NYI($feature) {
        self.typed_panic('X::Comp::NYI', :$feature)
    }
 
    method EXPR_nonassoc($cur$left$right) {
        self.typed_panic('X::Syntax::NonAssociative', :left(~$left), :right(~$right));
    }
 
    # "when" arg assumes more things will become obsolete after Perl 6 comes out...
    method obs($old$new$when = 'in Perl 6') {
        $*W.throw(self.MATCH(), ['X''Obsolete'],
            old         => $old,
            replacement => $new,
            when        => $when,
        );
    }
    method obsvar($name) {
        $*W.throw(self.MATCH(), ['X''Syntax''Perl5Var'], :$name);
    }
    method sorryobs($old$new$when = 'in Perl 6') {
        $*W.throw(self.MATCH(), ['X''Obsolete'],
            old         => $old,
            replacement => $new,
            when        => $when,
        );
    }
    method worryobs($old$new$when = 'in Perl 6') {
        self.typed_worry('X::Obsolete',
            old         => $old,
            replacement => $new,
            when        => $when,
        );
    }
    
    method check_variable($var) {
        my $varast := $var.ast;
        if nqp::istype($varast, QAST::Op) && $varast.op eq 'ifnull' {
            $varast := $varast[0];
        }
        if !$*IN_DECL && nqp::istype($varast, QAST::Var) && $varast.scope eq 'lexical' {
            my $name := $varast.name;
            if $name ne '%_' && $name ne '@_' && !$*W.is_lexical($name) {
                if $var<sigil> ne '&' {
                    if !$*STRICT {
                        $*W.auto_declare_var($var);
                    }
                    else {
                        my @suggestions := $*W.suggest_lexicals($name);
 
                        if nqp::can($*PACKAGE.HOW'get_attribute_for_usage') {
                            my $sigil    := nqp::substr($name, 0, 1);
                            my $twigil   := nqp::concat($sigil'!');
                            my $basename := nqp::substr($name, 1, nqp::chars($name) - 1);
                            my $attrname := nqp::concat($twigil$basename);
 
                            my $attribute := $*PACKAGE.HOW.get_attribute_for_usage($*PACKAGE$attrname);
                            nqp::push(@suggestions$attrname);
 
                            CATCH {}
                        }
                        $*W.throw($var, ['X''Undeclared'], symbol => $name, suggestions => @suggestions);
                    }
                }
                else {
                    $var.CURSOR.add_mystery($name$var.to'var');
                }
            }
            else {
                my $lex := $*W.cur_lexpad();
                my %sym := $lex.symbol($name);
                if %sym {
                    %sym<used> := 1;
                }
                else {
                    # Add mention-only record (used to poison outer
                    # usages and disambiguate hashes/blocks by use of
                    # $_ when $*IMPLICIT is in force).
                    my $au := $lex.ann('also_uses');
                    $lex.annotate('also_uses'$au := {}) unless $au;
                    $au{$name} := 1;
                }
            }
        }
        self
    }
}
 
grammar Perl6::Grammar is HLL::Grammar does STD {
    my $sc_id := 0;
    method TOP() {
        # Language braid.
        my %*LANG;
        %*LANG<Regex>           := Perl6::RegexGrammar;
        %*LANG<Regex-actions>   := Perl6::RegexActions;
        %*LANG<P5Regex>         := Perl6::P5RegexGrammar;
        %*LANG<P5Regex-actions> := Perl6::P5RegexActions;
        %*LANG<Q>               := Perl6::QGrammar;
        %*LANG<Q-actions>       := Perl6::QActions;
        %*LANG<MAIN>            := Perl6::Grammar;
        %*LANG<MAIN-actions>    := Perl6::Actions;
        
        # Package declarator to meta-package mapping. Starts pretty much empty;
        # we get the mappings either imported or supplied by the setting. One
        # issue is that we may have no setting to provide them, e.g. when we
        # compile the setting, but it still wants some kinda package. We just
        # fudge in knowhow for that.
        my %*HOW;
        my %*HOWUSE;
        %*HOW<knowhow> := nqp::knowhow();
        %*HOW<package> := nqp::knowhow();
        
        # Symbol table and serialization context builder - keeps track of
        # objects that cross the compile-time/run-time boundary that are
        # associated with this compilation unit.
        my $file := nqp::getlexdyn('$?FILES');
        my $source_id := nqp::sha1(
            nqp::defined(%*COMPILING<%?OPTIONS><outer_ctx>)
                ?? self.target() ~ $sc_id++
                !! self.target());
        my $*W := nqp::isnull($file) ??
            Perl6::World.new(:handle($source_id)) !!
            Perl6::World.new(:handle($source_id), :description($file));
        $*W.add_initializations();
 
        my $cursor := self.comp_unit;
        $*W.pop_lexpad(); # UNIT
        $*W.pop_lexpad(); # UNIT_OUTER
        $cursor;
    }
 
    ## Lexer stuff
 
    token apostrophe {
        <[ ' \- ]>
    }
 
    token identifier {
        <.ident> [ <.apostrophe> <.ident> ]*
    }
 
    token name {
        [
        | <identifier> <morename>*
        | <morename>+
        ]
    }
 
    token morename {
        :my $*QSIGIL := '';
        '::'
        [
        ||  <?before '(' | <alpha> >
            [
            | <identifier>
            | :dba('indirect name''(' ~ ')' <EXPR>
            ]
        || <?before '::'> <.typed_panic: "X::Syntax::Name::Null">
        ]?
    }
 
    token longname {
        <name> {} [ <?before ':' <+alpha+[\< \[ \« ]>> <colonpair> ]*
    }
 
    token deflongname {
        :dba('new name to be defined')
        <name> <colonpair>*
    }
 
    token subshortname {
        <desigilname>
    }
 
    token sublongname {
        <subshortname> <sigterm>?
    }
 
    token defterm {     # XXX this is probably too general
        :dba('new term to be defined')
        <identifier>
        [
        | <colonpair>+
            {
                if $<colonpair>[0]<coloncircumfix> -> $cf {
                    my $category := $<identifier>.Str;
                    my $opname := $cf<circumfix>
                        ?? $*W.colonpair_nibble_to_str($/, $cf<circumfix><nibble>)
                        !! '';
                    my $canname  := $category ~ ":sym<" ~ $opname ~ ">";
                    my $termname := $category ~ ":<" ~ $opname ~ ">";
                    $/.CURSOR.add_categorical($category, $opname, $canname, $termname, :defterm);
                }
            }
        | <?>
        ]
    }
 
    token module_name {
        <longname>
        [ <?[[]> :dba('generic role''[' ~ ']' <arglist> ]?
    }
 
    token end_keyword {
        <!before <[ \( \\ ' \- ]> || \h* '=>'> »
    }
    token spacey { <?[\s#]> }
 
    token ENDSTMT {
        [
        | \h* $$ <.ws> <?MARKER('endstmt')>
        | <.unv>? $$ <.ws> <?MARKER('endstmt')>
        ]?
    }
 
    # ws is highly performance sensitive. So, we check if we already marked it
    # at this point with a simple method, and only if that is not the case do
    # we bother doing any pattern matching.
    method ws() {
        if self.MARKED('ws') {
            self
        }
        else {
            self._ws()
        }
    }
    token _ws {
        :my $old_highexpect := self.'!fresh_highexpect'();
        :dba('whitespace')
        <!ww>
        [
        | <.vws> <.heredoc>
        | <.unv>
        | <.unsp>
        ]*
        <?MARKER('ws')>
        :my $stub := self.'!fresh_highexpect'();
    }
    
    token unsp {
        \\ <?before \s | '#'>
        :dba('unspace')
        [
        | <.vws>
        | <.unv>
        | <.unsp>
        ]*
    }
    
    token vws {
        :dba('vertical whitespace')
        [
            [
            | \v
            | '<<<<<<<' {} <?before [.*? \v '=======']: .*? \v '>>>>>>>' > <.sorry: 'Found a version control conflict marker'> \V* \v
            | '=======' {} .*? \v '>>>>>>>' \V* \v   # ignore second half
            ]
        ]+
    }
 
    token unv {
        :dba('horizontal whitespace')
        [
        | \h+
        | \h* <.comment>
        | <?before \h* '=' [ \w | '\\'] > ^^ <.pod_content_toplevel>
        ]
    }
 
    proto token comment { <...> }
 
    token comment:sym<#> {
       '#' {} \N*
    }
 
    token comment:sym<#`(...)> {
        '#`' <?opener> {}
        [ <.quibble(%*LANG<Q>)> || <.typed_panic: 'X::Syntax::Comment::Embedded'> ]
    }
 
    token comment:sym<#|(...)> {
        '#|' <?opener> <attachment=.quibble(%*LANG<Q>)>
        {
            unless $*POD_BLOCKS_SEENself.from() } {
                $*POD_BLOCKS_SEENself.from() } := 1;
                if $*DECLARATOR_DOCS eq '' {
                    $*DECLARATOR_DOCS := $<attachment><nibble>;
                } else {
                    $*DECLARATOR_DOCS := nqp::concat($*DECLARATOR_DOCS, nqp::concat("\n"$<attachment><nibble>));
                }
            }
        }
    }
 
    token comment:sym<#|> {
        '#|' \h+ $<attachment>=[\N*]
        {
            unless $*POD_BLOCKS_SEENself.from() } {
                $*POD_BLOCKS_SEENself.from() } := 1;
                if $*DECLARATOR_DOCS eq '' {
                    $*DECLARATOR_DOCS := $<attachment>;
                } else {
                    $*DECLARATOR_DOCS := nqp::concat($*DECLARATOR_DOCS, nqp::concat("\n"$<attachment>));
                }
            }
        }
    }
 
    token comment:sym<#=(...)> {
        '#=' <?opener> <attachment=.quibble(%*LANG<Q>)>
        {
            self.attach_trailing_docs(~$<attachment><nibble>);
        }
    }
 
    token comment:sym<#=> {
        '#=' \h+ $<attachment>=[\N*]
        {
            self.attach_trailing_docs(~$<attachment>);
        }
    }
 
    method attach_leading_docs() {
        if ~$*DOC ne '' {
            my $cont  := Perl6::Pod::serialize_aos(
                [Perl6::Pod::formatted_text(~$*DOC)]
            ).compile_time_value;
            my $block := $*W.add_constant(
                'Pod::Block::Declarator''type_new',
                :nocache, :leading([$cont]),
            );
            $*POD_BLOCK := $block.compile_time_value;
            $*POD_BLOCKS.push($*POD_BLOCK);
        }
        self
    }
 
    method attach_trailing_docs($doc) {
        unless $*POD_BLOCKS_SEENself.from() } {
            $*POD_BLOCKS_SEENself.from() } := 1;
            my $pod_block;
            if $doc ne '' {
                my $cont  := Perl6::Pod::serialize_aos(
                    [Perl6::Pod::formatted_text($doc)]
                ).compile_time_value;
                my $block := $*W.add_constant(
                    'Pod::Block::Declarator''type_new',
                    :nocache, :trailing([$cont]),
                );
                $pod_block := $block.compile_time_value;
            }
            unless $*PRECEDING_DECL =:= Mu {
                Perl6::Pod::document($/, $*PRECEDING_DECL$pod_block, :trailing);
            }
        }
    }
 
    token pod_content_toplevel {
        <pod_block>
    }
 
    proto token pod_content { <...> }
 
    token pod_content:sym<block> {
        <pod_newline>*
        <pod_block>
        <pod_newline>*
    }
 
    # any number of paragraphs of text
    token pod_content:sym<text> {
        <pod_newline>*
        <pod_textcontent>+ % <pod_newline>+
        <pod_newline>*
    }
 
    # not a block, just a directive
    token pod_content:sym<config> {
        <pod_newline>*
        ^^ \h* '=config' \h+ $<type>=\S+ <pod_configuration>
        <pod_newline>+
    }
 
    proto token pod_textcontent { <...> }
 
    # text not being code
    token pod_textcontent:sym<regular> {
        $<spaces>=[ \h* ]
         <?{ $*POD_IN_CODE_BLOCK
             || !$*ALLOW_INLINE_CODE
             || ($<spaces>.to - $<spaces>.from) <= $*VMARGIN }>
 
        $<text> = [
            \h* <!before '=' \w> <pod_string> [ <pod_newline> | $ ]
        ] +
    }
 
    token pod_textcontent:sym<code> {
        $<spaces>=[ \h* ]
        <?{ !$*POD_IN_CODE_BLOCK
            && $*ALLOW_INLINE_CODE
            && ($<spaces>.to - $<spaces>.from) > $*VMARGIN }>
        $<text> = [
            [<!before '=' \w> \N+]+ % [<pod_newline>+ $<spaces>]
        ]
    }
 
    token pod_formatting_code {
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        :my $*POD_IN_FORMATTINGCODE := nqp::getlexdyn('$*POD_IN_FORMATTINGCODE');
        :my $*POD_ANGLE_COUNT := nqp::getlexdyn('$*POD_ANGLE_COUNT');
        <?{ $*POD_ALLOW_FCODES }>
 
        :my $endtag;
        <code=[A..Z]>
        $<begin-tag>=['<'+ <![<]> | '«'] { $*POD_IN_FORMATTINGCODE := 1 }
        <?{
            my $codenum := nqp::ord($<code>.Str) - nqp::ord("A");
            if !($*POD_ALLOW_FCODES +& (2 ** $codenum)) {
                0
            } elsif ~$<begin-tageq '«' {
              $endtag := "»";
              $*POD_ANGLE_COUNT := -1;
              1
            } else {
              my int $ct := nqp::chars($<begin-tag>);
              $endtag := nqp::x(">"$ct);
              my $rv := $*POD_ANGLE_COUNT <= 0 || $*POD_ANGLE_COUNT >= $ct;
              $*POD_ANGLE_COUNT := $ct;
              $rv;
            }
        }>
        {
            if $<code>.Str eq "V" || $<code>.Str eq "C" {
                $*POD_ALLOW_FCODES := 0;
            }
        }
        [ <!{$<codeeq 'E'}>
          $<contents>=[
              <!before $endtag>
              [ <?{$<codene 'L' && $<codene 'D' && $<codene 'X' }> || <!before \s* \| > ]
              <pod_string_character>
          ]*
        ]?
        [
        | <?{$<codeeq 'L'}> \s* \| \s$<meta>=[<!before $endtag>.]+
        | <?{$<codeeq 'X'}> \s* \| \s* ( [$<meta>=[<!before $endtag | <[,;]> >.]+] +% \, ) +% \;
        | <?{$<codeeq 'D'}> \s* \| \s* [$<meta>=[<!before $endtag | \; >.]+] +% \;
        | <?{$<codeeq 'E'}> ( <integer> | $<uni_name>=<[A..Z\s]>+ <![a..z]> || $<html_ref>=<[A..Za..z]>+ ) +% \;
        ]?
        [ $endtag || <.worry: "Pod formatting code $<code> missing endtag '$endtag'."> ]
    }
 
    token pod_balanced_braces {
        <?{ $*POD_IN_FORMATTINGCODE }>
        :my $endtag;
        [
            $<braces>=[
                      || '<'+ <![<]>
                      || '>'+ <![>]>
                      ]
            <?{ nqp::chars($<braces>) < $*POD_ANGLE_COUNT || $*POD_ANGLE_COUNT < 0 }>
          ||
            <?{ $*POD_ANGLE_COUNT >= 1 }>
            $<start>=['<'+] <![<]>
            <?{ nqp::chars($<start>) == $*POD_ANGLE_COUNT || $*POD_ANGLE_COUNT < 0 }>
            {
                $endtag := nqp::x(">", nqp::chars($<start>));
            }
            $<contents>=[ <pod_string_character>*?]
            <!after '>'$<endtag>=[$endtag]
        ]
    }
 
    token pod_string {
        <pod_string_character>+
    }
 
    token pod_string_character {
        <pod_balanced_braces> || <pod_formatting_code> || $<char>=[ \N || [
            <?{ $*POD_IN_FORMATTINGCODE }> \n [
                <?{ $*POD_DELIMITED_CODE_BLOCK }> <!before \h* '=end' \h+ code> ||
                <!before \h* '=' \w>
                ]
            ]
        ]
    }
 
    proto token pod_block { <...> }
 
    token pod_configuration($spaces = '') {
        [ [\n $spaces '=']? \h+ <colonpair> ]*
    }
 
    token pod_block:sym<delimited_comment> {
        ^^
        $<spaces> = [ \h* ]
        '=begin' \h+ 'comment' {}
        <pod_configuration($<spaces>)> <pod_newline>+
        [
         $<pod_content> = [ .*? ]
         ^^ $<spaces'=end' \h+ 'comment' [ <pod_newline> | $ ]
         || {$/.CURSOR.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', type => 'comment', spaces => ~$<spaces>}
        ]
    }
 
    token pod_block:sym<delimited> {
        ^^
        $<spaces> = [ \h* ]
        '=begin'
        [ <?pod_newline>
          <.typed_panic('X::Syntax::Pod::BeginWithoutIdentifier')>
        ]?
        \h+ <!before 'finish'>
        {
            $*VMARGIN    := $<spaces>.to - $<spaces>.from;
        }
        :my $*ALLOW_INLINE_CODE := 0;
        $<type> = [
            <pod_code_parent> { $*ALLOW_INLINE_CODE := 1 }
            || <identifier>
        ]
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        <pod_configuration($<spaces>)> <pod_newline>+
        [
         <pod_content> *
         ^^ $<spaces'=end' \h+ $<type> [ <pod_newline> | $ ]
         || {$/.CURSOR.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', type => ~$<type>, spaces => ~$<spaces>}
        ]
    }
 
 
    token pod_block:sym<delimited_table> {
        ^^
        $<spaces> = [ \h* ]
        '=begin' \h+ 'table' {}
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        <pod_configuration($<spaces>)> <pod_newline>+
        [
         [ $<table_row>=<.table_row_or_blank> ]*
         ^^ \h* '=end' \h+ 'table' [ <pod_newline> | $ ]
         || {$/.CURSOR.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', type => 'table', spaces => ~$<spaces>}
        ]
    }
 
    token pod_block:sym<delimited_code> {
        ^^
        $<spaces> = [ \h* ]
        '=begin' \h+ 'code' {}
        :my $*POD_ALLOW_FCODES  := 0;
        :my $*POD_IN_CODE_BLOCK := 1;
        :my $*POD_DELIMITED_CODE_BLOCK := 1;
        <pod_configuration($<spaces>)> <pod_newline>+
        [
        || <delimited_code_content($<spaces>)>
        || {$/.CURSOR.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', type => 'code', spaces => ~$<spaces>}
        ]
    }
 
    token delimited_code_content($spaces = '') {
        ^^
        (
        | $spaces
            <!before '=end' \h+ 'code' [ <pod_newline> | $ ]>
            <pod_string>**0..1 <pod_newline>
        | <pod_newline>
        )*
        $spaces '=end' \h+ 'code' [ <pod_newline> | $ ]
    }
 
    token table_row {
        \h* <!before '=' \w> \N+ [ \n | $ ]
    }
 
    token table_row_or_blank {
        <.table_row> | [\h* <!before '=' \w> \n ]
    }
 
    token pod_block:sym<finish> {
        ^^ \h*
        [
            | '=begin' \h+ 'finish' <pod_newline>
            | '=for'   \h+ 'finish' <pod_newline>
            | '=finish' <pod_newline>
        ]
        .*
    }
 
    token pod_block:sym<paragraph> {
        ^^
        $<spaces> = [ \h* ]
        '=for' \h+ <!before 'finish'>
        {
            $*VMARGIN := $<spaces>.to - $<spaces>.from;
        }
        :my $*ALLOW_INLINE_CODE := 0;
        $<type> = [
            <pod_code_parent> { $*ALLOW_INLINE_CODE := 1 }
            || <identifier>
        ]
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        <pod_configuration($<spaces>)> <pod_newline>
        <pod_content=.pod_textcontent>**0..1
    }
 
    token pod_block:sym<paragraph_comment> {
        ^^
        $<spaces> = [ \h* ]
        '=for' \h+ 'comment' {}
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        <pod_configuration($<spaces>)> <pod_newline>
        $<pod_content> = [ \h* <!before '=' \w> \N+ [ \n | $ ] ]*
    }
 
    token pod_block:sym<paragraph_table> {
        ^^
        $<spaces> = [ \h* ]
        '=for' \h+ 'table' {}
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        <pod_configuration($<spaces>)> <pod_newline>
        [ <!before \h* \n> <table_row>]*
    }
 
    token pod_block:sym<paragraph_code> {
        ^^
        $<spaces> = [ \h* ]
        '=for' \h+ 'code' {}
        :my $*POD_ALLOW_FCODES := 0;
        :my $*POD_IN_CODE_BLOCK := 1;
        <pod_configuration($<spaces>)> <pod_newline>
        [ <!before \h* '=' \w> <pod_line> ]*
    }
 
    token pod_block:sym<abbreviated> {
        ^^
        $<spaces> = [ \h* ]
        '=' <!before begin || end || for || finish || config>
        {
            $*VMARGIN := $<spaces>.to - $<spaces>.from;
        }
        :my $*ALLOW_INLINE_CODE := 0;
        $<type> = [
            <pod_code_parent> { $*ALLOW_INLINE_CODE := 1 }
            || <identifier>
        ]
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        [\h*\n|\h+]
        <pod_content=.pod_textcontent>**0..1
    }
 
    token pod_block:sym<abbreviated_comment> {
        ^^
        $<spaces> = [ \h* ]
        '=comment' {}
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        [\h*\n|\h+]
        $<pod_content> = [ \h* <!before '=' \w> \N+ [ \n | $ ] ]*
    }
 
    token pod_block:sym<abbreviated_table> {
        ^^
        $<spaces> = [ \h* ]
        '=table' {}
        :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES');
        <pod_newline>
        [ <!before \h* \n> <table_row>]*
    }
 
    token pod_block:sym<abbreviated_code> {
        ^^
        $<spaces> = [ \h* ]
        '=code' {}
        :my $*POD_ALLOW_FCODES  := 0;
        :my $*POD_IN_CODE_BLOCK := 1;
        [\h*\n|\h+]
        [ <!before \h* '=' \w> <pod_line> ]*
    }
 
    token pod_line { <pod_string>**1 [ <pod_newline> | $ ] }
 
    token pod_newline {
        \h* \n
    }
 
    token pod_code_parent {
        [
        | [ 'pod' | 'item' \d* | 'nested' | 'defn' | 'finish' ]
        | <upper>+
        ]
        <![\w]>
    }
 
    token install_doc_phaser { <?> }
 
    token vnum {
        \d+ | '*'
    }
 
    token version {
        <?before v\d+> 'v' $<vstr>=[<vnum>+ % '.' '+'?]
        <!before '-'|\'> # cheat because of LTM fail
    }
 
    ## Top-level rules
 
    token comp_unit {
        # From STD.pm.
        :my $*LEFTSIGIL;                           # sigil of LHS for item vs list assignment
        :my $*SCOPE := '';                         # which scope declarator we're under
        :my $*MULTINESS := '';                     # which multi declarator we're under
        :my $*QSIGIL := '';                        # sigil of current interpolation
        :my $*IN_META := '';                       # parsing a metaoperator like [..]
        :my $*IN_REDUCE := 0;                      # attempting to parse an [op] construct
        :my $*IN_DECL;                             # what declaration we're in
        :my $*HAS_SELF := '';                      # is 'self' available? (for $.foo style calls)
        :my $*MONKEY_TYPING := 0;                  # whether augment/supersede are allowed
        :my $*begin_compunit := 1;                 # whether we're at start of a compilation unit
        :my $*DECLARAND;                           # the current thingy we're declaring, and subject of traits
        :my $*METHODTYPE;                          # the current type of method we're in, if any
        :my $*PKGDECL;                             # what type of package we're in, if any
        :my %*MYSTERY;                             # names we assume may be post-declared functions
        :my $*BORG;                                # who gets blamed for a missing block
        :my $*CCSTATE := '';
        :my $*STRICT;
        :my $*INVOCANT_OK := 0;
        :my $*INVOCANT;
        
        # Error related. There are three levels: worry (just a warning), sorry
        # (fatal but not immediately so) and panic (immediately deadly). There
        # is a limit on the number of sorrows also. Unlike STD, which emits the
        # textual messages as it goes, we keep track of the exception objects
        # and, if needed, make a compositite exception group.
        :my @*WORRIES;                             # exception objects resulting from worry
        :my @*SORROWS;                             # exception objects resulting from sorry
        :my $*SORRY_LIMIT := 10;                   # when sorrow turns to panic
 
        # Extras.
        :my %*METAOPGEN;                           # hash of generated metaops
        :my %*HANDLERS;                            # block exception handlers
        :my $*IMPLICIT;                            # whether we allow an implicit param
        :my $*FORBID_PIR := 0;                     # whether pir::op and Q:PIR { } are disallowed
        :my $*HAS_YOU_ARE_HERE := 0;               # whether {YOU_ARE_HERE} has shown up
        :my $*OFTYPE;
        :my $*VMARGIN    := 0;                     # pod stuff
        :my $*ALLOW_INLINE_CODE := 0;              # pod stuff
        :my $*POD_IN_CODE_BLOCK := 0;              # pod stuff
        :my $*POD_IN_FORMATTINGCODE := 0;          # pod stuff
        :my $*POD_ALLOW_FCODES := 0b11111111111111111111111111; # allow which fcodes?
        :my $*POD_ANGLE_COUNT := 0;                # pod stuff
        :my $*IN_REGEX_ASSERTION := 0;
        :my $*SOFT := 0;                           # is the soft pragma in effect
        :my $*IN_PROTO := 0;                       # are we inside a proto?
        
        # Various interesting scopes we'd like to keep to hand.
        :my $*GLOBALish;
        :my $*PACKAGE;
        :my $*SETTING;
        :my $*UNIT;
        :my $*UNIT_OUTER;
        :my $*EXPORT;
        # stack of packages, which the 'is export' needs
        :my @*PACKAGES := [];
 
        # A place for Pod
        :my $*POD_BLOCKS := [];
        :my $*POD_BLOCKS_SEEN := {};
        :my $*POD_PAST;
        :my $*DECLARATOR_DOCS;
        :my $*PRECEDING_DECL# for #= comments
        :my $*PRECEDING_DECL_LINE := -1; # XXX update this when I see another comment like it?
        
        # Quasis and unquotes
        :my $*IN_QUASI := 0;                       # whether we're currently in a quasi block
        :my $*MAIN := 'MAIN';
 
        # performance improvement stuff
        :my $*FAKE_INFIX_FOUND := 0;
 
        # Setting loading and symbol setup.
        {
            # Create unit outer (where we assemble any lexicals accumulated
            # from e.g. REPL) and the real UNIT.
            $*UNIT_OUTER := $*W.push_lexpad($/);
            $*UNIT := $*W.push_lexpad($/);
            
            # If we already have a specified outer context, then that's
            # our setting. Otherwise, load one.
            my $have_outer := nqp::defined(%*COMPILING<%?OPTIONS><outer_ctx>);
            if $have_outer {
                $*UNIT.annotate('IN_DECL''eval');
            }
            else {
                $*SETTING := $*W.load_setting($/, %*COMPILING<%?OPTIONS><setting> // 'CORE');
                $*UNIT.annotate('IN_DECL''mainline');
            }
            $/.CURSOR.unitstart();
            try {
                my $EXPORTHOW := $*W.find_symbol(['EXPORTHOW']);
                for $*W.stash_hash($EXPORTHOW) {
                    %*HOW{$_.key} := $_.value;
                }
            }
            
            # Create GLOBAL(ish), unless we were given one.
            if nqp::existskey(%*COMPILING<%?OPTIONS>, 'global') {
                $*GLOBALish := %*COMPILING<%?OPTIONS><global>;
            }
            elsif $have_outer && $*UNIT_OUTER.symbol('GLOBALish') {
                $*GLOBALish := $*W.force_value($*UNIT_OUTER.symbol('GLOBALish'), 'GLOBALish', 1);
            }
            else {
                $*GLOBALish := $*W.pkg_create_mo($/, %*HOW<package>, :name('GLOBAL'));
                $*W.pkg_compose($*GLOBALish);
            }
                
            # Create or pull in existing EXPORT.
            if $have_outer && $*UNIT_OUTER.symbol('EXPORT') {
                $*EXPORT := $*W.force_value($*UNIT_OUTER.symbol('EXPORT'), 'EXPORT', 1);
            }
            else {
                $*EXPORT := $*W.pkg_create_mo($/, %*HOW<package>, :name('EXPORT'));
                $*W.pkg_compose($*EXPORT);
            }
            
            # If there's a self in scope, set $*HAS_SELF.
            if $have_outer && $*UNIT_OUTER.symbol('self') {
                $*HAS_SELF := 'complete';
            }
                
            # Take current package from outer context if any, otherwise for a
            # fresh compilation unit we start in GLOBAL.
            if $have_outer && $*UNIT_OUTER.symbol('$?PACKAGE') {
                $*PACKAGE := $*W.force_value($*UNIT_OUTER.symbol('$?PACKAGE'), '$?PACKAGE', 1);
            }
            else {
                $*PACKAGE := $*GLOBALish;
            }
            
            # If we're eval'ing in the context of a %?LANG, set up our own
            # %*LANG based on it.
            if $have_outer && $*UNIT_OUTER.symbol('%?LANG') {
                for $*W.force_value($*UNIT_OUTER.symbol('%?LANG'), '%?LANG', 1).FLATTENABLE_HASH() {
                    %*LANG{$_.key} := $_.value;
                }
            }
            if $have_outer && $*UNIT_OUTER.symbol('$*MAIN') {
                $*MAIN := $*W.force_value($*UNIT_OUTER.symbol('$*MAIN'), '$*MAIN', 1);
            }
            if $have_outer && $*UNIT_OUTER.symbol('$?STRICT') {
                $*STRICT := $*W.force_value($*UNIT_OUTER.symbol('$*STRICT'), '$*STRICT', 1);
            }
            else {
                my $FILES := nqp::getlexdyn('$?FILES');
                $*STRICT := !nqp::isnull($FILES) && $FILES ne '-e';
            }
 
            # Install unless we've no setting, in which case we've likely no
            # static lexpad class yet either. Also, UNIT needs a code object.
            unless %*COMPILING<%?OPTIONS><setting> eq 'NULL' {
                $*W.install_lexical_symbol($*UNIT'GLOBALish'$*GLOBALish);
                $*W.install_lexical_symbol($*UNIT'EXPORT'$*EXPORT);
                $*W.install_lexical_symbol($*UNIT'$?PACKAGE'$*PACKAGE);
                $*W.install_lexical_symbol($*UNIT'::?PACKAGE'$*PACKAGE);
                $*DECLARAND := $*W.stub_code_object('Block');
            }
            my $M := %*COMPILING<%?OPTIONS><M>;
            if nqp::defined($M) {
                for nqp::islist($M) ?? $M !! [$M] -> $longname {
                    my $module := $*W.load_module($/, $longname, {}, $*GLOBALish);
                    do_import($/, $module$longname);
                    $/.CURSOR.import_EXPORTHOW($/, $module);
                }
            }
        }
        
        <.finishpad>
        <.bom>?
        <statementlist=.FOREIGN_LANG($*MAIN'statementlist', 1)>
 
        <.install_doc_phaser>
        
        [ $ || <.typed_panic: 'X::Syntax::Confused'> ]
        
        {
            # Emit any errors/worries.
            self.explain_mystery();
            if @*SORROWS {
                if +@*SORROWS == 1 && !@*WORRIES {
                    @*SORROWS[0].throw()
                }
                else {
                    $*W.group_exception(@*SORROWS.pop).throw();
                }
            }
            if @*WORRIES {
                nqp::printfh(nqp::getstderr(), $*W.group_exception().gist());
            }
        
            # Install POD-related variables.
            $*POD_PAST := $*W.add_constant(
                'Array''type_new', :nocache, |$*POD_BLOCKS
            );
            $*W.install_lexical_symbol(
                $*UNIT'$=pod'$*POD_PAST.compile_time_value
            );
            
            # Tag UNIT with a magical lexical. Also if we're compiling CORE,
            # give it such a tag too.
            if %*COMPILING<%?OPTIONS><setting> eq 'NULL' {
                my $marker := $*W.pkg_create_mo($/, %*HOW<package>, :name('!CORE_MARKER'));
                $marker.HOW.compose($marker);
                $*W.install_lexical_symbol($*UNIT'!CORE_MARKER'$marker);
            }
            else {
                my $marker := $*W.pkg_create_mo($/, %*HOW<package>, :name('!UNIT_MARKER'));
                $marker.HOW.compose($marker);
                $*W.install_lexical_symbol($*UNIT'!UNIT_MARKER'$marker);
            }
        }
        
        # CHECK time.
        { $*W.CHECK(); }
    }
    
    method import_EXPORTHOW($/, $UNIT) {    
        if nqp::existskey($UNIT'EXPORTHOW') {
            for $*W.stash_hash($UNIT<EXPORTHOW>) {
                my str $key := $_.key;
                if $key eq 'SUPERSEDE' {
                    my %SUPERSEDE := $*W.stash_hash($_.value);
                    for %SUPERSEDE {
                        my str $pdecl := $_.key;
                        my $meta  := nqp::decont($_.value);
                        unless nqp::existskey(%*HOW$pdecl) {
                            $/.CURSOR.typed_panic('X::EXPORTHOW::NothingToSupersede',
                                declarator => $pdecl);
                        }
                        if nqp::existskey(%*HOWUSE$pdecl) {
                            $/.CURSOR.typed_panic('X::EXPORTHOW::Conflict',
                                declarator => $pdecl, directive => $key);
                        }
                        %*HOW{$pdecl}    := $meta;
                        %*HOWUSE{$pdecl} := nqp::hash('SUPERSEDE'$meta);
                    }
                }
                elsif $key eq 'DECLARE' {
                    my %DECLARE := $*W.stash_hash($_.value);
                    for %DECLARE {
                        my str $pdecl := $_.key;
                        my $meta  := nqp::decont($_.value);
                        if nqp::existskey(%*HOW$pdecl) {
                            $/.CURSOR.typed_panic('X::EXPORTHOW::Conflict',
                                declarator => $pdecl, directive => $key);
                        }
                        %*HOW{$pdecl}    := $meta;
                        %*HOWUSE{$pdecl} := nqp::hash('DECLARE'$meta);
                        self.add_package_declarator($pdecl);
                    }
                }
                elsif $key eq 'COMPOSE' {
                    my %COMPOSE := $*W.stash_hash($_.value);
                    $/.CURSOR.NYI('EXPORTHOW::COMPOSE');
                }
                else {
                    if $key eq nqp::lc($key) {
                        # Support legacy API, which behaves like an unchecked
                        # supersede.
                        # XXX Can give deprecation warning in the future, remove
                        # before 6.0.0.
                        %*HOW{$key} := nqp::decont($_.value);
                    }
                    else {
                        $/.CURSOR.typed_panic('X::EXPORTHOW::InvalidDirective', directive => $key);
                    }
                }
            }
        }
    }
 
    method add_package_declarator(str $pdecl) {
        # Compute name of grammar/action entry.
        my $canname := 'package_declarator:sym<' ~ $pdecl ~ '>';
 
        # Add to grammar if needed.
        unless nqp::can(self$canname) {
            my role PackageDeclarator[$meth_name$declarator] {
                token ::($meth_name) {
                    :my $*OUTERPACKAGE := $*PACKAGE;
                    :my $*PKGDECL := $declarator;
                    :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
                    $<sym>=[$declarator] <.end_keyword> <package_def>
                }
            }
            self.HOW.mixin(self, PackageDeclarator.HOW.curry(PackageDeclarator, $canname$pdecl));
 
            # This also becomes the current MAIN. Also place it in %?LANG.
            %*LANG<MAIN> := self.WHAT;
            $*W.install_lexical_symbol($*W.cur_lexpad(), '%?LANG'$*W.p6ize_recursive(%*LANG));
        }
 
        # Add action method if needed.
        unless nqp::can($*ACTIONS$canname) {
            my role PackageDeclaratorAction[$meth] {
                method ::($meth)($/) {
                    make $<package_def>.ast;
                }
            };
            %*LANG<MAIN-actions> := $*ACTIONS.HOW.mixin($*ACTIONS,
                PackageDeclaratorAction.HOW.curry(PackageDeclaratorAction, $canname));
        }
    }
 
    rule statementlist($*statement_level = 0) {
        :my %*LANG   := self.shallow_copy(nqp::getlexdyn('%*LANG'));
        :my %*HOW    := self.shallow_copy(nqp::getlexdyn('%*HOW'));
        :my %*HOWUSE := nqp::hash();
        :my $*STRICT := nqp::getlexdyn('$*STRICT');
        :dba('statement list')
        ''
        [
        | $
        | <?before <[\)\]\}]>>
        | [ <statement> <.eat_terminator> ]*
        ]
    }
 
    method shallow_copy(%hash) {
        my %result;
        for %hash {
            %result{$_.key} := $_.value;
        }
        %result
    }
 
    rule semilist {
        :dba('lol composer')
        ''
        [
        | <?before <[)\]}]> >
        | [<statement><.eat_terminator> ]*
        ]
    }
 
    rule sequence {
        :dba('sequence of statements')
        ''
        [
        | <?before <[)\]}]> >
        | [<statement><.eat_terminator> ]*
        ]
    }
 
    token label {
        <identifier> ':' <?[\s]> <.ws>
        {
            $*LABEL := ~$<identifier>;
            if $*W.already_declared('my'$*PACKAGE$*W.cur_lexpad(), [$*LABEL]) {
                $*W.throw($/, ['X''Redeclaration'], symbol => $*LABEL);
            }
            my str $orig      := self.orig();
            my int $total     := nqp::chars($orig);
            my int $from      := self.MATCH.from();
            my int $to        := self.MATCH.to() + nqp::chars($*LABEL);
            my int $line      := HLL::Compiler.lineof($origself.from());
            my str $prematch  := nqp::substr($orig$from > 20 ?? $from - 20 !! 0, $from > 20 ?? 20 !! $from);
            my str $postmatch := nqp::substr($orig$to, 20);
            my $label     := $*W.find_symbol(['Label']).new( :name($*LABEL), :$line, :$prematch, :$postmatch );
            $*W.add_object($label);
            $*W.install_lexical_symbol($*W.cur_lexpad(), $*LABEL$label);
        }
    }
 
    token statement($*LABEL = '') {
        :my $*QSIGIL := '';
        :my $*SCOPE := '';
        :my $*ACTIONS := %*LANG<MAIN-actions>;
        <!before <[\])}]> | $ >
        <!stopper>
        <!!{ nqp::rebless($/.CURSOR, %*LANG<MAIN>) }>
        [
        | <label> <statement($*LABEL)> { $*LABEL := '' if $*LABEL }
        | <statement_control>
        | <EXPR> :dba('statement end')
            [
            || <?MARKED('endstmt')>
            || :dba('statement modifier') <.ws> <statement_mod_cond> <statement_mod_loop>?
            || :dba('statement modifier loop') <.ws> <statement_mod_loop>
                {
                    my $sp := $<EXPR><statement_prefix>;
                    if $sp && $sp<sym> eq 'do' {
                        my $s := $<statement_mod_loop><sym>;
                        $/.CURSOR.obs("do..." ~ $s"repeat...while or repeat...until");
                    }
                }
            ]?
        | <?[;]>
        | <?stopper>
        | {} <.panic: "Bogus statement">
        ]
    }
 
    token eat_terminator {
        || ';'
        || <?MARKED('endstmt')> <.ws>
        || <?before ')' | ']' | '}' >
        || $
        || <?stopper>
        || { $/.CURSOR.typed_panic( 'X::Syntax::Confused', reason => "Missing semicolon." ) }
    }
 
    token xblock($*IMPLICIT = 0) {
        :my $*GOAL := '{';
        :my $*BORG := {};
        <EXPR> <.ws> <pblock($*IMPLICIT)>
    }
 
    token pblock($*IMPLICIT = 0) {
        :my $*DECLARAND := $*W.stub_code_object('Block');
        :dba('parameterized block')
        [
        | <lambda>
            <.newpad>
            :my $*SCOPE := 'my';
            :my $*GOAL := '{';
            <signature>
            <blockoid>
        | <?[{]>
            <.newpad>
            <blockoid>
        || {
               if nqp::ishash($*BORG) && $*BORG<block> {
                   my $pos := $/.CURSOR.pos;
                   if $*BORG<name> {
                        $/.CURSOR.'!clear_highwater'();
                        $/.CURSOR.'!cursor_pos'($*BORG<block>.CURSOR.pos);
                        $/.CURSOR.typed_sorry('X::Syntax::BlockGobbled', what => ~$*BORG<name>);
                        $/.CURSOR.'!cursor_pos'($pos);
                        $/.CURSOR.missing("block (apparently taken by '" ~ $*BORG<name> ~ "')");
                   } else {
                        $/.CURSOR.'!clear_highwater'();
                        $/.CURSOR.'!cursor_pos'($*BORG<block>.CURSOR.pos);
                        $/.CURSOR.typed_sorry('X::Syntax::BlockGobbled');
                        $/.CURSOR.'!cursor_pos'($pos);
                        $/.CURSOR.missing("block (apparently taken by expression)");
                   }
               } elsif %*MYSTERY {
                   $/.CURSOR.missing("block (taken by some undeclared routine?)");
               } else {
                   $/.CURSOR.missing("block");
               }
           }
        ]
    }
 
    token lambda { '->' | '<->' }
 
    token block($*IMPLICIT = 0) {
        :my $*DECLARAND := $*W.stub_code_object('Block');
        :dba('scoped block')
        [ <?[{]> || <.missing: 'block'>]
        <.newpad>
        <blockoid>
    }
 
    token blockoid {
        :my $*CURPAD;
        :my %*HANDLERS;
        <.finishpad>
        [
        | '{YOU_ARE_HERE}' <you_are_here>
        | :dba('block''{' ~ '}' <statementlist(1)> <?ENDSTMT>
        | <?terminator> { $*W.throw($/, 'X::Syntax::Missing', what =>'block') }
        | <?> { $*W.throw($/, 'X::Syntax::Missing', what => 'block') }
        ]
        { $*CURPAD := $*W.pop_lexpad() }
    }
 
    token unitstart { <?> }
    token you_are_here { <?> }
    token newpad { <?> { $*W.push_lexpad($/) } }
    token finishpad { <?> }
 
    token bom { \xFEFF }
 
    proto token terminator { <...> }
 
    token terminator:sym<;> { <?[;]> }
    token terminator:sym<)> { <?[)]> }
    token terminator:sym<]> { <?[\]]> }
    token terminator:sym<}> { <?[}]> }
    token terminator:sym<ang> { <?[>]> <?{ $*IN_REGEX_ASSERTION }> }
    token terminator:sym<if>     { 'if'     <.end_keyword> }
    token terminator:sym<unless> { 'unless' <.end_keyword> }
    token terminator:sym<while>  { 'while'  <.end_keyword> }
    token terminator:sym<until>  { 'until'  <.end_keyword> }
    token terminator:sym<for>    { 'for'    <.end_keyword> }
    token terminator:sym<given>  { 'given'  <.end_keyword> }
    token terminator:sym<when>   { 'when'   <.end_keyword> }
    token terminator:sym<arrow>  { '-->' }
    
 
    token stdstopper {
        [
        || <?MARKED('endstmt')> <?>
        || [
           | <?terminator>
           | $
           ]
       ]
    }
 
    ## Statement control
 
    proto rule statement_control { <...> }
 
    rule statement_control:sym<if> {
        <sym><.end_keyword> {}
        <xblock>
        [
            [
            | 'else'\h*'if' <.typed_panic: 'X::Syntax::Malformed::Elsif'>
            | 'elif' { $/.CURSOR.typed_panic('X::Syntax::Malformed::Elsif', what => "elif") }
            | 'elsif'\s <xblock>
            ]
        ]*
        [ 'else'\s <else=.pblock> ]?
    }
 
    rule statement_control:sym<unless> {
        <sym><.end_keyword> {}
        <xblock>
        [ <!before 'else'> || <.typed_panic: 'X::Syntax::UnlessElse'> ]
    }
 
    rule statement_control:sym<while> {
        $<sym>=[while|until]<.end_keyword> {}
        <xblock>
    }
 
    rule statement_control:sym<repeat> {
        <sym><.end_keyword> {}
        [
        | $<wu>=[while|until]\s <xblock>
        | <pblock>
          [$<wu>=['while'|'until']\s || <.missing('"while" or "until"')>]
          <EXPR>
        ]
    }
 
    rule statement_control:sym<for> {
        <sym><.end_keyword> {}
        [ <?before 'my''$'\w+ '(' >
            <.typed_panic: 'X::Syntax::P5'> ]?
        [ <?before '(' <.EXPR>? ';' <.EXPR>? ';' <.EXPR>? ')' >
            <.obs('C-style "for (;;)" loop''"loop (;;)"')> ]?
        <xblock(1)>
    }
 
    rule statement_control:sym<foreach> {
        <sym><.end_keyword> <.obs("'foreach'""'for'")>
    }
 
    token statement_control:sym<loop> {
        <sym><.end_keyword>
        [ <?[({]> <.sorry: "Whitespace required after 'loop'"> ]?
        :s''
        [ '('
            <e1=.EXPR>? ';'
            <e2=.EXPR>? ';'
            <e3=.EXPR>?
        ')' ]?
        <block>
    }
 
    rule statement_control:sym<need> {
        <sym>
        [
        | <version>
        | <module_name>
        ] +% ','
        {
            for $<module_name> {
                my $lnd  := $*W.dissect_longname($_<longname>);
                my $name := $lnd.name;
                my %cp   := $lnd.colonpairs_hash('need');
                $*W.load_module($/, $name%cp$*GLOBALish);
            }
        }
    }
 
    token statement_control:sym<import> {
        <sym> <.ws>
        <module_name> [ <.spacey> <arglist> ]? <.ws>
        :my $*HAS_SELF := '';
        {
            my $longname := $*W.dissect_longname($<module_name><longname>);
            my $module;
            my $found := 0;
            try { $module := $*W.find_symbol($longname.components()); $found := 1; }
            if $found {
                # todo: fix arglist
                my $arglist;
                if $<arglist> {
                    $arglist := $*W.compile_time_evaluate($/, $<arglist><EXPR>.ast);
                    $arglist := nqp::getattr($arglist.list.eager,
                            $*W.find_symbol(['List']), '$!items');
                }
                do_import($/, $module.WHO$longname.name$arglist);
            }
            else {
                $/.CURSOR.panic("Could not find module " ~ ~$<module_name> ~
                    " to import symbols from");
            }
        }
    }
 
    token statement_control:sym<no> {
        :my $longname;
        <sym> <.ws>
        [
        | <module_name>
            {
                $longname := $<module_name><longname>;
 
                if $longname.Str eq 'strict' {
                    # Turn on lax mode.
                    $*STRICT := 0;
                }
                else {
                    nqp::die("Unknown pragma '$longname'");
                }
            }
        ]
        <.ws>
    }
 
    token statement_control:sym<use> {
        :my $longname;
        :my $*IN_DECL := 'use';
        :my $*HAS_SELF := '';
        :my $*SCOPE   := 'use';
        :my $OLD_MAIN := ~$*MAIN;
        $<doc>=[ 'DOC' \h+ ]**0..1
        <sym> <.ws>
        [
        | <version> [ <?{ ~$<version><vnum>[0] eq '5' }> {
                        my $module := $*W.load_module($/, 'Perl5', {}, $*GLOBALish);
                        do_import($/, $module'Perl5');
                        $/.CURSOR.import_EXPORTHOW($/, $module);
                    } ]?
                    [ <?{ ~$<version><vnum>[0] eq '6' }> {
                        $*MAIN   := 'MAIN';
                        $*STRICT := 1 if $*begin_compunit;
                    } ]?
        | <module_name>
            {
                $longname := $<module_name><longname>;
                
                # Some modules are handled in the actions are just turn on a
                # setting of some kind.
                if $longname.Str eq 'MONKEY_TYPING' {
                    $*MONKEY_TYPING := 1;
                    $longname := "";
                }
                elsif $longname.Str eq 'soft' {
                    # This is an approximation; need to pay attention to argument
                    # list really.
                    $*SOFT := 1;
                    $longname := "";
                }
                elsif $longname.Str eq 'strict' {
                    # Turn off lax mode.
                    $*STRICT  := 1;
                    $longname := "";
                }
                elsif $longname.Str eq 'FORBID_PIR' ||
                      $longname.Str eq 'Devel::Trace' ||
                      $longname.Str eq 'fatal' {
                    $longname := "";
                }
            }
            [
            || <.spacey> <arglist> <?{ $<arglist><EXPR> }>
                {
                    my $lnd     := $*W.dissect_longname($longname);
                    my $name    := $lnd.name;
                    my %cp      := $lnd.colonpairs_hash('use');
                    my $arglist := $*W.compile_time_evaluate($/,
                            $<arglist><EXPR>.ast);
                    $arglist    := nqp::getattr($arglist.list.eager,
                            $*W.find_symbol(['List']), '$!items');
                    my $module  := $*W.load_module($/, $name%cp$*GLOBALish);
                    do_import($/, $module$name$arglist);
                    $/.CURSOR.import_EXPORTHOW($/, $module);
                }
            || { 
                    unless ~$<doc> && !%*COMPILING<%?OPTIONS><doc> {
                        if $longname {
                            my $lnd    := $*W.dissect_longname($longname);
                            my $name   := $lnd.name;
                            my %cp     := $lnd.colonpairs_hash('use');
                            my $module := $*W.load_module($/, $name%cp$*GLOBALish);
                            do_import($/, $module$name);
                            $/.CURSOR.import_EXPORTHOW($/, $module);
                        }
                    }
                }
            ]
        ]
        [ <?{ $*MAIN ne $OLD_MAIN }>
          <.eat_terminator>
          <statementlist=.FOREIGN_LANG($*MAIN'statementlist', 1)>
        || <?> ]
        <.ws>
    }
 
    # This is like HLL::Grammar.LANG but it allows to call a token of a Perl 6 level grammar.
    method FOREIGN_LANG($lang$regex, *@args) {
        if nqp::istype(%*LANG{$lang}, NQPCursor) {
            return self.LANG($lang$regex@args)
        }
        else {
            my $Str := $*W.find_symbol(['Str']);
            my $lang_cursor := %*LANG{$lang}.'!cursor_init'($Str.new( :value(self.orig())), :p(self.pos()));
            if self.HOW.traced(self) {
                $lang_cursor.HOW.trace-on($lang_cursorself.HOW.trace_depth(self));
            }
            my $*ACTIONS := %*LANG{$lang ~ '-actions'};
            my $ret := $lang_cursor."$regex"(|@args);
 
            # Build up something NQP-levelish we can return.
            my $new := NQPCursor.'!cursor_init'(self.orig(), :p(self.pos()), :shared(self.'!shared'()));
            my $p6cursor := $*W.find_symbol(['Cursor']);
            nqp::bindattr_i($new, NQPCursor, '$!from',  nqp::getattr_i($ret$p6cursor'$!from'));
            nqp::bindattr_i($new, NQPCursor, '$!pos',   nqp::getattr_i($ret$p6cursor'$!pos'));
            nqp::bindattr($new,   NQPCursor, '$!name',  nqp::getattr($ret,   $p6cursor'$!name'));
 
            my $match := nqp::create(NQPMatch);
            nqp::bindattr($match, NQPMatch, '$!made', nqp::getattr($ret$p6cursor'$!made'));
            nqp::bindattr($new, NQPCursor, '$!match'$match);
            $new;
        }
    }
 
    sub do_import($/, $module$package_source_name$arglist?) {
        if nqp::existskey($module'EXPORT') {
            my $EXPORT := $*W.stash_hash($module<EXPORT>);
            my @to_import := ['MANDATORY'];
            my @positional_imports := [];
            if nqp::defined($arglist) {
                my $Pair := $*W.find_symbol(['Pair']);
                for $arglist -> $tag {
                    if nqp::istype($tag$Pair) {
                        $tag := nqp::unbox_s($tag.key);
                        if nqp::existskey($EXPORT$tag) {
                            $*W.import($/, $*W.stash_hash($EXPORT{$tag}), $package_source_name);
                        }
                        else {
                            nqp::die("Error while importing from '$package_source_name': no such tag '$tag'");
                        }
                    }
                    else {
                        nqp::push(@positional_imports$tag);
                    }
                }
            }
            else {
                nqp::push(@to_import'DEFAULT');
            }
            for @to_import -> $tag {
                if nqp::existskey($EXPORT$tag) {
                    $*W.import($/, $*W.stash_hash($EXPORT{$tag}), $package_source_name);
                }
            }
            if nqp::existskey($module'&EXPORT') {
                my $result := $module<&EXPORT>(|@positional_imports);
                my $EnumMap := $*W.find_symbol(['EnumMap']);
                if nqp::istype($result$EnumMap) {
                    my $storage := $result.hash.FLATTENABLE_HASH();
                    $*W.import($/, $storage$package_source_name);
                }
                else {
                    nqp::die("&EXPORT sub did not return an EnumMap");
                }
            }
            else {
                if +@positional_imports {
                    nqp::die("Error while importing from '$package_source_name': no EXPORT sub, but you provided positional argument in the 'use' statement");
                }
            }
        }
    }
 
    rule statement_control:sym<require> {
        <sym>
        [
        | <module_name>
        | <file=.variable>
        | <!sigil> <file=.term>
        ]
        <EXPR>?
    }
 
    rule statement_control:sym<given> {
        <sym><.end_keyword> <xblock(1)>
    }
    rule statement_control:sym<when> {
        <sym><.end_keyword> <xblock>
    }
    rule statement_control:sym<default> {
        <sym><.end_keyword> <block>
    }
 
    rule statement_control:sym<CATCH> {<sym> <block(1)> }
    rule statement_control:sym<CONTROL> {<sym> <block(1)> }
 
    proto token statement_prefix { <...> }
    token statement_prefix:sym<BEGIN>   { <sym> <blorst> }
    token statement_prefix:sym<COMPOSE> { <sym> <blorst> }
    token statement_prefix:sym<TEMP>    { <sym> <blorst> }
    token statement_prefix:sym<CHECK>   { <sym> <blorst> }
    token statement_prefix:sym<INIT>    { <sym> <blorst> }
    token statement_prefix:sym<ENTER>   { <sym> <blorst> }
    token statement_prefix:sym<FIRST>   { <sym> <blorst> }
    
    token statement_prefix:sym<END>   { <sym> <blorst> }
    token statement_prefix:sym<LEAVE> { <sym> <blorst> }
    token statement_prefix:sym<KEEP>  { <sym> <blorst> }
    token statement_prefix:sym<UNDO>  { <sym> <blorst> }
    token statement_prefix:sym<NEXT>  { <sym> <blorst> }
    token statement_prefix:sym<LAST>  { <sym> <blorst> }
    token statement_prefix:sym<PRE>   { <sym> <blorst> }
    token statement_prefix:sym<POST>  { <sym> <blorst> }
    
    token statement_prefix:sym<eager> { <sym> <blorst> }
    token statement_prefix:sym<lazy>  { <sym> <blorst> }
    token statement_prefix:sym<sink>  { <sym> <blorst> }
    token statement_prefix:sym<try>   { <sym> <blorst> }
    token statement_prefix:sym<gather>{ <sym> <blorst> }
    token statement_prefix:sym<once>  { <sym> <blorst> }
    token statement_prefix:sym<do>    { <sym> <blorst> }
    token statement_prefix:sym<DOC>   {
        <sym> \s <.ws> $<phase>=['BEGIN' || 'CHECK' || 'INIT']
        <blorst>
    }
 
    token blorst {
        \s <.ws> [ <?[{]> <block> | <![;]> <statement> || <.missing: 'block or statement'> ]
    }
 
    ## Statement modifiers
 
    proto rule statement_mod_cond { <...> }
 
    token modifier_expr { <EXPR> }
 
    rule statement_mod_cond:sym<if>     { <sym> <modifier_expr> }
    rule statement_mod_cond:sym<unless> { <sym> <modifier_expr> }
    rule statement_mod_cond:sym<when>   { <sym> <modifier_expr> }
 
    proto rule statement_mod_loop { <...> }
 
    rule statement_mod_loop:sym<while> { <sym> <smexpr=.EXPR> }
    rule statement_mod_loop:sym<until> { <sym> <smexpr=.EXPR> }
    rule statement_mod_loop:sym<for>   { <sym> <smexpr=.EXPR> }
    rule statement_mod_loop:sym<given> { <sym> <smexpr=.EXPR> }
 
    ## Terms
 
    token term:sym<fatarrow>           { <fatarrow> }
    token term:sym<colonpair>          { <colonpair> }
    token term:sym<variable>           { <variable> { $*VAR := $<variable> } }
    token term:sym<package_declarator> { <package_declarator> }
    token term:sym<scope_declarator>   { <scope_declarator> }
    token term:sym<routine_declarator> { <routine_declarator> }
    token term:sym<multi_declarator>   { <?before 'multi'|'proto'|'only'> <multi_declarator> }
    token term:sym<regex_declarator>   { <regex_declarator> }
    token term:sym<circumfix>          { <circumfix> }
    token term:sym<statement_prefix>   { <statement_prefix> }
    token term:sym<**>                 { <sym> }
    token term:sym<*>                  { <sym> }
    token term:sym<lambda>             { <?lambda> <pblock> {$*BORG<block> := $<pblockif nqp::ishash($*BORG)} }
    token term:sym<type_declarator>    { <type_declarator> }
    token term:sym<value>              { <value> }
    token term:sym<unquote>            { '{{{' <?{ $*IN_QUASI }> <statementlist> '}}}' }
 
    token term:sym<::?IDENT> {
        $<sym> = [ '::?' <identifier> ] »
    }
    
    token infix:sym<lambda> {
        <?before '{' | '->' > <!{ $*IN_META }> {
            my $needparens := 0;
            my $pos := $/.from;
            my $line := HLL::Compiler.lineof($/.orig, $/.from, :cache(1));
            my $lex := $*W.cur_lexpad();
            for 'if''unless''while''until''for''given''when''loop''sub''method' {
                $needparens++ if $_ eq 'loop';
                my $m := %*MYSTERY{$_ ~ '-' ~ $lex.cuid};
                next unless $m;
                my $m_pos  := $m<pos>[nqp::elems($m<pos>) - 1];
                my $m_line := HLL::Compiler.lineof($/.orig, $m_pos, :cache(1));
                if $line - $m_line < 5 {
                    if $m<ctx> eq '(' {
                        $/.CURSOR.'!clear_highwater'();
                        $/.CURSOR.'!cursor_pos'($m_pos);
                        $/.CURSOR.typed_sorry('X::Syntax::KeywordAsFunction',
                                word => $_,
                                :$needparens,
                        );
                        $/.CURSOR.'!cursor_pos'($pos);
                        $/.CURSOR.panic("Unexpected block in infix position (two terms in a row)");
                    }
                    else {
                        $/.CURSOR.'!clear_highwater'();
                        $/.CURSOR.'!cursor_pos'($m_pos);
                        $/.CURSOR.sorry("Word '$_' interpreted as a listop; please use 'do $_' to introduce the statement control word");
                        $/.CURSOR.'!cursor_pos'($pos);
                        $/.CURSOR.panic("Unexpected block in infix position (two terms in a row)");
                    }
                }
            }
        }
        [
        || <!{ $*IN_REDUCE }> {
            $/.CURSOR.panic("Unexpected block in infix position (two terms in a row, or previous statement missing semicolon?)");
        }
        || <!>
        ]
    }
    
    token term:sym<undef> {
        <sym> >> {}
        [ <?before \h*'$/' >
            <.obs('$/ variable as input record separator',
                 "the filehandle's .slurp method")>
        ]?
        [ <?before [ '(' || \h*<sigil><twigil>?\w ] >
            <.obs('undef as a verb''undefine function or assignment of Nil')>
        ]?
        <.obs('undef as a value'"something more specific:\n\tan undefined type object such as Any or Int,\n\t:!defined as a matcher,\n\tAny:U as a type constraint,\n\tNil as the absence of an expected value\n\tor fail() as a failure return\n\t   ")>
    }
 
    token term:sym<new> {
        'new' \h+ <longname> \h* <![:]> <.obs("C++ constructor syntax""method call syntax")>
    }
 
    token fatarrow {
        <key=.identifier> \h* '=>' <.ws> <val=.EXPR('i=')>
    }
    
    token coloncircumfix($front) {
        [
        | '<>' <.worry("Pair with <> really means an empty list, not null string; use :$front" ~ "('') to represent the null string,\n  or :$front" ~ "() to represent the empty list more accurately")>
        | {} <circumfix>
        ]
    }
 
    token colonpair {
        :my $*key;
        :my $*value;
 
        ':'
        :dba('colon pair')
        [
        | '!' [ <identifier> || <.panic: "Malformed False pair; expected identifier"> ]
            [ <[ \[ \( \< \{ ]> {
            $/.CURSOR.typed_panic('X::Syntax::NegatedPair'key => ~$<identifier>) } ]?
            { $*key := $<identifier>.Str$*value := 0; }
        | $<num> = [\d+] <identifier> [ <?before <[ \[ \( \< \{ ]>> {} <.sorry("Extra argument not allowed; pair already has argument of " ~ $<num>.Str)> <.circumfix> ]?
            { $*key := $<identifier>.Str$*value := +$<num>; }
        | <identifier>
            { $*key := $<identifier>.Str; }
            [
            || <.unsp>? :dba('pair value') <coloncircumfix($*key)> { $*value := $<coloncircumfix>; }
            || { $*value := 1; }
            ]
        | :dba('signature''(' ~ ')' <fakesignature>
        | <coloncircumfix('')>
            { $*key := ""$*value := $<coloncircumfix>; }
        | <var=.colonpair_variable>
            { $*key := $<var><desigilname>.Str$*value := $<var>; self.check_variable($*value); }
        ]
    }
    
    token colonpair_variable {
        <sigil> {}
        [
        | <twigil>? <desigilname>
        | $<capvar>='<' <desigilname> '>'
        ]
    }
 
    proto token special_variable { <...> }
 
    token special_variable:sym<$!{ }> {
        [ '$!{' .*? '}' | '%!' ]
        <.obsvar('%!')>
    }
 
    token special_variable:sym<$~> {
        <sym> <?before \h* '='>
        <.obsvar('$~')>
    }
 
    token special_variable:sym<$`> {
        <sym>  <?before \s | ',' | <terminator> >
        <.obsvar('$`')>
    }
 
    token special_variable:sym<$@> {
        <sym> <[ \s ; , ) ]> .
        <.obsvar('$@')>
    }
 
    # TODO: use actual variable in error message
    token special_variable:sym<$#> {
        <sym>
        [
        || \w+ <.obs('$#variable''@variable.end')>
        || <.obsvar('$#')>
        ]
    }
 
    token special_variable:sym<$$> {
        <sym> \W
        <.obsvar('$$')>
    }
    token special_variable:sym<$%> {
        <sym> <?before \h* '='>
        <.obsvar('$%')>
    }
 
    # TODO: $^X and other "caret" variables
 
    token special_variable:sym<$^> {
        <sym> <?before \h* '='>
        <.obsvar('$^')>
    }
 
    token special_variable:sym<$&> {
        <sym> <?before \s | ',' | <terminator> >
        <.obsvar('$&')>
    }
 
    token special_variable:sym<$*> {
        <sym> <?before \h* '='>
        <.obsvar('$*')>
    }
 
    token special_variable:sym<$=> {
        <sym> <?before \h+ '='>
        <.obsvar('$=')>
    }
 
    token special_variable:sym<@+> {
        <sym> <?before \s | ',' | <terminator> >
        <.obsvar('@+')>
    }
 
    token special_variable:sym<%+> {
        <sym> <?before \s | ',' | <terminator> >
        <.obsvar('%+')>
    }
 
    token special_variable:sym<$+[ ]> {
        '$+['
        <.obsvar('@+')>
    }
 
    token special_variable:sym<@+[ ]> {
        '@+['
        <.obsvar('@+')>
    }
 
    token special_variable:sym<@+{ }> {
        '@+{'
        <.obsvar('%+')>
    }
 
    token special_variable:sym<@-> {
        <sym> <?before \s | ',' | <terminator> >
        <.obsvar('@-')>
    }
 
    token special_variable:sym<%-> {
        <sym> <?before \s | ',' | <terminator> >
        <.obsvar('%-')>
    }
 
    token special_variable:sym<$-[ ]> {
        '$-['
        <.obsvar('@-')>
    }
 
    token special_variable:sym<@-[ ]> {
        '@-['
        <.obsvar('@-')>
    }
 
    token special_variable:sym<%-{ }> {
        '@-{'
        <.obsvar('%-')>
    }
 
    token special_variable:sym<$\\> {
        '$\\' <?before \s | ',' | '=' | <terminator> >
        <.obsvar('$\\')>
    }
 
    token special_variable:sym<$|> {
        <sym> <?before \h* '='>
        <.obsvar('$|')>
    }
 
    token special_variable:sym<$:> {
        <sym> <?before \h* '='>
        <.obsvar('$:')>
    }
 
    token special_variable:sym<$;> {
        <sym> <?before \h* '='>
        <.obsvar('$;')>
    }
 
    token special_variable:sym<$'> { #'
        <sym> <?before \s | ',' | <terminator> >
        <.obsvar('$' ~ "'")>
    }
 
    token special_variable:sym<$"> {
        <sym> <?before \h* '='>
        <.obsvar('$"')>
    }
 
    token special_variable:sym<$,> {
        <sym> <?before \h* '='>
        <.obsvar('$,')>
    }
 
    token special_variable:sym<$.> {
        <sym> {} <!before \w | '('>
        <.obsvar('$.')>
    }
 
    token special_variable:sym<$?> {
        <sym> {} <!before \w | '('>
        <.obsvar('$?')>
    }
 
    token special_variable:sym<$]> {
        <sym> {} <!before \w | '('>
        <.obsvar('$]')>
    }
    
    regex special_variable:sym<${ }> {
        <sigil> '{' {} $<text>=[.*?] '}'
        <!{ $*QSIGIL }>
        <?{
            my $sigil := $<sigil>.Str;
            my $text := $<text>.Str;
            my $bad := $sigil ~ '{' ~ $text ~ '}';
            $text := $text - 1 if $text ~~ /^\d+$/ && $text > 0;
            if !($text ~~ /^(\w|\:)+$/) {
                $/.CURSOR.obs($bad, $sigil ~ '(' ~ $text ~ ')');
            }
            elsif $*QSIGIL {
                $/.CURSOR.obs($bad, '{' ~ $sigil ~ $text ~ '}');
            }
            else {
                $/.CURSOR.obs($bad, $sigil ~ $text);
            }
        }>
    }
 
    token desigilname {
        [
        | <?before <.sigil> <.sigil> > <variable>
        | <?sigil>
            [ <?{ $*IN_DECL }> <.typed_panic: 'X::Syntax::Variable::IndirectDeclaration'> ]?
            <variable> {
                $*VAR := $<variable>;
                self.check_variable($*VAR);
            }
        | <longname>
        ]
    }
 
    token variable {
        :my $*IN_META := '';
        [
        | :dba('infix noun''&[' ~ ']' <infixish('[]')>
        | <sigil> <twigil>? <desigilname>
        | <special_variable>
        | <sigil> $<index>=[\d+]                              [<?{ $*IN_DECL }> <.typed_panic: "X::Syntax::Variable::Numeric">]?
        | <sigil> <?[<]> <postcircumfix>                      [<?{ $*IN_DECL }> <.typed_panic('X::Syntax::Variable::Match')>]?
        | :dba('contextualizer') <sigil> '(' ~ ')' <sequence> [<?{ $*IN_DECL }> <.panic: "Cannot declare a contextualizer">]?
        | $<sigil>=['$'] $<desigilname>=[<[/_!]>]
        | {} <sigil> <!{ $*QSIGIL }>  # try last, to allow sublanguages to redefine sigils (like & in regex)
        ]
        [ <?{ $<twigil> && $<twigil> eq '.' }>
            [ <.unsp> | '\\' | <?> ] <?[(]> <arglist=.postcircumfix>
        ]?
        { $*LEFTSIGIL := nqp::substr(self.orig(), self.from, 1) unless $*LEFTSIGIL }
    }
 
    token sigil { <[$@%&]> }
 
    proto token twigil { <...> }
    token twigil:sym<.> { <sym> <?before \w> }
    token twigil:sym<!> { <sym> <?before \w> }
    token twigil:sym<^> { <sym> <?before \w> }
    token twigil:sym<:> { <sym> <?before \w> }
    token twigil:sym<*> { <sym> <?before \w> }
    token twigil:sym<?> { <sym> <?before \w> }
    token twigil:sym<=> { <sym> <?before \w> }
    token twigil:sym<~> { <sym> <?before \w> }
 
    proto token package_declarator { <...> }
    token package_declarator:sym<package> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'package';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<module> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'module';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<class> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'class';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<grammar> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'grammar';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<role> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'role';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<knowhow> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'knowhow';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<native> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'native';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<slang> {
        :my $*OUTERPACKAGE := $*PACKAGE;
        :my $*PKGDECL := 'slang';
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <package_def>
    }
    token package_declarator:sym<trusts> {
        <sym> <.ws> <typename>
    }
    rule package_declarator:sym<also> {
        <sym>
        [ <trait>+ || <.panic: "No valid trait found after also"> ]
    }
 
    rule package_def {
        :my $longname;
        :my $outer := $*W.cur_lexpad();
        :my $*IMPLICIT := 0;
        :my $*DECLARAND;
        :my $*IN_DECL := 'package';
        :my $*HAS_SELF := '';
        :my $*CURPAD;
        :my $*DOC := $*DECLARATOR_DOCS;
        :my $*POD_BLOCK;
        { $*DECLARATOR_DOCS := '' }
        <.attach_leading_docs>
        
        # Type-object will live in here; also set default REPR (a trait
        # may override this, e.g. is repr('...')).
        :my $*PACKAGE;
        :my %*ATTR_USAGES;
        :my $*REPR;
        
        # Default to our scoped.
        { unless $*SCOPE { $*SCOPE := 'our'; } }
        
        [
            [ <longname> { $longname := $*W.dissect_longname($<longname>); } ]?
            <.newpad>
            
            [ :dba('generic role')
            <?{ ($*PKGDECL//'') eq 'role' }>
            { $*PACKAGE := $*OUTERPACKAGE } # in case signature tries to declare a package
            '[' ~ ']' <signature>
            { $*IN_DECL := ''; }
            ]?
            
            <trait>*
            
            {
                # Unless we're augmenting...
                if $*SCOPE ne 'augment' {
                    # Locate any existing symbol. Note that it's only a match
                    # with "my" if we already have a declaration in this scope.
                    my $exists := 0;
                    my @name := $longname ??
                        $longname.type_name_parts('package name', :decl(1)) !!
                        [];
                    my $target_package := $longname && $longname.is_declared_in_global()
                        ?? $*GLOBALish
                        !! $*OUTERPACKAGE;
                    if @name && $*SCOPE ne 'anon' {
                        if @name && $*W.already_declared($*SCOPE$target_package$outer@name) {
                            $*PACKAGE := $*W.find_symbol(@name);
                            $exists := 1;
                        }
                    }
 
                    # If it exists already, then it's either uncomposed (in which
                    # case we just stubbed it), a role (in which case multiple
                    # variants are OK) or else an illegal redecl.
                    if $exists && ($*PKGDECL ne 'role' || !nqp::can($*PACKAGE.HOW'configure_punning')) {
                        if $*PKGDECL eq 'role' || $*PACKAGE.HOW.is_composed($*PACKAGE) {
                            $*W.throw($/, ['X''Redeclaration'],
                                symbol => $longname.name(),
                            );
                        }
                    }
                    
                    # If it's not a role, or it is a role but one with no name,
                    # then just needs meta-object construction and installation.
                    elsif $*PKGDECL ne 'role' || !@name {
                        # Construct meta-object for this package.
                        my %args;
                        if @name {
                            %args<name> := $longname.name();
                        }
                        if $*REPR ne '' {
                            %args<repr> := $*REPR;
                        }
                        $*PACKAGE := $*W.pkg_create_mo($/, $*W.resolve_mo($/, $*PKGDECL), |%args);
                        
                        # Install it in the symbol table if needed.
                        if @name {
                            $*W.install_package($/, @name$*SCOPE$*PKGDECL$target_package$outer$*PACKAGE);
                        }
                    }
                    
                    # If it's a named role, a little trickier. We need to make
                    # a parametric role group for it (unless we got one), and
                    # then install it in that.
                    else {
                        # If the group doesn't exist, create it.
                        my $group;
                        if $exists {
                            $group := $*PACKAGE;
                        }
                        else {
                            $group := $*W.pkg_create_mo($/, $*W.resolve_mo($/, 'role-group'), :name($longname.name()), :repr($*REPR));
                            $*W.install_package($/, @name$*SCOPE$*PKGDECL$target_package$outer$group);
                        }
 
                        # Construct role meta-object with group.
                        sub needs_args($s) {
                            return 0 if !$s;
                            my @params := $s.ast<parameters>;
                            return 0 if nqp::elems(@params) == 0;
                            return nqp::elems(@params) > 1 || !@params[0]<optional>;
                        }
                        $*PACKAGE := $*W.pkg_create_mo($/, $*W.resolve_mo($/, $*PKGDECL), :name($longname.name()),
                            :repr($*REPR), :group($group), :signatured(needs_args($<signature>)));
                    }
                }
                else {
                    # Augment. Ensure we can.
                    if !$*MONKEY_TYPING && $longname.text ne 'Cool' {
                        $/.CURSOR.typed_panic('X::Syntax::Augment::WithoutMonkeyTyping');
                    }
                    elsif !$longname {
                        $*W.throw($/, 'X::Anon::Augment'package-kind => $*PKGDECL);
                    }
 
                    # Locate type.
                    my @name := 
                      $longname.type_name_parts('package name', :decl(1));
                    my $found;
                    try { $*PACKAGE := $*W.find_symbol(@name); $found := 1 }
                    unless $found {
                        $*W.throw($/, 'X::Augment::NoSuchType',
                            package-kind => $*PKGDECL,
                            package      => $longname.text(),
                        );
                    }
                    unless $*PACKAGE.HOW.archetypes.augmentable {
                        $/.CURSOR.typed_panic('X::Syntax::Augment::Illegal',
                            package      => $longname.text);
                    }
                }
                
                # Install $?PACKAGE, $?ROLE, $?CLASS, and :: variants as needed.
                my $curpad := $*W.cur_lexpad();
                unless $curpad.symbol('$?PACKAGE') {
                    $*W.install_lexical_symbol($curpad'$?PACKAGE'$*PACKAGE);
                    $*W.install_lexical_symbol($curpad'::?PACKAGE'$*PACKAGE);
                    if $*PKGDECL eq 'role' {
                        $*W.install_lexical_symbol($curpad'$?ROLE'$*PACKAGE);
                        $*W.install_lexical_symbol($curpad'::?ROLE'$*PACKAGE);
                        $*W.install_lexical_symbol($curpad'$?CLASS',
                            $*W.pkg_create_mo($/, $*W.resolve_mo($/, 'generic'), :name('$?CLASS')));
                        $*W.install_lexical_symbol($curpad'::?CLASS',
                            $*W.pkg_create_mo($/, $*W.resolve_mo($/, 'generic'), :name('::?CLASS')));
                    }
                    elsif $*PKGDECL ne 'package' && $*PKGDECL ne 'module' {
                        $*W.install_lexical_symbol($curpad'$?CLASS'$*PACKAGE);
                        $*W.install_lexical_symbol($curpad'::?CLASS'$*PACKAGE);
                    }
                }
                
                # Set declarand as the package.
                $*DECLARAND := $*PACKAGE;
 
                if $*PRECEDING_DECL_LINE < $*LINE_NO {
                    $*PRECEDING_DECL_LINE := $*LINE_NO;
                    $*PRECEDING_DECL := $*DECLARAND;
                }
                
                # Apply any traits.
                for $<trait> {
                    my $applier := $_.ast;
                    if $applier {
                        $applier($*DECLARAND);
                    }
                }
            }
            :!s
            { nqp::push(@*PACKAGES$*PACKAGE); }
            [
            || <?[{]> 
                [
                {
                    $*IN_DECL := '';
                    $*begin_compunit := 0;
                }
                <blockoid>
                ]
            
            || ';'
                [
                || <?{ $*begin_compunit }>
                    {
                        unless $longname {
                            $/.CURSOR.panic("Compilation unit cannot be anonymous");
                        }
                        unless $outer =:= $*UNIT {
                            $/.CURSOR.typed_panic("X::SemicolonForm::Invalid", what => $*PKGDECLwhere => "in subscopes");
                        }
                        if $*PKGDECL eq 'package' {
                            $/.CURSOR.panic('This appears to be Perl 5 code. If you intended it to be Perl 6 code, please use a Perl 6 style package block like "package Foo { ... }", or "module Foo; ...".');
                        }
                        $*begin_compunit := 0;
                    }
                    { $*IN_DECL := ''; }
                    <.finishpad>
                    <statementlist(1)>     # whole rest of file, presumably
                    { $*CURPAD := $*W.pop_lexpad() }
                || { $/.CURSOR.typed_panic("X::SemicolonForm::TooLate", what => $*PKGDECL); }
                ]
            || <.panic("Unable to parse $*PKGDECL definition")>
            ]
            { nqp::pop(@*PACKAGES); }
        ]:!s || { $/.CURSOR.malformed($*PKGDECL) }
    }
 
    token declarator {
        [
        # STD.pm6 uses <defterm> here, but we need different 
        # action methods
        | '\\' <defterm>
            [ <.ws> <term_init=initializer> || <.typed_panic: "X::Syntax::Term::MissingInitializer"> ]
        | <variable_declarator>
          [
          || <?{ $*SCOPE eq 'has' }> <.newpad> [ <.ws> <initializer> ]? { $*ATTR_INIT_BLOCK := $*W.pop_lexpad() }
          || [ <.ws> <initializer> ]?
          ]
        | '(' ~ ')' <signature> [ <.ws> <trait>+ ]? [ <.ws> <initializer> ]?
        | <routine_declarator>
        | <regex_declarator>
        | <type_declarator>
        ]
    }
 
    rule term:sym<winner>   { <sym><.end_keyword> <xblock> }   # DEPRECATED
    rule term:sym<earliest> { <sym><.end_keyword> <xblock> }
    rule term:sym<combine>{ <sym><.end_keyword> <xblock> }
    rule statement_control:sym<more>   { <sym><.end_keyword> <xblock(1)> }
    rule statement_control:sym<done>   { <sym><.end_keyword> <xblock(1)> }
    rule statement_control:sym<quit>   { <sym><.end_keyword> <xblock(1)> }
    rule statement_control:sym<wait>   { <sym><.end_keyword> <xblock(1)> }
 
    proto token multi_declarator { <...> }
    token multi_declarator:sym<multi> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> :my $*MULTINESS := 'multi'; <.end_keyword>
        <.ws> [ <declarator> || <routine_def('sub')> || <.malformed('multi')> ]
    }
    token multi_declarator:sym<proto> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> :my $*MULTINESS := 'proto'; :my $*IN_PROTO := 1; <.end_keyword>
        <.ws> [ <declarator> || <routine_def('sub')> || <.malformed('proto')> ]
    }
    token multi_declarator:sym<only> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> :my $*MULTINESS := 'only'; <.end_keyword>
        <.ws> [ <declarator> || <routine_def('sub')> || <.malformed('only')>]
    }
    token multi_declarator:sym<null> {
        :my $*MULTINESS := '';
        <declarator>
    }
 
    proto token scope_declarator { <...> }
    token scope_declarator:sym<my>        { <sym> <scoped('my')> }
    token scope_declarator:sym<our>       { <sym> <scoped('our')> }
    token scope_declarator:sym<has>       {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym>
        :my $*HAS_SELF := 'partial';
        :my $*ATTR_INIT_BLOCK;
        <scoped('has')>
    }
    token scope_declarator:sym<augment>   { <sym> <scoped('augment')> }
    token scope_declarator:sym<anon>      { <sym> <scoped('anon')> }
    token scope_declarator:sym<state>     { <sym> <scoped('state')> }
    token scope_declarator:sym<supersede> {
        <sym> <scoped('supersede')> <.NYI('"supersede"')>
    }
 
    token scoped($*SCOPE) {
        <.end_keyword>
        :dba('scoped declarator')
        [
        :my $*DOC := $*DECLARATOR_DOCS;
        :my $*POD_BLOCK;
        {
            if $*SCOPE eq 'has' {
                $*DECLARATOR_DOCS := '';
                if $*PRECEDING_DECL_LINE < $*LINE_NO {
                    $*PRECEDING_DECL_LINE := $*LINE_NO;
                    $*PRECEDING_DECL := Mu; # actual declarand comes later, in Actions::declare_variable
                }
                self.attach_leading_docs;
            }
        }
        <.ws>
        [
        | <DECL=declarator>
        | <DECL=regex_declarator>
        | <DECL=package_declarator>
        | [<typename><.ws>]+
          {
            if +$<typename> > 1 {
                $/.CURSOR.NYI('Multiple prefix constraints');
            }
            $*OFTYPE := $<typename>[0];
          }
          <DECL=multi_declarator>
        | <DECL=multi_declarator>
        ]
        || <.ws>[<typename><.ws>]* <ident> <?before <.ws> [':'?':'?'=' | ';' | '}' ]> {}
            <.malformed("$*SCOPE (did you mean to declare a sigilless \\{~$<ident>} or \${~$<ident>}?)")>
        || <.ws><typo_typename> <!>
        || <.malformed($*SCOPE)>
        ]
    }
 
    token variable_declarator {
        :my $*IN_DECL := 'variable';
        :my $var;
        <variable>
        {
            $var := $<variable>.Str;
            $/.CURSOR.add_variable($var);
            $*IN_DECL := '';
        }
        [
            <.unsp>?
            $<shape>=[
            | '(' ~ ')' <signature>
                {
                    my $sigil := nqp::substr($var, 0, 1);
                    if $sigil eq '&' {
                        self.typed_sorry('X::Syntax::Reserved',
                            reserved => '() shape syntax in routine declarations',
                            instead => ' (maybe use :() to declare a longname?)'
                        );
                    }
                    elsif $sigil eq '@' {
                        self.typed_sorry('X::Syntax::Reserved',
                            reserved => '() shape syntax in array declarations');
                    }
                    elsif $sigil eq '%' {
                        self.typed_sorry('X::Syntax::Reserved',
                            reserved => '() shape syntax in hash declarations');
                    }
                    else {
                        self.typed_sorry('X::Syntax::Reserved',
                            reserved => '() shape syntax in variable declarations');
                    }
                }
            | :dba('shape definition''[' ~ ']' <semilist> <.NYI: "Shaped variable declarations">
            | :dba('shape definition''{' ~ '}' <semilist>
            | <?[<]> <postcircumfix> <.NYI: "Shaped variable declarations">
            ]+
        ]?
 
        [ <.ws> <trait>+ ]?
        [ <.ws> <post_constraint>+ <.NYI: "Post-constraints on variables"> ]?
    }
 
    proto token routine_declarator { <...> }
    token routine_declarator:sym<sub> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <routine_def('sub')>
    }
    token routine_declarator:sym<method> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <method_def('method')>
    }
    token routine_declarator:sym<submethod> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <method_def('submethod')>
    }
    token routine_declarator:sym<macro> {
        :my $*LINE_NO := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <sym> <.end_keyword> <macro_def()>
    }
 
    rule routine_def($d) {
        :my $*IN_DECL := $d;
        :my $*METHODTYPE;
        :my $*IMPLICIT := 0;
        :my $*DOC := $*DECLARATOR_DOCS;
        { $*DECLARATOR_DOCS := '' }
        :my $*POD_BLOCK;
        :my $*DECLARAND := $*W.stub_code_object('Sub');
        :my $*CURPAD;
        :my $outer := $*W.cur_lexpad();
        {
            if $*PRECEDING_DECL_LINE < $*LINE_NO {
                $*PRECEDING_DECL_LINE := $*LINE_NO;
                $*PRECEDING_DECL := $*DECLARAND;
            }
        }
        <.attach_leading_docs>
        <deflongname>?
        {
            if $<deflongname> && $<deflongname><colonpair>[0]<coloncircumfix> -> $cf {
                # It's an (potentially new) operator, circumfix, etc. that we
                # need to tweak into the grammar.
                my $category := $<deflongname><name>.Str;
                my $opname := $cf<circumfix>
                    ?? $*W.colonpair_nibble_to_str($/, $cf<circumfix><nibble>)
                    !! '';
                my $canname := $category ~ ":sym<" ~ $opname ~ ">";
                $/.CURSOR.add_categorical($category$opname$canname$<deflongname>.ast, $*DECLARAND);
            }
        }
        <.newpad>
        [ '(' <multisig> ')' ]?
        <trait>* :!s
        { $*IN_DECL := ''; }
        [
        || ';'
            {
                if $<deflongnamene 'MAIN' {
                    $/.CURSOR.typed_panic("X::SemicolonForm::Invalid", what => "sub"where => "except on MAIN subs");
                }
                unless $*begin_compunit {
                    $/.CURSOR.typed_panic("X::SemicolonForm::TooLate", what => "sub");
                }
                unless $*MULTINESS eq '' || $*MULTINESS eq 'only' {
                    $/.CURSOR.typed_panic("X::SemicolonForm::Invalid", what => "sub"where => "on $*MULTINESS subs");
                }
                unless $outer =:= $*UNIT {
                    $/.CURSOR.typed_panic("X::SemicolonForm::Invalid", what => "sub"where => "in subscopes");
                }
                $*begin_compunit := 0;
            }
            <.finishpad>
            <statementlist(1)>
            { $*CURPAD := $*W.pop_lexpad() }
        || <onlystar>
        || <!before '{'> <possibly_subname=.deflongname> { if self.parse($<deflongname>.Str, :rule('typename')) { $/.CURSOR.panic("Did you mean to write \"my $<deflongname> sub $<possibly_subname>\" or put \"returns $<deflongname>\" before the block?"); } } <!>
        || <blockoid>
        ]
    }
 
    rule method_def($d) {
        :my $*IN_DECL := $d;
        :my $*METHODTYPE := $d;
        :my $*HAS_SELF := $d eq 'submethod' ?? 'partial' !! 'complete';
        :my $*DOC := $*DECLARATOR_DOCS;
        { $*DECLARATOR_DOCS := '' }
        :my $*POD_BLOCK;
        :my $*DECLARAND := $*W.stub_code_object($d eq 'submethod' ?? 'Submethod' !! 'Method');
        {
            if $*PRECEDING_DECL_LINE < $*LINE_NO {
                $*PRECEDING_DECL_LINE := $*LINE_NO;
                $*PRECEDING_DECL := $*DECLARAND;
            }
        }
        <.attach_leading_docs>
        [
            <.newpad>
            [
            | $<specials>=[<[ ! ^ ]>?]<longname> [ '(' <multisig> ')' ]? <trait>*
            | '(' <multisig> ')' <trait>*
            | <sigil>'.':!s
                :dba('subscript signature')
                [
                | '(' ~ ')' <multisig>
                | '[' ~ ']' <multisig>
                | '{' ~ '}' <multisig>
                ]:s
                <trait>*
            | <?>
            ]
            { $*IN_DECL := ''; }
            [
            || <onlystar>
            || <blockoid>
            ]
        ] || <.malformed('method')>
    }
 
    rule macro_def() {
        :my $*IN_DECL := 'macro';
        :my $*IMPLICIT := 0;
        :my $*DOC := $*DECLARATOR_DOCS;
        { $*DECLARATOR_DOCS := '' }
        :my $*POD_BLOCK;
        :my $*DECLARAND := $*W.stub_code_object('Macro');
        {
            if $*PRECEDING_DECL_LINE < $*LINE_NO {
                $*PRECEDING_DECL_LINE := $*LINE_NO;
                $*PRECEDING_DECL := $*DECLARAND;
            }
        }
        <.attach_leading_docs>
        <deflongname>?
        {
            if $<deflongname> && $<deflongname><colonpair>[0]<coloncircumfix> -> $cf {
                # It's an (potentially new) operator, circumfix, etc. that we
                # need to tweak into the grammar.
                my $category := $<deflongname><name>.Str;
                my $opname := $cf<circumfix>
                    ?? $*W.colonpair_nibble_to_str($/, $cf<circumfix><nibble>)
                    !! '';
                my $canname := $category ~ ":sym<" ~ $opname ~ ">";
                $/.CURSOR.add_categorical($category$opname$canname$<deflongname>.ast, $*DECLARAND);
            }
        }
        <.newpad>
        [ '(' <multisig> ')' ]?
        <trait>*
        { $*IN_DECL := ''; }
        [
        || <onlystar>
        || <blockoid>
        ]
    }
    
    token onlystar {
        :my $*CURPAD;
        <?{ $*MULTINESS eq 'proto' }>
        '{' <.ws> '*' <.ws> '}'
        <?ENDSTMT>
        <.finishpad>
        { $*CURPAD := $*W.pop_lexpad() }
    }
 
    ###########################
    # Captures and Signatures #
    ###########################
 
    token capterm {
        '\\'
        [
        | '(' <.ws> <capture>? ')'
        | <?before \S> <termish>
        | {} <.panic: "You can't backslash that">
        ]
    }
 
    rule capture {
        <EXPR>
    }
 
    rule param_sep {
        '' $<sep>=[','|':'|';;'|';'] { @*seps.push($<sep>) }
    }
 
    # XXX Not really implemented yet.
    token multisig {
        :my $*SCOPE := 'my';
        <signature>
    }
 
    token sigterm {
        :dba('signature')
        ':(' ~ ')' <fakesignature>
    }
 
    token fakesignature {
        <.newpad>
        <signature>
    }
 
    token signature {
        :my $*IN_DECL := 'sig';
        :my $*zone := 'posreq';
        :my @*seps := nqp::list();
        :my $*INVOCANT_OK := 1;
        <.ws>
        [
        | <?before '-->' | ')' | ']' | '{' | ':'\s | ';;' >
        | [ <parameter> || <.malformed('parameter')> ]
        ]+ % <param_sep>
        <.ws>
        { $*IN_DECL := ''; }
        [ '-->' <.ws> <typename> || '-->' <.ws> <typo_typename> ]?
        { $*LEFTSIGIL := '@'; }
    }
 
    token parameter {
        # We'll collect parameter information into a hash, then use it to
        # build up the parameter object in the action method
        :my %*PARAM_INFO;
        [
        | <type_constraint>+
            [
            | $<quant>=['**'|'*'] <param_var>
            | $<quant>=['\\'|'|'] <param_var> { nqp::printfh(nqp::getstderr(), "Obsolete use of | or \\ with sigil on param { $<param_var> }\n") }
            | $<quant>=['\\'|'|'] <defterm>?
 
            | [ <param_var> | <named_param> ] $<quant>=['?'|'!'|<?>]
            | <?>
            ]
        | $<quant>=['**'|'*'] <param_var>
        | $<quant>=['\\'|'|'] <param_var> { nqp::printfh(nqp::getstderr, "Obsolete use of | or \\ with sigil on param { $<param_var> }\n") }
        | $<quant>=['\\'|'|'] <defterm>?
        | [ <param_var> | <named_param> ] $<quant>=['?'|'!'|<?>]
        | <longname>
            {
                my $name := $*W.dissect_longname($<longname>);
                $*W.throw($/, ['X''Parameter''InvalidType'],
                    :typename($name.name),
                    :suggestions($*W.suggest_typename($name.name)));
            }
        ]
        <.ws>
        <trait>*
        <post_constraint>*
        [
            <default_value>
            [ <modifier=.trait> {
                self.typed_panic: "X::Parameter::AfterDefault", type => "trait", modifier => $<modifier>, default => $<default_value>
            }]?
            [ <modifier=.post_constraint> {
                self.typed_panic: "X::Parameter::AfterDefault", type => "post constraint", modifier => $<modifier>, default => $<default_value>
            }]?
        ]**0..1
 
        # enforce zone constraints
        {
            my $kind :=
                $<named_param>                      ?? '*' !!
                $<quanteq '?' || $<default_value> ?? '?' !!
                $<quanteq '!'                     ?? '!' !!
                $<quantne '' && $<quantne '\\'  ?? '*' !!
                                                       '!';
            my $name := %*PARAM_INFO<variable_name> // '';
            if $kind eq '!' {
                if $*zone eq 'posopt' {
                    $/.CURSOR.typed_panic('X::Parameter::WrongOrder', misplaced => 'required'after => 'optional', parameter => $name);
                }
                elsif $*zone eq 'var' {
                    $/.CURSOR.typed_panic('X::Parameter::WrongOrder', misplaced => 'required'after => 'variadic', parameter => $name);
                }
            }
            elsif $kind eq '?' {
                if $*zone  eq 'posreq' {
                        $*zone := 'posopt';
                }
                elsif $*zone eq  'var' {
                    $/.CURSOR.typed_panic('X::Parameter::WrongOrder', misplaced => 'optional positional'after => 'variadic', parameter => $name);
                }
            }
            elsif $kind eq '*' {
                $*zone := 'var';
            }
 
            %*PARAM_INFO<node> := $/;
        }
    }
 
    token param_var {
        :dba('formal parameter')
        :my $*DOC := $*DECLARATOR_DOCS# these get cleared later
        :my $*POD_BLOCK;
        <.attach_leading_docs>
        {
            my $line_no := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
            if $*PRECEDING_DECL_LINE < $line_no {
                $*PRECEDING_DECL_LINE := $line_no;
                my $par_type := $*W.find_symbol(['Parameter']);
                $*PRECEDING_DECL := nqp::create($par_type); # actual declarand comes later, in World::create_parameter
            }
        }
        [
        | '[' ~ ']' <signature>
        | '(' ~ ')' <signature>
        | <sigil> <twigil>?
          [
          || <?{ $<sigil>.Str eq '&' }> <?identifier> {}
             <name=.sublongname>
          || <name=.identifier>
          || <name=.decint> { $*W.throw($/, 'X::Syntax::Variable::Numeric', what => 'parameter') }
          || $<name>=[<[/!]>]
          ]?
 
          :dba('shape declaration')
          :my $*IN_DECL := '';
          [
          | <?before ':('>  ':'  # XXX allow fakesig parsed as subsig for the moment
          | <?before '('>         <.sorry: "Shape declaration with () is reserved;\n  please use whitespace if you meant a subsignature for unpacking,\n  or use the :() form if you meant to add signature info to the function's type">
          | <?before '['>         <.sorry: 'Shape declaration is not yet implemented; please use whitespace if you meant a subsignature for unpacking'>
               <postcircumfix>
          | <?before <[ { < « ]>> <.sorry: 'Shape declaration is not yet implemented; please use whitespace if you meant something else'>
               <postcircumfix>
          ]?
        ]
    }
 
    token named_param {
        :my $*GOAL := ')';
        :dba('named parameter')
        ':'
        [
        | <name=.identifier> '(' <.ws>
            [ <named_param> | <param_var> <.ws> ]
            [ ')' || <.panic: 'Unable to parse named parameter; couldnt find right parenthesis'> ]
        | <param_var>
        ]
    }
 
    rule default_value {
        :my $*IN_DECL := '';
        '=' <EXPR('i=')>
    }
 
    token type_constraint {
        :my $*IN_DECL := '';
        [
        | <value>
        | <typename>
        | where <.ws> <EXPR('i=')>
        ]
        <.ws>
    }
 
    rule post_constraint {
        :my $*IN_DECL := '';
        :dba('constraint')
        [
        | '[' ~ ']' <signature>
        | '(' ~ ')' <signature>
        | where <EXPR('i=')>
        ]
    }
 
    proto token regex_declarator { <...> }
    token regex_declarator:sym<rule> {
        <sym>
        :my %*RX;
        :my $*INTERPOLATE := 1;
        :my $*METHODTYPE := 'rule';
        :my $*IN_DECL    := 'rule';
        :my $*LINE_NO    := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        {
            %*RX<s> := 1;
            %*RX<r> := 1;
        }
        <regex_def>
    }
    token regex_declarator:sym<token> {
        <sym>
        :my %*RX;
        :my $*INTERPOLATE := 1;
        :my $*METHODTYPE := 'token';
        :my $*IN_DECL    := 'token';
        :my $*LINE_NO    := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        {
            %*RX<r> := 1;
        }
        <regex_def>
    }
    token regex_declarator:sym<regex> {
        <sym>
        :my %*RX;
        :my $*INTERPOLATE := 1;
        :my $*METHODTYPE := 'regex';
        :my $*IN_DECL    := 'regex';
        :my $*LINE_NO    := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
        <regex_def>
    }
 
    rule regex_def {
        <.end_keyword>
        :my $*CURPAD;
        :my $*HAS_SELF := 'complete';
        :my $*DOC := $*DECLARATOR_DOCS;
        { $*DECLARATOR_DOCS := '' }
        :my $*POD_BLOCK;
        :my $*DECLARAND := $*W.stub_code_object('Regex');
        {
            if $*PRECEDING_DECL_LINE < $*LINE_NO {
                $*PRECEDING_DECL_LINE := $*LINE_NO;
                $*PRECEDING_DECL := $*DECLARAND;
            }
        }
        <.attach_leading_docs>
        [
          <deflongname>?
          { if $<deflongname> { %*RX<name> := ~$<deflongname>.ast } }
          { $*IN_DECL := '' }
           <.newpad>
          [ [ ':'?'(' <signature')' ] | <trait> ]*
          '{'
          [
          | ['*'|'<...>'|'<*>'] <?{ $*MULTINESS eq 'proto' }> $<onlystar>={1}
          | <nibble(self.quote_lang(%*RX<P5> ?? %*LANG<P5Regex> !! %*LANG<Regex>, '{''}'))>
          ]
          '}'<?ENDSTMT>
          { $*CURPAD := $*W.pop_lexpad() }
        ] || <.malformed('regex')>
    }
 
    proto token type_declarator { <...> }
 
    token type_declarator:sym<enum> {
        <sym>  <.end_keyword> <.ws>
        :my $*IN_DECL := 'enum';
        :my $*DOC := $*DECLARATOR_DOCS;
        { $*DECLARATOR_DOCS := '' }
        :my $*POD_BLOCK;
        :my $*DECLARAND;
        {
            my $line_no := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
            if $*PRECEDING_DECL_LINE < $line_no {
                $*PRECEDING_DECL_LINE := $line_no;
                $*PRECEDING_DECL      := Mu; # actual declarand comes later, in Actions::type_declarator:sym<enum>
            }
        }
        <.attach_leading_docs>
        [
        | <longname>
            {
                my $longname := $*W.dissect_longname($<longname>);
                my @name := $longname.type_name_parts('enum name', :decl(1));
                if $*W.already_declared($*SCOPE$*PACKAGE$*W.cur_lexpad(), @name) {
                    $*W.throw($/, ['X''Redeclaration'],
                        symbol => $longname.name(),
                    );
                }
            }
        | <variable>
        | <?>
        ]
        { $*IN_DECL := ''; }
        <.ws>
        <trait>*
        <?[<(«]> <term> <.ws>
    }
 
    rule type_declarator:sym<subset> {
        <sym><.end_keyword> :my $*IN_DECL := 'subset';
        :my $*DOC := $*DECLARATOR_DOCS;
        { $*DECLARATOR_DOCS := '' }
        :my $*POD_BLOCK;
        :my $*DECLARAND;
        {
            my $line_no := HLL::Compiler.lineof(self.orig(), self.from(), :cache(1));
            if $*PRECEDING_DECL_LINE < $line_no {
                $*PRECEDING_DECL_LINE := $line_no;
                $*PRECEDING_DECL      := Mu; # actual declarand comes later, in Actions::type_declarator:sym<subset>
            }
        }
        <.attach_leading_docs>
        [
            [
                [
                    <longname>
                    {
                        my $longname := $*W.dissect_longname($<longname>);
                        my @name := $longname.type_name_parts('subset name', :decl(1));
                        if $*W.already_declared($*SCOPE$*PACKAGE$*W.cur_lexpad(), @name) {
                            $*W.throw($/, ['X''Redeclaration'],
                                symbol => $longname.name(),
                            );
                        }
                    }
                ]?
                { $*IN_DECL := '' }
                <trait>*
                [ where <EXPR('e=')> ]?
            ]
            || <.malformed('subset')>
        ]
    }
 
    token type_declarator:sym<constant> {
        :my $*IN_DECL := 'constant';
        <sym> <.end_keyword> <.ws>
 
        [
        | '\\'? <defterm>
        | <variable>
        | <?>
        ]
        { $*IN_DECL := ''; }
        <.ws>
 
        <trait>*
 
        { $*W.push_lexpad($/) }
        [
        || <initializer>
        || <.missing: "initializer on constant declaration">
        ]
    }
 
    proto token initializer { <...> }
    token initializer:sym<=> {
        <sym>
        [
            <.ws>
            [
            || <?{ $*LEFTSIGIL eq '$' }> <EXPR('i=')>
            || <EXPR('e=')>
            ]
            || <.malformed: 'initializer'>
        ]
    }
    token initializer:sym<:=> {
        <sym> [ <.ws> <EXPR('e=')> || <.malformed: 'binding'> ]
    }
    token initializer:sym<::=> {
        <sym> [ <.ws> <EXPR('e=')> || <.malformed: 'binding'> ]
    }
    token initializer:sym<.=> {
        <sym> [ <.ws> <dottyopish> || <.malformed: 'mutator method call'> ]
    }
 
    rule trait {
        :my $*IN_DECL := '';
        [
        | <trait_mod>
        | <colonpair>
        ]
    }
 
    proto rule trait_mod { <...> }
    rule trait_mod:sym<is>      { <sym> [ [<longname><circumfix>**0..1] || <.panic: 'Invalid name'>] }
    rule trait_mod:sym<hides>   { <sym> [ <typename> || <.panic: 'Invalid typename'>] }
    rule trait_mod:sym<does>    { <sym> [ <typename> || <.panic: 'Invalid typename'>] }
    rule trait_mod:sym<will>    { <sym> [ <identifier> || <.panic: 'Invalid name'>] <pblock> }
    rule trait_mod:sym<of>      { <sym> [ <typename> || <.panic: 'Invalid typename'>] }
    rule trait_mod:sym<as>      { <sym> [ <typename> || <.panic: 'Invalid typename'>] }
    rule trait_mod:sym<returns> { <sym> [ <typename> || <.panic: 'Invalid typename'>] }
    rule trait_mod:sym<handles> { <sym> [ <term> || <.panic: 'Invalid term'>] }
 
    ## Terms
 
    proto token term { <...> }
 
    token term:sym<self> {
        <sym> <.end_keyword>
        {
            $*HAS_SELF || self.typed_sorry('X::Syntax::Self::WithoutObject')
        }
    }
 
    token term:sym<now> { <sym> <.end_keyword> }
 
    token term:sym<time> { <sym> <.end_keyword> }
 
    token term:sym<empty_set> { "" <!before <[ \( \\ ' \- ]> || \h* '=>'> }
 
    token term:sym<rand> {
        <sym> »
        [ <?before '('? \h* [\d|'$']> <.obs('rand(N)''N.rand for Num or (^N).pick for Int result')> ]?
        [ <?before '()'> <.obs('rand()''rand')> ]?
        <.end_keyword>
    }