use NQPP6QRegex; use NQPP5QRegex; #------------------------------------------------------------------------------- # The classes of the AST nodes come from the Raku setting bootstrap, so # we need to load them from there. We also need the OperatorProperties # class, so fetch that as well. my $RakuAST-WHO; # RakuAST.WHO my $OperatorProperties; # OperatorProperties class # Logic for setting up RakuAST-WHO and OperatorProperties sub setup-RakuAST-WHO() { unless nqp::isconcrete($RakuAST-WHO) { my $loader := nqp::gethllsym('Raku','ModuleLoader'); my $unit := $loader.load_module('Perl6::BOOTSTRAP::v6c',{},GLOBALish); my $export := $unit.WHO.WHO; $RakuAST-WHO := nqp::existskey($export,'RakuAST') ?? nqp::atkey($export,'RakuAST').WHO !! nqp::die('Cannot find RakuAST nodes'); $OperatorProperties := nqp::atkey($export,'OperatorProperties'); } } # Provide easy lookup of RakuAST:: classes at runtime of the # actions. These classes can **NOT** be referenced directly # as they may not yet be known when the grammar / actions # run. Note that direct specification of RakuAST classes # *will* compile, but may cause compile time issues of Raku # code, typically resulting in error messages stating a method # having been called on VMNull. sub Nodify(*@todo) { my $res := nqp::atkey($RakuAST-WHO,nqp::shift(@todo)); my $last; while @todo && !nqp::isnull($res) { $res := nqp::atkey($res.WHO,$last := nqp::shift(@todo)); } nqp::ifnull( $res, nqp::stmts( nqp::push(@todo,$last), nqp::die('No such node RakuAST::' ~ nqp::join('::',@todo)) ) ) } #------------------------------------------------------------------------------- # Role for all Action classes associated with Raku grammar slangs role Raku::CommonActions { # Some AST nodes need symbol resolution or attachment of position # information as we go. This factors out that process and attaches # the AST to the match object. method attach($/, $node, :$as-key-origin) { if nqp::istype($node, Nodify('ImplicitLookups')) { $node.resolve-implicit-lookups-with($*R); } if nqp::istype($node, Nodify('Attaching')) { $node.attach($*R); } self.SET-NODE-ORIGIN($/, $node, :$as-key-origin); make $node; } method SET-NODE-ORIGIN($/, $node, :$as-key-origin) { # XXX This is a temporary stub to avoid unimplemented nodes. # Must be replaced with exception throwing when RakuAST is # considered ready for this. unless nqp::isconcrete($node) { return } if nqp::istype($node, Nodify('Node')) { unless nqp::isconcrete($node.origin) { $node.set-origin( Nodify('Origin').new( :from($/.from), :to($/.to), :source($*ORIGIN-SOURCE))); } if $as-key-origin { my $nestings := @*ORIGIN-NESTINGS; unless nqp::istype($node, Nodify('CompUnit')) { @*PARENT-NESTINGS.push($node) } $node.origin.set-nestings($nestings); } } } method key-origin($/) { self.SET-NODE-ORIGIN($/, $/.ast, :as-key-origin); } method quibble($/) { self.attach: $/, $.ast; } # Grammars also need to be able to lookup RakuAST nodes. Historically # this was done with the "r" method. Since it is apparently impossible # to reliably export Nodify, this interface is kept alive. method r(*@parts) { Nodify(|@parts) } } #------------------------------------------------------------------------------- # The actions associated with the base Raku grammar class Raku::Actions is HLL::Actions does Raku::CommonActions { method OperatorProperties() { $OperatorProperties } #------------------------------------------------------------------------------- # Compilation unit, language version and other entry point bits # Thread-safely produce a unique serialization context ID my $count := -1; my $lock := NQPLock.new; sub next-id() { $lock.protect({ ++$count }) } # Given a package, returns a low-level hash for its stash sub stash-hash($package) { my $hash := $package.WHO; nqp::ishash($hash) ?? $hash !! $hash.FLATTENABLE_HASH } # Perform all actions that are needed before any actual parsing can # be done by a grammar. method comp-unit-prologue($/) { # Be ready to do Nodify lookups setup-RakuAST-WHO(); # Be ready to report locations in the source. $*ORIGIN-SOURCE := Nodify('Origin', 'Source').new(:orig($/.target())); # Set up the literals builder, so we can produce and intern literal # values. $*LITERALS := Nodify('LiteralBuilder').new; # Set up the base resolver my %OPTIONS := %*OPTIONS; my $context := %OPTIONS; my $resolver-type := Nodify('Resolver', 'Compile'); my $RESOLVER := $*R := nqp::isconcrete($context) ?? $resolver-type.from-context( :$context, :global(%OPTIONS), :resolver($*OUTER-RESOLVER) ) !! $resolver-type.from-setting( :setting-name(%OPTIONS // 'CORE.d') ); # Make debugging a *lot* easier &*DD := $RESOLVER.setting-constant('&dd'); } # Perform all actions related to "use vxxx" and loading appropriate # (default) settings and configuring the compilation unit and resolver method lang-setup($/) { # Look up these dynamic vars only once my $HLL-COMPILER := $*HLL-COMPILER; my %OPTIONS := %*OPTIONS; my $LANG := $*LANG; my $RESOLVER := $*R; # Some shortcuts; my $language-revision := $HLL-COMPILER.language_revision; my $is-EVAL := nqp::isconcrete(%OPTIONS); my $setting-name := %OPTIONS; # Helper sub to configure the resolver with selected language revision my sub resolver-from-revision() { $setting-name := 'CORE.' ~ $HLL-COMPILER.lvs.p6rev($language-revision); $RESOLVER.set-setting(:$setting-name); } # Not EVALling and explicit setting requested if $setting-name && !$is-EVAL { # TODO This branch is for when we start compiling the CORE. if nqp::eqat($setting-name, 'NULL.', 0) { $*COMPILING_CORE_SETTING := 1; if $setting-name ne 'NULL.c' { my $loader := nqp::gethllsym('Raku', 'ModuleLoader'); $*R.set-setting(:setting-name($loader.previous_setting_name($setting-name))); } else { # TODO CORE.c is being compiled. What resolver is to be used? nqp::die("Can't compiler CORE.c yet"); } } # Setting name is explicitly set. Use it to determine the # default language revision. else { $RESOLVER.set-setting(:setting-name($setting-name)); $language-revision := nqp::unbox_i($RESOLVER.setting-constant('CORE-SETTING-REV')); $HLL-COMPILER.set_language_revision($language-revision); } } # Seen a -use vxxx- statement my $version := $ ?? ~$ !! nqp::getenvhash() || ""; if $version { my @vparts := $HLL-COMPILER.lvs.from-public-repr($version); my %lang-revisions := $HLL-COMPILER.language_revisions; my @final-version; my $modifier-deprecated; # Globbed version needs a bit of research needs to be done first. if nqp::index($version,'*') >= 0 || nqp::index($version,'+') >= 0 { my $Version := $RESOLVER.setting-constant('Version'); my $ver-requested := $Version.new( $HLL-COMPILER.lvs.from-public-repr($version, :as-str) ); my @can-versions := $HLL-COMPILER.can_language_versions; my $can-version; my $i := nqp::elems(@can-versions); # Iterate over the version candidates from higher to lower # ones, skip these that don't match the requested version # glob, and these without a modifier but one is required. # Like 6.e would be a valid version in the future, but for # now it has to be 6.e.PREVIEW. while --$i >= 0 { $can-version := $Version.new(@can-versions[$i]); next unless $ver-requested.ACCEPTS($can-version); # If version candidate my $can-parts := $can-version.parts; my $can-revision := nqp::unbox_i($can-parts.head); last unless $can-parts.elems == 1 && nqp::existskey(%lang-revisions{$can-revision},'require'); } if $i < 0 { $/.typed-panic: 'X::Language::Unsupported', :$version; } # Are there any easier way to unbox boxable types? my $Int := $RESOLVER.setting-constant('Int'); my $Str := $RESOLVER.setting-constant('Str'); my @can-parts := nqp::getattr($can-version, $Version, '$!parts'); for @can-parts -> $part { @final-version.push: nqp::isint($part) || nqp::isstr($part) ?? $part !! nqp::istype($part, $Int) ?? nqp::unbox_i($part) !! nqp::istype($part, $Str) ?? nqp::unbox_s($part) !! nqp::die( "Don't know how to handle version part of '" ~ $part.HOW.name($part) ~ "' type" ); } } # A non-globbed version can be used as-is, make sure it is valid else { my $revision := @vparts[0]; # Consider version to have a language modifier if the last # part of is a string of non-zero length. my $modifier := @vparts > 1 && nqp::objprimspec(@vparts[-1]) == 3 ?? @vparts[-1] !! nqp::null(); # Do we know this language version? unless nqp::existskey(%lang-revisions, $revision) && (!$modifier || nqp::existskey(%lang-revisions{$revision}, $modifier)) { $/.typed-panic: 'X::Language::Unsupported', :$version; } my %config := %lang-revisions{$revision}; # If version is known, is it used with a required modifier? if nqp::existskey(%config,'require') && (!$modifier || %config ne $modifier) { $/.typed-panic: 'X::Language::ModRequired', :$version, :modifier(%config); } # We can't issue a worry immediately because the current # resolver is temporary, so just set a flag if $modifier && %config{$modifier} { $modifier-deprecated := $modifier; } @final-version := @vparts; } $HLL-COMPILER.set_language_version(@final-version); $language-revision := @final-version[0]; resolver-from-revision(); # Now the resolver is final, express our modifier concern! if $modifier-deprecated { # At this point our compiler version is final. $/.worry: "$modifier-deprecated modifier is deprecated for Raku v" ~ $HLL-COMPILER.language_version; } } # No version seen and not in an EVAL elsif !$is-EVAL { resolver-from-revision(); } # Locate an EXPORTHOW and set those mappings on our current language. my $EXPORTHOW := $RESOLVER.resolve-lexical-constant('EXPORTHOW').compile-time-value; for stash-hash($EXPORTHOW) { $LANG.set_how($_.key, $_.value); } my $package-how := $LANG.how('package'); my $export-package := $package-how.new_type(name => 'EXPORT'); $export-package.HOW.compose($export-package); $RESOLVER.set-export-package($export-package); $*EXPORT := $export-package; # Create a compilation unit. my $comp-unit-name := $*ORIGIN-SOURCE.original-file ~ $/.target; # It's an EVAL. We'll take our GLOBAL, $?PACKAGE, etc. from that. if $is-EVAL { $*CU := Nodify('CompUnit').new( :comp-unit-name($comp-unit-name ~ next-id), # uniqify :$setting-name, :eval, :outer-cu($*OUTER-CU), :$language-revision ); } # Top-level compilation. else { $*CU := Nodify('CompUnit').new( :$comp-unit-name, :$setting-name, :global-package-how($package-how), :precompilation-mode(%OPTIONS), :$export-package, :$language-revision ); # Create a GLOBAL using the correct package meta-object. my $global := $*CU.generated-global; $RESOLVER.set-global($global); nqp::bindhllsym('Raku','GLOBAL',$global); } $*LITERALS.set-resolver($RESOLVER); } method comp-unit($/) { # Do dynamic lookups once my $COMPUNIT := $*CU; my %OPTIONS := %*OPTIONS; my $RESOLVER := $*R; # Put the body in place. $COMPUNIT.replace-statement-list($.ast); # Sort out sinking; the compilation unit is sunk as a whole if we are # not in a REPL or EVAL context. $COMPUNIT.mark-sunk() unless nqp::existskey(%OPTIONS,'outer_ctx'); $COMPUNIT.calculate-sink(); # if --(raku)doc specified, add INIT phaser that handles that if nqp::existskey(%OPTIONS,'doc') { $COMPUNIT.add-INIT-phaser-for-doc-handling( 'Pod', %OPTIONS || 'Text' ); } elsif nqp::existskey(%OPTIONS,'rakudoc') { $COMPUNIT.add-INIT-phaser-for-doc-handling( 'RakuDoc', %OPTIONS || 'Text' ); } # Have check time. $COMPUNIT.check($RESOLVER); my $exception := $RESOLVER.produce-compilation-exception; if nqp::isconcrete($exception) { if $RESOLVER.has-compilation-errors { # Really has errors, so report them. $exception.throw; } else { # Only potential difficulties, just just print them. stderr().print($exception.gist); } } self.attach: $/, $COMPUNIT, :as-key-origin; } # Action method to load any modules specified with -M method load-M-modules($/) { my $M := %*OPTIONS; return Nil unless nqp::defined($M); # nothing to do here # shortcuts my $R := $*R; my $context := $*CU.context; # Create a RakuAST statement list with -use- statements # of the specified module names and attach that my $ast := Nodify('StatementList').new; for nqp::islist($M) ?? $M !! [$M] -> $longname { my $use := Nodify('Statement', 'Use').new( module-name => Nodify('Name').from-identifier-parts( |nqp::split('::', $longname) ) ); $use.ensure-begin-performed($R, $context); $ast.add-statement: $use; } self.attach: $/, $ast; } #------------------------------------------------------------------------------- # Statement level handling # Helper method to collect statements and potentially and declarator # docs for an actual StatementList, StatementSequence or SemiList. method collect-statements($/, $typename) { my $statements := Nodify($typename).new; for $ { $_.ast.add-to-statements($statements); } self.attach: $/, $statements; $statements } # Action methods for the various collectors of statements method statementlist($/) { my $statements := self.collect-statements($/, 'StatementList'); # Add any uncollected doc blocks. This can happen if there # are no statements in a statementlist, e.g. in a rakudoc # only file. for $*DOC-BLOCKS-COLLECTED { $statements.add-doc-block($_); } $*DOC-BLOCKS-COLLECTED := []; } method semilist($/) { self.collect-statements($/, 'SemiList') } method sequence($/) { self.collect-statements($/, 'StatementSequence') } # Action method for handling an actual statement method statement($/) { # Setting label on already created statement if $