Skip to content

Commit

Permalink
allow code block in ** quantifiers, / foo ** { $a := 42 } /
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
FROGGS committed Jul 25, 2014
1 parent bb70129 commit 3097e14
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 9 deletions.
9 changes: 9 additions & 0 deletions src/QRegex/Cursor.nqp
Expand Up @@ -478,6 +478,15 @@ role NQPCursorRole is export {
$cur;
}

method !DYNQUANT_LIMITS($mm) {
if nqp::islist($mm) {
+$mm > 1 ?? nqp::list_i($mm[0], $mm[1]) !! nqp::list_i($mm[0], $mm[0])
}
else {
nqp::list_i($mm, $mm)
}
}

method at($pos) {
my $cur := self."!cursor_start_cur"();
$cur."!cursor_pass"($!pos) if +$pos == $!pos;
Expand Down
45 changes: 37 additions & 8 deletions src/QRegex/P6Regex/Actions.nqp
Expand Up @@ -149,17 +149,46 @@ class QRegex::P6Regex::Actions is HLL::Actions {

method quantifier:sym<**>($/) {
my $qast;
my $min := $<min>.ast;
my $max := -1;
if ! $<max> { $max := $min }
elsif $<max> ne '*' {
$max := $<max>.ast;
$/.CURSOR.panic("Empty range") if $min > $max;
}
$qast := QAST::Regex.new( :rxtype<quant>, :min($min), :max($max), :node($/) );
if $<codeblock> {
$qast := QAST::Regex.new( :rxtype<dynquant>, :node($/),
QAST::Op.new( :op('callmethod'), :name('!DYNQUANT_LIMITS'),
QAST::Var.new( :name(''), :scope('lexical') ),
$<codeblock>.ast
),
);
}
else {
my $min := $<min>.ast;
my $max := -1;
if ! $<max> { $max := $min }
elsif $<max> ne '*' {
$max := $<max>.ast;
$/.CURSOR.panic("Empty range") if $min > $max;
}
$qast := QAST::Regex.new( :rxtype<quant>, :min($min), :max($max), :node($/) );
}
make backmod($qast, $<backmod>);
}

method codeblock($/) {
my $block := $<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 metachar:sym<[ ]>($/) {
make $<nibbler>.ast;
}
Expand Down
6 changes: 5 additions & 1 deletion src/QRegex/P6Regex/Grammar.nqp
Expand Up @@ -225,10 +225,14 @@ grammar QRegex::P6Regex::Grammar is HLL::Grammar {
]
]?
{ $/.CURSOR.panic("Negative numbers are not allowed as quantifiers") if $<min>.Str < 0 }
| <?[{]> { $/.CURSOR.panic("Block case of ** quantifier not yet implemented") }
| <?[{]> <codeblock>
]
}

token codeblock {
<block=.LANG('MAIN','pblock')>
}

token backmod { ':'? [ '?' | '!' | <!before ':'> ] }

proto token metachar { <...> }
Expand Down
4 changes: 4 additions & 0 deletions src/QRegex/P6Regex/Optimizer.nqp
Expand Up @@ -171,6 +171,10 @@ class QRegex::Optimizer {
} elsif $type eq 'cclass' {
} elsif $type eq 'scan' {
} elsif $type eq 'charrange' {
} elsif $type eq 'dynquant' {
if $!main_opt {
$node[$i] := $!main_opt($node[$i]);
}
} elsif $type eq 'pass' || $type eq 'fail' {
} else {
# alt, altseq, conjseq, conj, quant
Expand Down
154 changes: 154 additions & 0 deletions src/vm/moar/QAST/QASTRegexCompilerMAST.nqp
Expand Up @@ -624,6 +624,160 @@ class QAST::MASTRegexCompiler {
@ins
}

method dynquant($node) {
my @ins := nqp::list();

my $backtrack := $node.backtrack || 'g';
my $sep := $node[2];
my $prefix := self.unique($*RXPREFIX ~ '_rxdynquant_' ~ $backtrack);
my $looplabel_index := rxjump($prefix ~ '_loop');
my $looplabel := @*RXJUMPS[$looplabel_index];
my $donelabel_index := rxjump($prefix ~ '_done');
my $donelabel := @*RXJUMPS[$donelabel_index];
my $skip0label := label($prefix ~ '_skip0');
my $skip1label := label($prefix ~ '_skip1');
my $skip2label := label($prefix ~ '_skip2');
my $skip3label := label($prefix ~ '_skip3');
my $skip4label := label($prefix ~ '_skip4');
my $skip5label := label($prefix ~ '_skip5');
my $skip6label := label($prefix ~ '_skip6');
my $skip7label := label($prefix ~ '_skip7');
my $skip8label := label($prefix ~ '_skip8');
my $needrep := fresh_i();
my $needmark := fresh_i();
my $rep := %*REG<rep>;
my $pos := %*REG<pos>;
my $ireg := fresh_i();

my $minmax := $node[1];
my $minmax_reg := fresh_o();
my $min_reg := fresh_i();
my $max_reg := fresh_i();
my $zero := fresh_i();
my $one := fresh_i();

my $minmax_mast := $*QASTCOMPILER.as_mast($minmax, :want($MVM_reg_obj));
my $res_reg := $minmax_mast.result_reg;
merge_ins(@ins, $minmax_mast.instructions);
merge_ins(@ins, [
op('const_i64', $zero, ival(0)),
op('const_i64', $one, ival(1)),
op('atpos_i', $min_reg, $res_reg, $zero),
op('atpos_i', $max_reg, $res_reg, $one),
]);

# return if $min == 0 && $max == 0;
merge_ins(@ins, [
op('if_i', $min_reg, $skip8label),
op('unless_i', $max_reg, $skip7label),
$skip8label
]);

# $needrep := $min > 1 || $max > 1;
merge_ins(@ins, [
op('gt_i', $needrep, $min_reg, $one),
op('if_i', $needrep, $skip0label),
op('gt_i', $needrep, $max_reg, $one),
$skip0label
]);

# $needmark := $needrep || $backtrack eq 'r';
if $backtrack eq 'r' {
nqp::push(@ins, op('set', $needmark, $one));
}
else {
nqp::push(@ins, op('set', $needmark, $needrep));
}

if $backtrack eq 'f' {
my $seplabel := label($prefix ~ '_sep');
nqp::push(@ins, op('set', $rep, %*REG<zero>));

nqp::push(@ins, op('ge_i', $ireg, $min_reg, $one)); # if $min < 1 {
nqp::push(@ins, op('if_i', $ireg, $skip1label));
self.regex_mark(@ins, $looplabel_index, $pos, $rep);
nqp::push(@ins, op('goto', $donelabel));
nqp::push(@ins, $skip1label); # }

nqp::push(@ins, op('goto', $seplabel)) if $sep;
nqp::push(@ins, $looplabel);
nqp::push(@ins, op('set', $ireg, $rep));
if $sep {
merge_ins(@ins, self.regex_mast($sep));
nqp::push(@ins, $seplabel);
}
merge_ins(@ins, self.regex_mast($node[0]));
merge_ins(@ins, [
op('set', $rep, $ireg),
op('inc_i', $rep),

op('le_i', $ireg, $min_reg, $one), # if $min > 1 {
op('if_i', $ireg, $skip2label),
op('lt_i', $ireg, $rep, $min_reg),
op('if_i', $ireg, $looplabel),
$skip2label, # }

op('le_i', $ireg, $max_reg, $one), # if $max > 1 {
op('if_i', $ireg, $skip3label),
op('ge_i', $ireg, $rep, $max_reg),
op('if_i', $ireg, $donelabel),
$skip3label, # }

op('eq_i', $ireg, $max_reg, $one), # unless $max == 1 {
op('if_i', $ireg, $skip4label),
]);
self.regex_mark(@ins, $looplabel_index, $pos, $rep);
nqp::push(@ins, $skip4label); # }

nqp::push(@ins, $donelabel);
}
else {
nqp::push(@ins, op('if_i', $min_reg, $skip1label)); # if $min == 0 {
self.regex_mark(@ins, $donelabel_index, $pos, %*REG<zero>);
nqp::push(@ins, $skip1label); # }

nqp::push(@ins, op('unless_i', $min_reg, $skip2label)); # elsif $needmark {
nqp::push(@ins, op('unless_i', $needmark, $skip2label));
self.regex_mark(@ins, $donelabel_index, %*REG<negone>, %*REG<zero>);
nqp::push(@ins, $skip2label); # }

nqp::push(@ins, $looplabel);
merge_ins(@ins, self.regex_mast($node[0]));

nqp::push(@ins, op('unless_i', $needmark, $skip3label)); # if $needmark {
self.regex_peek(@ins, $donelabel_index, MAST::Local.new(:index(-1)), $rep);
self.regex_commit(@ins, $donelabel_index) if $backtrack eq 'r';
merge_ins(@ins, [
op('inc_i', $rep),

op('le_i', $ireg, $max_reg, $one), # if $max > 1 {
op('if_i', $ireg, $skip4label),
op('ge_i', $ireg, $rep, $max_reg),
op('if_i', $ireg, $donelabel),
$skip4label, # }
$skip3label, # }

op('eq_i', $ireg, $max_reg, $one), # unless $max == 1 {
op('if_i', $ireg, $skip5label),
]);
self.regex_mark(@ins, $donelabel_index, $pos, $rep);
merge_ins(@ins, self.regex_mast($sep)) if $sep;
merge_ins(@ins, [
op('goto', $looplabel),
$skip5label, # }
$donelabel,

op('le_i', $ireg, $min_reg, $one), # if $min > 1 {
op('if_i', $ireg, $skip6label),
op('lt_i', $ireg, $rep, $min_reg),
op('if_i', $ireg, %*REG<fail>),
$skip6label, # }
]);
}
nqp::push(@ins, $skip7label);
@ins
}

method quant($node) {
my @ins := nqp::list();
my $min := $node.min;
Expand Down

0 comments on commit 3097e14

Please sign in to comment.