Skip to content

Commit 608ff67

Browse files
committed
allow code block in ** quantifiers, / foo ** { $a := 42 } /
In NQP the block can either provide a single item, which would be handled as foo**42, means that it must match exactly $item times. If the block provides a list [$min, $max], then the first two elems are used, like in foo**0..3. Note that the behaviour in rakudo is different, since we have Range objects there.
1 parent bb70129 commit 608ff67

File tree

5 files changed

+209
-9
lines changed

5 files changed

+209
-9
lines changed

src/QRegex/Cursor.nqp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,15 @@ role NQPCursorRole is export {
478478
$cur;
479479
}
480480

481+
method !DYNQUANT_LIMITS($mm) {
482+
if nqp::islist($mm) {
483+
+$mm > 1 ?? nqp::list_i($mm[0], $mm[1]) !! nqp::list_i($mm[0], $mm[0])
484+
}
485+
else {
486+
nqp::list_i($mm, $mm)
487+
}
488+
}
489+
481490
method at($pos) {
482491
my $cur := self."!cursor_start_cur"();
483492
$cur."!cursor_pass"($!pos) if +$pos == $!pos;

src/QRegex/P6Regex/Actions.nqp

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,46 @@ class QRegex::P6Regex::Actions is HLL::Actions {
149149

150150
method quantifier:sym<**>($/) {
151151
my $qast;
152-
my $min := $<min>.ast;
153-
my $max := -1;
154-
if ! $<max> { $max := $min }
155-
elsif $<max> ne '*' {
156-
$max := $<max>.ast;
157-
$/.CURSOR.panic("Empty range") if $min > $max;
158-
}
159-
$qast := QAST::Regex.new( :rxtype<quant>, :min($min), :max($max), :node($/) );
152+
if $<codeblock> {
153+
$qast := QAST::Regex.new( :rxtype<dynquant>, :node($/),
154+
QAST::Op.new( :op('callmethod'), :name('!DYNQUANT_LIMITS'),
155+
QAST::Var.new( :name(''), :scope('lexical') ),
156+
$<codeblock>.ast
157+
),
158+
);
159+
}
160+
else {
161+
my $min := $<min>.ast;
162+
my $max := -1;
163+
if ! $<max> { $max := $min }
164+
elsif $<max> ne '*' {
165+
$max := $<max>.ast;
166+
$/.CURSOR.panic("Empty range") if $min > $max;
167+
}
168+
$qast := QAST::Regex.new( :rxtype<quant>, :min($min), :max($max), :node($/) );
169+
}
160170
make backmod($qast, $<backmod>);
161171
}
162172

173+
method codeblock($/) {
174+
my $block := $<block>.ast;
175+
$block.blocktype('immediate');
176+
my $ast :=
177+
QAST::Stmts.new(
178+
QAST::Op.new(
179+
:op('bind'),
180+
QAST::Var.new( :name('$/'), :scope('lexical') ),
181+
QAST::Op.new(
182+
QAST::Var.new( :name(''), :scope('lexical') ),
183+
:name('MATCH'),
184+
:op('callmethod')
185+
)
186+
),
187+
$block
188+
);
189+
make $ast;
190+
}
191+
163192
method metachar:sym<[ ]>($/) {
164193
make $<nibbler>.ast;
165194
}

src/QRegex/P6Regex/Grammar.nqp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,14 @@ grammar QRegex::P6Regex::Grammar is HLL::Grammar {
225225
]
226226
]?
227227
{ $/.CURSOR.panic("Negative numbers are not allowed as quantifiers") if $<min>.Str < 0 }
228-
| <?[{]> { $/.CURSOR.panic("Block case of ** quantifier not yet implemented") }
228+
| <?[{]> <codeblock>
229229
]
230230
}
231231

232+
token codeblock {
233+
<block=.LANG('MAIN','pblock')>
234+
}
235+
232236
token backmod { ':'? [ '?' | '!' | <!before ':'> ] }
233237

234238
proto token metachar { <...> }

src/QRegex/P6Regex/Optimizer.nqp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ class QRegex::Optimizer {
171171
} elsif $type eq 'cclass' {
172172
} elsif $type eq 'scan' {
173173
} elsif $type eq 'charrange' {
174+
} elsif $type eq 'dynquant' {
175+
if $!main_opt {
176+
$node[$i] := $!main_opt($node[$i]);
177+
}
174178
} elsif $type eq 'pass' || $type eq 'fail' {
175179
} else {
176180
# alt, altseq, conjseq, conj, quant

src/vm/moar/QAST/QASTRegexCompilerMAST.nqp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,160 @@ class QAST::MASTRegexCompiler {
624624
@ins
625625
}
626626

627+
method dynquant($node) {
628+
my @ins := nqp::list();
629+
630+
my $backtrack := $node.backtrack || 'g';
631+
my $sep := $node[2];
632+
my $prefix := self.unique($*RXPREFIX ~ '_rxdynquant_' ~ $backtrack);
633+
my $looplabel_index := rxjump($prefix ~ '_loop');
634+
my $looplabel := @*RXJUMPS[$looplabel_index];
635+
my $donelabel_index := rxjump($prefix ~ '_done');
636+
my $donelabel := @*RXJUMPS[$donelabel_index];
637+
my $skip0label := label($prefix ~ '_skip0');
638+
my $skip1label := label($prefix ~ '_skip1');
639+
my $skip2label := label($prefix ~ '_skip2');
640+
my $skip3label := label($prefix ~ '_skip3');
641+
my $skip4label := label($prefix ~ '_skip4');
642+
my $skip5label := label($prefix ~ '_skip5');
643+
my $skip6label := label($prefix ~ '_skip6');
644+
my $skip7label := label($prefix ~ '_skip7');
645+
my $skip8label := label($prefix ~ '_skip8');
646+
my $needrep := fresh_i();
647+
my $needmark := fresh_i();
648+
my $rep := %*REG<rep>;
649+
my $pos := %*REG<pos>;
650+
my $ireg := fresh_i();
651+
652+
my $minmax := $node[1];
653+
my $minmax_reg := fresh_o();
654+
my $min_reg := fresh_i();
655+
my $max_reg := fresh_i();
656+
my $zero := fresh_i();
657+
my $one := fresh_i();
658+
659+
my $minmax_mast := $*QASTCOMPILER.as_mast($minmax, :want($MVM_reg_obj));
660+
my $res_reg := $minmax_mast.result_reg;
661+
merge_ins(@ins, $minmax_mast.instructions);
662+
merge_ins(@ins, [
663+
op('const_i64', $zero, ival(0)),
664+
op('const_i64', $one, ival(1)),
665+
op('atpos_i', $min_reg, $res_reg, $zero),
666+
op('atpos_i', $max_reg, $res_reg, $one),
667+
]);
668+
669+
# return if $min == 0 && $max == 0;
670+
merge_ins(@ins, [
671+
op('if_i', $min_reg, $skip8label),
672+
op('unless_i', $max_reg, $skip7label),
673+
$skip8label
674+
]);
675+
676+
# $needrep := $min > 1 || $max > 1;
677+
merge_ins(@ins, [
678+
op('gt_i', $needrep, $min_reg, $one),
679+
op('if_i', $needrep, $skip0label),
680+
op('gt_i', $needrep, $max_reg, $one),
681+
$skip0label
682+
]);
683+
684+
# $needmark := $needrep || $backtrack eq 'r';
685+
if $backtrack eq 'r' {
686+
nqp::push(@ins, op('set', $needmark, $one));
687+
}
688+
else {
689+
nqp::push(@ins, op('set', $needmark, $needrep));
690+
}
691+
692+
if $backtrack eq 'f' {
693+
my $seplabel := label($prefix ~ '_sep');
694+
nqp::push(@ins, op('set', $rep, %*REG<zero>));
695+
696+
nqp::push(@ins, op('ge_i', $ireg, $min_reg, $one)); # if $min < 1 {
697+
nqp::push(@ins, op('if_i', $ireg, $skip1label));
698+
self.regex_mark(@ins, $looplabel_index, $pos, $rep);
699+
nqp::push(@ins, op('goto', $donelabel));
700+
nqp::push(@ins, $skip1label); # }
701+
702+
nqp::push(@ins, op('goto', $seplabel)) if $sep;
703+
nqp::push(@ins, $looplabel);
704+
nqp::push(@ins, op('set', $ireg, $rep));
705+
if $sep {
706+
merge_ins(@ins, self.regex_mast($sep));
707+
nqp::push(@ins, $seplabel);
708+
}
709+
merge_ins(@ins, self.regex_mast($node[0]));
710+
merge_ins(@ins, [
711+
op('set', $rep, $ireg),
712+
op('inc_i', $rep),
713+
714+
op('le_i', $ireg, $min_reg, $one), # if $min > 1 {
715+
op('if_i', $ireg, $skip2label),
716+
op('lt_i', $ireg, $rep, $min_reg),
717+
op('if_i', $ireg, $looplabel),
718+
$skip2label, # }
719+
720+
op('le_i', $ireg, $max_reg, $one), # if $max > 1 {
721+
op('if_i', $ireg, $skip3label),
722+
op('ge_i', $ireg, $rep, $max_reg),
723+
op('if_i', $ireg, $donelabel),
724+
$skip3label, # }
725+
726+
op('eq_i', $ireg, $max_reg, $one), # unless $max == 1 {
727+
op('if_i', $ireg, $skip4label),
728+
]);
729+
self.regex_mark(@ins, $looplabel_index, $pos, $rep);
730+
nqp::push(@ins, $skip4label); # }
731+
732+
nqp::push(@ins, $donelabel);
733+
}
734+
else {
735+
nqp::push(@ins, op('if_i', $min_reg, $skip1label)); # if $min == 0 {
736+
self.regex_mark(@ins, $donelabel_index, $pos, %*REG<zero>);
737+
nqp::push(@ins, $skip1label); # }
738+
739+
nqp::push(@ins, op('unless_i', $min_reg, $skip2label)); # elsif $needmark {
740+
nqp::push(@ins, op('unless_i', $needmark, $skip2label));
741+
self.regex_mark(@ins, $donelabel_index, %*REG<negone>, %*REG<zero>);
742+
nqp::push(@ins, $skip2label); # }
743+
744+
nqp::push(@ins, $looplabel);
745+
merge_ins(@ins, self.regex_mast($node[0]));
746+
747+
nqp::push(@ins, op('unless_i', $needmark, $skip3label)); # if $needmark {
748+
self.regex_peek(@ins, $donelabel_index, MAST::Local.new(:index(-1)), $rep);
749+
self.regex_commit(@ins, $donelabel_index) if $backtrack eq 'r';
750+
merge_ins(@ins, [
751+
op('inc_i', $rep),
752+
753+
op('le_i', $ireg, $max_reg, $one), # if $max > 1 {
754+
op('if_i', $ireg, $skip4label),
755+
op('ge_i', $ireg, $rep, $max_reg),
756+
op('if_i', $ireg, $donelabel),
757+
$skip4label, # }
758+
$skip3label, # }
759+
760+
op('eq_i', $ireg, $max_reg, $one), # unless $max == 1 {
761+
op('if_i', $ireg, $skip5label),
762+
]);
763+
self.regex_mark(@ins, $donelabel_index, $pos, $rep);
764+
merge_ins(@ins, self.regex_mast($sep)) if $sep;
765+
merge_ins(@ins, [
766+
op('goto', $looplabel),
767+
$skip5label, # }
768+
$donelabel,
769+
770+
op('le_i', $ireg, $min_reg, $one), # if $min > 1 {
771+
op('if_i', $ireg, $skip6label),
772+
op('lt_i', $ireg, $rep, $min_reg),
773+
op('if_i', $ireg, %*REG<fail>),
774+
$skip6label, # }
775+
]);
776+
}
777+
nqp::push(@ins, $skip7label);
778+
@ins
779+
}
780+
627781
method quant($node) {
628782
my @ins := nqp::list();
629783
my $min := $node.min;

0 commit comments

Comments
 (0)