- O(spec [, save])
-
This subrule attaches operator precedence information to a match object (such as an operator token). A typical invocation for the subrule might be:
token infix:sym<+> { <sym> <O( q{ %additive, :pirop<add> } )> }
This says to add all of the attribute of the
%additive
hash (described below) and apirop
entry into the match object returned by theinfix:sym<+>
token (as theO
named capture). Note that this is a alphabetic 'O", not a digit zero.Currently the
O
subrule accepts a string argument describing the hash to be stored. (Note theq{ ... }
above. Eventually it may be possible to omit the 'q' such that an actual (constant) hash constructor is passed as an argument toO
.The hash built via the string argument to
O
is cached, so that subsequent parses of the same token re-use the hash built from previous parses of the token, rather than building a new hash on each invocation.The
save
argument is used to build "hash" aggregates that can be referred to by subsequent calls toO
. For example,NQP::Grammar.O(':prec<t=>, :assoc<left>', '%additive' );
specifies the values to be associated with later references to "%additive". Eventually it will likely be possible to use true hashes from a package namespace, but this works for now.
Currently the only pairs recognized have the form
:pair
,:!pair
, and:pair<strval>
.# First, get the hash cache. Right now we have one # cache for all grammars; eventually we may need a way to # separate them out by cursor type. .local pmc ohash ohash = get_global '%!ohash' unless null ohash goto have_ohash ohash = new ['Hash'] set_global '%!ohash', ohash have_ohash: # See if we've already created a Hash for the current # specification string -- if so, use that. .local pmc hash hash = ohash[spec] unless null hash goto hash_done # Otherwise, we need to build a new one. hash = new ['Hash'] .local int pos, eos pos = 0 eos = length spec spec_loop: pos = find_not_cclass .CCLASS_WHITESPACE, spec, pos, eos if pos >= eos goto spec_done $S0 = substr spec, pos, 1 if $S0 == ',' goto spec_comma if $S0 == ':' goto spec_pair # If whatever we found doesn't start with a colon, treat it # as a lookup of a previously saved hash to be merged in. .local string lookup .local int lpos # Find the first whitespace or comma lpos = find_cclass .CCLASS_WHITESPACE, spec, pos, eos $I0 = index spec, ',', pos if $I0 < 0 goto have_lookup_lpos if $I0 >= lpos goto have_lookup_lpos lpos = $I0 have_lookup_lpos: $I0 = lpos - pos lookup = substr spec, pos, $I0 .local pmc lhash, lhash_it lhash = ohash[lookup] if null lhash goto err_lookup lhash_it = iter lhash lhash_loop: unless lhash_it goto lhash_done $S0 = shift lhash_it $P0 = lhash[$S0] hash[$S0] = $P0 goto lhash_loop lhash_done: pos = lpos goto spec_loop # We just ignore commas between elements for now. spec_comma: inc pos goto spec_loop # If we see a colon, then we want to parse whatever # comes next like a pair. spec_pair: # eat colon inc pos .local string name .local pmc value value = new ['Boolean'] # If the pair is of the form :!name, then reverse the value # and skip the colon. $S0 = substr spec, pos, 1 $I0 = iseq $S0, '!' pos += $I0 $I0 = not $I0 value = $I0 # Get the name of the pair. lpos = find_not_cclass .CCLASS_WORD, spec, pos, eos $I0 = lpos - pos name = substr spec, pos, $I0 pos = lpos # Look for a <...> that follows. $S0 = substr spec, pos, 1 unless $S0 == '<' goto have_value inc pos lpos = index spec, '>', pos $I0 = lpos - pos $S0 = substr spec, pos, $I0 value = box $S0 pos = lpos + 1 have_value: # Done processing the pair, store it in the hash. hash[name] = value goto spec_loop spec_done: # Done processing the spec string, cache the hash for later. ohash[spec] = hash hash_done: # If we've been called as a subrule, then build a pass-cursor # to indicate success and set the hash as the subrule's match object. if has_save goto save_hash ($P0, $S0, $I0) = self.'!cursor_start'() $P0.'!cursor_pass'($I0, '') setattribute $P0, cur_class, '$!match', hash .return ($P0) # save the hash under a new entry save_hash: ohash[save] = hash .return (self) err_lookup: self.'panic'('Unknown operator precedence specification "', lookup, '"') }; }
- panic([args :slurpy])
-
Throw an exception at the current cursor location. If the message doesn't end with a newline, also output the line number and offset of the match.
method panic(*@args) { my $pos := self.pos(); my $target := nqp::getattr_s(self, NQPCursor, '$!target'); @args.push(' at line '); @args.push(HLL::Compiler.lineof($target, $pos) + 1); @args.push(', near "'); @args.push(pir::escape__SS(nqp::substr($target, $pos, 10))); @args.push('"'); nqp::die(nqp::join('', @args)) } method FAILGOAL($goal) { my $sub := Q:PIR { %r = getinterp %r = %r['sub';1] }; self.panic("Unable to parse ", ~$sub, ", couldn't find final $goal"); }
- peek_delimiters(target, pos)
-
Return the start/stop delimiter pair based on peeking at
target
positionpos
.method peek_delimiters($target, $pos) { Q:PIR { .local pmc self self = find_lex 'self' .local string target $P0 = find_lex '$target' target = $P0 .local int pos $P0 = find_lex '$pos' pos = $P0 .local string brackets, start, stop $P0 = find_lex '$brackets' brackets = $P0 # peek at the next character start = substr target, pos, 1 # colon and word characters aren't valid delimiters if start == ':' goto err_colon_delim $I0 = is_cclass .CCLASS_WORD, start, 0 if $I0 goto err_word_delim $I0 = is_cclass .CCLASS_WHITESPACE, start, 0 if $I0 goto err_ws_delim # assume stop delim is same as start, for the moment stop = start # see if we have an opener or closer $I0 = index brackets, start if $I0 < 0 goto bracket_end # if it's a closing bracket, that's an error also $I1 = $I0 % 2 if $I1 goto err_close # it's an opener, so get the closing bracket inc $I0 stop = substr brackets, $I0, 1 # see if the opening bracket is repeated .local int len len = 0 bracket_loop: inc pos inc len $S0 = substr target, pos, 1 if $S0 == start goto bracket_loop if len == 1 goto bracket_end start = repeat start, len stop = repeat stop, len bracket_end: .return (start, stop, pos) err_colon_delim: self.'panic'('Colons may not be used to delimit quoting constructs') err_word_delim: self.'panic'('Alphanumeric character is not allowed as a delimiter') err_ws_delim: self.'panic'('Whitespace character is not allowed as a delimiter') err_close: self.'panic'('Use of a closing delimiter for an opener is reserved') }; } token quote_EXPR(*@args) { :my %*QUOTEMOD; :my $*QUOTE_START; :my $*QUOTE_STOP; { Q:PIR { .local pmc self, cur_class, args self = find_lex 'self' cur_class = find_lex '$cursor_class' args = find_lex '@args' .local pmc quotemod, true quotemod = find_lex '%*QUOTEMOD' true = box 1 args_loop: unless args goto args_done .local string mod mod = shift args mod = substr mod, 1 quotemod[mod] = true if mod == 'qq' goto opt_qq if mod == 'b' goto opt_b goto args_loop opt_qq: quotemod['s'] = true quotemod['a'] = true quotemod['h'] = true quotemod['f'] = true quotemod['c'] = true quotemod['b'] = true opt_b: quotemod['q'] = true goto args_loop args_done: .local pmc start, stop .local string target .local int pos target = repr_get_attr_str self, cur_class, '$!target' pos = repr_get_attr_int self, cur_class, '$!pos' (start, stop) = self.'peek_delimiters'(target, pos) store_lex '$*QUOTE_START', start store_lex '$*QUOTE_STOP', stop } } <quote_delimited> } token quotemod_check($mod) { <?{ %*QUOTEMOD{$mod} }> } method starter() { Q:PIR { .local pmc self, cur .local string target, start .local int pos self = find_lex 'self' (cur, target, pos) = self.'!cursor_start'() $P0 = find_dynamic_lex '$*QUOTE_START' if null $P0 goto fail start = $P0 $I0 = length start $S0 = substr target, pos, $I0 unless $S0 == start goto fail pos += $I0 cur.'!cursor_pass'(pos, 'starter') fail: .return (cur) }; } method stopper() { Q:PIR { .local pmc self, cur .local string target, stop .local int pos self = find_lex 'self' (cur, target, pos) = self.'!cursor_start'() $P0 = find_dynamic_lex '$*QUOTE_STOP' if null $P0 goto fail stop = $P0 $I0 = length stop $S0 = substr target, pos, $I0 unless $S0 == stop goto fail pos += $I0 cur.'!cursor_pass'(pos, 'stopper') fail: .return (cur) }; } our method split_words($words) { Q:PIR { .include 'src/Regex/constants.pir' .local string words $P0 = find_lex '$words' words = $P0 .local int pos, eos .local pmc result pos = 0 eos = length words result = new ['ResizablePMCArray'] split_loop: pos = find_not_cclass .CCLASS_WHITESPACE, words, pos, eos unless pos < eos goto split_done $I0 = find_cclass .CCLASS_WHITESPACE, words, pos, eos $I1 = $I0 - pos $S0 = substr words, pos, $I1 push result, $S0 pos = $I0 goto split_loop split_done: .return (result) }; }
- EXPR(...)
-
An operator precedence parser.
method EXPR($preclim = '') { Q:PIR { .local pmc self, cur_class self = find_lex 'self' cur_class = find_lex '$cursor_class' .local string preclim $P0 = find_lex '$preclim' preclim = $P0 .local pmc here .local string tgt .local int pos (here, tgt, pos) = self.'!cursor_start'() .local string termishrx termishrx = 'termish' .local pmc opstack, termstack opstack = new ['ResizablePMCArray'] .lex '@opstack', opstack termstack = new ['ResizablePMCArray'] .lex '@termstack', termstack term_loop: .local pmc termcur repr_bind_attr_int here, cur_class, "$!pos", pos termcur = here.termishrx() pos = repr_get_attr_int termcur, cur_class, "$!pos" repr_bind_attr_int here, cur_class, "$!pos", pos if pos < 0 goto fail .local pmc termish termish = termcur.'MATCH'() # interleave any prefix/postfix we might have found .local pmc termOPER, prefixish, postfixish termOPER = termish termOPER_loop: $I0 = exists termOPER['OPER'] unless $I0 goto termOPER_done termOPER = termOPER['OPER'] goto termOPER_loop termOPER_done: prefixish = termOPER['prefixish'] postfixish = termOPER['postfixish'] if null prefixish goto prefix_done prepostfix_loop: unless prefixish goto prepostfix_done unless postfixish goto prepostfix_done .local pmc preO, postO .local string preprec, postprec $P0 = prefixish[0] $P0 = $P0['OPER'] preO = $P0['O'] preprec = preO['prec'] $P0 = postfixish[-1] $P0 = $P0['OPER'] postO = $P0['O'] postprec = postO['prec'] if postprec < preprec goto post_shift if postprec > preprec goto pre_shift $S0 = postO['uassoc'] if $S0 == 'right' goto pre_shift post_shift: $P0 = pop postfixish push opstack, $P0 goto prepostfix_loop pre_shift: $P0 = shift prefixish push opstack, $P0 goto prepostfix_loop prepostfix_done: prefix_loop: unless prefixish goto prefix_done $P0 = shift prefixish push opstack, $P0 goto prefix_loop prefix_done: delete termish['prefixish'] postfix_loop: if null postfixish goto postfix_done unless postfixish goto postfix_done $P0 = pop postfixish push opstack, $P0 goto postfix_loop postfix_done: delete termish['postfixish'] $P0 = termish['term'] push termstack, $P0 # Now see if we can fetch an infix operator .local pmc wscur, infixcur, infix # First, we need ws to match. repr_bind_attr_int here, cur_class, "$!pos", pos wscur = here.'ws'() pos = repr_get_attr_int wscur, cur_class, '$!pos' if pos < 0 goto term_done repr_bind_attr_int here, cur_class, "$!pos", pos # Next, try the infix itself. infixcur = here.'infixish'() pos = repr_get_attr_int infixcur, cur_class, '$!pos' if pos < 0 goto term_done infix = infixcur.'MATCH'() # We got an infix. .local pmc inO $P0 = infix['OPER'] inO = $P0['O'] termishrx = inO['nextterm'] if termishrx goto have_termishrx nonextterm: termishrx = 'termish' have_termishrx: .local string inprec, inassoc, opprec inprec = inO['prec'] unless inprec goto err_inprec if inprec < preclim goto term_done inassoc = inO['assoc'] $P0 = inO['sub'] if null $P0 goto subprec_done inO['prec'] = $P0 subprec_done: reduce_loop: unless opstack goto reduce_done $P0 = opstack[-1] $P0 = $P0['OPER'] $P0 = $P0['O'] opprec = $P0['prec'] unless opprec > inprec goto reduce_gt_done self.'EXPR_reduce'(termstack, opstack) goto reduce_loop reduce_gt_done: unless opprec == inprec goto reduce_done # equal precedence, use associativity to decide unless inassoc == 'left' goto reduce_done # left associative, reduce immediately self.'EXPR_reduce'(termstack, opstack) reduce_done: push opstack, infix # The Shift repr_bind_attr_int here, cur_class, "$!pos", pos wscur = here.'ws'() pos = repr_get_attr_int wscur, cur_class, '$!pos' repr_bind_attr_int here, cur_class, "$!pos", pos if pos < 0 goto fail goto term_loop term_done: opstack_loop: unless opstack goto opstack_done self.'EXPR_reduce'(termstack, opstack) goto opstack_loop opstack_done: expr_done: .local pmc term term = pop termstack pos = here.'pos'() here = self.'!cursor_start'() here.'!cursor_pass'(pos) repr_bind_attr_int here, cur_class, '$!pos', pos setattribute here, cur_class, '$!match', term here.'!reduce'('EXPR') goto done fail: done: .return (here) err_internal: $I0 = termstack here.'panic'('Internal operator parser error, @termstack == ', $I0) err_inprec: infixcur.'panic'('Missing infixish operator precedence') }; } method EXPR_reduce($termstack, $opstack) { Q:PIR { .local pmc self, termstack, opstack self = find_lex 'self' termstack = find_lex '$termstack' opstack = find_lex '$opstack' .local pmc op, opOPER, opO .local string opassoc op = pop opstack # Give it a fresh capture list, since we'll have assumed it has # no positional captures and not taken them. .local pmc cap_class cap_class = find_lex 'NQPCapture' $P0 = new ['ResizablePMCArray'] setattribute op, cap_class, '@!array', $P0 opOPER = op['OPER'] opO = opOPER['O'] $P0 = opO['assoc'] opassoc = $P0 if opassoc == 'unary' goto op_unary if opassoc == 'list' goto op_list op_infix: .local pmc right, left right = pop termstack left = pop termstack op[0] = left op[1] = right $P0 = opO['reducecheck'] if null $P0 goto op_infix_1 $S0 = $P0 self.$S0(op) op_infix_1: self.'!reduce_with_match'('EXPR', 'INFIX', op) goto done op_unary: .local pmc arg, afrom, ofrom arg = pop termstack op[0] = arg afrom = arg.'from'() ofrom = op.'from'() if afrom < ofrom goto op_postfix op_prefix: self.'!reduce_with_match'('EXPR', 'PREFIX', op) goto done op_postfix: self.'!reduce_with_match'('EXPR', 'POSTFIX', op) goto done op_list: .local string sym sym = '' $P0 = opOPER['sym'] if null $P0 goto op_list_1 sym = $P0 op_list_1: arg = pop termstack unshift op, arg op_sym_loop: unless opstack goto op_sym_done $S0 = '' $P0 = opstack[-1] $P0 = $P0['OPER'] $P0 = $P0['sym'] if null $P0 goto op_sym_1 $S0 = $P0 op_sym_1: if sym != $S0 goto op_sym_done arg = pop termstack unshift op, arg $P0 = pop opstack goto op_sym_loop op_sym_done: arg = pop termstack unshift op, arg self.'!reduce_with_match'('EXPR', 'LIST', op) goto done done: push termstack, op }; } method ternary($match) { $match[2] := $match[1]; $match[1] := $match{'infix'}{'EXPR'}; } method MARKER($markname) { my %markhash := Q:PIR { %r = get_global '%!MARKHASH' unless null %r goto have_markhash %r = new ['Hash'] set_global '%!MARKHASH', %r have_markhash: }; my $cur := self."!cursor_start"(); $cur."!cursor_pass"(self.pos()); %markhash{$markname} := $cur; } method MARKED($markname) { my %markhash := Q:PIR { %r = get_global '%!MARKHASH' unless null %r goto have_markhash %r = new ['Hash'] set_global '%!MARKHASH', %r have_markhash: }; my $cur := %markhash{$markname}; unless nqp::istype($cur, NQPCursor) && $cur.pos() == self.pos() { $cur := self."!cursor_start"(); } $cur } method LANG($lang, $regex) { my $lang_cursor := %*LANG{$lang}.'!cursor_init'(self.target(), :p(self.pos())); if self.HOW.traced(self) { $lang_cursor.HOW.trace-on($lang_cursor, self.HOW.trace_depth(self)); } my $*ACTIONS := %*LANG{$lang ~ '-actions'}; $lang_cursor."$regex"(); } }
9 POD Errors
The following errors were encountered while parsing the POD:
- Around line 130:
=begin without a target?
- Around line 132:
'=item' outside of any '=over'
=over without closing =back
- Around line 167:
'=end method O($spec, $save?) { Q:PIR { .local pmc self, cur_class .local string spec, save .local int has_save self = find_lex 'self' cur_class = find_lex '$cursor_class' $P0 = find_lex '$spec' spec = $P0 has_save = 0 $P0 = find_lex '$save' unless $P0 goto no_save save = $P0 has_save = 1 no_save:' is invalid. (Stack: =over)
- Around line 304:
=begin without a target?
- Around line 312:
'=end' without a target?
- Around line 334:
=begin without a target?
- Around line 341:
'=end' without a target?
- Around line 529:
=begin without a target?
- Around line 535:
'=end' without a target?