Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[GGE::Exp] codegen reimplementation

We no longer use TreeSpider to execute the regular expression. Instead, the
required Perl 6 code is generated and then compiled.

After this change, all of the old tests pass, and a few more to boot.
  • Loading branch information...
commit f9e2d4a3d33533270a54a523277d0b9bebd995cc 1 parent ad73e3f
Carl Mäsak authored January 19, 2010
4  Makefile.in
@@ -2,8 +2,8 @@ PERL6=<PERL6>
2 2
 RAKUDO_DIR=<RAKUDO_DIR>
3 3
 PERL6LIB='<PERL6LIB>:$(RAKUDO_DIR)'
4 4
 
5  
-SOURCES=lib/GGE/Match.pm lib/GGE/Exp.pm lib/GGE/TreeSpider.pm \
6  
-        lib/GGE/OPTable.pm lib/GGE/Perl6Regex.pm lib/GGE.pm
  5
+SOURCES=lib/GGE/Match.pm lib/GGE/Exp.pm lib/GGE/OPTable.pm \
  6
+        lib/GGE/Perl6Regex.pm lib/GGE.pm
7 7
 
8 8
 PIRS=$(SOURCES:.pm=.pir)
9 9
 
789  lib/GGE/Exp.pm
... ...
@@ -1,6 +1,27 @@
1 1
 use v6;
2 2
 use GGE::Match;
3 3
 
  4
+class CodeString {
  5
+    has Str $!contents = '';
  6
+    my $counter = 0;
  7
+
  8
+    method emit($string, *@args, *%kwargs) {
  9
+        $!contents ~= $string\
  10
+                        .subst(/\%(\d)/, {   @args[$0] // '...' }, :g)\
  11
+                        .subst(/\%(\w)/, { %kwargs{$0} // '...' }, :g); 
  12
+    }
  13
+
  14
+    method escape($string) {
  15
+        q['] ~ $string.trans( [ q['], q[\\] ] => [ q[\\'], q[\\\\] ] ) ~ q['];
  16
+    }
  17
+
  18
+    method unique($prefix = '') {
  19
+        $prefix ~ $counter++
  20
+    }
  21
+
  22
+    method Str { $!contents }
  23
+}
  24
+
4 25
 # a GGE::Exp describing what it contains, most commonly its .ast property,
5 26
 # but sometimes other things.
6 27
 role GGE::ShowContents {
@@ -9,36 +30,16 @@ role GGE::ShowContents {
9 30
     }
10 31
 }
11 32
 
12  
-# The set of possible responses sent back to GGE::TreeSpider.
13  
-
14  
-# RAKUDO: Could name this one GGE::Exp::Actions or something, if enums
  33
+# RAKUDO: Could name this one GGE::Exp::CUT or something, if enums
15 34
 #         with '::' in them worked, which they don't. [perl #71460]
16  
-enum Action <
17  
-    DESCEND
18  
-    MATCH
19  
-    FAIL
20  
-    FAIL_GROUP
21  
-    FAIL_RULE
22  
-    BACKTRACK
23  
->;
24  
-
25  
-role GGE::Backtracking {}  # a GGE::Exp involved in backtracking
26  
-role GGE::Container {}     # a GGE::Exp containing other GGE::Exp nodes
27  
-role GGE::MultiChild does GGE::Container {}  # ...containing several...
28  
-
29  
-# RAKUDO: Blablabla, GGE::Exp::Cut, blablabla, see above. [perl #71460]
30  
-enum Cut <
31  
-    CUT_GROUP
32  
-    CUT_RULE
33  
-    CUT_MATCH
34  
->;
  35
+enum CUT (
  36
+    CUT_GROUP => -1,
  37
+    CUT_RULE  => -2,
  38
+    CUT_MATCH => -3,
  39
+);
35 40
 
36 41
 class GGE::Exp is GGE::Match {
37  
-    method start($, $, %) { MATCH }
38  
-    method succeeded($, %) { MATCH }
39  
-    method failed($, %) { FAIL }
40  
-    method failed-group($, %) { FAIL_GROUP }
41  
-    method failed-rule($, %) { FAIL_RULE }
  42
+    my $group;
42 43
 
43 44
     method structure($indent = 0) {
44 45
         # RAKUDO: The below was originally written as a map, but there's
@@ -58,22 +59,166 @@ class GGE::Exp is GGE::Match {
58 59
         $contents ~= $inside;
59 60
         '  ' x $indent ~ self.WHAT.perl.subst(/^.*':'/, '') ~ $contents;
60 61
     }
61  
-}
62 62
 
63  
-class GGE::Exp::Literal is GGE::Exp does GGE::ShowContents {
64  
-    method start($string, $pos is rw, %pad) {
65  
-        my $value = ~self.ast;
66  
-        if $pos < $string.chars
67  
-           && (self.hash-access('ignorecase')
68  
-                 && $string.substr($pos, $value.chars).lc eq $value.lc
69  
-               || $string.substr($pos, $value.chars) eq $value) {
70  
-            $pos += $value.chars;
71  
-            MATCH
  63
+    method compile(:$debug) {
  64
+        my $source = self.root-p6(:$debug);
  65
+        if $debug {
  66
+            say $source;
  67
+            say '';
72 68
         }
73  
-        else {
74  
-            FAIL
  69
+        my $binary = eval $source
  70
+            or die ~$!;
  71
+        return $binary;
  72
+    }
  73
+
  74
+    method reduce() {
  75
+        self;
  76
+    }
  77
+
  78
+    method root-p6(:$debug) {
  79
+        my $code = CodeString.new();
  80
+        $code.unique(); # XXX: Remove this one when we do other real calls
  81
+        $code.emit( q[[sub ($target, :$debug) {
  82
+    my $mob = GGE::Match.new(:$target);
  83
+    my $mfrom;
  84
+    my $cpos = 0;
  85
+    my $pos;
  86
+    my $rep;
  87
+    my $lastpos = $target.chars;
  88
+    my $cutmark;
  89
+    my @gpad;             # TODO: PGE generates this one only when needed
  90
+    my @ustack;           # TODO: PGE generates this one only when needed
  91
+    my $captscope = $mob; # TODO: PGE generates this one only when needed
  92
+    my $captob;           # TODO: PGE generates this one only when needed
  93
+    my @cstack = 'try_match';
  94
+    my &goto = -> $label { @cstack[*-1] = $label };
  95
+    my &local-branch = -> $label {
  96
+        @cstack[*-1] ~= '_cont';
  97
+        @cstack.push($label)
  98
+    };
  99
+    my &local-return = -> { @cstack.pop };
  100
+    loop {
  101
+        given @cstack[*-1] {
  102
+            when 'try_match' {
  103
+                if $cpos > $lastpos { goto('fail_rule'); break; }
  104
+                $mfrom = $pos = $cpos;
  105
+                $cutmark = 0;
  106
+                local-branch('R');
  107
+            }
  108
+            when 'try_match_cont' {
  109
+                if $cutmark <= %0 { goto('fail_cut'); break; }
  110
+                ++$cpos;
  111
+                goto('try_match');
  112
+            }
  113
+            when 'fail_rule' {
  114
+                # $cutmark = %0 # XXX: Not needed yet
  115
+                goto('fail_cut');
  116
+            }
  117
+            when 'fail_cut' {
  118
+                $mob.from = 0;
  119
+                $mob.to = -2;
  120
+                return $mob;
  121
+            }
  122
+            when 'succeed' {
  123
+                $mob.from = $mfrom;
  124
+                $mob.to = $pos;
  125
+                return $mob;
  126
+            }
  127
+            when 'fail' {
  128
+                local-return();
  129
+            } ]], CUT_RULE);
  130
+        my $explabel = 'R';
  131
+        $GGE::Exp::group = self;
  132
+        my $exp = self.reduce;
  133
+        if $debug {
  134
+            say $exp.structure;
  135
+            say '';
  136
+        }
  137
+        $exp.p6($code, $explabel, 'succeed');
  138
+        $code.emit( q[[
  139
+            default {
  140
+                die "No such label: {@cstack[*-1]}";
  141
+            }
75 142
         }
76 143
     }
  144
+} ]]);
  145
+    }
  146
+
  147
+    method getargs($label, $next, %hash?) {
  148
+        %hash<L S> = $label, $next;
  149
+        if %hash.exists('quant') {
  150
+            my $quant = %hash<quant>;
  151
+            %hash<m> = $quant.hash-access('min');
  152
+            %hash<M> = %hash<m> == 0   ?? '### ' !! '';
  153
+            %hash<n> = $quant.hash-access('max');
  154
+            %hash<N> = %hash<n> == Inf ?? '### ' !! '';
  155
+            my $bt = $quant.hash-access('backtrack').name.lc;
  156
+            %hash<Q> = sprintf '%s..%s (%s)', %hash<m>, %hash<n>, $bt;
  157
+        }
  158
+        return %hash;
  159
+    }
  160
+
  161
+    method gencapture($label) {
  162
+        my $cname = self.hash-access('cname');
  163
+        my $captgen  = CodeString.new;
  164
+        my $captsave = CodeString.new;
  165
+        my $captback = CodeString.new;
  166
+        if self.hash-access('iscapture') {
  167
+            if self.hash-access('isarray') {
  168
+                $captsave.emit('$captscope[%0].push($captob);', $cname);
  169
+                $captback.emit('$captscope[%0].pop();', $cname);
  170
+                $captgen.emit( q[[if defined $captscope[%0] {
  171
+                    goto('%1_cgen');
  172
+                    break;
  173
+                }
  174
+                $captscope[%0] = [];
  175
+                local-branch('%1_cgen');
  176
+            }
  177
+            when '%1_cont' {
  178
+                $captscope[%0] = undef;
  179
+                goto('fail');
  180
+            }
  181
+            when '%1_cgen' { ]], $cname, $label);
  182
+            }
  183
+            else {
  184
+                if $cname.substr(0, 1) eq q['] {
  185
+                    $captsave.emit('$captscope.hash-access(%0) = $captob;',
  186
+                                   $cname);
  187
+                    $captback.emit('$captscope.delete(%0);', $cname);
  188
+                }
  189
+                else {
  190
+                    $captsave.emit('$captscope[%0] = $captob;', $cname);
  191
+                    $captback.emit('$captscope[%0] = undef;', $cname);
  192
+                }
  193
+            }
  194
+        }
  195
+        # RAKUDO: Cannot do multiple returns yet.
  196
+        return ($captgen, $captsave, $captback);
  197
+    }
  198
+}
  199
+
  200
+class GGE::Exp::Literal is GGE::Exp does GGE::ShowContents {
  201
+    method p6($code, $label, $next) {
  202
+        my %args = self.getargs($label, $next);
  203
+        my $literal = self.ast;
  204
+        my $litlen = $literal.chars;
  205
+        %args<I> = '';
  206
+        if self.hash-access('ignorecase') {
  207
+            %args<I> = '.lc';
  208
+            $literal .= lc;
  209
+        }
  210
+        $literal = $code.escape($literal);
  211
+        $code.emit( q[
  212
+            when '%L' {
  213
+                if $pos + %0 > $lastpos
  214
+                   || $target.substr($pos, %0)%I ne %1 {
  215
+                    goto('fail');
  216
+                    break;
  217
+                }
  218
+                $pos += %0;
  219
+                goto('%S');
  220
+            } ], $litlen, $literal, |%args);
  221
+    }
77 222
 }
78 223
 
79 224
 enum GGE_BACKTRACK <
@@ -82,7 +227,7 @@ enum GGE_BACKTRACK <
82 227
     NONE
83 228
 >;
84 229
 
85  
-class GGE::Exp::Quant is GGE::Exp does GGE::Backtracking does GGE::Container {
  230
+class GGE::Exp::Quant is GGE::Exp {
86 231
     method contents() {
87 232
         my ($min, $max, $bt) = map { self.hash-access($_) },
88 233
                                    <min max backtrack>;
@@ -90,142 +235,240 @@ class GGE::Exp::Quant is GGE::Exp does GGE::Backtracking does GGE::Container {
90 235
         "{$bt.name.lc} $min..$max"
91 236
     }
92 237
 
93  
-    method start($_: $, $, %pad is rw) {
94  
-        %pad<reps> = 0;
95  
-        my $bt = .hash-access('backtrack') // GREEDY;
96  
-        if .hash-access('min') > 0 {
97  
-            DESCEND
98  
-        }
99  
-        elsif .hash-access('max') > 0 && $bt != EAGER {
100  
-            if %pad<reps> >= .hash-access('min') {
101  
-                (%pad<mempos> //= []).push(%pad<pos>);
  238
+    method p6($code, $label, $next) {
  239
+        my %args = self.getargs($label, $next, { quant => self });
  240
+        my $replabel = $label ~ '_repeat';
  241
+        my $nextlabel = $code.unique('R');
  242
+        %args<c C> = 0, '### ';
  243
+        given self.hash-access('backtrack') {
  244
+            when EAGER {
  245
+                $code.emit( q[[
  246
+            when '%L' { # quant %Q eager
  247
+                push @gpad, 0;
  248
+                local-branch('%0');
102 249
             }
103  
-            DESCEND
104  
-        }
105  
-        else {
106  
-            MATCH
107  
-        }
108  
-    }
109  
-
110  
-    method succeeded($_: $, %pad is rw) {
111  
-        ++%pad<reps>;
112  
-        if (.hash-access('backtrack') // GREEDY) != EAGER
113  
-           && %pad<reps> < .hash-access('max') {
114  
-            if %pad<reps> > .hash-access('min') {
115  
-                (%pad<mempos> //= []).push(%pad<pos>);
  250
+            when '%L_cont' {
  251
+                pop @gpad;
  252
+                goto('fail');
  253
+            }
  254
+            when '%0' {
  255
+                $rep = @gpad[*-1];
  256
+                %Mif $rep < %m { goto('%L_1'); break; }
  257
+                pop @gpad;
  258
+                push @ustack, $pos;
  259
+                push @ustack, $rep;
  260
+                local-branch('%S');
  261
+            }
  262
+            when '%0_cont' {
  263
+                $rep = pop @ustack;
  264
+                $pos = pop @ustack;
  265
+                push @gpad, $rep;
  266
+                goto('%L_1');
  267
+            }
  268
+            when '%L_1' {
  269
+                %Nif $rep >= %n { goto('fail'); break; }
  270
+                ++$rep;
  271
+                @gpad[*-1] = $rep;
  272
+                goto('%1');
  273
+            } ]], $replabel, $nextlabel, |%args);
  274
+            }
  275
+            when NONE {
  276
+                %args<c C> = $code.unique(), '';
  277
+                if self.hash-access('min') != 0
  278
+                   || self.hash-access('max') != Inf {
  279
+                    continue;
  280
+                }
  281
+                $code.emit( q[[
  282
+            when '%L' { # quant 0..Inf none
  283
+                local-branch('%0');
  284
+            }
  285
+            when '%L_cont' {
  286
+                if $cutmark != %c { goto('fail'); break; }
  287
+                $cutmark = 0;
  288
+                goto('fail');
  289
+            }
  290
+            when '%0' {
  291
+                push @ustack, $pos;
  292
+                local-branch('%1');
  293
+            }
  294
+            when '%0_cont' {
  295
+                $pos = pop @ustack;
  296
+                if $cutmark != 0 { goto('fail'); break; }
  297
+                local-branch('%S');
  298
+            }
  299
+            when '%0_cont_cont' {
  300
+                if $cutmark != 0 { goto('fail'); break; }
  301
+                $cutmark = %c;
  302
+                goto('fail');
  303
+            } ]], $replabel, $nextlabel, |%args);
  304
+            }
  305
+            default {
  306
+                $code.emit( q[[
  307
+            when '%L' { # quant %Q greedy/none
  308
+                push @gpad, 0;
  309
+                local-branch('%0');
  310
+            }
  311
+            when '%L_cont' {
  312
+                pop @gpad;
  313
+                %Cif $cutmark != %c { goto('fail'); break; }
  314
+                %C$cutmark = 0;
  315
+                goto('fail');
  316
+            }
  317
+            when '%0' {
  318
+                $rep = @gpad[*-1];
  319
+                %Nif $rep >= %n { goto('%L_1'); break; }
  320
+                ++$rep;
  321
+                @gpad[*-1] = $rep;
  322
+                push @ustack, $pos;
  323
+                push @ustack, $rep;
  324
+                local-branch('%1');
  325
+            }
  326
+            when '%0_cont' {
  327
+                $rep = pop @ustack;
  328
+                $pos = pop @ustack;
  329
+                if $cutmark != 0 { goto('fail'); break; }
  330
+                --$rep;
  331
+                goto('%L_1');
  332
+            }
  333
+            when '%L_1' {
  334
+                %Mif $rep < %m { goto('fail'); break; }
  335
+                pop @gpad;
  336
+                push @ustack, $rep;
  337
+                local-branch('%S');
  338
+            }
  339
+            when '%L_1_cont' {
  340
+                $rep = pop @ustack;
  341
+                push @gpad, $rep;
  342
+                if $cutmark != 0 { goto('fail'); break; }
  343
+                %C$cutmark = %c;
  344
+                goto('fail');
  345
+            } ]], $replabel, $nextlabel, |%args);
116 346
             }
117  
-            DESCEND
118  
-        }
119  
-        else {
120  
-            MATCH
121  
-        }
122  
-    }
123  
-
124  
-    method failed($_: $pos, %pad is rw) {
125  
-        if %pad<reps> >= .hash-access('min') {
126  
-            MATCH
127  
-        }
128  
-        else {
129  
-            FAIL
130  
-        }
131  
-    }
132  
-
133  
-    method backtracked($_: $pos is rw, %pad) {
134  
-        my $bt = .hash-access('backtrack') // GREEDY;
135  
-        if $bt == EAGER
136  
-           && %pad<reps> < .hash-access('max') {
137  
-            DESCEND
138  
-        }
139  
-        elsif $bt == GREEDY && +%pad<mempos> {
140  
-            $pos = pop %pad<mempos>;
141  
-            MATCH
142  
-        }
143  
-        else {
144  
-            FAIL
145 347
         }
  348
+        self[0].p6($code, $nextlabel, $replabel);
146 349
     }
147 350
 }
148 351
 
149 352
 class GGE::Exp::CCShortcut is GGE::Exp does GGE::ShowContents {
150  
-    method start($string, $pos is rw, %pad) {
151  
-        my $cc-char = self.ast.substr(1);
152  
-        if $pos >= $string.chars {
153  
-            FAIL
154  
-        }
155  
-        elsif self.ast eq '.'
156  
-           || self.ast eq '\\N' && !($string.substr($pos, 1) eq "\n"|"\r")
157  
-           || self.ast eq '\\s' && $string.substr($pos, 1) ~~ /\s/
158  
-           || self.ast eq '\\S' && $string.substr($pos, 1) ~~ /\S/
159  
-           || self.ast eq '\\w' && $string.substr($pos, 1) ~~ /\w/
160  
-           || self.ast eq '\\W' && $string.substr($pos, 1) ~~ /\W/
161  
-           || self.ast eq '\\d' && $string.substr($pos, 1) ~~ /\d/
162  
-           || self.ast eq '\\D' && $string.substr($pos, 1) ~~ /\D/ {
163  
-            ++$pos;
164  
-            MATCH
165  
-        }
166  
-        else {
167  
-            FAIL
168  
-        }
  353
+    method p6($code, $label, $next) {
  354
+        my $failcond = self.ast eq '.'
  355
+                       ?? 'False'
  356
+                       !! sprintf '$target.substr($pos, 1) !~~ /%s/', self.ast;
  357
+        $code.emit( q[
  358
+            when '%0' { # ccshortcut %1
  359
+                if $pos >= $lastpos || %2 {
  360
+                    goto('fail');
  361
+                    break;
  362
+                }
  363
+                ++$pos;
  364
+                goto('%3');
  365
+            } ], $label, self.ast, $failcond, $next );
169 366
     }
170 367
 }
171 368
 
172 369
 class GGE::Exp::Newline is GGE::Exp does GGE::ShowContents {
173  
-    method start($string, $pos is rw, %pad) {
174  
-        if $pos >= $string.chars {
175  
-            FAIL
176  
-        }
177  
-        elsif $string.substr($pos, 2) eq "\r\n" {
178  
-            $pos += 2;
179  
-            MATCH
180  
-        }
181  
-        elsif $string.substr($pos, 1) eq "\n"|"\r" {
182  
-            ++$pos;
183  
-            MATCH
184  
-        }
185  
-        else {
186  
-            FAIL
187  
-        }
  370
+    method p6($code, $label, $next) {
  371
+        $code.emit( q[
  372
+            when '%0' { # newline
  373
+                unless $target.substr($pos, 1) eq "\n"|"\r" {
  374
+                    goto('fail');
  375
+                    break;
  376
+                }
  377
+                my $twochars = $target.substr($pos, 2);
  378
+                ++$pos;
  379
+                if $twochars eq "\r\n" {
  380
+                    ++$pos;
  381
+                }
  382
+                goto('%1');
  383
+            } ], $label, $next);
188 384
     }
189 385
 }
190 386
 
191 387
 class GGE::Exp::Anchor is GGE::Exp does GGE::ShowContents {
192  
-    method start($string, $pos is rw, %pad) {
193  
-        my $matches = self.ast eq '^' && $pos == 0
194  
-            || self.ast eq '$' && $pos == $string.chars
195  
-            || self.ast eq '<<' && $string.substr($pos, 1) ~~ /\w/
196  
-               && ($pos == 0 || $string.substr($pos - 1, 1) !~~ /\w/)
197  
-            || self.ast eq '>>' && $pos > 0
198  
-               && $string.substr($pos - 1, 1) ~~ /\w/
199  
-               && ($pos == $string.chars || $string.substr($pos, 1) !~~ /\w/)
200  
-            || self.ast eq '^^' && ($pos == 0 || $pos < $string.chars
201  
-               && $string.substr($pos - 1, 1) eq "\n")
202  
-            || self.ast eq '$$' && ($string.substr($pos, 1) eq "\n"
203  
-               || $pos == $string.chars
204  
-                  && ($pos < 1 || $string.substr($pos - 1, 1) ne "\n"));
205  
-        $matches ?? MATCH !! FAIL;
  388
+    method p6($code, $label, $next) {
  389
+        $code.emit( q[
  390
+            when '%0' { # anchor %1 ], $label, self.ast );
  391
+        given self.ast {
  392
+            when '^' {
  393
+                $code.emit( q[
  394
+                if $pos == 0 { goto('%0'); break; }
  395
+                goto('fail'); ], $next );
  396
+            }
  397
+            when '$' {
  398
+                $code.emit( q[
  399
+                if $pos == $lastpos { goto('%0'); break; }
  400
+                goto('fail'); ], $next );
  401
+            }
  402
+            when '<<' {
  403
+                $code.emit( q[
  404
+                if $target.substr($pos, 1) ~~ /\w/
  405
+                   && ($pos == 0 || $target.substr($pos - 1, 1) !~~ /\w/) {
  406
+                    goto('%0');
  407
+                    break;
  408
+                }
  409
+                goto('fail'); ], $next );
  410
+            }
  411
+            when '>>' {
  412
+                $code.emit( q[
  413
+                if $pos > 0 && $target.substr($pos - 1, 1) ~~ /\w/
  414
+                   && ($pos == $lastpos || $target.substr($pos, 1) !~~ /\w/) {
  415
+                    goto('%0');
  416
+                    break;
  417
+                }
  418
+                goto('fail'); ], $next );
  419
+            }
  420
+            when '^^' {
  421
+                $code.emit( q[
  422
+                if $pos == 0 || $pos < $lastpos
  423
+                                && $target.substr($pos - 1, 1) eq "\n" {
  424
+                    goto('%0');
  425
+                    break;
  426
+                }
  427
+                goto('fail'); ], $next );
  428
+            }
  429
+            when '$$' {
  430
+                $code.emit( q[
  431
+                if $target.substr($pos, 1) eq "\n"
  432
+                   || $pos == $lastpos
  433
+                      && ($pos < 1 || $target.substr($pos - 1, 1) ne "\n") {
  434
+                    goto('%0');
  435
+                    break;
  436
+                }
  437
+                goto('fail'); ], $next );
  438
+            }
  439
+        }
  440
+        $code.emit( q[
  441
+            } ]);
206 442
     }
207 443
 }
208 444
 
209  
-class GGE::Exp::Concat is GGE::Exp does GGE::MultiChild {
210  
-    method start($, $, %pad is rw) {
211  
-        %pad<child> = 0;
212  
-        DESCEND
  445
+class GGE::Exp::Concat is GGE::Exp {
  446
+    method reduce() {
  447
+        my $n = self.elems;
  448
+        my @old-children = self.llist;
  449
+        self.clear;
  450
+        for @old-children -> $old-child {
  451
+            my $new-child = $old-child.reduce();
  452
+            self.push($new-child);
  453
+        }
  454
+        return self.llist == 1 ?? self[0] !! self;
213 455
     }
214 456
 
215  
-    method succeeded($, %pad is rw) {
216  
-        if ++%pad<child> == self.elems {
217  
-            MATCH
218  
-        }
219  
-        else {
220  
-            DESCEND
  457
+    method p6($code, $label, $next) {
  458
+        $code.emit( q[
  459
+            # concat ]);
  460
+        my $cl = $label;
  461
+        my $nl;
  462
+        my $end = self.llist.elems - 1;
  463
+        for self.llist.kv -> $i, $child {
  464
+            $nl = $i == $end ?? $next !! $code.unique('R');
  465
+            $child.p6($code, $cl, $nl);
  466
+            $cl = $nl;
221 467
         }
222 468
     }
223 469
 }
224 470
 
225  
-class GGE::Exp::Modifier   is GGE::Exp
226  
-                         does GGE::ShowContents
227  
-                         does GGE::Container
228  
-{
  471
+class GGE::Exp::Modifier   is GGE::Exp does GGE::ShowContents {
229 472
     method contents() {
230 473
         self.hash-access('key');
231 474
     }
@@ -241,94 +484,210 @@ class GGE::Exp::EnumCharList is GGE::Exp does GGE::ShowContents {
241 484
         qq[$zw$neg$list]
242 485
     }
243 486
 
244  
-    method start($string, $pos is rw, %pad) {
245  
-        if $pos >= $string.chars && !self.hash-access('iszerowidth') {
246  
-            FAIL
247  
-        }
248  
-        elsif defined(self.ast.index($string.substr($pos, 1)))
249  
-           xor self.hash-access('isnegated') {
250  
-            unless self.hash-access('iszerowidth') {
  487
+    method p6($code, $label, $next) {
  488
+        my $test = self.hash-access('isnegated') ?? 'defined' !! '!defined';
  489
+        my $charlist = $code.escape(self.ast);
  490
+        $code.emit( q[
  491
+            when '%0' {
  492
+                if $pos >= $lastpos
  493
+                   || %1 %2.index($target.substr($pos, 1)) {
  494
+                    goto('fail');
  495
+                    break;
  496
+                }
251 497
                 ++$pos;
252  
-            }
253  
-            MATCH
254  
-        }
255  
-        else {
256  
-            FAIL
257  
-        }
  498
+                goto('%3');
  499
+            } ], $label, $test, $charlist, $next);
258 500
     }
259 501
 }
260 502
 
261  
-class GGE::Exp::Alt is GGE::Exp does GGE::MultiChild does GGE::Backtracking {
262  
-    method start($, $pos, %pad) {
263  
-        %pad<child> = 0;
264  
-        %pad<orig-pos> = $pos;
265  
-        DESCEND
266  
-    }
267  
-
268  
-    method failed($pos is rw, %pad is rw) {
269  
-        FAIL
  503
+class GGE::Exp::Alt is GGE::Exp {
  504
+    method reduce() {
  505
+        self[0] .= reduce;
  506
+        self[1] .= reduce;
  507
+        return self;
270 508
     }
271 509
 
272  
-    method backtracked($pos is rw, %pad is rw) {
273  
-        if %pad<child> {
274  
-            FAIL
275  
-        }
276  
-        else {
277  
-            $pos = %pad<orig-pos>;
278  
-            %pad<child> = 1;
279  
-            DESCEND
280  
-        }
  510
+    method p6($code, $label, $next) {
  511
+        my $exp0label = $code.unique('R');
  512
+        my $exp1label = $code.unique('R');
  513
+        $code.emit( q[
  514
+            when '%0' { # alt %1, %2
  515
+                push @ustack, $pos;
  516
+                local-branch('%1');
  517
+            }
  518
+            when '%0_cont' {
  519
+                $pos = pop @ustack;
  520
+                if $cutmark != 0 { goto('fail'); break; }
  521
+                goto('%2');
  522
+            } ], $label, $exp0label, $exp1label);
  523
+        self[0].p6($code, $exp0label, $next);
  524
+        self[1].p6($code, $exp1label, $next);
281 525
     }
282 526
 }
283 527
 
284  
-class GGE::Exp::Conj is GGE::Exp does GGE::MultiChild {
285  
-    method start($, $pos, %pad) {
286  
-        %pad<child> = 0;
287  
-        %pad<orig-pos> = $pos;
288  
-        DESCEND
  528
+class GGE::Exp::Conj is GGE::Exp {
  529
+    method reduce() {
  530
+        self[0] .= reduce;
  531
+        self[1] .= reduce;
  532
+        return self;
289 533
     }
290 534
 
291  
-    method succeeded($pos is rw, %pad) {
292  
-        if %pad<child> {
293  
-            if $pos == %pad<firstmatch-pos> {
294  
-                MATCH
  535
+    method p6($code, $label, $next) {
  536
+        my $exp0label = $code.unique('R');
  537
+        my $exp1label = $code.unique('R');
  538
+        my $chk0label = $label ~ '_chk0';
  539
+        my $chk1label = $label ~ '_chk1';
  540
+        $code.emit( q[[
  541
+            when '%0' { # conj %1, %2
  542
+                push @gpad, $pos, $pos;
  543
+                local-branch('%1');
295 544
             }
296  
-            else {
297  
-                FAIL
  545
+            when '%0_cont' {
  546
+                pop @gpad;
  547
+                pop @gpad;
  548
+                goto('fail');
298 549
             }
299  
-        }
300  
-        else {
301  
-            %pad<firstmatch-pos> = $pos;
302  
-            $pos = %pad<orig-pos>;
303  
-            %pad<child> = 1;
304  
-            DESCEND
305  
-        }
  550
+            when '%3' {
  551
+                @gpad[*-1] = $pos;
  552
+                $pos = @gpad[*-2];
  553
+                goto('%2');
  554
+            }
  555
+            when '%4' {
  556
+                if $pos != @gpad[*-1] {
  557
+                    goto('fail');
  558
+                    break;
  559
+                }
  560
+                my $p1 = pop @gpad;
  561
+                my $p2 = pop @gpad;
  562
+                push @ustack, $p2, $p1;
  563
+                local-branch('%5');
  564
+            }
  565
+            when '%4_cont' {
  566
+                my $p1 = pop @ustack;
  567
+                my $p2 = pop @ustack;
  568
+                push @gpad, $p2, $p1;
  569
+                goto('fail');
  570
+            } ]], $label, $exp0label, $exp1label, $chk0label, $chk1label,
  571
+                  $next);
  572
+        self[0].p6($code, $exp0label, $chk0label);
  573
+        self[1].p6($code, $exp1label, $chk1label);
306 574
     }
307 575
 }
308 576
 
309  
-class GGE::Exp::Group is GGE::Exp does GGE::Container {
310  
-    method start($, $, %) { DESCEND }
311  
-    method failed-group($, %) { FAIL }
  577
+class GGE::Exp::Group is GGE::Exp {
  578
+    method reduce() {
  579
+        my $group = $GGE::Exp::group;
  580
+        $GGE::Exp::group = self;
  581
+        self[0] .= reduce;
  582
+        $GGE::Exp::group = $group;
  583
+        return self.exists('cutmark') && self.hash-access('cutmark') > 0
  584
+            || self.exists('iscapture') && self.hash-access('iscapture') != 0
  585
+                ?? self
  586
+                !! self[0];
  587
+    }
  588
+
  589
+    method p6($code, $label, $next) {
  590
+        self[0].p6($code, $label, $next);
  591
+    }
312 592
 }
313 593
 
314  
-class GGE::Exp::CGroup is GGE::Exp::Group does GGE::Backtracking {
315  
-    method start($, $, %) { DESCEND }
316  
-    method failed-group($, %) { FAIL }
317  
-    method backtracked($, %) { FAIL }
  594
+class GGE::Exp::CGroup is GGE::Exp::Group {
  595
+    method p6($code, $label, $next) {
  596
+        my $explabel = $code.unique('R');
  597
+        my $expnext = $label ~ '_close';
  598
+        my %args = self.getargs($label, $next);
  599
+        my ($captgen, $captsave, $captback) = self.gencapture($label);
  600
+        %args<c C> = self.hash-access('cutmark'), '### ';
  601
+        %args<X> = self.hash-access('isscope') ?? '' !! '### ';
  602
+        $code.emit( q[[
  603
+            when '%L' { # capture
  604
+                %0
  605
+                goto('%L_1');
  606
+            }
  607
+            when '%L_1' {
  608
+                $captob = $captscope.new($captscope);
  609
+                $captob.from = $pos; # XXX: PGE uses .pos here somehow.
  610
+                push @gpad, $captscope;
  611
+                push @gpad, $captob;
  612
+                %X$captscope = $captob;
  613
+                local-branch('%E');
  614
+            }
  615
+            when '%L_1_cont' {
  616
+                $captob = pop @gpad;
  617
+                $captscope = pop @gpad;
  618
+                %Cif $cutmark != %c { goto('fail'); break; }
  619
+                %C$cutmark = 0;
  620
+                goto('fail');
  621
+            }
  622
+            when '%L_close' {
  623
+                push @ustack, $captscope;
  624
+                $captob = pop @gpad;
  625
+                $captscope = pop @gpad;
  626
+                $captob.to = $pos;
  627
+                %1
  628
+                push @ustack, $captob;
  629
+                local-branch('%S');
  630
+            }
  631
+            when '%L_close_cont' {
  632
+                $captob = pop @ustack;
  633
+                %2
  634
+                push @gpad, $captscope;
  635
+                push @gpad, $captob;
  636
+                $captscope = pop @ustack;
  637
+                goto('fail');
  638
+            } ]], $captgen, $captsave, $captback, :E($explabel), |%args);
  639
+        self[0].p6($code, $explabel, $expnext);
  640
+    }
318 641
 }
319 642
 
320  
-class GGE::Exp::Cut is GGE::Exp does GGE::Backtracking {
321  
-    method backtracked($pos, %pad) {
322  
-        if self.hash-access('cutmark') == CUT_GROUP {
323  
-            FAIL_GROUP
324  
-        }
325  
-        else {
326  
-            FAIL_RULE
  643
+class GGE::Exp::Cut is GGE::Exp {
  644
+    method reduce() {
  645
+        if self.hash-access('cutmark') > CUT_RULE {
  646
+            my $group = $GGE::Exp::group;
  647
+            if !$group.hash-access('cutmark') {
  648
+                $group.hash-access('cutmark') = CodeString.unique();
  649
+            }
  650
+            self.hash-access('cutmark') = $group.hash-access('cutmark');
327 651
         }
  652
+        return self;
  653
+    }
  654
+
  655
+    method p6($code, $label, $next) {
  656
+        my $cutmark = self.hash-access('cutmark') // 'NO_CUTMARK';
  657
+        $code.emit( q[
  658
+            when '%0' { # cut %2
  659
+                local-branch('%1');
  660
+            }
  661
+            when '%0_cont' {
  662
+                $cutmark = %2;
  663
+                goto('fail');
  664
+            } ], $label, $next, $cutmark);
328 665
     }
329 666
 }
330 667
 
331 668
 class GGE::Exp::Scalar is GGE::Exp does GGE::ShowContents {
  669
+    method p6($code, $label, $next) {
  670
+        my $cname = self.hash-access('cname');
  671
+        my $C = $cname.substr(0, 1) eq q[']
  672
+                ?? '$mob.hash-access(' ~ $cname ~ ')'
  673
+                !! '$mob[' ~ $cname ~ ']';
  674
+        $code.emit( q[[
  675
+            when '%0' { # scalar %2
  676
+                my $capture = %C;
  677
+                if $capture ~~ Array {
  678
+                    $capture = $capture[*-1];
  679
+                }
  680
+                my $length = $capture.chars;
  681
+                if $pos + $length > $lastpos
  682
+                   || $target.substr($pos, $length) ne $capture {
  683
+                    goto('fail');
  684
+                    break;
  685
+                }
  686
+                $pos += $length;
  687
+                goto('%1');
  688
+            } ]], $label, $next, $cname, :$C);
  689
+        return;
  690
+    }
332 691
 }
333 692
 
334 693
 class GGE::Exp::Alias is GGE::Exp {
253  lib/GGE/Perl6Regex.pm
@@ -2,43 +2,62 @@ use v6;
2 2
 use GGE::Match;
3 3
 use GGE::Exp;
4 4
 use GGE::OPTable;
5  
-use GGE::TreeSpider;
6  
-
7  
-class GGE::Exp::WS is GGE::Exp does GGE::Backtracking {
8  
-    # XXX: This class should really derive from GGE::Exp::Subrule, but
9  
-    #      that class hasn't been implemented yet, so...
10  
-    method start($string, $pos is rw, %pad) {
11  
-        %pad<from> = $pos;
12  
-        if $pos >= $string.chars {
13  
-            %pad<mpos> = $pos;
14  
-            MATCH
15  
-        }
16  
-        elsif $pos == 0 || $string.substr($pos, 1) ~~ /\W/
17  
-              || $string.substr($pos - 1, 1) ~~ /\W/ {
18  
-            while $pos < $string.chars && $string.substr($pos, 1) ~~ /\s/ {
19  
-                ++$pos;
20  
-            }
21  
-            %pad<mpos> = $pos;
22  
-            MATCH
23  
-        }
24  
-        else {
25  
-            FAIL
26  
-        }
27  
-    }
28 5
 
29  
-    method backtracked($_: $pos is rw, %pad) {
30  
-        $pos = --%pad<mpos>;
31  
-        if $pos >= %pad<from> {
32  
-            MATCH
33  
-        }
34  
-        else {
35  
-            FAIL
36  
-        }
  6
+class GGE::Exp::WS is GGE::Exp {
  7
+    # The below code is a working implementation of <.ws>, but it shouldn't
  8
+    # be defined here. It should be defined in a method called 'ws' in the
  9
+    # GGE::Match class. However, before we start calling other rules, this
  10
+    # will do.
  11
+    method p6($code, $label, $next) {
  12
+        my %args = self.getargs($label, $next);
  13
+        my $replabel = $label ~ '_repeat';
  14
+        $code.emit( q[[
  15
+            when '%L' { # ws
  16
+                if $pos >= $lastpos {
  17
+                    goto('%S');
  18
+                }
  19
+                elsif $pos == 0 || $target.substr($pos, 1) ~~ /\W/
  20
+                      || $target.substr($pos - 1, 1) ~~ /\W/ {
  21
+                    push @gpad, 0;
  22
+                    local-branch('%0');
  23
+                }
  24
+                else {
  25
+                    goto('fail');
  26
+                }
  27
+            }
  28
+            when '%L_cont' {
  29
+                pop @gpad;
  30
+                goto('fail');
  31
+            }
  32
+            when '%0' {
  33
+                $rep = @gpad[*-1];
  34
+                ++$rep;
  35
+                if $target.substr($pos, 1) ~~ /\s/ {
  36
+                    ++$pos;
  37
+                    goto('%0');
  38
+                    break;
  39
+                }
  40
+                if $cutmark != 0 { goto('fail'); break; }
  41
+                --$rep;
  42
+                goto('%L_1')
  43
+            }
  44
+            when '%L_1' {
  45
+                pop @gpad;
  46
+                push @ustack, $rep;
  47
+                local-branch('%S');
  48
+            }
  49
+            when '%L_1_cont' {
  50
+                $rep = pop @ustack;
  51
+                push @gpad, $rep;
  52
+                if $cutmark != 0 { goto('fail'); break; }
  53
+                goto('fail');
  54
+            } ]], $replabel, |%args);
37 55
     }
38 56
 }
39 57
 
40 58
 class GGE::Perl6Regex {
41  
-    has $!regex;
  59
+    has GGE::Exp $!exp;
  60
+    has Callable $!binary;
42 61
 
43 62
     my &unescape = -> @codes { join '', map { chr(:16($_)) }, @codes };
44 63
     my $h-whitespace = unescape <0009 0020 00a0 1680 180e 2000 2001 2002 2003
@@ -54,95 +73,93 @@ class GGE::Perl6Regex {
54 73
         't' => "\t",
55 74
     ;
56 75
 
57  
-    method new($pattern) {
58  
-        my $optable = GGE::OPTable.new();
59  
-        $optable.newtok('term:',     :precedence('='),
60  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_term));
61  
-        $optable.newtok('term:#',    :equiv<term:>,
62  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_term_ws));
63  
-        $optable.newtok('term:\\',   :equiv<term:>,
64  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_term_backslash));
65  
-        $optable.newtok('term:^',    :equiv<term:>,
66  
-                        :nows, :match(GGE::Exp::Anchor));
67  
-        $optable.newtok('term:^^',   :equiv<term:>,
68  
-                        :nows, :match(GGE::Exp::Anchor));
69  
-        $optable.newtok('term:$$',   :equiv<term:>,
70  
-                        :nows, :match(GGE::Exp::Anchor));
71  
-        $optable.newtok('term:<<',   :equiv<term:>,
72  
-                        :nows, :match(GGE::Exp::Anchor));
73  
-        $optable.newtok('term:>>',   :equiv<term:>,
74  
-                        :nows, :match(GGE::Exp::Anchor));
75  
-        $optable.newtok('term:.',    :equiv<term:>,
76  
-                        :nows, :match(GGE::Exp::CCShortcut));
77  
-        $optable.newtok('term:\\d',  :equiv<term:>,
78  
-                        :nows, :match(GGE::Exp::CCShortcut));
79  
-        $optable.newtok('term:\\D',  :equiv<term:>,
80  
-                        :nows, :match(GGE::Exp::CCShortcut));
81  
-        $optable.newtok('term:\\s',  :equiv<term:>,
82  
-                        :nows, :match(GGE::Exp::CCShortcut));
83  
-        $optable.newtok('term:\\S',  :equiv<term:>,
84  
-                        :nows, :match(GGE::Exp::CCShortcut));
85  
-        $optable.newtok('term:\\w',  :equiv<term:>,
86  
-                        :nows, :match(GGE::Exp::CCShortcut));
87  
-        $optable.newtok('term:\\W',  :equiv<term:>,
88  
-                        :nows, :match(GGE::Exp::CCShortcut));
89  
-        $optable.newtok('term:\\N',  :equiv<term:>,
90  
-                        :nows, :match(GGE::Exp::CCShortcut));
91  
-        $optable.newtok('term:\\n',  :equiv<term:>,
92  
-                        :nows, :match(GGE::Exp::Newline));
93  
-        $optable.newtok('term:$',    :equiv<term:>,
94  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_dollar));
95  
-        $optable.newtok('term:<[',   :equiv<term:>,
96  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_enumcharclass));
97  
-        $optable.newtok('term:<-',   :equiv<term:>,
98  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_enumcharclass));
99  
-        $optable.newtok("term:'",    :equiv<term:>,
100  
-                        :nows, :parsed(&GGE::Perl6Regex::parse_quoted_literal));
101  
-        $optable.newtok('term:::',   :equiv<term:>,
102  
-                        :nows, :match(GGE::Exp::Cut));
103  
-        $optable.newtok('term::::',  :equiv<term:>,
104  
-                        :nows, :match(GGE::Exp::Cut));
105  
-        $optable.newtok('term:<commit>', :equiv<term:>,
106  
-                        :nows, :match(GGE::Exp::Cut));
107  
-        $optable.newtok('circumfix:[ ]', :equiv<term:>,
108  
-                        :nows, :match(GGE::Exp::Group));
109  
-        $optable.newtok('circumfix:( )', :equiv<term:>,
110  
-                        :nows, :match(GGE::Exp::CGroup));
111  
-        $optable.newtok('postfix:*', :looser<term:>,
112  
-                        :parsed(&GGE::Perl6Regex::parse_quant));
113  
-        $optable.newtok('postfix:+', :equiv<postfix:*>,
114  
-                        :parsed(&GGE::Perl6Regex::parse_quant));
115  
-        $optable.newtok('postfix:?', :equiv<postfix:*>,
116  
-                        :parsed(&GGE::Perl6Regex::parse_quant));
117  
-        $optable.newtok('postfix::', :equiv<postfix:*>,
118  
-                        :parsed(&GGE::Perl6Regex::parse_quant));
119  
-        $optable.newtok('postfix:**', :equiv<postfix:*>,
120  
-                        :parsed(&GGE::Perl6Regex::parse_quant));
121  
-        $optable.newtok('infix:',    :looser<postfix:*>, :assoc<list>,
122  
-                        :nows, :match(GGE::Exp::Concat));
123  
-        $optable.newtok('infix:&',   :looser<infix:>,
124  
-                        :nows, :match(GGE::Exp::Conj));
125  
-        $optable.newtok('infix:|',   :looser<infix:&>,
126  
-                        :nows, :match(GGE::Exp::Alt));
127  
-        $optable.newtok('prefix:|',  :equiv<infix:|>,
128  
-                        :nows, :match(GGE::Exp::Alt));
129  
-        $optable.newtok('infix:=',   :tighter<infix:>, :assoc<right>,
130  
-                        :match(GGE::Exp::Alias));
131  
-        $optable.newtok('prefix::',  :looser<infix:|>,
132  
-                        :parsed(&GGE::Perl6Regex::parse_modifier));
  76
+    my $optable = GGE::OPTable.new();
  77
+    $optable.newtok('term:',     :precedence('='),
  78
+                    :nows, :parsed(&GGE::Perl6Regex::parse_term));
  79
+    $optable.newtok('term:#',    :equiv<term:>,
  80
+                    :nows, :parsed(&GGE::Perl6Regex::parse_term_ws));
  81
+    $optable.newtok('term:\\',   :equiv<term:>,
  82
+                    :nows, :parsed(&GGE::Perl6Regex::parse_term_backslash));
  83
+    $optable.newtok('term:^',    :equiv<term:>,
  84
+                    :nows, :match(GGE::Exp::Anchor));
  85
+    $optable.newtok('term:^^',   :equiv<term:>,
  86
+                    :nows, :match(GGE::Exp::Anchor));
  87
+    $optable.newtok('term:$$',   :equiv<term:>,
  88
+                    :nows, :match(GGE::Exp::Anchor));
  89
+    $optable.newtok('term:<<',   :equiv<term:>,
  90
+                    :nows, :match(GGE::Exp::Anchor));
  91
+    $optable.newtok('term:>>',   :equiv<term:>,
  92
+                    :nows, :match(GGE::Exp::Anchor));
  93
+    $optable.newtok('term:.',    :equiv<term:>,
  94
+                    :nows, :match(GGE::Exp::CCShortcut));
  95
+    $optable.newtok('term:\\d',  :equiv<term:>,
  96
+                    :nows, :match(GGE::Exp::CCShortcut));
  97
+    $optable.newtok('term:\\D',  :equiv<term:>,
  98
+                    :nows, :match(GGE::Exp::CCShortcut));
  99
+    $optable.newtok('term:\\s',  :equiv<term:>,
  100
+                    :nows, :match(GGE::Exp::CCShortcut));
  101
+    $optable.newtok('term:\\S',  :equiv<term:>,
  102
+                    :nows, :match(GGE::Exp::CCShortcut));
  103
+    $optable.newtok('term:\\w',  :equiv<term:>,
  104
+                    :nows, :match(GGE::Exp::CCShortcut));
  105
+    $optable.newtok('term:\\W',  :equiv<term:>,
  106
+                    :nows, :match(GGE::Exp::CCShortcut));
  107
+    $optable.newtok('term:\\N',  :equiv<term:>,
  108
+                    :nows, :match(GGE::Exp::CCShortcut));
  109
+    $optable.newtok('term:\\n',  :equiv<term:>,
  110
+                    :nows, :match(GGE::Exp::Newline));
  111
+    $optable.newtok('term:$',    :equiv<term:>,
  112
+                    :nows, :parsed(&GGE::Perl6Regex::parse_dollar));
  113
+    $optable.newtok('term:<[',   :equiv<term:>,
  114
+                    :nows, :parsed(&GGE::Perl6Regex::parse_enumcharclass));
  115
+    $optable.newtok('term:<-',   :equiv<term:>,
  116
+                    :nows, :parsed(&GGE::Perl6Regex::parse_enumcharclass));
  117
+    $optable.newtok("term:'",    :equiv<term:>,
  118
+                    :nows, :parsed(&GGE::Perl6Regex::parse_quoted_literal));
  119
+    $optable.newtok('term:::',   :equiv<term:>,
  120
+                    :nows, :match(GGE::Exp::Cut));
  121
+    $optable.newtok('term::::',  :equiv<term:>,
  122
+                    :nows, :match(GGE::Exp::Cut));
  123
+    $optable.newtok('term:<commit>', :equiv<term:>,
  124
+                    :nows, :match(GGE::Exp::Cut));
  125
+    $optable.newtok('circumfix:[ ]', :equiv<term:>,
  126
+                    :nows, :match(GGE::Exp::Group));
  127
+    $optable.newtok('circumfix:( )', :equiv<term:>,
  128
+                    :nows, :match(GGE::Exp::CGroup));
  129
+    $optable.newtok('postfix:*', :looser<term:>,
  130
+                    :parsed(&GGE::Perl6Regex::parse_quant));
  131
+    $optable.newtok('postfix:+', :equiv<postfix:*>,
  132
+                    :parsed(&GGE::Perl6Regex::parse_quant));
  133
+    $optable.newtok('postfix:?', :equiv<postfix:*>,
  134
+                    :parsed(&GGE::Perl6Regex::parse_quant));
  135
+    $optable.newtok('postfix::', :equiv<postfix:*>,
  136
+                    :parsed(&GGE::Perl6Regex::parse_quant));
  137
+    $optable.newtok('postfix:**', :equiv<postfix:*>,
  138
+                    :parsed(&GGE::Perl6Regex::parse_quant));
  139
+    $optable.newtok('infix:',    :looser<postfix:*>, :assoc<list>,
  140
+                    :nows, :match(GGE::Exp::Concat));
  141
+    $optable.newtok('infix:&',   :looser<infix:>,
  142
+                    :nows, :match(GGE::Exp::Conj));
  143
+    $optable.newtok('infix:|',   :looser<infix:&>,
  144
+                    :nows, :match(GGE::Exp::Alt));
  145
+    $optable.newtok('prefix:|',  :equiv<infix:|>,
  146
+                    :nows, :match(GGE::Exp::Alt));
  147
+    $optable.newtok('infix:=',   :tighter<infix:>, :assoc<right>,
  148
+                    :match(GGE::Exp::Alias));
  149
+    $optable.newtok('prefix::',  :looser<infix:|>,
  150
+                    :parsed(&GGE::Perl6Regex::parse_modifier));
  151
+
  152
+    method new($pattern, :$debug) {
133 153
         my $match = $optable.parse($pattern);
134 154
         die 'Perl6Regex rule error: can not parse expression'
135 155
             if $match.to < $pattern.chars;
136  
-        my $expr = $match.hash-access('expr');
137  
-        return self.bless(*, :regex(perl6exp($expr, {})));
  156
+        my $exp = perl6exp($match.hash-access('expr'), {});
  157
+        my $binary = $exp.compile(:$debug);
  158
+        return self.bless(*, :$exp, :$binary);
138 159
     }
139 160
 
140 161
     method postcircumfix:<( )>($target, :$debug) {
141  
-        if $debug {
142  
-            say $!regex.structure;
143  
-            say '';
144  
-        }
145  
-        GGE::TreeSpider.new(:$!regex, :$target, :pos(*)).crawl(:$debug);
  162
+        $!binary($target, :$debug);
146 163
     }
147 164
 
148 165
     sub parse_term($mob) {
@@ -405,6 +422,9 @@ class GGE::Perl6Regex {
405 422
             if $m.target.substr($m.to, 2) eq '..' {
406 423
                 $m.to += 2;
407 424
                 $m.hash-access('max') = $m.target.substr($m.to, 1);
  425
+                if $m.hash-access('max') eq '*' {
  426
+                    $m.hash-access('max') = 'Inf';
  427
+                }
408 428
                 ++$m.to;
409 429
             }
410 430
             if $brackets {
@@ -493,7 +513,7 @@ class GGE::Perl6Regex {
493 513
         %pad{$key} = $exp.ast;
494 514
         $exp[0] = perl6exp($exp[0], %pad);
495 515
         %pad{$key} = $temp;
496  
-        return $exp;
  516
+        return $exp[0];
497 517
     }
498 518
 
499 519
     multi sub perl6exp(GGE::Exp::Concat $exp is rw, %pad) {
@@ -556,6 +576,7 @@ class GGE::Perl6Regex {
556 576
     }
557 577
 
558 578
     multi sub perl6exp(GGE::Exp::CGroup $exp is rw, %pad) {
  579
+        $exp.hash-access('iscapture') = True;
559 580
         unless $exp.exists('isscope') {
560 581
             $exp.hash-access('isscope') = True;
561 582
         }
236  lib/GGE/TreeSpider.pm
... ...
@@ -1,236 +0,0 @@
1  
-use v6;
2  
-use GGE::Exp;
3  
-
4  
-class GGE::TreeSpider {
5  
-    has GGE::Exp   $!top;
6  
-    has Str        $!target;
7