diff --git a/src/Perl6/Actions.nqp b/src/Perl6/Actions.nqp index faf5140c90a..27b25b7fcb0 100644 --- a/src/Perl6/Actions.nqp +++ b/src/Perl6/Actions.nqp @@ -1073,6 +1073,10 @@ role STDActions { } class Perl6::Actions is HLL::Actions does STDActions { + #================================================================ + # AMBIENT AND POD-COMMON CODE HANDLERS + #================================================================ + our @MAX_PERL_VERSION; # Could add to this based on signatures. @@ -1441,443 +1445,180 @@ class Perl6::Actions is HLL::Actions does STDActions { } } - method pod_content_toplevel($/) { - my $child := $.ast; - # make sure we don't push the same thing twice - if $child { - my $id := $/.from ~ "," ~ ~$/.to; - if !$*POD_BLOCKS_SEEN{$id} { - $*POD_BLOCKS.push($child); - $*POD_BLOCKS_SEEN{$id} := 1; - } - } - make $child; - } - - method pod_content:sym($/) { - make $.ast; - } - - # TODO The spaces arg from Grammar.nqp seems - # NOT to be handled. That shows up - # in testing for config continuation lines. - method pod_configuration($/) { - make Perl6::Pod::make_config($/); + method unitstart($/) { + # Use SET_BLOCK_OUTER_CTX (inherited from HLL::Actions) + # to set dynamic outer lexical context and namespace details + # for the compilation unit. + self.SET_BLOCK_OUTER_CTX($*UNIT_OUTER); } - method pod_block:sym($/) { - if $.Str ~~ /^defn/ { - make Perl6::Pod::defn($/, $delim-block); + method statementlist($/) { + my $past := QAST::Stmts.new( :node($/) ); + if $ { + my int $i := 0; + my int $e := nqp::elems($) - 1; + while $i <= $e { + my $ast := $[$i].ast; + if $ast { + if $ast.ann('statement_level') && $*statement_level { + $ast.ann('statement_level')(); + } + if $ast.ann('sink_ast') { + $ast := QAST::Want.new($ast, 'v', $ast.ann('sink_ast')); + $ast := UNWANTED($ast, 'statementlist/sink_ast') if $i < $e; + } + elsif $ast.ann('bare_block') { + if $i < $e { + $ast := UNWANTED(autosink($ast.ann('bare_block')), "statementlist/bare_block"); + } + elsif $*ESCAPEBLOCK { + $ast := WANTED($ast.ann('bare_block'),'statementlist/escape'); + } + else { + $ast := autosink($ast.ann('bare_block')); + } + } + else { + if nqp::istype($ast,QAST::Op) && ($ast.op eq 'while' || $ast.op eq 'until' || $ast.op eq 'repeat_while' || $ast.op eq 'repeat_until') { + $ast := UNWANTED($ast,'statementlist/loop'); # statement level loops never want return value + } + elsif $i == $e && $*ESCAPEBLOCK { + $ast := QAST::Stmt.new(autosink(WANTED($ast,'statementlist/else')), :returns($ast.returns)); + } + else { + $ast := QAST::Stmt.new(autosink($ast), :returns($ast.returns)); + } + } + $ast.node($[$i]); + $past.push( $ast ); + } + ++$i; + } } - else { - make Perl6::Pod::any_block($/, $delim-block); + if +$past.list < 1 { + $past.push(QAST::WVal.new( :value($*W.find_symbol(['Nil'])) )); } - } - - method pod_block:sym($/) { - make Perl6::Pod::raw_block($/); - } - - method pod_block:sym($/) { - make Perl6::Pod::table($/, $delim-block); - } - - method pod_block:sym($/) { - # TODO add numbered-alias handling - my $config := $.ast; - my @contents := $.ast; - @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; - make Perl6::Pod::serialize_object('Pod::Block::Code', - :@contents,:$config).compile_time_value - } - - method delimited_code_content($/) { - my @contents := []; - for $/[0] { - if $_ { - nqp::splice(@contents, - Perl6::Pod::pod_strings_from_matches($_), - +@contents, 0); - nqp::push(@contents, $*W.add_constant( - 'Str', 'str', ~$_ - ).compile_time_value); - } else { - @contents.push($*W.add_constant('Str', 'str', "\n").compile_time_value); + else { + my $pl := $past[+@($past) - 1]; + if $pl.sunk { + $past.push(QAST::WVal.new( :value($*W.find_symbol(['Nil'])) )); + } + else { + $pl.final(1); + $past.returns($pl.returns); } } - make @contents; + make $past; } - method pod_block:sym($/) { - if $.Str ~~ /^defn/ { - make Perl6::Pod::defn($/, $para-block); + # Produces a LoL from a semicolon list + method semilist($/) { + if $ { + my $past := QAST::Stmts.new( :node($/) ); + if $ > 1 { + my $l := QAST::Op.new( :name('&infix:<,>'), :op('call') ); + for $ { + my $sast := $_.ast || QAST::WVal.new( :value($*W.find_symbol(['Nil'])) ); + $l.push(wanted($sast, 'semilist')); + } + $past.push($l); + $past.annotate('multislice', 1); + } + else { + $past.push($[0].ast || QAST::WVal.new( :value($*W.find_symbol(['Nil'])) )); + } + make $past; } else { - make Perl6::Pod::any_block($/, $para-block); + make QAST::Op.new( :op('call'), :name('&infix:<,>') ); } } - method pod_block:sym($/) { - make Perl6::Pod::raw_block($/); - } - - method pod_block:sym($/) { - make Perl6::Pod::table($/, $para-block); - } - - method pod_block:sym($/) { - # TODO make config via call to make_config in Pod.nqp - my $config := $.ast; - my @contents := []; - for $ { - nqp::splice(@contents, $_.ast, +@contents, 0); + method sequence($/) { + my $past := QAST::Stmts.new( :node($/) ); + if $ { + for $ { $past.push($_.ast) if $_.ast; } } - @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; - make Perl6::Pod::serialize_object('Pod::Block::Code', - :@contents,:$config).compile_time_value; + unless +@($past) { + $past.push( QAST::Op.new( :op('call'), :name('&infix:<,>') ) ); + } + make $past; } - method pod_block:sym($/) { - if $.Str ~~ /^defn/ { - make Perl6::Pod::defn($/, $abbrev-block); - } - else { - make Perl6::Pod::any_block($/, $abbrev-block); + method statement($/) { + my $past; + if $ { + my $mc := $; + my $ml := $; + $past := $.ast; + if $mc { + if ~$mc eq 'with' { + make thunkity_thunk($/,'.b',QAST::Op.new( :op('call'), :name('&infix:')),[$mc,$]); + return; + } + elsif ~$mc eq 'without' { + make thunkity_thunk($/,'.b',QAST::Op.new( :op('call'), :name('&infix:')),[$mc,$]); + return; + } + my $mc_ast := $mc.ast; + if $past.ann('bare_block') { + my $cond_block := $past.ann('past_block'); + remove_block($*W.cur_lexpad(), $cond_block); + $cond_block.blocktype('immediate'); + $past := $cond_block; + } + $mc_ast.push($past); + $mc_ast.push(QAST::WVal.new( :value($*W.find_symbol(['Empty'])) )); + $past := $mc_ast; + } + if $ml { + $past.okifnil(1); + $past[0].okifnil(1) if +@($past); + my $cond := $ml.ast; + if ~$ml eq 'given' { + unless $past.ann('bare_block') { + $past := make_topic_block_ref($/, $past, migrate_stmt_id => $*STATEMENT_ID); + } + $past := QAST::Op.new( :op('call'), block_closure($past), $cond ); + } + elsif ~$ml eq 'for' { + unless $past.ann('past_block') { + $past := make_topic_block_ref($/, $past, migrate_stmt_id => $*STATEMENT_ID); + } + my $fornode := QAST::Op.new( + :op, :node($/), + $cond, + block_closure($past), + ); + $past := QAST::Want.new( + $fornode, + 'v', QAST::Op.new(:op, $fornode), + ); + $past[2].sunk(1); + my $sinkee := $past[0]; + $past.annotate('statement_level', -> { + UNWANTED($sinkee, 'force for mod'); + $fornode.op('p6forstmt') if can-use-p6forstmt($fornode[1]); + $fornode.annotate('IterationEnd', $*W.find_symbol(['IterationEnd'])); + $fornode.annotate('Nil', $*W.find_symbol(['Nil'])); + }); + } + else { + $past := QAST::Op.new($cond, $past, :op(~$ml), :node($/) ); + } + } } - } + elsif $ { $past := $.ast; } + elsif $ { $past := $.ast; } + else { $past := 0; } - method pod_block:sym($/) { - make Perl6::Pod::raw_block($/); - } + if $past { + my $id := $*STATEMENT_ID; + $past.annotate('statement_id', $id); - method pod_block:sym($/) { - make Perl6::Pod::table($/, $abbrev-block); - } - - method pod_block:sym($/) { - my @contents := []; - for $ { - nqp::splice(@contents, $_.ast, +@contents, 0); - } - @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; - make Perl6::Pod::serialize_object( - 'Pod::Block::Code', :@contents - ).compile_time_value - } - - method pod_line ($/) { - my @contents := Perl6::Pod::pod_strings_from_matches($); - @contents.push($*W.add_constant( - 'Str', 'str', ~$ - ).compile_time_value); - make @contents; - } - - method pod_block:sym($/) { - $*W.install_lexical_symbol( - $*UNIT,'$=finish', nqp::hllizefor(~$, 'perl6')); - } - - method pod_content:sym($/) { - make Perl6::Pod::config($/); - } - - method pod_content:sym($/) { - my @ret := []; - for $ { - @ret.push($_.ast); - } - my $past := Perl6::Pod::serialize_array(@ret); - make $past.compile_time_value; - } - - method pod_textcontent:sym($/) { - my @contents := Perl6::Pod::pod_strings_from_matches($); - @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; - make Perl6::Pod::serialize_object('Pod::Block::Para', :@contents).compile_time_value - } - - method pod_textcontent:sym($/) { - my $s := $.Str; - my $t := subst($.Str, /\n$s/, "\n", :global); - $t := subst($t, /\n$/, ''); # chomp! - my $past := Perl6::Pod::serialize_object( - 'Pod::Block::Code', - :contents(Perl6::Pod::serialize_aos([$t]).compile_time_value), - ); - make $past.compile_time_value; - } - - method pod_formatting_code($/) { - if $ eq 'V' { - make ~$; - } elsif $ eq 'E' { - my @contents := []; - my @meta := []; - for $/[0] { - if $_ { - @contents.push(~$_); - @meta.push($*W.add_string_constant(~$_).compile_time_value); - #my $s := Perl6::Pod::str_from_entity(~$_); - #$s ?? @contents.push($s) && @meta.push(~$_) - # !! $/.worry("\"$_\" is not a valid HTML5 entity."); - } else { - my $n := $_ - ?? $_.made - !! nqp::codepointfromname(~$_); - if $n >= 0 { - @contents.push(nqp::chr($n)); - @meta.push($n); - } else { - $/.worry("\"$_\" is not a valid Unicode character name or code point."); - } - } - } - @contents := Perl6::Pod::serialize_aos(@contents).compile_time_value; - @meta := Perl6::Pod::serialize_array(@meta).compile_time_value; - make Perl6::Pod::serialize_object( - 'Pod::FormattingCode', - :type($*W.add_string_constant(~$).compile_time_value), - :@contents, - :@meta, - ).compile_time_value; - } else { - my @chars := Perl6::Pod::build_pod_chars($); - my @meta := []; - if $ eq 'X' { - for $/[0] { - my @tmp := []; - for $_ { - @tmp.push(~$_); - } - @meta.push(@tmp); - } - @meta := Perl6::Pod::serialize_aoaos(@meta).compile_time_value; - } else { - for $ { - @meta.push(~$_) - } - @meta := Perl6::Pod::serialize_aos(@meta).compile_time_value; - } - my @contents := Perl6::Pod::build_pod_strings([@chars]); - @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; - my $past := Perl6::Pod::serialize_object( - 'Pod::FormattingCode', - :type($*W.add_string_constant(~$).compile_time_value), - :@contents, - :meta(@meta), - ); - make $past.compile_time_value; - } - } - - method pod_string($/) { - make Perl6::Pod::build_pod_chars($); - } - - method pod_balanced_braces($/) { - if $ { - my @chars := Perl6::Pod::build_pod_chars($); - @chars.unshift(~$); - @chars.push(~$); - make @chars; - } else { - make ~$ - } - } - - method pod_string_character($/) { - if $ { - make $.ast - } elsif $ { - make $.ast - } else { - make ~$; - } - } - - method table_row($/) { - make ~$/ - } - - method table_row_or_blank($/) { - make ~$/ - } - - method unitstart($/) { - # Use SET_BLOCK_OUTER_CTX (inherited from HLL::Actions) - # to set dynamic outer lexical context and namespace details - # for the compilation unit. - self.SET_BLOCK_OUTER_CTX($*UNIT_OUTER); - } - - method statementlist($/) { - my $past := QAST::Stmts.new( :node($/) ); - if $ { - my int $i := 0; - my int $e := nqp::elems($) - 1; - while $i <= $e { - my $ast := $[$i].ast; - if $ast { - if $ast.ann('statement_level') && $*statement_level { - $ast.ann('statement_level')(); - } - if $ast.ann('sink_ast') { - $ast := QAST::Want.new($ast, 'v', $ast.ann('sink_ast')); - $ast := UNWANTED($ast, 'statementlist/sink_ast') if $i < $e; - } - elsif $ast.ann('bare_block') { - if $i < $e { - $ast := UNWANTED(autosink($ast.ann('bare_block')), "statementlist/bare_block"); - } - elsif $*ESCAPEBLOCK { - $ast := WANTED($ast.ann('bare_block'),'statementlist/escape'); - } - else { - $ast := autosink($ast.ann('bare_block')); - } - } - else { - if nqp::istype($ast,QAST::Op) && ($ast.op eq 'while' || $ast.op eq 'until' || $ast.op eq 'repeat_while' || $ast.op eq 'repeat_until') { - $ast := UNWANTED($ast,'statementlist/loop'); # statement level loops never want return value - } - elsif $i == $e && $*ESCAPEBLOCK { - $ast := QAST::Stmt.new(autosink(WANTED($ast,'statementlist/else')), :returns($ast.returns)); - } - else { - $ast := QAST::Stmt.new(autosink($ast), :returns($ast.returns)); - } - } - $ast.node($[$i]); - $past.push( $ast ); - } - ++$i; - } - } - if +$past.list < 1 { - $past.push(QAST::WVal.new( :value($*W.find_symbol(['Nil'])) )); - } - else { - my $pl := $past[+@($past) - 1]; - if $pl.sunk { - $past.push(QAST::WVal.new( :value($*W.find_symbol(['Nil'])) )); - } - else { - $pl.final(1); - $past.returns($pl.returns); - } - } - make $past; - } - - # Produces a LoL from a semicolon list - method semilist($/) { - if $ { - my $past := QAST::Stmts.new( :node($/) ); - if $ > 1 { - my $l := QAST::Op.new( :name('&infix:<,>'), :op('call') ); - for $ { - my $sast := $_.ast || QAST::WVal.new( :value($*W.find_symbol(['Nil'])) ); - $l.push(wanted($sast, 'semilist')); - } - $past.push($l); - $past.annotate('multislice', 1); - } - else { - $past.push($[0].ast || QAST::WVal.new( :value($*W.find_symbol(['Nil'])) )); - } - make $past; - } - else { - make QAST::Op.new( :op('call'), :name('&infix:<,>') ); - } - } - - method sequence($/) { - my $past := QAST::Stmts.new( :node($/) ); - if $ { - for $ { $past.push($_.ast) if $_.ast; } - } - unless +@($past) { - $past.push( QAST::Op.new( :op('call'), :name('&infix:<,>') ) ); - } - make $past; - } - - method statement($/) { - my $past; - if $ { - my $mc := $; - my $ml := $; - $past := $.ast; - if $mc { - if ~$mc eq 'with' { - make thunkity_thunk($/,'.b',QAST::Op.new( :op('call'), :name('&infix:')),[$mc,$]); - return; - } - elsif ~$mc eq 'without' { - make thunkity_thunk($/,'.b',QAST::Op.new( :op('call'), :name('&infix:')),[$mc,$]); - return; - } - my $mc_ast := $mc.ast; - if $past.ann('bare_block') { - my $cond_block := $past.ann('past_block'); - remove_block($*W.cur_lexpad(), $cond_block); - $cond_block.blocktype('immediate'); - $past := $cond_block; - } - $mc_ast.push($past); - $mc_ast.push(QAST::WVal.new( :value($*W.find_symbol(['Empty'])) )); - $past := $mc_ast; - } - if $ml { - $past.okifnil(1); - $past[0].okifnil(1) if +@($past); - my $cond := $ml.ast; - if ~$ml eq 'given' { - unless $past.ann('bare_block') { - $past := make_topic_block_ref($/, $past, migrate_stmt_id => $*STATEMENT_ID); - } - $past := QAST::Op.new( :op('call'), block_closure($past), $cond ); - } - elsif ~$ml eq 'for' { - unless $past.ann('past_block') { - $past := make_topic_block_ref($/, $past, migrate_stmt_id => $*STATEMENT_ID); - } - my $fornode := QAST::Op.new( - :op, :node($/), - $cond, - block_closure($past), - ); - $past := QAST::Want.new( - $fornode, - 'v', QAST::Op.new(:op, $fornode), - ); - $past[2].sunk(1); - my $sinkee := $past[0]; - $past.annotate('statement_level', -> { - UNWANTED($sinkee, 'force for mod'); - $fornode.op('p6forstmt') if can-use-p6forstmt($fornode[1]); - $fornode.annotate('IterationEnd', $*W.find_symbol(['IterationEnd'])); - $fornode.annotate('Nil', $*W.find_symbol(['Nil'])); - }); - } - else { - $past := QAST::Op.new($cond, $past, :op(~$ml), :node($/) ); - } - } - } - elsif $ { $past := $.ast; } - elsif $ { $past := $.ast; } - else { $past := 0; } - - if $past { - my $id := $*STATEMENT_ID; - $past.annotate('statement_id', $id); - - # only trace when running in source - if $/.pragma('trace') && !$*W.is_precompilation_mode { - my $code := ~$/; + # only trace when running in source + if $/.pragma('trace') && !$*W.is_precompilation_mode { + my $code := ~$/; # don't bother putting ops for activating it if $code eq 'use trace' { @@ -10244,130 +9985,404 @@ class Perl6::Actions is HLL::Actions does STDActions { $i++; } - # go through any remaining children and just migrate QAST::Blocks - my $qels := nqp::elems($qast); - while $i < $qels { - find_block_calls_and_migrate($cur_lexpad, $curry, $qast[$i]); - $i++; + # go through any remaining children and just migrate QAST::Blocks + my $qels := nqp::elems($qast); + while $i < $qels { + find_block_calls_and_migrate($cur_lexpad, $curry, $qast[$i]); + $i++; + } + + # Bake the signature for our curry + my %sig_info := hash(parameters => @params); + my $signature := $*W.create_signature_and_params: + $/, %sig_info, $curry, 'Mu'; + add_signature_binding_code($curry, $signature, @params); + + fatalize($curry[1]) if $*FATAL; + + # Create a code object for our curry + my $code := $*W.create_code_object: $curry, 'WhateverCode', $signature; + $qast := block_closure(reference_to_code_object($code, $curry)); + $qast.returns: $WhateverCode; + $qast.arity: nqp::elems(@params); + + # Hyperspace! + $qast := QAST::Op.new: :op, :name<&HYPERWHATEVER>, $qast + if $hyperwhatever; + $qast; + } + + sub find_block_calls_and_migrate($from, $to, $qast) { + if nqp::can($qast, 'ann') && $qast.ann('past_block') -> $block { + $to[0].push: $block; + remove_block($from, $block, :ignore-not-found); + } + elsif nqp::istype($qast, QAST::Node) { + for @($qast) { + find_block_calls_and_migrate($from, $to, $_); + } + } + } + + sub remove_block($from, $block, :$ignore-not-found) { + # Remove the QAST::Block $block from $from[0]; die if not found. + my @decls := $from[0].list; + my int $i := 0; + my int $n := nqp::elems(@decls); + while $i < $n { + my $consider := @decls[$i]; + if $consider =:= $block { + @decls[$i] := QAST::Op.new( :op('null') ); + return 1; + } + elsif nqp::istype($consider, QAST::Stmt) || nqp::istype($consider, QAST::Stmts) { + if $consider[0] =:= $block { + $consider[0] := QAST::Op.new( :op('null') ); + return 1; + } + } + $i++; + } + nqp::die('Internal error: failed to remove block') + unless $ignore-not-found; + } + + sub wrap_return_type_check($wrappee, $code_obj) { + my $ret := %*SIG_INFO; + return $wrappee if nqp::isconcrete($ret) || $ret.HOW.name($ret) eq 'Nil'; + QAST::Op.new( + :op('p6typecheckrv'), + $wrappee, + QAST::WVal.new( :value($code_obj) ), + QAST::WVal.new( :value($*W.find_symbol(['Nil'])) ) + ); + } + + sub wrap_return_handler($past) { + wrap_return_type_check( + QAST::Op.new( + :op, + # If we fall off the bottom, decontainerize if + # rw not set. + QAST::Op.new( :op('p6decontrv'), QAST::WVal.new( :value($*DECLARAND) ), $past ), + 'RETURN', + QAST::Op.new( :op ) + ), + $*DECLARAND + ) + } + + # Works out how to look up a type. If it's not generic and is in an SC, we + # statically resolve it. Otherwise, we punt to a runtime lexical lookup. + sub instantiated_type(@name, $/) { + CATCH { + $*W.throw($/, ['X', 'NoSuchSymbol'], symbol => join('::', @name)); + } + my $type := $*W.find_symbol(@name); + my $is_generic := 0; + try { $is_generic := $type.HOW.archetypes.generic } + my $past; + if $is_generic || nqp::isnull(nqp::getobjsc($type)) || istype($type.HOW,$/.how('package')) { + $past := $*W.symbol_lookup(@name, $/); + $past.set_compile_time_value($type); + } + else { + $past := QAST::WVal.new( :value($type) ); + } + $past.returns($type.WHAT); + $past + } + + # Ensures that the given PAST node has a value known at compile + # time and if so obtains it. Otherwise reports an error, involving + # the $usage parameter to make it more helpful. + sub compile_time_value_str($past, $usage, $/) { + if $past.has_compile_time_value { + nqp::unbox_s($past.compile_time_value); + } + else { + $*W.throw($/, ['X', 'Value', 'Dynamic'], what => $usage); + } + } + + sub istype($val, $type) { + try { return nqp::istype($val, $type) } + 0 + } + + #================================================================ + # POD-ONLY CODE HANDLERS + #================================================================ + # move ALL Pod-only action objects here + + method pod_content_toplevel($/) { + my $child := $.ast; + # make sure we don't push the same thing twice + if $child { + my $id := $/.from ~ "," ~ ~$/.to; + if !$*POD_BLOCKS_SEEN{$id} { + $*POD_BLOCKS.push($child); + $*POD_BLOCKS_SEEN{$id} := 1; + } + } + make $child; + } + + method pod_content:sym($/) { + make $.ast; + } + + # TODO The spaces arg from Grammar.nqp seems + # NOT to be handled. That shows up + # in testing for config continuation lines. + method pod_configuration($/) { + make Perl6::Pod::make_config($/); + } + + method pod_block:sym($/) { + if $.Str ~~ /^defn/ { + make Perl6::Pod::defn($/, $delim-block); + } + else { + make Perl6::Pod::any_block($/, $delim-block); + } + } + + method pod_block:sym($/) { + make Perl6::Pod::raw_block($/); + } + + method pod_block:sym($/) { + make Perl6::Pod::table($/, $delim-block); + } + + method pod_block:sym($/) { + # TODO add numbered-alias handling + my $config := $.ast; + my @contents := $.ast; + @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; + make Perl6::Pod::serialize_object('Pod::Block::Code', + :@contents,:$config).compile_time_value + } + + method delimited_code_content($/) { + my @contents := []; + for $/[0] { + if $_ { + nqp::splice(@contents, + Perl6::Pod::pod_strings_from_matches($_), + +@contents, 0); + nqp::push(@contents, $*W.add_constant( + 'Str', 'str', ~$_ + ).compile_time_value); + } else { + @contents.push($*W.add_constant('Str', 'str', "\n").compile_time_value); + } + } + make @contents; + } + + method pod_block:sym($/) { + if $.Str ~~ /^defn/ { + make Perl6::Pod::defn($/, $para-block); + } + else { + make Perl6::Pod::any_block($/, $para-block); + } + } + + method pod_block:sym($/) { + make Perl6::Pod::raw_block($/); + } + + method pod_block:sym($/) { + make Perl6::Pod::table($/, $para-block); + } + + method pod_block:sym($/) { + # TODO make config via call to make_config in Pod.nqp + my $config := $.ast; + my @contents := []; + for $ { + nqp::splice(@contents, $_.ast, +@contents, 0); + } + @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; + make Perl6::Pod::serialize_object('Pod::Block::Code', + :@contents,:$config).compile_time_value; + } + + method pod_block:sym($/) { + if $.Str ~~ /^defn/ { + make Perl6::Pod::defn($/, $abbrev-block); + } + else { + make Perl6::Pod::any_block($/, $abbrev-block); } + } - # Bake the signature for our curry - my %sig_info := hash(parameters => @params); - my $signature := $*W.create_signature_and_params: - $/, %sig_info, $curry, 'Mu'; - add_signature_binding_code($curry, $signature, @params); + method pod_block:sym($/) { + make Perl6::Pod::raw_block($/); + } - fatalize($curry[1]) if $*FATAL; + method pod_block:sym($/) { + make Perl6::Pod::table($/, $abbrev-block); + } - # Create a code object for our curry - my $code := $*W.create_code_object: $curry, 'WhateverCode', $signature; - $qast := block_closure(reference_to_code_object($code, $curry)); - $qast.returns: $WhateverCode; - $qast.arity: nqp::elems(@params); + method pod_block:sym($/) { + my @contents := []; + for $ { + nqp::splice(@contents, $_.ast, +@contents, 0); + } + @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; + make Perl6::Pod::serialize_object( + 'Pod::Block::Code', :@contents + ).compile_time_value + } - # Hyperspace! - $qast := QAST::Op.new: :op, :name<&HYPERWHATEVER>, $qast - if $hyperwhatever; - $qast; + method pod_line ($/) { + my @contents := Perl6::Pod::pod_strings_from_matches($); + @contents.push($*W.add_constant( + 'Str', 'str', ~$ + ).compile_time_value); + make @contents; } - sub find_block_calls_and_migrate($from, $to, $qast) { - if nqp::can($qast, 'ann') && $qast.ann('past_block') -> $block { - $to[0].push: $block; - remove_block($from, $block, :ignore-not-found); - } - elsif nqp::istype($qast, QAST::Node) { - for @($qast) { - find_block_calls_and_migrate($from, $to, $_); - } + method pod_block:sym($/) { + $*W.install_lexical_symbol( + $*UNIT,'$=finish', nqp::hllizefor(~$, 'perl6')); + } + + method pod_content:sym($/) { + make Perl6::Pod::config($/); + } + + method pod_content:sym($/) { + my @ret := []; + for $ { + @ret.push($_.ast); } + my $past := Perl6::Pod::serialize_array(@ret); + make $past.compile_time_value; } - sub remove_block($from, $block, :$ignore-not-found) { - # Remove the QAST::Block $block from $from[0]; die if not found. - my @decls := $from[0].list; - my int $i := 0; - my int $n := nqp::elems(@decls); - while $i < $n { - my $consider := @decls[$i]; - if $consider =:= $block { - @decls[$i] := QAST::Op.new( :op('null') ); - return 1; + method pod_textcontent:sym($/) { + my @contents := Perl6::Pod::pod_strings_from_matches($); + @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; + make Perl6::Pod::serialize_object('Pod::Block::Para', :@contents).compile_time_value + } + + method pod_textcontent:sym($/) { + my $s := $.Str; + my $t := subst($.Str, /\n$s/, "\n", :global); + $t := subst($t, /\n$/, ''); # chomp! + my $past := Perl6::Pod::serialize_object( + 'Pod::Block::Code', + :contents(Perl6::Pod::serialize_aos([$t]).compile_time_value), + ); + make $past.compile_time_value; + } + + method pod_formatting_code($/) { + if $ eq 'V' { + make ~$; + } elsif $ eq 'E' { + my @contents := []; + my @meta := []; + for $/[0] { + if $_ { + @contents.push(~$_); + @meta.push($*W.add_string_constant(~$_).compile_time_value); + #my $s := Perl6::Pod::str_from_entity(~$_); + #$s ?? @contents.push($s) && @meta.push(~$_) + # !! $/.worry("\"$_\" is not a valid HTML5 entity."); + } else { + my $n := $_ + ?? $_.made + !! nqp::codepointfromname(~$_); + if $n >= 0 { + @contents.push(nqp::chr($n)); + @meta.push($n); + } else { + $/.worry("\"$_\" is not a valid Unicode character name or code point."); + } + } } - elsif nqp::istype($consider, QAST::Stmt) || nqp::istype($consider, QAST::Stmts) { - if $consider[0] =:= $block { - $consider[0] := QAST::Op.new( :op('null') ); - return 1; + @contents := Perl6::Pod::serialize_aos(@contents).compile_time_value; + @meta := Perl6::Pod::serialize_array(@meta).compile_time_value; + make Perl6::Pod::serialize_object( + 'Pod::FormattingCode', + :type($*W.add_string_constant(~$).compile_time_value), + :@contents, + :@meta, + ).compile_time_value; + } else { + my @chars := Perl6::Pod::build_pod_chars($); + my @meta := []; + if $ eq 'X' { + for $/[0] { + my @tmp := []; + for $_ { + @tmp.push(~$_); + } + @meta.push(@tmp); + } + @meta := Perl6::Pod::serialize_aoaos(@meta).compile_time_value; + } else { + for $ { + @meta.push(~$_) } + @meta := Perl6::Pod::serialize_aos(@meta).compile_time_value; } - $i++; + my @contents := Perl6::Pod::build_pod_strings([@chars]); + @contents := Perl6::Pod::serialize_array(@contents).compile_time_value; + my $past := Perl6::Pod::serialize_object( + 'Pod::FormattingCode', + :type($*W.add_string_constant(~$).compile_time_value), + :@contents, + :meta(@meta), + ); + make $past.compile_time_value; } - nqp::die('Internal error: failed to remove block') - unless $ignore-not-found; } - sub wrap_return_type_check($wrappee, $code_obj) { - my $ret := %*SIG_INFO; - return $wrappee if nqp::isconcrete($ret) || $ret.HOW.name($ret) eq 'Nil'; - QAST::Op.new( - :op('p6typecheckrv'), - $wrappee, - QAST::WVal.new( :value($code_obj) ), - QAST::WVal.new( :value($*W.find_symbol(['Nil'])) ) - ); + method pod_string($/) { + make Perl6::Pod::build_pod_chars($); } - sub wrap_return_handler($past) { - wrap_return_type_check( - QAST::Op.new( - :op, - # If we fall off the bottom, decontainerize if - # rw not set. - QAST::Op.new( :op('p6decontrv'), QAST::WVal.new( :value($*DECLARAND) ), $past ), - 'RETURN', - QAST::Op.new( :op ) - ), - $*DECLARAND - ) + method pod_balanced_braces($/) { + if $ { + my @chars := Perl6::Pod::build_pod_chars($); + @chars.unshift(~$); + @chars.push(~$); + make @chars; + } else { + make ~$ + } } - # Works out how to look up a type. If it's not generic and is in an SC, we - # statically resolve it. Otherwise, we punt to a runtime lexical lookup. - sub instantiated_type(@name, $/) { - CATCH { - $*W.throw($/, ['X', 'NoSuchSymbol'], symbol => join('::', @name)); - } - my $type := $*W.find_symbol(@name); - my $is_generic := 0; - try { $is_generic := $type.HOW.archetypes.generic } - my $past; - if $is_generic || nqp::isnull(nqp::getobjsc($type)) || istype($type.HOW,$/.how('package')) { - $past := $*W.symbol_lookup(@name, $/); - $past.set_compile_time_value($type); - } - else { - $past := QAST::WVal.new( :value($type) ); + method pod_string_character($/) { + if $ { + make $.ast + } elsif $ { + make $.ast + } else { + make ~$; } - $past.returns($type.WHAT); - $past } - # Ensures that the given PAST node has a value known at compile - # time and if so obtains it. Otherwise reports an error, involving - # the $usage parameter to make it more helpful. - sub compile_time_value_str($past, $usage, $/) { - if $past.has_compile_time_value { - nqp::unbox_s($past.compile_time_value); - } - else { - $*W.throw($/, ['X', 'Value', 'Dynamic'], what => $usage); - } + method table_row($/) { + make ~$/ } - sub istype($val, $type) { - try { return nqp::istype($val, $type) } - 0 + method table_row_or_blank($/) { + make ~$/ } + + + + #================================================================ + # end of class Perl6::Actions block + #================================================================ } class Perl6::QActions is HLL::Actions does STDActions { diff --git a/src/Perl6/Grammar.nqp b/src/Perl6/Grammar.nqp index cdd04750c3f..f7225757e89 100644 --- a/src/Perl6/Grammar.nqp +++ b/src/Perl6/Grammar.nqp @@ -473,6 +473,9 @@ role STD { } grammar Perl6::Grammar is HLL::Grammar does STD { + #================================================================ + # AMBIENT AND POD-COMMON CODE HANDLERS + #================================================================ my class SerializationContextId { my $count := 0; my $lock := NQPLock.new; @@ -714,1628 +717,1102 @@ grammar Perl6::Grammar is HLL::Grammar does STD { ] } - proto token comment { <...> } - - token comment:sym<#> { - '#' {} \N* - } + token install_doc_phaser { } - token comment:sym<#`(...)> { - '#`' {} - [ <.quibble(self.slang_grammar('Quote'))> || <.typed_panic: 'X::Syntax::Comment::Embedded'> ] + token vnum { + \w+ | '*' } - token comment:sym<#|(...)> { - '#|' - { - unless $*POD_BLOCKS_SEEN{ self.from() } { - $*POD_BLOCKS_SEEN{ self.from() } := 1; - if $*DECLARATOR_DOCS eq '' { - $*DECLARATOR_DOCS := $; - } else { - $*DECLARATOR_DOCS := nqp::concat($*DECLARATOR_DOCS, nqp::concat("\n", $)); - } - } - } + token version { + 'v' $=[+ % '.' '+'?] + # cheat because of LTM fail } - token comment:sym<#|> { - '#|' \h+ $=[\N*] - { - unless $*POD_BLOCKS_SEEN{ self.from() } { - $*POD_BLOCKS_SEEN{ self.from() } := 1; - if $*DECLARATOR_DOCS eq '' { - $*DECLARATOR_DOCS := $; - } else { - $*DECLARATOR_DOCS := nqp::concat($*DECLARATOR_DOCS, nqp::concat("\n", $)); - } - } - } - } + ## Top-level rules - token comment:sym<#=(...)> { - '#=' - { - self.attach_trailing_docs(~$); - } - } + 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 $*IN_RETURN := 0; # are we in a return? + :my $*HAS_SELF := ''; # is 'self' available? (for $.foo style calls) + :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 $*CODE_OBJECT; # the code object we're currently inside + :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; + :my $*ARG_FLAT_OK := 0; + :my $*WHENEVER_COUNT := -1; # -1 indicates whenever not valid here - token comment:sym<#=> { - '#=' \h+ $=[\N*] - { - self.attach_trailing_docs(~$); - } - } + # 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 composite 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 - method attach_leading_docs() { - # TODO allow some limited text layout here - if ~$*DOC ne '' { - my $cont := Perl6::Pod::serialize_aos( - [Perl6::Pod::normalize_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 - } + # Extras. + :my @*NQP_VIOLATIONS; # nqp::ops per line number + :my %*HANDLERS; # block exception handlers + :my $*IMPLICIT; # whether we allow an implicit param + :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 $*IN_PROTO := 0; # are we inside a proto? + :my $*NEXT_STATEMENT_ID := 1; # to give each statement an ID + :my $*IN_STMT_MOD := 0; # are we inside a statement modifier? + :my $*COMPILING_CORE_SETTING := 0; # are we compiling CORE.setting? + # TODO XXX: see https://github.com/rakudo/rakudo/issues/2432 + :my $*SET_DEFAULT_LANG_VER := 1; + :my %*SIG_INFO; # information about recent signature - method attach_trailing_docs($doc) { - # TODO allow some limited text layout here - unless $*POD_BLOCKS_SEEN{ self.from() } { - $*POD_BLOCKS_SEEN{ self.from() } := 1; - my $pod_block; - if $doc ne '' { - my $cont := Perl6::Pod::serialize_aos( - [Perl6::Pod::normalize_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(self.MATCH, $*PRECEDING_DECL, $pod_block, :trailing); - } - } - } + # Various interesting scopes we'd like to keep to hand. + :my $*GLOBALish; + :my $*PACKAGE; + :my $*UNIT; + :my $*UNIT_OUTER; + :my $*EXPORT; + # stack of packages, which the 'is export' needs + :my @*PACKAGES := []; - token pod_content_toplevel { - - } + # 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? + # TODO use these vars to implement S26 pod data block handling + :my $*DATA-BLOCKS := []; + :my %*DATA-BLOCKS := {}; - proto token pod_content { <...> } + # Quasis and unquotes + :my $*IN_QUASI := 0; # whether we're currently in a quasi block + :my $*MAIN := 'MAIN'; - token pod_content:sym { - * - - * - } + # performance improvement stuff + :my $*FAKE_INFIX_FOUND := 0; - # any number of paragraphs of text - # the paragraphs are separated by one - # or more pod_newlines - # each paragraph originally could have - # consisted of more than one line of - # text that were subsequently squeezed - # into one line - token pod_content:sym { - * + # for runaway detection + :my $*LASTQUOTE := [0,0]; - # TODO get first line if IN-DEFN-BLOCK - + % + + { + nqp::getcomp('perl6').reset_language_version(); + $*W.loading_and_symbol_setup($/) + } - * - } + <.finishpad> + <.bom>? + + - # not a block, just a directive - token pod_content:sym { - * - ^^ \h* '=config' \h+ $=\S+ - + - } + <.install_doc_phaser> - proto token pod_textcontent { <...> } + [ $ || <.typed_panic: 'X::Syntax::Confused'> ] - # for non-code (i.e., regular) text - token pod_textcontent:sym { - $=[ \h* ] - .to - $.from) <= $*VMARGIN }> + <.explain_mystery> + <.cry_sorrows> - $ = [ - \h* [ | $ ] - ] + + { $*W.mop_up_and_check($/) } } - token pod_textcontent:sym { - $=[ \h* ] - .to - $.from) > $*VMARGIN }> + method clonecursor() { + my $new := self.'!cursor_init'( + self.orig(), + :p(self.pos()), + :shared(self.'!shared'()), + :braid(self."!braid"()."!clone"())); + $new; + } - # TODO get first line if IN-DEFN-BLOCK - $ = [ - [ \N+]+ % [+ $] + rule lang-version { + :my $comp := nqp::getcomp('perl6'); + [ + <.ws>? 'use' {} # <-- update $/ so we can grab $ + # we parse out the numeral, since we could have "6d" + :my $version := nqp::radix(10,$[0],0,0)[0]; + [ + || { $*W.load-lang-ver: $, $comp } + || { $/.typed_panic: 'X::Language::Unsupported', + version => ~$ } + ] + || { + # This is the path we take when the user did not + # provide any `use v6.blah` lang version statement + $*W.load-lang-ver: 'v6', $comp if $*SET_DEFAULT_LANG_VER; + } ] } - 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'); - - - :my $endtag; - - $=['<'+ | '«'] { $*POD_IN_FORMATTINGCODE := 1 } - .Str) - nqp::ord("A"); - if !($*POD_ALLOW_FCODES +& (2 ** $codenum)) { - 0 - } elsif ~$ eq '«' { - $endtag := "»"; - $*POD_ANGLE_COUNT := -1; - 1 - } else { - my int $ct := nqp::chars($); - $endtag := nqp::x(">", $ct); - my $rv := $*POD_ANGLE_COUNT <= 0 || $*POD_ANGLE_COUNT >= $ct; - $*POD_ANGLE_COUNT := $ct; - $rv; - } - }> - { - if $.Str eq "V" || $.Str eq "C" { - $*POD_ALLOW_FCODES := 0; - } - } - [ eq 'E'}> - $=[ - - [ ne 'L' && $ ne 'D' && $ ne 'X' }> || ] - - ]* - ]? - [ - | eq 'L'}> \s* \| \s* $=[.]+ - | eq 'X'}> \s* \| \s* ( [$=[ >.]+] +%% \, ) +%% \; - | eq 'D'}> \s* \| \s* [$=[.]+] +%% \; - | eq 'E'}> ( | $=<[A..Z\s]>+ || $=<[A..Za..z]>+ ) +%% \; - ]? - [ $endtag || <.worry: "Pod formatting code $ missing endtag '$endtag'."> ] - } + rule statementlist($*statement_level = 0) { + :my $*LANG; + :my $*LEAF; + :my %*LANG := self.shallow_copy(self.slangs); # XXX deprecated + :my $*STRICT := nqp::getlexdyn('$*STRICT'); - token pod_balanced_braces { - - :my $endtag; + :dba('statement list') +# <.check_LANG_oopsies('statementlist')> + <.ws> + # Define this scope to be a new language. + [ - $=[ - || '<'+ - || '>'+ ]> - ] - ) < $*POD_ANGLE_COUNT || $*POD_ANGLE_COUNT < 0 }> - || - = 1 }> - $=['<'+] - ) == $*POD_ANGLE_COUNT || $*POD_ANGLE_COUNT < 0 }> - { - $endtag := nqp::x(">", nqp::chars($)); - } - $=[ *?] - '> $=[$endtag] + | $ + | > + | [ <.eat_terminator> ]* ] + <.set_braid_from(self)> # any language tweaks must not escape + } - token pod_string { - + + method shallow_copy(%hash) { + my %result; + for %hash { + %result{$_.key} := $_.value; + } + %result } - token pod_string_character { - || || $=[ \N || [ - \n [ - > || - - ] - ] + rule semilist { + :dba('list composer') + '' + [ + | > + | [<.eat_terminator> ]* ] } - proto token pod_block { <...> } - - token pod_configuration($spaces = '') { - [ [\n $spaces '=']? \h+ ]* - } - - token pod_block:sym { - ^^ - $ = [ \h* ] - '=begin' \h+ 'comment' {} - )> + + rule sequence { + :dba('sequence of statements') + '' [ - $ = [ .*? ] - ^^ $ '=end' \h+ - [ - 'comment' [ | $ ] - || $=? { - $/.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', - type => 'comment', - spaces => ~$, - instead => $ ?? ~$ !! '' - } - ] + | > + | [<.eat_terminator> ]* ] } - regex pod_block:sym { - ^^ - $ = [ \h* ] - '=begin' - [ - <.typed_panic('X::Syntax::Pod::BeginWithoutIdentifier')> - ]? - \h+ + token label { + ':' <.ws> { - $*VMARGIN := $.to - $.from; - } - :my $*ALLOW_INLINE_CODE := 0; - $ = [ - { - $*ALLOW_INLINE_CODE := 1; + $*LABEL := ~$; + if $*W.already_declared('my', self.package, $*W.cur_lexpad(), [$*LABEL]) { + $*W.throw($/, ['X', 'Redeclaration'], symbol => $*LABEL); } - || - ] - :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES'); - )> + - [ - # TODO need first line to check for ws-separated '#' - * - ^^ $ '=end' \h+ - [ - $ [ | $ ] - || $=? { - $/.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', - type => ~$, - spaces => ~$, - instead => $ ?? ~$ !! '' - } - ] - ] + 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($orig, self.from(), :cache(1)); + 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_if_no_sc($label); + $*W.install_lexical_symbol($*W.cur_lexpad(), $*LABEL, $label); + } } + token statement($*LABEL = '') { + :my $*QSIGIL := ''; + :my $*SCOPE := ''; - token pod_block:sym { - ^^ - $ = [ \h* ] - '=begin' \h+ 'table' {} - :my $*POD_ALLOW_FCODES := nqp::getlexdyn('$*POD_ALLOW_FCODES'); - )> + + # NOTE: annotations that use STATEMENT_ID often also need IN_STMT_MOD annotation, in order + # to correctly migrate QAST::Blocks in constructs inside topics of statement modifiers + :my $*STATEMENT_ID := $*NEXT_STATEMENT_ID++; + :my $*IN_STMT_MOD := nqp::getlexdyn('$*IN_STMT_MOD'); + + :my $*ESCAPEBLOCK := 0; + :my $actions := self.slang_actions('MAIN'); + + | $ > + + [ - [ $=<.table_row_or_blank> ]* - ^^ \h* '=end' \h+ - [ - 'table' [ | $ ] - || $=? { - $/.typed_panic: 'X::Syntax::Pod::BeginWithoutEnd', - type => 'table', - spaces => ~$, - instead => $ ?? ~$ !! '' + |