class NQP::Actions is HLL::Actions { sub xblock_immediate($xblock) { $xblock[1] := block_immediate($xblock[1]); $xblock; } sub block_immediate($block) { $block.blocktype('immediate'); unless $block.symtable() { my $stmts := QAST::Stmts.new( :node($block.node) ); for $block.list { $stmts.push($_); } $block := $stmts; } $block; } sub default_for($sigil) { if $sigil eq '@' { QAST::Op.new( :op('list') ) } elsif $sigil eq '%' { QAST::Op.new( :op('hash') ) } else { my $default; try { $default := QAST::WVal.new( :value($*W.find_sym(['NQPMu'])) ); CATCH { $default := QAST::Op.new( :op('null') ) } } $default } } sub default_value_for_prim($prim) { $prim == 1 ?? QAST::IVal.new( :value(0) ) !! $prim == 2 ?? QAST::NVal.new( :value(0.0) ) !! QAST::SVal.new( :value('') ) } method TOP($/) { make $.made; } method deflongname($/) { make $ ?? ~$ ~ ':' ~ $.made.named ~ colonpair_str($.made) !! ~$/; } sub colonpair_str($ast) { my $s; if nqp::istype($ast, QAST::Op) { my @parts; for $ast.list { @parts.push($_.value) } $s := join(' ', @parts) } else { $s := $ast.value } $s ~~ /<[ < > ]>/ ?? '«' ~ $s ~ '»' !! '<' ~ $s ~ '>'; } method comp_unit($/) { my $mainline := $.ast; my $unit := $*W.pop_lexpad(); $unit.name(''); # If our caller wants to know the mainline ctx, provide it here. # (CTXSAVE is inherited from HLL::Actions.) Don't do this when # there was an explicit {YOU_ARE_HERE}. unless $*HAS_YOU_ARE_HERE { $unit.push( self.CTXSAVE() ); } # Detect if we're the main unit by if we were given any args. If so, # register the mainline as a module (so trying to use ourself in the # program will not explode). If we have a MAIN sub, call it at end of # mainline. $unit.unshift(QAST::Var.new( :scope('lexical'), :name('@ARGS'), :decl('param'), :slurpy(1) )); if $*MAIN_SUB { $mainline.push(QAST::Op.new( :op('if'), QAST::Var.new( :scope('lexical'), :name('@ARGS') ), QAST::Op.new( :op('call'), :name('&' ~ $*MAIN_SUB.name), QAST::Var.new( :scope('lexical'), :name('@ARGS'), :flat(1) ) ) )); } # Push mainline statements into UNIT. $unit.push($mainline); # Load the needed libraries. $unit.push($*W.libs()); # Wrap everything in a QAST::CompUnit. my $compunit := QAST::CompUnit.new( :hll('nqp'), # Serialization related bits. :sc($*W.sc()), :code_ref_blocks($*W.code_ref_blocks()), :compilation_mode($*W.is_precompilation_mode()), :pre_deserialize($*W.load_dependency_tasks()), :post_deserialize($*W.fixup_tasks()), # If this unit is loaded as a module, we want it to automatically # execute the mainline code above after all other initializations # have occurred. :load(QAST::Op.new( :op('call'), QAST::BVal.new( :value($unit) ) )), # If we're executed as the mainline, get the command line args # and pass them along. :main(QAST::Stmts.new( QAST::Op.new( :op('call'), QAST::BVal.new( :value($unit) ), QAST::Var.new( :name('ARGS'), :scope('local'), :decl('param'), :slurpy(1), :flat(1) ) ) )), # Finally, UNIT, which in turn contains all of the other program # elements. $unit ); $*W.cleanup(); make $compunit; } method statementlist($/) { my $ast_list := QAST::Stmts.new( :node($/) ); if $ { for $ { my $ast := $_.ast; my $sunk := $ast.ann('sink'); $ast := $sunk if nqp::defined($sunk); if $ast.ann('bareblock') { $ast := block_immediate($ast[0]); } $ast := QAST::Stmts.new($ast, :node($_)) if nqp::istype($ast, QAST::Node); $ast_list.push( $ast ); } } else { $ast_list.push(default_for('$')); } make $ast_list; } method statement($/, $key?) { my $ast; if $ { my $mc := $; my $ml := $; $ast := $.ast; if $mc { $ast := QAST::Op.new($mc.ast, $ast, :op(~$mc), :node($/) ); } if $ml { if ~$ml eq 'for' { $ast := QAST::Block.new( :blocktype('immediate'), QAST::Var.new( :name('$_'), :scope('lexical'), :decl('param') ), $ast); $ast.symbol('$_', :scope('lexical') ); $ast.arity(1); $ast := QAST::Op.new($ml.ast, $ast, :op(~$ml), :node($/) ); } else { $ast := QAST::Op.new($ml.ast, $ast, :op(~$ml), :node($/) ); } } if $ast.ann('var_initialized') { # Variable declared and unconditionally initialized; can strip # the added just-to-be-safe initialization of the lexical and # just have the var decl. my $decls := $*W.cur_lexpad()[0]; $decls.push($decls.pop()[0]); # First child of bind node is var decl } } elsif $ { $ast := $.ast; } elsif $ { $ast := $.ast; } else { $ast := 0; } make $ast; } method xblock($/) { make QAST::Op.new( $.ast, $.ast, :op('if'), :node($/) ); } method pblock($/) { make $.ast; } method block($/) { make $.ast; } method blockoid($/) { my $BLOCK := $*W.pop_lexpad(); if $ { my $ast := $.ast; if %*HANDLERS { $ast := QAST::Op.new( :op('handle'), $ast ); my %handlers := %*HANDLERS; for sorted_keys(%handlers) { $ast.push($_); $ast.push(%handlers{$_}); } } $BLOCK.push($ast); $BLOCK.node($/); $BLOCK.annotate('handlers', %*HANDLERS) if %*HANDLERS; make $BLOCK; } else { if $*HAS_YOU_ARE_HERE { $/.panic('{YOU_ARE_HERE} may only appear once in a setting'); } $*HAS_YOU_ARE_HERE := 1; make $.ast; } } method newpad($/) { $*W.push_lexpad($/) } method outerctx($/) { unless nqp::defined(%*COMPILING<%?OPTIONS>) { # We haven't got a specified outer context already, so load a # setting. my $SETTING := $*W.load_setting(%*COMPILING<%?OPTIONS> // 'NQPCORE'); # If it exports HOWs, grab them. Also, if we're loading the # setting, also by default load Regex library (we can't load # this in the setting as Regex depends on the setting). unless %*COMPILING<%?OPTIONS> eq 'NULL' { import_HOW_exports($SETTING); unless %*COMPILING<%?OPTIONS> { if %*COMPILING<%?OPTIONS> -> $lib { $*W.load_module($lib, $*GLOBALish); } else { $*W.load_module('NQPP6QRegex', $*GLOBALish); } } } } self.SET_BLOCK_OUTER_CTX($*W.cur_lexpad()); } sub import_HOW_exports($UNIT) { # See if we've exported any HOWs. if nqp::existskey($UNIT, 'EXPORTHOW') { for $UNIT.WHO { $*LANG.set_how($_.key, $_.value); } } } method you_are_here($/) { make self.CTXSAVE(); } ## Statement control method statement_control:sym($/) { my $module := $*W.load_module(~$, $*GLOBALish); if nqp::defined($module) { $*W.import($module.WHO.WHO) if nqp::existskey($module, 'EXPORT') && nqp::existskey($module.WHO, 'DEFAULT'); import_HOW_exports($module); } make QAST::Stmts.new(); } method statement_control:sym($/) { my $count := nqp::elems($) - 1; my $ast := xblock_immediate( $[$count].ast ); if $ { $ast.push( block_immediate( $.ast ) ); } # build if/then/elsif structure while $count > 0 { --$count; my $else := $ast; $ast := xblock_immediate( $[$count].ast ); $ast.push($else); } make $ast; } method statement_control:sym($/) { my $ast := xblock_immediate( $.ast ); $ast.op('unless'); $/.prune(); make $ast; } method statement_control:sym($/) { my $ast := xblock_immediate( $.ast ); $ast.op(~$); if $*LABEL { $ast.push(QAST::WVal.new( :value($*W.find_sym([$*LABEL])), :named('label') )); } elsif !$*CONTROL_USED { $ast.push(QAST::IVal.new( :value(1), :named('nohandler') )); } $/.prune(); make $ast; } method statement_control:sym($/) { my $op := 'repeat_' ~ ~$; my $ast; if $ { $ast := xblock_immediate( $.ast ); $ast.op($op); } else { $ast := QAST::Op.new( $.ast, block_immediate( $.ast ), :op($op), :node($/) ); } if $*LABEL { $ast.push(QAST::WVal.new( :value($*W.find_sym([$*LABEL])), :named('label') )); } elsif !$*CONTROL_USED { $ast.push(QAST::IVal.new( :value(1), :named('nohandler') )); } $/.prune(); make $ast; } method statement_control:sym($/) { my $ast := $.ast; $ast.op('for'); my $block := $ast[1]; unless $block.arity { $block[0].push( QAST::Var.new( :name('$_'), :scope('lexical'), :decl('param') ) ); $block.symbol('$_', :scope('lexical') ); $block.arity(1); } $block.blocktype('immediate'); if $*LABEL { $ast.push(QAST::WVal.new( :value($*W.find_sym([$*LABEL])), :named('label') )); } elsif !$*CONTROL_USED { $ast.push(QAST::IVal.new( :value(1), :named('nohandler') )); } $/.prune(); make $ast; } method statement_control:sym($/) { my $block := $.ast; set_block_handler($/, $block, 'CATCH'); $/.prune(); make default_for('$'); } method statement_control:sym($/) { my $block := $.ast; set_block_handler($/, $block, 'CONTROL'); $/.prune(); make default_for('$'); } sub set_block_handler($/, $block, $type) { if nqp::existskey(%*HANDLERS, $type) { nqp::die("Duplicate $type handler in block"); } unless $block.arity { $block.unshift( QAST::Op.new( :op('bind'), QAST::Var.new( :scope('lexical'), :name('$!'), :decl('var') ), QAST::Var.new( :scope('lexical'), :name('$_')), ), ); $block.unshift(QAST::Var.new( :name('$_'), :scope('lexical'), :decl('param') )); $block.symbol('$_', :scope('lexical') ); $block.symbol('$!', :scope('lexical') ); $block.arity(1); } $block.blocktype('declaration'); %*HANDLERS{$type} := QAST::Stmts.new( QAST::Op.new( :op('call'), $block, QAST::Op.new( :op('exception') ), ), default_for('$')); } method statement_prefix:sym($/) { make $*W.run_begin_block($.ast); $/.prune(); } method statement_prefix:sym($/) { $*W.cur_lexpad().push($.ast); make QAST::Stmts.new(); $/.prune(); } method statement_prefix:sym($/) { my $ast := $.ast; if nqp::istype($ast, QAST::Block) { my $handlers := $ast.ann('handlers'); if $handlers && nqp::existskey($handlers, 'CATCH') { make $ast; return 1; } else { $ast.blocktype('immediate'); } } make QAST::Op.new( :op('handle'), $ast, 'CATCH', QAST::Stmts.new( default_for('$') )); $/.prune(); } method blorst($/) { make $ ?? block_immediate($.ast) !! $.ast; $/.prune(); } # Statement modifiers method statement_mod_cond:sym($/) { make $.ast; } method statement_mod_cond:sym($/) { make $.ast; } method statement_mod_loop:sym($/) { make $.ast; } method statement_mod_loop:sym($/) { make $.ast; } ## Terms method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make $.ast; } method term:sym($/) { make QAST::Op.new( :op('takeclosure'), $.ast ); } method fatarrow($/) { my $ast := $.ast; $ast.named( $.Str ); make $ast; $/.prune; } method colonpair($/) { if $ { $.ast.named(~$); make $.ast; } else { my $ast := $ ?? $.ast !! QAST::IVal.new( :value( !$ ) ); $ast.named( ~$ ); make $ast; } $/.prune; } method variable($/) { my $ast; if $ { $ast := $.ast; $ast.unshift(QAST::VarWithFallback.new( :name('$/'), :scope('lexical'), :fallback(default_for('$')) )); } else { my @name := NQP::Compiler.parse_name(~$/); if +@name > 1 { if $ { $/.panic("Twigil not allowed on multi-part name"); } $ast := lexical_package_lookup(@name, $/); } elsif $ eq '*' { my $global_fallback := QAST::Op.new( :op('ifnull'), lexical_package_lookup(['GLOBAL', ~$ ~ $], $/), QAST::Op.new( :op('die_s'), QAST::SVal.new( :value('Contextual ' ~ ~$/ ~ ' not found') ) )); $ast := QAST::VarWithFallback.new( :name(~@name.pop), :scope('contextual'), :fallback($global_fallback) ); } elsif $ eq '!' { my $name := ~@name.pop; my $ch; my $package := $/.package; if $*PKGDECL eq 'role' { $ch := QAST::Var.new( :name('$?CLASS'), :scope('typevar') ); $ch.set_compile_time_value($package); } else { $ch := QAST::WVal.new( :value($package) ); } $ast := QAST::Var.new( :name($name), :scope('attribute'), QAST::Op.new( :op('decont'), QAST::Var.new( :name('self'), :scope('lexical') ) ), $ch ); # Make sure the attribute exists and add type info. unless $*IN_DECL { my $attr; for $package.HOW.attributes($package, :local(1)) { if $_.name eq $name { $attr := $_; last; } } if nqp::defined($attr) { if nqp::can($attr, 'type') { $ast.returns($attr.type); } } else { $/.panic("Attribute '$name' not declared"); } } } elsif $ { my $name := ~$ eq '@' ?? 'list' !! ~$ eq '%' ?? 'hash' !! 'item'; $ast := QAST::Op.new( :op('callmethod'), :name($name), $.ast ); } elsif $*W.is_package(~@name[0]) { $ast := lexical_package_lookup(@name, $/); $ast.fallback( default_for( $ ) ); } else { my str $name := ~@name.pop; my int $is_lex := 0; if $*IN_DECL eq 'variable' || $name eq '$_' || $name eq '$/' || $name eq '$¢' || $name eq '$!' || $ eq '?' || ($is_lex := $*W.is_lexical($name)) { $ast := QAST::Var.new( :name($name), :scope($name eq '$?CLASS' ?? 'typevar' !! 'lexical') ); $ast.returns($*W.lexical_type($name)) if $is_lex; } else { $/.panic("Use of undeclared variable '$name'"); } } } make $ast; } method package_declarator:sym($/) { make $.ast } method package_declarator:sym($/) { make $.ast } method package_declarator:sym($/) { make $.ast } method package_declarator:sym($/) { make $.ast } method package_declarator:sym($/) { make $.ast } method package_declarator:sym($/) { make $.ast } method package_declarator:sym($/) { # Construct meta-object with specified metaclass, adding it to the # serialization context for this compilation unit. my $HOW := $*W.find_sym($); my %args; if $ { %args := ~$[0]; } my $PACKAGE := $*W.pkg_create_mo($HOW, :name(~$), |%args); # Install it in the current package or current lexpad as needed. if $*SCOPE eq 'our' || $*SCOPE eq '' { $*W.install_package_symbol($*OUTERPACKAGE, $, $PACKAGE); if +$ == 1 { $*W.install_lexical_symbol($*W.cur_lexpad(), ~$[0], $PACKAGE); } } elsif $*SCOPE eq 'my' { if +$ != 1 { $.panic("A my scoped package cannot have a multi-part name yet"); } $*W.install_lexical_symbol($*W.cur_lexpad(), ~$[0], $PACKAGE); } else { $/.panic("$*SCOPE scoped packages are not supported"); } make QAST::Stmts.new(); $/.prune; } method package_def($/) { # Get name and meta-object. my @ns := nqp::clone($); my $name := ~@ns.pop; my $how := $/.how($*PKGDECL); my $package := $/.package; # Get the body code. my $ast; if $ { $ast := $.ast; } else { $ast := $*W.pop_lexpad(); $ast.push($.ast); } # Evaluate everything in the package in-line unless this is a generic # type in which case it needs delayed evaluation. Normally, $?CLASS is # a fixed lexical, but for generic types it becomes a parameter. Also # for parametric types, pass along the role body block. if nqp::can($how, 'parametric') && $how.parametric($how) { $ast.blocktype('declaration_static'); my $params := QAST::Stmts.new( QAST::Var.new( :name('$?CLASS'), :scope('lexical'), :decl('param') ) ); if $ { for $ { $params.push($_.ast); } } $ast.unshift($params); $ast.push(QAST::Op.new( :op('curlexpad') )); $ast.symbol('$?CLASS', :scope('lexical')); $*W.pkg_set_body_block($package, $ast); } else { $ast.blocktype('immediate'); } # Add parent, if we have one; otherwise set default. if $ { my $parent; my $parent_found; try { $parent := $*W.find_sym(nqp::clone($)); $parent_found := 1; } if $parent_found { $*W.pkg_add_parent_or_role($package, "add_parent", $parent); } else { $/.panic("Could not find parent class '" ~ ~$ ~ "'"); } } elsif nqp::can($how, 'set_default_parent') { my $default := $*PKGDECL eq 'grammar' ?? ['NQPMatch'] !! ['NQPMu']; $*W.pkg_add_parent_or_role($package, "set_default_parent", $*W.find_sym($default)); } # Add any done roles. if $ { for $ { my $role; my $role_found; try { $role := $*W.find_sym(nqp::clone($_)); $role_found := 1; } if $role_found { $*W.pkg_add_parent_or_role($package, "add_role", $role); } else { $/.panic("Could not find role '" ~ ~$_ ~ "'"); } } } # Extra traits, if present. if $ { $package.HOW.set_nativesize($package, +$); } if $ { $package.HOW.set_unsigned($package, 1); } # Finally, compose. $*W.pkg_compose($package); # If it's a grammar, pre-compute the NFAs. if $*PKGDECL eq 'grammar' && nqp::can($package, '!precompute_nfas') { $package.'!precompute_nfas'(); } # Export if needed. if $ { $*EXPORT.WHO.WHO{$name} := $package; } make $ast; $/.prune; } method role_params($/) { for $ { my $var := $_.ast; $var.scope('lexical'); $var.decl('param'); $*W.cur_lexpad().symbol($var.name, :scope('lexical')); } } method scope_declarator:sym($/) { make $.ast; $/.prune } method scope_declarator:sym($/) { make $.ast; $/.prune } method scope_declarator:sym($/) { make $.ast; $/.prune } method scope_declarator:sym($/) { make $.ast; $/.prune } method scoped($/) { make $ ?? $.ast !! $ ?? $.ast !! $ ?? $.ast !! $.ast; $/.prune; } method declarator($/) { make $ ?? $.ast !! $.ast; } method multi_declarator:sym($/) { make $ ?? $.ast !! $.ast; $/.prune } method multi_declarator:sym($/) { make $ ?? $.ast !! $.ast; $/.prune } method multi_declarator:sym($/) { make $.ast; $/.prune } method variable_declarator($/) { my $ast := $.ast; my $sigil := $; my $name := $ast.name; my $BLOCK := $*W.cur_lexpad(); my $*DECLARAND_ATTR; if $name && $BLOCK.symbol($name) { $/.panic("Redeclaration of symbol ", $name); } if $*SCOPE eq 'has' { # Initializer not allowed. if $ { $/.panic('Initiailizers not supported on has-scoped variables'); } # Locate the type of meta-attribute we need. unless $/.know_how($*PKGDECL ~ '-attr') { $/.panic("$*PKGDECL packages do not support attributes"); } # Set up arguments for meta-attribute instantiation. my %lit_args; my %obj_args; %lit_args := $name; if $ { %obj_args := $*W.find_sym([~$]); } if $sigil eq '$' || $sigil eq '&' { if $ { %obj_args := %obj_args; } else { try %obj_args := $*W.find_sym(['NQPMu']); } } # Add it. $*DECLARAND_ATTR := $*W.pkg_add_attribute($/.package, $/.how($*PKGDECL ~ '-attr'), %lit_args, %obj_args); $ast := QAST::Stmts.new(); } elsif $*SCOPE eq 'our' { # Depending on if this was already considered our scoped, # we may or may not have got a node in $var that's set up # right already. We build it here just to be sure. if $ { $/.panic("Cannot put types on our-scoped variables"); } $name := ~$; $ast := lexical_package_lookup([$name], $/); $BLOCK.symbol($name, :scope('package') ); if $ { $ast := QAST::Op.new( :op('bind'), $ast, $.ast ); } } else { my $type; my $default; if $ { unless $sigil eq '$' { $/.panic("Only typed scalars are currently supported in NQP"); } $type := $*W.find_sym([~$]); if nqp::objprimspec($type) -> $prim_spec { $default := default_value_for_prim($prim_spec); } else { $/.panic("Only native types are currently supported/checked"); } } else { $default := default_for($sigil); } $BLOCK[0].push(QAST::Op.new( :op('bind'), :node($/), QAST::Var.new( :name($name), :scope('lexical'), :decl('var'), :returns($type) ), $default )); if $ { $ast := QAST::Op.new( :op('bind'), :node($/), $ast, $.ast ); $ast.annotate('var_initialized', 1); } $BLOCK.symbol($name, :scope('lexical'), :type($type) ); } # Apply traits. if $ { for $ { $_.ast()($/); } } make $ast; $/.prune; $/.prune; } method initializer($/) { make $.ast; $/.prune; } method constant_declarator($/) { my $sym := ~$; my $value := $ ?? $.ast.value !! $*W.evaluate_constant($.ast); if $*SCOPE eq 'my' || $*SCOPE eq 'our' { $*W.install_lexical_symbol($*W.cur_lexpad(), $sym, $value); if $*SCOPE eq 'our' { $*W.install_package_symbol($*PACKAGE, [$sym], $value); } } else { $/.panic("Cannot have a $*SCOPE scoped constant"); } make QAST::WVal.new( :$value ); } method routine_declarator:sym($/) { make $.ast; $/.prune } method routine_declarator:sym($/) { make $.ast; $/.prune } method routine_def($/) { # If it's just got * as a body, make a multi-dispatch enterer. # Otherwise, need to build a sub. my $ast; my int $onlystar; if $ { $ast := only_star_block(); $onlystar := 1; } else { $ast := $.ast; if $*RETURN_USED { $ast[1] := wrap_return_handler($ast[1]); } } $ast.blocktype('declaration'); my $block := $ast; if $ { my $name := ~$ ~ $.ast; $ast.name($name); if $*SCOPE eq '' || $*SCOPE eq 'my' || $*SCOPE eq 'our' { if $*MULTINESS eq 'multi' { # Does the current block have a proto? if $*SCOPE eq 'our' { nqp::die('a multi can not be our-scoped') } my $proto; my %sym := $*W.cur_lexpad().symbol('&' ~ $name); if %sym { $proto := %sym; } # Otherwise, no candidate holder, so add one. else { # Check we have a proto in scope. my $found_proto; for $*W.get_legacy_block_list() { my %sym := $_.symbol('&' ~ $name); if %sym { $proto := %sym; $found_proto := 1; } elsif %sym { $/.panic("Cannot declare a multi when an only is already in scope."); } } # If we didn't find a proto, error for now. unless $found_proto { $/.panic("Sorry, no proto sub in scope, and auto-generation of protos is not yet implemented."); } # Set up dispatch routine in this scope. nqp::die("Dispatcher derivation NYI"); } # Create a code object and attach the signature. my $code := $*W.create_code($ast, $name, 0); attach_multi_signature($code, $ast); # Add this candidate to the proto. $proto.add_dispatchee($code); # Ensure we emit the code block. # XXX We'll mark it static so the code object inside the # proto is captured correctly. Technically this is wrong, # as the multi may be nested in another sub. $ast.blocktype('declaration_static'); my $BLOCK := $*W.cur_lexpad(); $BLOCK[0].push($ast); } elsif $*MULTINESS eq 'proto' { # Create a candidate list holder for the dispatchees # this proto will work over, and install them along # with the proto. if $*SCOPE eq 'our' { nqp::die('our-scoped protos not yet implemented') } my $code := $*W.create_code($ast, $name, 1, :$onlystar); my $BLOCK := $*W.cur_lexpad(); $BLOCK[0].push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('&' ~ $name), :scope('lexical'), :decl('var') ), QAST::Op.new( :op('getcodeobj'), $ast ) )); $BLOCK.symbol('&' ~ $name, :scope('lexical'), :proto(1), :value($code), :declared(1) ); #?if !moar # Also stash the current lexical dispatcher and capture, for the {*} # to resolve. $block[0].push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('CURRENT_DISPATCH_CAPTURE'), :scope('lexical'), :decl('var') ), QAST::Op.new( :op('savecapture') ) )); $block[0].push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('&*CURRENT_DISPATCHER'), :scope('lexical'), :decl('var') ), QAST::Op.new( :op('getcodeobj'), QAST::Op.new( :op('curcode') ) ) )); #?endif } else { my $BLOCK := $*W.cur_lexpad(); $BLOCK[0].push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('&' ~ $name), :scope('lexical'), :decl('var') ), $ast )); $BLOCK.symbol('&' ~ $name, :scope('lexical'), :declared(1)); if $*SCOPE eq 'our' { # Need to install it at loadinit time but also re-bind # it per invocation. $*W.install_package_routine($/.package, $name, $ast); $BLOCK[0].push(QAST::Op.new( :op('bind'), lexical_package_lookup([$name], $/), QAST::Var.new( :name('&' ~ $name), :scope('lexical') ) )); # Static code object needs re-capturing also, as it's # our-scoped. $ast.blocktype('declaration_static'); # Also need to make sure it gets a code object so it's # in the SC. $*W.create_code($ast, $name, 0); } } $ast := QAST::Var.new( :name('&' ~ $name), :scope('lexical') ); } elsif $*SCOPE eq 'anon' { if $*W.is_precompilation_mode() { $*W.create_code($ast, $name, 0); } } else { $/.panic("$*SCOPE scoped routines are not supported yet"); } # Is it the MAIN sub? if $name eq 'MAIN' && $*MULTINESS ne 'multi' { $*MAIN_SUB := $block; } } else { if $*W.is_precompilation_mode() { $*W.create_code($ast, '', 0) } } make QAST::Op.new( :op('takeclosure'), $ast ).annotate_self('sink', $ast).annotate_self('block_ast', $block); # Apply traits. if $ { for $ { $_.ast()($/); } } $/.prune; } method method_def($/) { # If it's just got * as a body, make a multi-dispatch enterer. # Otherwise, build method block QAST. my $ast; my int $onlystar; my $package := $/.package; if $ { $ast := only_star_block(); $onlystar := 1; } else { $ast := $.ast; if $*RETURN_USED { $ast[1] := wrap_return_handler($ast[1]); } } $ast.blocktype('declaration_static'); # Always need an invocant. unless $ast.ann('signature_has_invocant') { $ast[0].unshift(QAST::Var.new( :name('self'), :scope('lexical'), :decl('param'), :returns($package) )); } $ast.symbol('self', :scope('lexical') ); # Install it where it should go (methods table / namespace). my $name := ""; if $ { $name := ~$ ~ ~$.ast; } elsif $ { if $*PKGDECL ne 'role' { $/.panic("Late-bound method name only valid in role"); } $name := "!!LATENAME!!" ~ ~$; } if $name ne "" { # Set name. $ast.name($name); # Insert it into the method table. my $meta_meth := $*MULTINESS eq 'multi' ?? 'add_multi_method' !! 'add_method'; my $is_dispatcher := $*MULTINESS eq 'proto'; my $code := $*W.create_code($ast, $name, $is_dispatcher, :$onlystar); if $*MULTINESS eq 'multi' { attach_multi_signature($code, $ast); } $*W.pkg_add_method($package, $meta_meth, $name, $code); $ast.annotate('code_obj', $code); # Install it in the package also if needed. if $*SCOPE eq 'our' { $*W.install_package_routine($package, $name, $ast); } #?if !moar # If it's a proto, also stash the current lexical dispatcher, for the {*} # to resolve. if $is_dispatcher { $ast[0].push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('CURRENT_DISPATCH_CAPTURE'), :scope('lexical'), :decl('var') ), QAST::Op.new( :op('savecapture') ) )); $ast[0].push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('&*CURRENT_DISPATCHER'), :scope('lexical'), :decl('var') ), QAST::Op.new( :op('getcodeobj'), QAST::Op.new( :op('curcode') ) ) )); } #?endif } # Install AST node in match object, then apply traits. make QAST::Op.new( :op('takeclosure'), $ast ).annotate_self( 'sink', $ast ).annotate_self( 'block_ast', $ast ).annotate_self('code_obj', $ast.ann('code_obj')); if $ { for $ { $_.ast()($/); } } $/.prune; } sub only_star_block() { my $ast := $*W.pop_lexpad(); #?if moar $ast.push(QAST::Op.new( :op('dispatch'), QAST::SVal.new( :value('boot-resume') ) )); #?endif #?if !moar $ast.push(QAST::Op.new( :op('invokewithcapture'), QAST::Op.new( :op('ifnull'), QAST::Op.new( :op('multicachefind'), QAST::Var.new( :name('$!dispatch_cache'), :scope('attribute'), QAST::Op.new( :op('getcodeobj'), QAST::Op.new( :op('curcode') ) ), QAST::WVal.new( :value($*W.find_sym(['NQPRoutine'])) ), ), QAST::Op.new( :op('usecapture') ) ), QAST::Op.new( :op('callmethod'), :name('dispatch'), QAST::Op.new( :op('getcodeobj'), QAST::Op.new( :op('curcode') ) ), QAST::Op.new( :op('savecapture') ) ) ), QAST::Op.new( :op('usecapture') ) )); #?endif $ast } sub attach_multi_signature($code_obj, $routine) { my $types := nqp::list(); my $definednesses := nqp::list(); for @($routine[0]) { if nqp::istype($_, QAST::Var) && $_.decl eq 'param' && !$_.named { $types.push($_.returns =:= NQPMu ?? nqp::null() !! $_.returns); my $defann := $_.ann('definedness'); $definednesses.push($defann eq 'D' ?? 1 !! $defann eq 'U' ?? 2 !! 0); } } $*W.set_routine_signature($code_obj, $types, $definednesses); } sub wrap_return_handler($ast) { QAST::Op.new( :op, $ast, 'RETURN', QAST::Op.new( :op ) ) } method signature($/) { my $BLOCK := $*W.cur_lexpad(); my $BLOCKINIT := $BLOCK[0]; if $ { my $inv := $.ast; $BLOCKINIT.push($inv); $BLOCKINIT.push(QAST::Op.new( :op('bind'), QAST::Var.new( :name('self'), :scope('lexical'), :decl('var') ), QAST::Var.new( :scope('lexical'), :name($inv.name) ) )); $BLOCK.annotate('signature_has_invocant', 1); } if $ { for $ { $BLOCKINIT.push($_.ast); } } $/.prune; } method parameter($/) { my $quant := $; my $ast; if $ { $ast := $.ast; if $quant ne '!' { $ast.default( default_for($) ); } } else { $ast := $.ast; if $quant eq '*' { $ast.slurpy(1); $ast.named( $ eq '%' ); } elsif $quant eq '?' { $ast.default( default_for($) ); } } if $ { if $quant eq '*' { $/.panic("Can't put default on slurpy parameter"); } if $quant eq '!' { $/.panic("Can't put default on required parameter"); } $ast.default( $[0].ast ); } unless $ast.default { $*W.cur_lexpad().arity( +$*W.cur_lexpad().arity + 1 ); } # Set the type of the parameter. if $ { my $type := $[0].ast.value; $ast.returns($type); if nqp::objprimspec($type) -> $prim { $*W.cur_lexpad().symbol($ast.name, :type($type)); if $ast.default && !$ { $ast.default(default_value_for_prim($prim)); } } } # Set definedness flag (XXX want a better way to do this). if $ { $ast.annotate('definedness', ~$[0]); } make $ast; $/.prune(); } method param_var($/) { my $name := ~$/; my $ast := QAST::Var.new( :name($name), :scope('lexical'), :decl('param'), :node($/) ); $*W.cur_lexpad().symbol($name, :scope('lexical') ); make $ast; } method named_param($/) { my $ast := $.ast; $ast.named( ~$ ); make $ast; } method typename($/) { # Try to locate the symbol. We'll emit a lookup via the SC so # the scope we emit code to do the symbol lookup in won't matter, # and so we can complain about non-existent type names. my @name := HLL::Compiler.parse_name(~$/); my $found := 0; try { my $sym := $*W.find_sym(@name); make QAST::WVal.new( :value($sym) ); $found := 1; } unless $found { $/.panic("Use of undeclared type '" ~ ~$/ ~ "'"); } $/.prune; } method trait($/) { make $.ast; $/.prune; } method trait_mod:sym($/) { if $ eq 'positional_delegate' { make -> $m { $*DECLARAND_ATTR.set_positional_delegate(1) }; } elsif $ eq 'associative_delegate' { make -> $m { $*DECLARAND_ATTR.set_associative_delegate(1) }; } elsif $ eq 'export' { make -> $match { my $ast := $match.ast; my $name := $ast.ann('block_ast').name; $*EXPORT.WHO.WHO{'&' ~ $name} := $ast.ann('code_obj') // $*W.create_code($ast.ann('block_ast'), $name, 0); }; } elsif $ eq 'box_target' { make -> $m { $*DECLARAND_ATTR.set_box_target(1) }; } else { $/.panic("Trait '$' not implemented"); } $/.prune; } method regex_declarator($/, $key?) { my $name; my $package := $/.package; if $ { $name := ~$.ast; } else { if $*PKGDECL ne 'role' { $/.panic("Late-bound method name only valid in role"); } $name := "!!LATENAME!!" ~ ~$; } my $ast; if $ { $ast := QAST::Block.new( :name($name), QAST::Op.new( QAST::Var.new( :name('self'), :scope('local'), :decl('param') ), QAST::SVal.new( :value($name) ), :name('!protoregex'), :op('callmethod') ), :blocktype('declaration_static'), :node($/) ); $*W.pkg_add_method($package, 'add_method', $name, $*W.create_code($ast, $name, 0, :code_type_name)); } else { my $block := $*W.pop_lexpad(); $block[0].unshift(QAST::Var.new(:name, :scope, :decl)); $block[0].push(QAST::Op.new( :op('bind'), QAST::Var.new(:name, :scope, :decl ), QAST::Var.new( :name, :scope('lexical') ))); $block[0].push(QAST::Var.new(:name<$¢>, :scope, :decl)); $block[0].push(QAST::Var.new(:name<$/>, :scope, :decl)); $block.symbol('$¢', :scope); $block.symbol('$/', :scope); my $code := %*RX; my $regex := $/.slang_actions('Regex').qbuildsub($.ast, $block, code_obj => $code); $regex.name($name); if $*PKGDECL && nqp::can($package.HOW, 'add_method') { # Add the actual method, marking it as a static declaration # since it's reachable through the method table. $block.blocktype('declaration_static'); $*W.pkg_add_method($package, 'add_method', $name, $code); } # If this appears in a role, its NFA may depend on generic args. # If it does, we store the generic version of it. if $*PKGDECL eq 'role' { my $gen_nfa := QRegex::NFA.new(); $gen_nfa.addnode($.ast, :vars_as_generic); if $gen_nfa.generic { $code.SET_GENERIC_NFA($gen_nfa); } } # In sink context, we don't need the Regex::Regex object. $ast := QAST::Op.new( :op, :name, lexical_package_lookup(['NQPRegexMethod'], $/), $regex); $ast.annotate('sink', $regex); } make $ast; } method dotty($/) { my $ast := $ ?? $[0].ast !! QAST::Op.new( :node($/) ); if $ { $ast.unshift($.ast); $ast.op('callmethod'); } elsif $ eq 'HOW' { $ast.op('how'); } elsif $ eq 'WHAT' { $ast.op('what'); } elsif $ eq 'WHO' { $ast.op('who'); } elsif $ eq 'REPR' { $ast.op('reprname'); } else { $ast.name(~$); $ast.op('callmethod'); } make $ast; $/.prune; } ## Terms method term:sym($/) { make QAST::Op.new( :op('decont'), QAST::Var.new( :name('self'), :scope('lexical') ) ); } method term:sym($/) { my $ast := $.ast; $ast.name('&' ~ ~$); $ast.node($/); make $ast; $/.prune; } method term:sym($/) { # See if it's a lexical symbol (known in any outer scope). my $var; if $*W.is_lexical(~$) { unless $ { try { my $sym := $*W.find_sym([~$]); unless nqp::isnull(nqp::getobjsc($sym)) { $var := QAST::WVal.new( :value($sym) ); } } } unless $var { $var := QAST::Var.new( :name(~$), :scope('lexical') ); } } else { my @ns := nqp::clone($); unless $ { try { my $sym := $*W.find_sym(@ns); unless nqp::isnull(nqp::getobjsc($sym)) { $var := QAST::WVal.new( :value($sym) ); } } } unless $var { $var := lexical_package_lookup(@ns, $/); } } # If it's a call, add the arguments. my $ast := $var; if $ { $ast := $[0].ast; $ast.unshift($var); } make $ast; $/.prune; } method term:sym($/) { my $op := ~$; my @args := $ ?? $[0].ast.list !! []; if $op eq 'handle' || $op eq 'handlepayload' { my int $i := 1; my int $n := nqp::elems(@args); while $i < $n { @args[$i] := @args[$i].value; $i := $i + 2; } } my $ast := QAST::Op.new( :op($op), |@args, :node($/) ); make $ast; $/.prune; } method term:sym($/) { make QAST::Op.new( :op('const'), :name(~$) ); $/.prune; } method term:sym($/) { #?if moar make QAST::Op.new( :op('dispatch'), QAST::SVal.new( :value('boot-resume') ) ); #?endif #?if !moar my $dc_name := QAST::Node.unique('dispatch_cap'); my $stmts := QAST::Stmts.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($dc_name), :scope('local'), :decl('var') ), QAST::Var.new( :name('CURRENT_DISPATCH_CAPTURE'), :scope('lexical') ) ), QAST::Op.new( :op('invokewithcapture'), QAST::Op.new( :op('ifnull'), QAST::Op.new( :op('multicachefind'), QAST::Var.new( :name('$!dispatch_cache'), :scope('attribute'), QAST::Var.new( :name('&*CURRENT_DISPATCHER'), :scope('lexical') ), QAST::WVal.new( :value($*W.find_sym(['NQPRoutine'])) ), ), QAST::Var.new( :name($dc_name), :scope('local') ) ), QAST::Op.new( :op('callmethod'), :name('dispatch'), QAST::Var.new( :name('&*CURRENT_DISPATCHER'), :scope('lexical') ), QAST::Var.new( :name($dc_name), :scope('local') ) ) ), QAST::Var.new( :name($dc_name), :scope('local') ) )); make QAST::Op.new( :op('locallifetime'), $stmts, $dc_name ); #?endif $/.prune; } method args($/) { make $.ast; } method arglist($/) { my $ast := QAST::Op.new( :op('call'), :node($/) ); if $ { my $expr := $.ast; if nqp::istype($expr, QAST::Op) && $expr.name eq '&infix:<,>' && !$expr.named { for $expr.list { $ast.push($_); } } else { $ast.push($expr); } } my $i := 0; my $n := nqp::elems($ast.list); while $i < $n { if nqp::istype($ast[$i], QAST::Op) && $ast[$i].name eq '&prefix:<|>' { $ast[$i] := $ast[$i][0]; $ast[$i].flat(1); $ast[$i].named(1) if nqp::istype($ast[$i], QAST::Var) && nqp::eqat($ast[$i].name, '%', 0); } ++$i; } make $ast; } method term:sym($/) { make $.ast; $/.prune; } method term:sym($/) { make $.ast; $/.prune; } method circumfix:sym<( )>($/) { make $ ?? $[0].ast !! QAST::Op.new( :op('list'), :node($/) ); $/.prune; } method circumfix:sym<[ ]>($/) { my $ast; if $ { $ast := $[0].ast; unless nqp::istype($ast, QAST::Op) && $ast.name eq '&infix:<,>' { $ast := QAST::Op.new( $ast, :op('list') ); } } else { $ast := QAST::Op.new( :op('list') ); } $ast.name('&circumfix:<[ ]>'); make $ast; $/.prune; } method circumfix:sym($/) { make $.ast; $/.prune } method circumfix:sym<« »>($/) { make $.ast; $/.prune } method circumfix:sym<{ }>($/) { if +$ > 0 { make QAST::Op.new( :op('takeclosure'), $.ast ).annotate_self('bareblock', 1); } elsif $ { make $.ast; } else { make default_for('%'); } $/.prune; } method coloncircumfix($/) { make $.ast } method semilist($/) { make $.ast; $/.prune; } method postcircumfix:sym<[ ]>($/) { make QAST::VarWithFallback.new( :scope('positional'), $.ast, :fallback(default_for('$')) ); } method postcircumfix:sym<{ }>($/) { make QAST::VarWithFallback.new( :scope('associative'), $.ast, :fallback(default_for('$')) ); } method postcircumfix:sym($/) { make QAST::VarWithFallback.new( :scope('associative'), $.ast, :fallback(default_for('$')) ); } method postcircumfix:sym<( )>($/) { make $.ast; } method value($/) { make $ ?? $.ast !! $.ast; $/.prune; } method number($/) { my $value := $ ?? $.ast !! $.ast; if ~$ eq '-' { $value := $ ?? nqp::neg_n($value) !! nqp::neg_i($value); } make $ ?? QAST::NVal.new( :value($value) ) !! QAST::IVal.new( :value($value) ); $/.prune; } method quote:sym($/) { make $.ast; } method quote:sym($/) { make $.ast; } method quote:sym($/) { make $.ast; } method quote:sym($/) { make $.ast; } method quote:sym($/) { make $.ast; } method quote:sym($/) { my $block := $*W.pop_lexpad(); $block[0].push(QAST::Var.new(:name, :scope, :decl)); $block[0].push(QAST::Op.new( :op('bind'), QAST::Var.new(:name, :scope, :decl('var') ), QAST::Var.new( :name, :scope('lexical') ))); $block[0].push(QAST::Var.new(:name<$¢>, :scope, :decl('var'))); $block[0].push(QAST::Var.new(:name<$/>, :scope, :decl('var'))); $block.symbol('$¢', :scope); $block.symbol('$/', :scope); my $regex := $/.slang_actions('Regex').qbuildsub($.ast, $block); my $ast := QAST::Op.new( :op, :name, lexical_package_lookup(['NQPRegex'], $/), $regex); # In sink context, we don't need the Regex::Regex object. $ast.annotate('sink', $regex); make $ast; } method quote_escape:sym<$>($/) { make $.ast; } method quote_escape:sym<{ }>($/) { make QAST::Op.new( :op('stringify'), block_immediate($.ast), :node($/) ); } method quote_escape:sym($/) { make "\c[27]"; } ## Operators method postfix:sym<.>($/) { make $.ast; } method term:sym($/) { make QAST::Op.new( :op('throwpayloadlex'), QAST::IVal.new( :value(nqp::const::CONTROL_RETURN) ), $ ?? $.ast !! QAST::WVal.new( :value($*W.find_sym(['NQPMu']))) ); } method prefix:sym($/) { make QAST::Op.new( QAST::Var.new( :name('$/'), :scope('lexical') ), :op('callmethod'), :name('make'), :node($/) ); } method term:sym($/) { my $ast := QAST::Op.new( :op('control'), :name('next') ); if $ { $ast.push(QAST::WVal.new( :value($*W.find_sym([$])), :named('label') )); } make $ast } method term:sym($/) { my $ast := QAST::Op.new( :op('control'), :name('last') ); if $ { $ast.push(QAST::WVal.new( :value($*W.find_sym([$])), :named('label') )); } make $ast } method term:sym($/) { my $ast := QAST::Op.new( :op('control'), :name('redo') ); if $ { $ast.push(QAST::WVal.new( :value($*W.find_sym([$])), :named('label') )); } make $ast } method infix:sym<~~>($/) { make QAST::Op.new( :op, :name, :node($/) ); } method infix:sym($/) { make QAST::Op.new( :op, :name, :node($/) ); } # Takes a multi-part name that we know is in a package and generates # QAST to look it up using NQP package semantics. sub lexical_package_lookup(@name, $/) { # Catch empty names and die helpfully. if +@name == 0 { $/.panic("Cannot compile empty name"); } # The final lookup will always be just a keyed access to a # symbol table. my $final_name := @name.pop(); my $lookup := QAST::VarWithFallback.new( :scope('associative'), QAST::SVal.new( :value(~$final_name) ) ); # If there's no explicit qualification, then look it up in the # current package, and fall back to looking in GLOBAL. if +@name == 0 { $lookup.unshift(QAST::Op.new( :op('who'), QAST::Var.new( :name('$?PACKAGE'), :scope('lexical') ) )); $lookup.fallback(QAST::Op.new( :op('ifnull'), QAST::Op.new( :op('atkey'), QAST::Op.new( :op('who'), QAST::WVal.new( :value($*GLOBALish) ) ), QAST::SVal.new( :value(~$final_name) ) ), default_for(nqp::substr(~$final_name, 0, 1)))); } # Otherwise, see if the first part of the name is lexically # known. If not, it's in GLOBAL. Also, if first part is GLOBAL # then strip it off. else { my $path; if $*W.is_lexical(~@name[0]) { try { my $first := @name.shift(); $path := QAST::WVal.new( :value($*W.find_sym([$first])) ); CATCH { $path := QAST::Var.new( :name($first), :scope('lexical') ); } } } else { $path := QAST::WVal.new( :value($*GLOBALish) ); } if @name[0] eq 'GLOBAL' { @name.shift(); } for @name { my $path_temp := QAST::Node.unique('pkg_lookup_tmp'); $path := QAST::Stmts.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($path_temp), :scope('local'), :decl('var') ), $path ), QAST::Op.new( :op('if'), QAST::Op.new( :op('existskey'), QAST::Op.new( :op('who'), QAST::Var.new( :name($path_temp), :scope('local') ) ), QAST::SVal.new( :value(~$_) ) ), QAST::Op.new( :op('atkey'), QAST::Op.new( :op('who'), QAST::Var.new( :name($path_temp), :scope('local') ) ), QAST::SVal.new( :value(~$_) ) ), default_for('$') )); $path := QAST::Op.new( :op('locallifetime'), $path, $path_temp ); } $lookup.unshift(QAST::Op.new(:op('who'), $path)); my $sigil := nqp::substr(~$final_name, 0, 1); if $sigil eq '@' || $sigil eq '%' { my $viv_temp := QAST::Node.unique('pkg_viv_tmp'); $lookup[0] := QAST::Op.new( :op('bind'), QAST::Var.new( :name($viv_temp), :scope('local'), :decl('var') ), $lookup[0]); $lookup.fallback(QAST::Op.new( :op('bindkey'), QAST::Var.new( :name($viv_temp), :scope('local') ), $lookup[1], default_for($sigil) )); } else { $lookup.fallback(default_for($sigil)); } } return $lookup; } } class NQP::RegexActions is QRegex::P6Regex::Actions { method metachar:sym<:my>($/) { my $ast := $.ast; make QAST::Regex.new( $ast, :rxtype('qastnode'), :subtype('declarative'), :node($/) ); } method metachar:sym<{ }>($/) { make QAST::Regex.new( $.ast, :rxtype, :node($/) ); } method metachar:sym($/) { make QAST::Regex.new( QAST::NodeList.new( QAST::SVal.new( :value('!INTERPOLATE') ), $.ast, QAST::IVal.new( :value($*SEQ ?? 1 !! 0) ) ), :rxtype, :subtype, :node($/)); } method assertion:sym<{ }>($/) { make QAST::Regex.new( QAST::NodeList.new( QAST::SVal.new( :value('!INTERPOLATE_REGEX') ), $.ast), :rxtype, :subtype, :node($/)); } method assertion:sym($/) { make QAST::Regex.new( $.ast, :subtype, :negate( $ eq '!' ), :rxtype, :node($/) ); } method assertion:sym($/) { make QAST::Regex.new( QAST::NodeList.new( QAST::SVal.new( :value('!INTERPOLATE_REGEX') ), $.ast), :rxtype, :subtype, :node($/)); } method codeblock($/) { my $block := $.ast; $block.blocktype('immediate'); my $ast := QAST::Stmts.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name('$/'), :scope('lexical') ), QAST::Op.new( QAST::Var.new( :name('$¢'), :scope('lexical') ), :name('MATCH'), :op('callmethod') ) ), $block ); make $ast; } method assertion:sym($/) { my $name := ~$; my $qast; if $ { $qast := $.ast; if $qast.rxtype eq 'subrule' { self.subrule_alias($qast, $name); } else { $qast := QAST::Regex.new( $qast, :name($name), :rxtype, :node($/) ); } } elsif $name eq 'sym' { my str $fullrxname := %*RX; my str $rxname := ""; my int $loc := nqp::index($fullrxname, ':sym'); if $loc >= 0 { $rxname := nqp::substr($fullrxname, $loc + 5 ); $rxname := nqp::substr( $rxname, 0, nqp::chars($rxname) - 1); } else { $loc := nqp::index($fullrxname, ':'); my $angleloc := nqp::index($fullrxname, '<', $loc); $angleloc := nqp::index($fullrxname, '«', $loc) if $angleloc < 0; $rxname := nqp::substr($fullrxname, $loc + 1, $angleloc - $loc - 1) unless $loc < 0; } if $loc >= 0 { $qast := QAST::Regex.new(:name('sym'), :rxtype, :node($/), QAST::Regex.new(:rxtype, $rxname, :node($/))); } else { self.panic(" only valid in multiregexes"); } } else { $qast := QAST::Regex.new(:rxtype, :subtype, :node($/), :name($name), QAST::NodeList.new( QAST::SVal.new( :value($name) ) ) ); if $ { for $.ast.list { $qast[0].push( $_ ) } } elsif $ { if $name eq 'after' { my int $litlen := self.offset_ast($.ast); if $litlen >= 0 { $qast[0][0].value('before'); $qast[0].push(self.qbuildsub($.ast, :anon(1), :addself(1))); $qast[0].push(QAST::IVal.new( :value($litlen) )); # optional offset to before } else { $qast[0].push(self.qbuildsub(self.flip_ast($.ast), :anon(1), :addself(1))); } } else { $qast[0].push(self.qbuildsub($.ast, :anon(1), :addself(1))); } } } make $qast; } method arglist($/) { make $.ast } method arg($/) { make $.ast; } method create_regex_code_object($block) { my $code := $*W.create_code($block, '', 0, :code_type_name); if nqp::existskey(%*RX, 'code') { %*RX.ADD_NESTED_CODE($code); } $code } method store_regex_nfa($code_obj, $block, $nfa) { $code_obj.SET_NFA($nfa.save); } method store_regex_caps($code_obj, $block, %caps) { $code_obj.SET_CAPS(%caps); } method set_cursor_type($qast) { my $cursor_type := nqp::null(); try { $cursor_type := $*W.find_sym(['NQPMatch']); }; $qast.cursor_type($cursor_type) unless nqp::isnull($cursor_type); } }