NQPP6QRegex; use NQPP5QRegex; use Raku::Actions; #------------------------------------------------------------------------------- # Roles used by multiple slangs my role startstops[$start, $stop1, $stop2] { token starter { $start } token stopper { $stop1 | $stop2 } } my role startstop[$start, $stop] { token starter { $start } token stopper { $stop } } my role stop[$stop] { token starter { } token stopper { $stop } } #------------------------------------------------------------------------------- # Functionality common to all Raku grammars role Raku::Common { token O(*%spec) { <.panic: "Internal error: O() should not be used anymore"> } # The alternatives for fat arrows token fatty { \h* [ '=>' | '⇒' ] } # The current language revision (1 = 6.c, 2 = 6.d, 3 = 6.e) method language-revision() { nqp::getcomp('Raku').language_revision } # Helper method for chars of match method leading-char() { nqp::substr(self.orig, self.from, 1) } method preceding-char() { nqp::substr(self.orig, self.from - 1, 1) } # Helper method for determining type smileys method type-smiley(str $key) { $key eq 'D' || $key eq 'U' || $key eq '_' } # Helper method to see whether the string of the given node does not # have any (hidden) synthetics. Go over each character in the string # and check $ch.chr eq $ch.ord.chr to fail any matches that have # synthetics, such as 7\x[308] method no-synthetics($node) { my str $string := ~$node; my int $chars := nqp::chars($string); if $chars == 1 { $string eq nqp::chr(nqp::ord($string)) } else { my int $i := -1; while ++$i < $chars && nqp::eqat($string,nqp::chr(nqp::ord($string,$i)),$i) { } $i == $chars } } #------------------------------------------------------------------------------- # Cursor methods # # These mostly exist for debugging, commenting and for possible future # replacement by actual Raku grammar versions. They interface to private # cursor methods in NQP's regex engine for now. # Produce fresh new cursor method new-cursor() { self.'!cursor_start_cur'() } # Produce fresh new cursor at given position method new-cursor-at(int $pos) { my $cursor := self.'!cursor_start_cur'(); $cursor.set-pos($pos); $cursor } # Set position of cursor method set-pos(int $pos) { nqp::bindattr_i(self,NQPMatch,'$!pos',$pos) } # Pass at current position of cursor method pass-at-current() { self.'!cursor_pass_quick'(self.pos) } # Produce a new cursor for given lang at current position method lang-cursor($lang) { $lang.'!cursor_init'(self.orig, :p(self.pos), :shared(self.'!shared'())) } # Produce a new cursor for given lang at given position method lang-cursor-at($lang, int $p) { $lang.'!cursor_init'(self.orig, :$p, :shared(self.'!shared'())) } # Produce match with item at given position method match-with-at($match, int $pos) { self.'!clone_match_at'($match, $pos) } # Produce match with item at current position method match-with($match) { self.'!clone_match_at'($match, self.pos) } # Produce match with item at current position of match method match-with-match($match) { self.'!clone_match_at'($match, $match.pos) } # Reset expectations method reset-expectations() { nqp::setelems(self.'!highexpect'(),0); self } #------------------------------------------------------------------------------- # Quote parsing method Regex($P5?) { self.slang_grammar($P5 ?? 'P5Regex' !! 'Regex') } method Quote() { self.slang_grammar('Quote') } method quote-Q($opener = "「", $closer = "」") { self.quote-lang(self.Quote, $opener, $closer) } method quote-q($opener = "'", $closer = "'") { self.quote-lang(self.Quote, $opener, $closer, 'q') } method quote-qq($opener = '"', $closer = '"') { self.quote-lang(self.Quote, $opener, $closer, 'qq') } method quote-qw() { self.quote-lang(self.Quote, "<", ">", 'q', [['w',1], ['v',1]]) } method quote-qqw($opener = "<<", $closer = ">>") { self.quote-lang( self.Quote, $opener, $closer, 'qq', [['ww',1], ['v',1]] ) } token opener { <[ \x0028 \x003C \x005B \x007B \x00AB \x0F3A \x0F3C \x169B \x2018 \x201A \x201B \x201C \x201E \x201F \x2039 \x2045 \x207D \x208D \x2208 \x2209 \x220A \x2215 \x223C \x2243 \x2252 \x2254 \x2264 \x2266 \x2268 \x226A \x226E \x2270 \x2272 \x2274 \x2276 \x2278 \x227A \x227C \x227E \x2280 \x2282 \x2284 \x2286 \x2288 \x228A \x228F \x2291 \x2298 \x22A2 \x22A6 \x22A8 \x22A9 \x22AB \x22B0 \x22B2 \x22B4 \x22B6 \x22C9 \x22CB \x22D0 \x22D6 \x22D8 \x22DA \x22DC \x22DE \x22E0 \x22E2 \x22E4 \x22E6 \x22E8 \x22EA \x22EC \x22F0 \x22F2 \x22F3 \x22F4 \x22F6 \x22F7 \x2308 \x230A \x2329 \x23B4 \x2768 \x276A \x276C \x276E \x2770 \x2772 \x2774 \x27C3 \x27C5 \x27D5 \x27DD \x27E2 \x27E4 \x27E6 \x27E8 \x27EA \x2983 \x2985 \x2987 \x2989 \x298B \x298D \x298F \x2991 \x2993 \x2995 \x2997 \x29C0 \x29C4 \x29CF \x29D1 \x29D4 \x29D8 \x29DA \x29F8 \x29FC \x2A2B \x2A2D \x2A34 \x2A3C \x2A64 \x2A79 \x2A7D \x2A7F \x2A81 \x2A83 \x2A8B \x2A91 \x2A93 \x2A95 \x2A97 \x2A99 \x2A9B \x2AA1 \x2AA6 \x2AA8 \x2AAA \x2AAC \x2AAF \x2AB3 \x2ABB \x2ABD \x2ABF \x2AC1 \x2AC3 \x2AC5 \x2ACD \x2ACF \x2AD1 \x2AD3 \x2AD5 \x2AEC \x2AF7 \x2AF9 \x2E02 \x2E04 \x2E09 \x2E0C \x2E1C \x2E20 \x2E28 \x3008 \x300A \x300C \x300E \x3010 \x3014 \x3016 \x3018 \x301A \x301D \xFD3E \xFE17 \xFE35 \xFE37 \xFE39 \xFE3B \xFE3D \xFE3F \xFE41 \xFE43 \xFE47 \xFE59 \xFE5B \xFE5D \xFF08 \xFF1C \xFF3B \xFF5B \xFF5F \xFF62 ]> } method balanced($start, $stop) { my $HOW := self.HOW; nqp::istype($stop,VMArray) ?? $HOW.mixin(self, startstops.HOW.curry(startstops, $start, $stop[0], $stop[1]) ) !! $HOW.mixin(self, startstop.HOW.curry(startstop, $start, $stop)); } method unbalanced($stop) { self.HOW.mixin(self, stop.HOW.curry(stop, $stop)); } token starter { } token stopper { } # Updates to the quote lang cache need to be thread-safe my $quote-lang-lock := NQPLock.new; # Define a quote language, a combination of a base grammar with a # set of base tweaks, and a set of additional tweaks. For a quote # string such as qq:!s/foo bar/. method quote-lang( $l, # grammar class to be used $start, # the starter string $stop, # the string marking the end of the quote language $base?, # base quote-language, e.g. 'q' for q/foobar/ @tweaks? # :adverbs, 's' in q:s/foobar/, as [key,Bool] lists ) { # Check validity of extra tweaks for @tweaks { my $t := $_[0]; if $t eq 'o' || $t eq 'format' { unless self.language-revision >= 3 { self.panic("Unrecognized adverb: :$t"); } } } # Return a key to identify this quote language in a cache. The # .WHICH of the quote language, if you will. sub key-for-quote-lang() { # Assemble the parts of the key my @keybits := [self.HOW.name(self), $l.HOW.name($l), $start]; @keybits.push(nqp::istype($stop,VMArray) ?? nqp::join(' ',$stop) !! $stop ); @keybits.push($base) if $base; for @tweaks { my str $t := $_[0]; @keybits.push($t eq 'to' ?? 'HEREDOC' # all heredocs share the same lang !! $t ~ '=' ~ $_[1] # cannot use nqp::join as [1] is Bool ); } nqp::join("\0", @keybits) } # Create a new type for the given quote language arguments sub create-quote-lang-type() { my $lang := self.lang-cursor($l); $lang.clone_braid_from(self); # mixin any base tweak other than Q $lang := $lang."tweak_$base"(1) if $base; # mixin any extra tweaks for @tweaks { my str $t := $_[0]; nqp::can($lang,"tweak_$t") ?? ($lang := $lang."tweak_$t"($_[1])) !! self.panic("Unrecognized adverb: :$t"); } # make sure any actions are available and the stopper is known for self.slangs { if nqp::istype($lang, $_.value) { $lang.set_actions(self.slang_actions($_.key)); last; } } $lang.set_pragma("STOPPER",$stop); # balanced if stopper different from starter, or multiple stoppers nqp::istype($stop,VMArray) || $start ne $stop ?? $lang.balanced($start, $stop) !! $lang.unbalanced($stop) } # get language from cache or derive it. my $key := key-for-quote-lang(); my %cache := %*QUOTE-LANGS; # Read from / Update to cache in a thread-safe manner nqp::lock($quote-lang-lock); my $quote-lang := nqp::ifnull( nqp::atkey(%cache,$key), nqp::bindkey(%cache,$key,create-quote-lang-type()) ); nqp::unlock($quote-lang-lock); $quote-lang.set_package(self.package); $quote-lang } # Note, $lang must carry its own actions by the time we call this. method nibble($lang) { self.lang-cursor($lang).nibbler.set_braid_from(self) } method fail-terminator ($/, $start, $stop, $line?) { my $message; if $start ne nqp::chr(nqp::ord($start)) { $message := "Starter $start is immediately followed by a combining codepoint. Please use {nqp::chr(nqp::ord($start))} without a combining glyph"; if $line { $message := "$message ($start was at line $line)"; } } else { $message := "Couldn't find terminator $stop"; if $line { $message := "$message (corresponding $start was at line $line)"; } } $/.panic($message, expected => [$stop]); } #------------------------------------------------------------------------------- # Heredoc handling my class Herestub { has $!delim; has $!orignode; has $!grammar; method delim() { $!delim } method orignode() { $!orignode } method grammar() { $!grammar } } role herestop { token starter { } token stopper { ^^ {} $=(\h*) $*DELIM \h* $$ [\r\n | \v]? } method parsing-heredoc() { 1 } } method heredoc () { my $CU := $*CU; my $actions := self.actions; if $CU && my @herestub_queue := $CU.herestub-queue { my $here := self.new-cursor-at(self.pos); while @herestub_queue { my $herestub := nqp::shift(@herestub_queue); my $*DELIM := $herestub.delim; my $lang := $herestub.grammar.HOW.mixin($herestub.grammar, herestop); for self.slangs { if nqp::istype($lang, $_.value) { $lang.set_actions(self.slang_actions($_.key)); last; } } my $doc := $here.nibble($lang); if $doc { # Match stopper. my $stop := self.lang-cursor-at($lang, $doc.pos).stopper; $stop.clone_braid_from(self); unless $stop { self.panic("Ending delimiter $*DELIM not found"); } $here.set-pos($stop.pos); # Get it trimmed and AST updated. my $heredoc := $herestub.orignode.MATCH.ast; $heredoc.replace-segments-from($doc.MATCH.ast); $heredoc.steal-processors-from($doc.MATCH.ast); $heredoc.set-stop(~$stop); my str $ws := $stop.MATCH.Str; my int $actualchars := nqp::chars($ws); my int $indent := $actualchars; my int $tabstop := $*R.resolve-lexical('$?TABSTOP').compile-time-value; my int $checkidx := -1; while ++$checkidx < $actualchars { if nqp::eqat($ws, "\t", $checkidx) { $indent := $indent + ($tabstop - 1); } } $heredoc.set-indent($indent); $heredoc.trim(); } else { self.panic("Ending delimiter $*DELIM not found"); } } $here.pass-at-current; $here.set_actions($actions); $here } else { self } } token cheat-heredoc { \h* <[ ; } ]> \h* :my $R; :my $scope; { $R := $*R; $scope := $R.current-scope; $R.leave-scope; } <.ws> { $R.enter-scope($scope) } } token quibble($l, $base?, *@tweaks) { :my $lang; :my $start; :my $stop; { my $B := $.ast; $lang := $B[0]; $start := $B[1]; $stop := $B[2]; } $start [ || $stop || { my $B := $; self.fail-terminator($/, $start, $stop, HLL::Compiler.lineof($B.orig(), $B.from(), :cache(1)) ); } ] { if nqp::can($lang,'herelang') { my $delim := $.ast.literal-value // $/.panic( "Stopper '" ~ $ ~ "' too complex for heredoc" ); $*CU.queue-heredoc(Herestub.new( :$delim, :grammar($lang.herelang), :orignode(self) )); } } } token babble($l, $base?, @tweaks?) { [ <.ws> { my $pair := $[-1].ast; my $k := self.adverb-q2str($pair.key); my $v := $pair.value; nqp::can($v,'compile-time-value') ?? nqp::push(@tweaks, [$k, $v.compile-time-value]) !! self.panic("Invalid adverb value for " ~ $[-1].Str ~ ': ' ~ $v.HOW.name($v) ); } ]* $=[] { # Work out the delimiters. my $c := $/; my @delims := $c.peek_delimiters($c.target, $c.pos); my $start := @delims[0]; my $stop := @delims[1]; # Get the language. my $lang := self.quote-lang($l, $start, $stop, $base, @tweaks); $.make([$lang, $start, $stop]); } } # Handle restricted code tests token RESTRICTED { [ [ # checking for restricted code $ # end of source reached, ok || <.typed-panic: # OR we've run into restricted code 'X::SecurityPolicy::Eval', :payload($*RESTRICTED)> ] ]? } #------------------------------------------------------------------------------- # Error handling # Specific error handling method NYI($feature) { self.typed-panic: 'X::Comp::NYI', :$feature; } method malformed($what, $additional?) { my $name := 'X::Syntax::Malformed'; if $additional { $name := $name ~ '::' ~ $additional; } self.typed-panic: $name, :$what; } method missing($what) { self.typed-panic: 'X::Syntax::Missing', :$what; } method missing-block($borg, $has-mystery) { my $marked := self.MARKED('ws'); my $pos := $marked ?? $marked.from !! self.pos; my $block := $borg; if $block { my $name := $borg // ''; self.typed-sorry-at: $block.pos, 'X::Syntax::BlockGobbled', :what($name); self.missing: "block (apparently claimed by " ~ ($name ?? "'$name'" !! "expression") ~ ")"; } else { self.missing: $pos > 0 && nqp::eqat(self.orig(),'}',$pos - 1) ?? "block (whitespace needed before curlies taken as a hash subscript?)" !! $has-mystery ?? "block (taken by some undeclared routine?)" !! "block"; } } # Shadow error handling from HLL::Grammar method dupprefix($prefixes) { self.typed-panic: 'X::Syntax::DuplicatedPrefix', :$prefixes; } # All sorts of ad-hoc exception handling method panic(*@args, *%nameds) { self.typed-panic: 'X::Comp::AdHoc', payload => nqp::join('', @args), |%nameds } method sorry(*@args) { self.typed-sorry: 'X::Comp::AdHoc', payload => nqp::join('', @args) } method worry(*@args) { self.typed-worry: 'X::Comp::AdHoc', payload => nqp::join('', @args) } # All sorts of typed exception handling method typed-panic($name, *%opts) { $*R.panic: self.build-exception($name, |%opts); } method typed-sorry($name, *%opts) { # Still allowing sorries if $*SORRY_REMAINING-- { $*R.add-sorry: self.build-exception($name, |%opts); self } # Too many sorries, call it a day else { self.typed-panic($name, |%opts) } } method typed-sorry-at(int $pos, $name, *%opts) { self.'!clear_highwater'(); my $original-pos := self.pos; self.set-pos($pos); self.typed-sorry($name, |%opts); self.set-pos($original-pos); } method typed-worry($name, *%opts) { $*R.add-worry: self.build-exception($name, |%opts); self } # Build an exception by name through the current resolver method build-exception($name, *%opts) { # Set up absolute path if possible my $file := nqp::getlexdyn('$?FILES'); if nqp::isnull($file) { $file := ''; } elsif !nqp::eqat($file,'/', 0) # does not start with / && !nqp::eqat($file,'-e',0) # and it's not -e && !nqp::eqat($file,':', 1) { # and no drive letter at start $file := nqp::cwd ~ '/' ~ $file; } my $cursor := %opts ?? self.PRECURSOR !! self; my @prepost := self.prepost($cursor); $*R.build-exception: $name, line => HLL::Compiler.lineof($cursor.orig, $cursor.pos, :cache(1)), pos => $cursor.pos, pre => @prepost[0], post => @prepost[1], file => $file, |%opts } # Separate text before/after given cursor for error messages method prepost($cursor) { my $orig := $cursor.orig; my $marked := $cursor.MARKED('ws'); my int $pos := $marked && nqp::index(" }])>»", nqp::substr($orig, $cursor.pos, 1)) < 0 ?? $marked.from !! $cursor.pos; my int $distance := 40; my int $prestart := $pos - $distance; $prestart := 0 if $prestart < 0; # FIXME workaround for when $pos is -3. Need to figure out how to # get the real pos $pos := 0 if $pos < 0; my $pre := nqp::substr($orig,$prestart,$pos - $prestart); $pre := subst($pre, /.*\n/, "", :global); $pre := '' if $pre eq ''; my int $postchars := $pos + $distance > nqp::chars($orig) ?? nqp::chars($orig) - $pos !! $distance; my $post := nqp::substr($orig, $pos, $postchars); $post := subst($post, /\n.*/, "", :global); $post := '' if $post eq ''; [$pre, $post] } method FAILGOAL($goal, $dba?) { my $stopper; unless $dba { $dba := nqp::getcodename(nqp::callercode()); # Handle special case to hide variable name leaked by core grammar if ~$goal eq '$stopper ' { my $ch := $dba ~~ / [post]? circumfix\:sym[ \< | \« ] \S+ \s+ (\S+) [ \> | \» ] /; $ch := ~$ch[0]; $stopper := "'$ch'" if nqp::chars($ch); } } # core grammar also has a penchant for sending us trailing .ws contents $stopper := $stopper // $goal; $stopper := $stopper ~~ / (.*\S) \s* /; $stopper := ~$stopper[0]; self.typed-panic: 'X::Comp::FailGoal', :$dba, :goal($stopper), :line-real(HLL::Compiler.lineof(self.orig(), self.from(), :cache(1))) ; } # "when" arg assumes more things will become obsolete after Raku comes out method obs($old, $replacement, $when = 'in Raku', :$ism = 'p5isms') { $*LANG.pragma($ism) ?? self !! self.typed-panic: 'X::Obsolete', :$old, :$replacement, :$when } method obsvar($name, $identifier-name?) { $*LANG.pragma('p5isms') ?? self !! self.typed-panic: 'X::Syntax::Perl5Var', :$name, :$identifier-name } method sorryobs($old, $replacement, $when = 'in Raku') { self.typed-sorry('X::Obsolete', :$old, :$replacement, :$when) unless $*LANG.pragma('p5isms'); self } method obsbrace() { self.obs: 'curlies around escape argument', 'square brackets'; } # Check the validity of a variable, handle meta-ops for Callables method check-variable($var) { my $ast := $var.ast; # Not capable of checking return Nil unless nqp::eqaddr($ast.WHAT,self.actions.r('Var', 'Lexical').WHAT); return Nil if nqp::isconcrete($*DECLARE-TARGETS) && $*DECLARE-TARGETS == 0; # Nothing to do? $ast.resolve-with($*R); return Nil if $ast.is-resolved; my $name := $ast.name; if $ast.sigil eq '&' { # Nothing to do? return Nil unless $ast.IMPL-IS-META-OP; my $op := $ast.desigilname.colonpairs[0].literal-value; return Nil if $op eq '!=' || $op eq '≠'; my $lang := self.'!cursor_init'($op, :p(0)); $lang.clone_braid_from(self); my $category := $ast.desigilname.canonicalize(:colonpairs(0)); my $method := $category eq 'infix' ?? 'infixish' !! $category eq 'prefix' ?? nqp::eqat($op,"[",0) && nqp::eqat($op,"]",nqp::chars($op)-1) ?? 'term:sym' !! 'prefixish' !! $category eq 'postfix' ?? 'postfixish' !! $category; my $cursor := $lang."$method"(); if $cursor.pos == nqp::chars($op) { my $match := $cursor.MATCH; if $match || $match || $match || $match || $match || $match { my $META := $match.ast; $META.IMPL-CHECK($*R, $*CU.context, 1); my $meta-op := $META.IMPL-HOP-INFIX; $ast.set-resolution( self.actions.r('Declaration','External','Constant').new( lexical-name => $name, compile-time-value => $meta-op ) ); } } } # Not resolved and not a Callable else { self.typed-panic: 'X::Undeclared', symbol => $name, is-compile-time => 1, suggestions => $*R.suggest-lexicals($name); } } # Provide parent's rule/token @*ORIGIN-NESTINGS to ease and unify # creating a stack of key AST nodes. method PARENT-NESTINGS() { # Expect to be called immediately from the nesting token. my $parent-ctx := nqp::ctxcallerskipthunks(nqp::ctxcaller(nqp::ctx())); nqp::getlexreldyn($parent-ctx, '@*ORIGIN-NESTINGS'); } method key-origin($subrule, *@pos, *%named) { my @*PARENT-NESTINGS := self.PARENT-NESTINGS(); my @*ORIGIN-NESTINGS := []; my $rc := self."$subrule"(|@pos, |%named); self.actions.key-origin($rc) if $rc; $rc } } #------------------------------------------------------------------------------- # Compilation unit, language version and other entry point bits grammar Raku::Grammar is HLL::Grammar does Raku::Common { #------------------------------------------------------------------------------- # Translatable tokens # These tokens replace bare strings so that they can be localized by # mixing in tokens with translated localizations. token block-default { default} token block-else { else} token block-elsif { elsif} token block-for { for} token block-given { given} token block-if { if} token block-loop { loop} token block-orwith { orwith} token block-repeat { repeat} token block-unless { unless} token block-until { until} token block-when { when} token block-whenever { whenever} token block-while { while} token block-with { with} token block-without { without} token constraint-where { where} token infix-after { after} token infix-and { and} token infix-andthen { andthen} token infix-before { before} token infix-but { but} token infix-cmp { cmp} token infix-coll { coll} token infix-pcontp { '(cont)'} token infix-div { div} token infix-does { does} token infix-pelemp { '(elem)'} token infix-eq { eq} token infix-eqv { eqv} token infix-ff { ff} token infix-cff { '^ff'} token infix-ffc { 'ff^'} token infix-cffc { '^ff^'} token infix-fff { fff} token infix-cfff { '^fff'} token infix-fffc { 'fff^'} token infix-cfffc { '^fff^'} token infix-gcd { gcd} token infix-ge { ge} token infix-gt { gt} token infix-lcm { lcm} token infix-le { le} token infix-leg { leg} token infix-lt { lt} token infix-max { max} token infix-min { min} token infix-minmax { minmax} token infix-mod { mod} token infix-ne { ne} token infix-notandthen { notandthen} token infix-or { or} token infix-orelse { orelse} token infix-unicmp { unicmp} token infix-x { x} token infix-xor { xor} token infix-xx { xx} token meta-R { R} token meta-X { X} token meta-Z { Z} token modifier-for { for} token modifier-given { given} token modifier-if { if} token modifier-unless { unless} token modifier-until { until} token modifier-when { when} token modifier-while { while} token modifier-with { with} token modifier-without { without} token multi-multi { multi} token multi-only { only} token multi-proto { proto} token package-class { class} token package-grammar { grammar} token package-knowhow { knowhow} token package-module { module} token package-native { native} token package-package { package} token package-role { role} token phaser-BEGIN { BEGIN} token phaser-CATCH { CATCH} token phaser-CHECK { CHECK} token phaser-CLOSE { CLOSE} token phaser-CONTROL { CONTROL} token phaser-DOC { DOC} token phaser-END { END} token phaser-ENTER { ENTER} token phaser-FIRST { FIRST} token phaser-INIT { INIT} token phaser-KEEP { KEEP} token phaser-LAST { LAST} token phaser-LEAVE { LEAVE} token phaser-NEXT { NEXT} token phaser-POST { POST} token phaser-PRE { PRE} token phaser-QUIT { QUIT} token phaser-UNDO { UNDO} token prefix-let { let} token prefix-not { not} token prefix-so { so} token prefix-temp { temp} token quote-lang-m { m} token quote-lang-ms { ms} token quote-lang-q { q} token quote-lang-Q { Q} token quote-lang-qq { qq} token quote-lang-rx { rx} token quote-lang-s { s} token quote-lang-S { S} token quote-lang-ss { ss} token quote-lang-Ss { Ss} token routine-method { method} token routine-sub { sub} token routine-regex { regex} token routine-rule { rule} token routine-submethod { submethod} token routine-token { token} token scope-anon { anon} token scope-augment { augment} token scope-constant { constant} token scope-has { has} token scope-HAS { HAS} token scope-my { my} token scope-our { our} token scope-state { state} token scope-supersede { supersede} token scope-unit { unit} token stmt-prefix-also { also} token stmt-prefix-do { do} token stmt-prefix-eager { eager} token stmt-prefix-gather { gather} token stmt-prefix-hyper { hyper} token stmt-prefix-lazy { lazy} token stmt-prefix-once { once} token stmt-prefix-quietly { quietly} token stmt-prefix-race { race} token stmt-prefix-react { react} token stmt-prefix-sink { sink} token stmt-prefix-start { start} token stmt-prefix-supply { supply} token stmt-prefix-try { try} token term-self { self} token term-nano { nano} token term-now { now} token term-rand { rand} token term-time { time} token traitmod-does { does} token traitmod-handles { handles} token traitmod-hides { hides} token traitmod-is { is} token traitmod-of { of} token traitmod-returns { returns} token typer-enum { enum} token typer-subset { subset} token use-import { import} token use-need { need} token use-no { no} token use-require { require} token use-use { use} # Convert the invocant, a match that is expected to have a RakuAST::Name # object as its ".ast" (or to have no ".ast" at all), to a RakuAST::Name # object with the name of the core functionality if there is an original # name known. Otherwise it should just return the ".ast" of the invocant. method core2ast() { self.ast // self.actions.r('Name').from-identifier(~self) } # Convert the invocant, a match that is expected to have a RakuAST::Name # object as its ".ast" (or to have no ".ast" at all), to a RakuAST::Name # object with the name of the trait_mod: name if there is an # original name known. Otherwise it should just return the ".ast" ofi # the invocant. method trait-is2ast() { self.ast // self.actions.r('Name').from-identifier(~self) } # Convert the given postcircumfix adverb if there is an original name # for it. Otherwise it should just return the adverb unchanged. method adverb-pc2str(str $key) { $key } # Convert the given quoting adverb if there is an original name # for it. Otherwise it should just return the adverb unchanged. method adverb-q2str(str $key) { $key } # Convert the given regex adverb if there is an original name # for it. Otherwise it should just return the adverb unchanged. method adverb-rx2str(str $key) { $key } # Convert the given named argument if there is an original name # for it. Otherwise it should just return the named argument unchanged. method named2str(str $key) { $key } # Convert the given pragma if there is an original name # for it. # Otherwise it should just return the named argument unchanged. method pragma2str(str $key) { $key } #------------------------------------------------------------------------------- # Grammar entry point method TOP() { # Set up the language braid. my $*LANG := self; my $*MAIN := 'MAIN'; self.define_slang('MAIN', self.WHAT, self.actions); self.define_slang('Quote', Raku::QGrammar, Raku::QActions); self.define_slang('Regex', Raku::RegexGrammar, Raku::RegexActions); self.define_slang('P5Regex', Raku::P5RegexGrammar, Raku::P5RegexActions); # we default to strict! self.set_pragma('strict',1); # Variables used during the parse. my $*IN-DECL; # what declaration we're in my $*OFTYPE; # type of the current declarator my $*LEFTSIGIL; # sigil of LHS for item vs list assignment my $*IN-META := ''; # parsing a metaoperator like [..] my $*IN_REDUCE := 0; # attempting to parse an [op] construct my %*QUOTE-LANGS; # quote language cache my $*LASTQUOTE := [0,0]; # for runaway quote detection my $*SORRY_REMAINING := 10; # decremented on each sorry; panic when 0 my $*BORG := {}; # who gets blamed for a missing block # -1 indicates we're outside of any "supply" or "react" block my $*WHENEVER-COUNT := -1; # This contains the given options, from the command line if invoked # from there, otherwise from EVAL invocations. my %*OPTIONS := %*COMPILING<%?OPTIONS>; # This contains the current index to determine the order in which # legacy $=pod is being filled by ::Doc::Block and ::Doc::Declarator # blocks. Whenever a new object of these is made, the value of # this dynamic variable will be stored in the object, and then # incremented. At CHECK time, the generated Pod:: objects will # be bound to the index given at creation time. my $*LEGACY-POD-INDEX := 0; # $/.from locations of declarator doc and rakudo blocks that have # been seen and handled before. Needed because the grammar can # actually visit the same piece of the code more than once. my $*FROM-SEEN := {}; # Set to True if parsing inside a DOC-BLOCK, to prevent attaching # of doc blocks to statements that will not actually be CHECKed my $*PARSING-DOC-BLOCK; # RakuDoc blocks collected so far, to be included with next statement # into its statement list. my $*DOC-BLOCKS-COLLECTED := []; # RakuDoc aliases (=alias -> A<>) collected so far my $*DOC-ALIASES := {}; # RakuDoc config info (=config foo :allow) my $*DOC-CONFIG := {}; # Any resolver that exists outside this grammar: usually this is the # resolver that is active whenever code is being EVALled inside BEGIN # block, which would create a new resolver and put it in $*R. my $*OUTER-RESOLVER := $*R; # Parse a compilation unit. self.comp-unit($*CU) } # The ByteOrderMarker token bom { \xFEFF } # Set up the language to be used, possibly specified by "use vxxx" rule lang-setup($*OUTER-CU) { # TODO validate this and pay attention to it in actions [ <.ws>? use ';'? ]? } # This is like HLL::Grammar.LANG but it allows to call a token of a # Raku level grammar. Takes the language (usually 'MAIN') and the # name of the regex to be executed. method FOREIGN-LANG($langname, $regex) { my $grammar := self.slang_grammar($langname); if nqp::istype($grammar, NQPMatch) { self.LANG($langname, $regex); } else { nqp::die('FOREIGN-LANG non-NQP branch NYI') } } # Set up compilation unit and symbol resolver according to the language # version that is declared, if any. Then parse the outer statement list. token comp-unit($outer-cu) { <.bom>? # ignore any ByteOrderMark :my $*CU; # current RakuAST::CompUnit object :my $*ORIGIN-SOURCE; # current RakuAST::Origin::Source object :my @*ORIGIN-NESTINGS := []; # handling nested origins :my $*R; # current RakuAST::Resolver::xxx object :my $*LITERALS; # current RakuAST::LiteralBuilder object :my &*DD; # debug helper to dd() { self.actions.comp-unit-prologue($/); # set the above variables } :my $*IN-TYPENAME; # fallback for inside typename flag :my $*ADVERB-AS-INFIX; # fallback for fake infix handling :my @*LEADING-DOC := []; # temp storage leading declarator doc :my $*DECLARAND; # target for trailing declarator doc :my $*LAST-TRAILING-LINE := -1; # number of last line with trailing doc :my $*IGNORE-NEXT-DECLARAND; # True if next declarand to be ignored :my $*DECLARAND-WORRIES := {}; # $/ of worries when clearing DECLARAND :my $*EXPORT; :my $*COMPILING_CORE_SETTING := 0; :my $*NEXT-STATEMENT-ID := 0; # to give each statement an ID :my $*START-OF-COMPUNIT := 1; # flag: start of a compilation unit? <.lang-setup($outer-cu)> # set the above variables # Further needed initializations { $*R.enter-scope($*CU); $*R.create-scope-implicits(); self.actions.load-M-modules($/); } # Perform the actual parsing of the code, using origin tracking # All parsed so far [ $ # all ok, reach the end || <.typed-panic: 'X::Syntax::Confused'> # huh?? ] { $*R.leave-scope } } #------------------------------------------------------------------------------- # Statement level parsing # Parsing zero or more statements, e.g. inside a (pointy) block rule statementlist { :dba('statement list') <.ws> :my $*LANG; # Define this scope to be a new language [ | $ # the end of code | > # or bumping into ) ] } | [ # or statement with tracking <.eat-terminator> # until any terminator ]* ] <.set_braid_from(self)> # Language tweaks must not escape } # Parsing zero or more statements in an expression, e.g @a[10;20] rule semilist { :dba('list composer') '' [ | > # bumping into ) ] } | [ # or the statement (without origin tracking) <.eat-terminator> # until any terminator ]* ] } # Parsing zero or more statements in a contextualizer, e.g $(10;20) rule sequence { :dba('sequence of statements') '' [ | > # bumping into ) ] } | [ # or the statement (without origin tracking) <.eat-terminator> # until any terminator ]* ] } # Parsing an actual Raku statement token statement { :my $*QSIGIL := ''; # init quote lang :my $*SCOPE := ''; # init scope type :my $*STATEMENT-ID := ++$*NEXT-STATEMENT-ID; # set statement ID :my $actions := self.slang_actions('MAIN'); # shortcut to actions | $ > # [ |