diff --git a/lib/SAFE.setting b/lib/SAFE.setting index 50b17fd3..14b152b4 100644 --- a/lib/SAFE.setting +++ b/lib/SAFE.setting @@ -16,6 +16,7 @@ my class Mu { $tn ~ "()" } } + method dump() { self.defined ?? "Unknown{self.Str}" !! "undef" } method item() { self } method so() { self.Bool } method not() { ! self.Bool } @@ -104,6 +105,7 @@ my class Num is Cool { } } method Numeric() { self } method ACCEPTS($t) { self == $t } + method dump() { self.Str } } my class Str is Cool { @@ -123,6 +125,7 @@ my class Str is Cool { [cast int (unbox num (@ {$from}))] [cast int (unbox num (@ {$len}))])) } } + method dump() { '"' ~ self ~ '"' } } my class Scalar { @@ -440,6 +443,8 @@ my class List is Cool { } } + method dump() { '[' ~ self.map(*.dump).join(', ') ~ ']' } + #| Takes an object and applies whatever semantics the List subclass #| needs to apply on stuff out of the iterator stack method _elem(\$x) { $x } @@ -688,6 +693,9 @@ my class Hash { }; } + method iterator () { self.list.iterator } + method dump () { '{' ~ self.list.map(*.dump).join(', ') ~ '}' } + # TODO: We need something like pir:: notation for this to not suck method at-key($key) { Q:CgOp { @@ -723,6 +731,8 @@ my class Enum is Cool { ($.key, $.value); } + method dump() { self.key.dump ~ ' => ' ~ self.value.dump } + method pairs() { self.flat; } @@ -828,6 +838,8 @@ my class Cursor { (@ {self}) (unbox str (@ {$str})))) } } method pos() { Q:CgOp { (box Num (cast num (cursor_pos (cast cursor (@ {self}))))) } } + method to() { Q:CgOp { (box Num (cast num (cursor_pos + (cast cursor (@ {self}))))) } } method cursor($np) { Q:CgOp { (ns (cursor_butpos (cast cursor (@ {self})) (cast int (unbox num (@ {$np}))))) } } @@ -864,6 +876,24 @@ my class Match { (box Str (cursor_backing (cast cursor (@ {self})))) } } method chars() { $.defined ?? $.to - $.from !! 0 } method Str() { $.defined ?? $.orig.substr($.from, $.chars) !! "" } + method dump() { + "#" + } + method synthetic(:$cursor!, :$method!, :@captures!, :$from!, :$to!) { + my $m = Q:CgOp { + (newscalar (cursor_synthetic + (cast cursor (@ {$cursor})) (unbox str (@ {$method.Str})) + (cast int (unbox num (@ {$from}))) + (cast int (unbox num (@ {$to}))))) + }; + # this is wrong. I need a better way to pass lists into primitives. + for @captures -> $pair { + Q:CgOp { (rnull + (cursor_synthcap (cast cursor (@ {$m})) + (unbox str (@ {$pair.key.Str})) (@ {$pair.value}))) }; + } + $m + } } my class Regex is Sub { diff --git a/src/Niecza/Actions.pm b/src/Niecza/Actions.pm index 5775b98b..a50399a4 100644 --- a/src/Niecza/Actions.pm +++ b/src/Niecza/Actions.pm @@ -293,22 +293,26 @@ sub quote__S_Q { my ($cl, $M) = @_; $M->{_ast} = $M->{quibble}{_ast}; } -sub quote__S_Slash_Slash { my ($cl, $M) = @_; - my @lift = $M->{nibble}{_ast}->oplift; +sub op_for_regex { my ($cl, $M, $rxop) = @_; + my @lift = $rxop->oplift; { local $::paren = 0; - $M->{nibble}{_ast}->check + $rxop->check } - my ($rxop, $mb) = Optimizer::RxSimple::run($M->{nibble}{_ast}); - $M->{_ast} = Op::SubDef->new( + my ($orxop, $mb) = Optimizer::RxSimple::run($rxop); + Op::SubDef->new(node($M), var => $cl->gensym, body => Body->new( transparent => 1, class => 'Regex', type => 'regex', signature => Sig->simple->for_regex, - do => Op::RegexBody->new(canback => $mb, pre => \@lift, - rxop => $rxop))); + do => Op::RegexBody->new(node($M), canback => $mb, pre => \@lift, + rxop => $orxop))); +} + +sub quote__S_Slash_Slash { my ($cl, $M) = @_; + $M->{_ast} = $cl->op_for_regex($M, $M->{nibble}{_ast}); } sub encapsulate_regex { my ($cl, $M, $rxop, %args) = @_; @@ -766,8 +770,8 @@ sub assertion__S_name { my ($cl, $M) = @_; } if ($M->{nibbler}[0]) { - my $args = [$M->{nibbler}[0]{_ast}]; - $M->{_ast} = RxOp::Subrule->new(zyg => $args, method => $name); + $M->{_ast} = RxOp::Subrule->new(method => $name, + arglist => [ $cl->op_for_regex($M, $M->{nibbler}[0]{_ast}) ]); } else { my $args = ($M->{arglist}[0] ? $M->{arglist}[0]{_ast} : []); $M->{_ast} = RxOp::Subrule->new(arglist => $args, method => $name); diff --git a/test2.pl b/test2.pl index 41ce4ffb..a26f4246 100644 --- a/test2.pl +++ b/test2.pl @@ -2,6 +2,22 @@ use Test; use MONKEY_TYPING; +augment class Cursor { + method suppose($rx) { + my $*IN_SUPPOSE = True; + my $*FATALS = 0; + my @*WORRIES; + my %*WORRIES; + my $*HIGHWATER = -1; + my $*HIGHEXPECT = {}; + try { + my @ret := $rx(self); + if (@ret) { return @( self, ) } + }; + return (); + } +} + { my $m = "ab" ~~ / (.) /; is (@$m)[0], "a", "Match.list returns positional captures"; @@ -36,6 +52,9 @@ my $i = 0; $i++ until $i == 10; is $i, 10, "until loops functional"; + + ok "foo" !~~ / f <.suppose { die }> /, ".suppose works (F)"; + ok "foo" ~~ / f <.suppose o> oo /, ".suppose works (T)"; } # { diff --git a/test3.pl b/test3.pl index 6fbf3b66..f0b14f23 100644 --- a/test3.pl +++ b/test3.pl @@ -2,57 +2,6 @@ use Test; use MONKEY_TYPING; -augment class Hash { - method iterator () { self.list.iterator } - method dump () { '{' ~ self.list.map(*.dump).join(', ') ~ '}' } -} - -augment class Pair { - method dump() { self.key.dump ~ ' => ' ~ self.value.dump } -} - -augment class Str { - method dump() { '"' ~ self ~ '"' } -} - -augment class Num { - method dump() { self.Str } -} - -augment class List { - method dump() { '[' ~ self.map(*.dump).join(', ') ~ ']' } -} - -augment class Cursor { - method to() { Q:CgOp { (box Num (cast num (cursor_pos - (cast cursor (@ {self}))))) } } -} - -augment class Mu { - method dump() { self.defined ?? "Unknown{self.Str}" !! "undef" } -} - -augment class Match { - method dump() { - "#" - } - method synthetic(:$cursor!, :$method!, :@captures!, :$from!, :$to!) { - my $m = Q:CgOp { - (newscalar (cursor_synthetic - (cast cursor (@ {$cursor})) (unbox str (@ {$method.Str})) - (cast int (unbox num (@ {$from}))) - (cast int (unbox num (@ {$to}))))) - }; - # this is wrong. I need a better way to pass lists into primitives. - for @captures -> $pair { - Q:CgOp { (rnull - (cursor_synthcap (cast cursor (@ {$m})) - (unbox str (@ {$pair.key.Str})) (@ {$pair.value}))) }; - } - $m - } -} - package DEBUG { our $EXPR = False } grammar WithOPP { diff --git a/v6/STD.pm6 b/v6/STD.pm6 index c7f676b9..6c0cc6a8 100644 --- a/v6/STD.pm6 +++ b/v6/STD.pm6 @@ -2002,11 +2002,8 @@ grammar P6 is STD { [ | | - { - $¢.
  = $
:delete;
-                $¢. = $:delete;
-                $¢.<~CAPS> = $<~CAPS>;
-            }
+            $
 = { $
 }
+            $ = { $ }
         | 
         ]
     }
@@ -2037,7 +2034,6 @@ grammar P6 is STD {
         ]
         {
             self.check_variable($*VAR) if $*VAR;
-            $¢.<~CAPS> = $<~CAPS>;
         }
     }
 
@@ -5596,6 +5592,285 @@ method lookup_compiler_var($name, $default?) {
     }
 }
 
+#######################
+# Operator Precedence #
+#######################
+
+method EXPR ($preclvl?) {
+    my $preclim = $preclvl ?? $preclvl. // $LOOSEST !! $LOOSEST;
+    my $*LEFTSIGIL = '';        # XXX P6
+    my $*PRECLIM = $preclim;
+    my @termstack;
+    my @opstack;
+    my $termish = 'termish';
+
+    sub _top(@a) { @a[ @a.elems - 1 ] }
+
+    my $state;
+    my $here;
+
+    sub reduce() {
+        self.deb("entering reduce, termstack == ", +@termstack, " opstack == ", +@opstack) if $DEBUG::EXPR;
+        my $op = pop @opstack;
+        my $sym = $op;
+        my $assoc = $op // 'unary';
+        if $assoc eq 'chain' {
+            self.deb("reducing chain") if $DEBUG::EXPR;
+            my @chain;
+            push @chain, pop(@termstack);
+            push @chain, $op;
+            while @opstack {
+                last if $op ne _top(@opstack);
+                push @chain, pop(@termstack);
+                push @chain, pop(@opstack);
+            }
+            push @chain, pop(@termstack);
+            my $endpos = @chain[0].to;
+            @chain = reverse @chain if (+@chain) > 1;
+            my $startpos = @chain[0].from;
+            my $i = True;
+            my @caplist;
+            for @chain -> $c {
+                push @caplist, ($i ?? 'term' !! 'op') => $c;
+                $i = !$i;
+            }
+            push @termstack, Match.synthetic(
+                :captures(@caplist, :_arity, :chain(@chain)),
+                :method,
+                :cursor(self),
+                :from($startpos),
+                :to($endpos));
+        }
+        elsif $assoc eq 'list' {
+            self.deb("reducing list") if $DEBUG::EXPR;
+            my @list;
+            my @delims = $op;
+            push @list, pop(@termstack);
+            while @opstack {
+                self.deb($sym ~ " vs " ~ _top(@opstack)) if $DEBUG::EXPR;
+                last if $sym ne _top(@opstack);
+                if @termstack and defined @termstack[0] {
+                    push @list, pop(@termstack);
+                }
+                else {
+                    self.worry("Missing term in " ~ $sym ~ " list");
+                }
+                push @delims, pop(@opstack);
+            }
+            if @termstack and defined @termstack[0] {
+                push @list, pop(@termstack);
+            }
+            else {
+                self.worry("Missing final term in '" ~ $sym ~ "' list");
+            }
+            my $endpos = @list[0].to;
+            @list = reverse @list if (+@list) > 1;
+            my $startpos = @list[0].from;
+            @delims = reverse @delims if (+@delims) > 1;
+            my @caps;
+            if @list {
+                push @caps, (elem => @list[0]) if @list[0];
+                my $i = 0;
+                while $i < (+@delims)-1 {
+                    my $d = @delims[$i];
+                    my $l = @list[$i+1];
+                    push @caps, (delim => $d);
+                    push @caps, (elem => $l) if $l;  # nullterm?
+                    $i++;
+                }
+            }
+            push @termstack, Match.synthetic(
+                :method, :cursor(self),
+                :from($startpos), :to($endpos),
+                :captures(@caps, :_arity, :delims(@delims),
+                    :list(@list), :O($op), :sym($sym)));
+        }
+        elsif $assoc eq 'unary' {
+            self.deb("reducing") if $DEBUG::EXPR;
+            self.deb("Termstack size: ", +@termstack) if $DEBUG::EXPR;
+
+            my $arg = pop @termstack;
+            if $arg.from < $op.from { # postfix
+                push @termstack, Match.synthetic(
+                    :cursor(self), :to($op.to), :from($arg.from),
+                    :captures(arg => $arg, op => $op, _arity => 'UNARY'),
+                    :method);
+            }
+            elsif $arg.to > $op.to {   # prefix
+                push @termstack, Match.synthetic(
+                    :cursor(self), :to($arg.to), :from($op.from),
+                    :captures(op => $op, arg => $arg, _arity => 'UNARY'),
+                    :method);
+            }
+        }
+        else {
+            self.deb("reducing") if $DEBUG::EXPR;
+            self.deb("Termstack size: ", +@termstack) if $DEBUG::EXPR;
+
+            my $right = pop @termstack;
+            my $left = pop @termstack;
+
+            push @termstack, Match.synthetic(
+                :to($right.to), :from($left.from), :cursor(self),
+                :captures(:left($left), :infix($op), :right($right),
+                    :_arity), :method);
+
+#           self.deb(_top(@termstack).dump) if $DEBUG::EXPR;
+            my $ck;
+            if $ck = $op<_reducecheck> {
+                _top(@termstack) = $ck(_top(@termstack));
+            }
+        }
+    }
+
+    sub termstate() {
+        $here.deb("Looking for a term") if $DEBUG::EXPR;
+        $here.deb("Top of opstack is ", _top(@opstack).dump) if $DEBUG::EXPR;
+        $*LEFTSIGIL = _top(@opstack) gt $item_assignment_prec
+            ?? '@' !! '';     # XXX P6
+        my $term =
+            ($termish eq 'termish') ?? $here.termish.head !!
+            ($termish eq 'nulltermish') ?? $here.nulltermish.head !!
+            ($termish eq 'statement') ?? $here.statement.head !!
+            ($termish eq 'dottyopish') ?? $here.dottyopish.head !!
+            die "weird value of $termish";
+
+        if not $term {
+            $here.deb("Didn't find it") if $DEBUG::EXPR;
+            $here.panic("Bogus term") if (+@opstack) > 1;
+            return 2;
+        }
+        $here.deb("Found term to {$term.to}") if $DEBUG::EXPR;
+        $here = $here.cursor($term.to);
+        $termish = 'termish';
+        my @PRE = @( $term
 // [] );
+        my @POST = reverse @( $term // [] );
+
+        # interleave prefix and postfix, pretend they're infixish
+        # note that we push loose stuff onto opstack before tight stuff
+        while @PRE and @POST {
+            my $postO = @POST[0];
+            my $preO = @PRE[0];
+            if $postO lt $preO {
+                push @opstack, shift @POST;
+            }
+            elsif $postO gt $preO {
+                push @opstack, shift @PRE;
+            }
+            elsif $postO eq 'left' {
+                push @opstack, shift @POST;
+            }
+            elsif $postO eq 'right' {
+                push @opstack, shift @PRE;
+            }
+            else {
+                $here.sorry('"' ~ @PRE[0] ~ '" and "' ~ @POST[0] ~ '" are not associative');
+            }
+        }
+        push @opstack, @PRE,@POST;
+
+        push @termstack, $term;
+        $here.deb("after push: " ~ (+@termstack)) if $DEBUG::EXPR;
+
+        say "methodcall break" if $preclim eq $methodcall_prec; # in interpolation, probably   # XXX P6
+        $state = &infixstate;
+        return 0;
+    }
+
+    # std bug sees infixstate as unused
+    sub infixstate() { #OK
+        $here.deb("Looking for an infix") if $DEBUG::EXPR;
+        return 1 if (@*MEMOS[$here.pos] // 0) == 2;  # XXX P6
+        $here = $here.cursor($here.ws.head.to);
+        my $infix = $here.infixish.head;
+        return 1 unless $infix;
+
+        my $inO = $infix;
+        my $inprec = $inO;
+        if not defined $inprec {
+            die "No prec given in infix!";
+        }
+
+        if $inprec le $preclim {
+            if $preclim ne $LOOSEST {
+                my $dba = $*prevlevel.;
+                my $h = $*HIGHEXPECT;
+                %$h = ();
+                $h.{"an infix operator with precedence tighter than $dba"} = 1;
+            }
+            return 1;
+        }
+
+        $here = $here.cursor($infix.to);
+        $here = $here.cursor($here.ws.head.to);
+
+        # substitute precedence for listops
+        $inO = $inO if $inO;
+
+        # Does new infix (or terminator) force any reductions?
+        while _top(@opstack) gt $inprec {
+            reduce;
+        }
+
+        # Not much point in reducing the sentinels...
+        return 1 if $inprec lt $LOOSEST;
+
+        if $infix {
+            push @opstack, $infix;
+            reduce();
+            return 0;  # not really an infix, so keep trying
+        }
+
+        # Equal precedence, so use associativity to decide.
+        if _top(@opstack) eq $inprec {
+            my $assoc = 1;
+            my $atype = $inO;
+            if $atype eq 'non'   { $assoc = 0; }
+            elsif $atype eq 'left'  { reduce() }   # reduce immediately
+            elsif $atype eq 'right' { }            # just shift
+            elsif $atype eq 'chain' { }            # just shift
+            elsif $atype eq 'unary' { }            # just shift
+            elsif $atype eq 'list'  {
+                $assoc = 0 unless $infix eq _top(@opstack);
+            }
+            else { $here.panic('Unknown associativity "' ~ $_ ~ '" for "' ~ $infix ~ '"') }
+            if not $assoc {
+               $here.sorry('"' ~ _top(@opstack) ~ '" and "' ~ $infix.Str ~ '" are non-associative and require parens');
+            }
+        }
+
+        $termish = $inO if $inO;
+        push @opstack, $infix;              # The Shift
+        $state = &termstate;
+        return 0;
+    }
+
+    push @opstack, { 'O' => %terminator, 'sym' => '' };         # (just a sentinel value)
+    self.deb(@opstack.dump) if $DEBUG::EXPR;
+
+    $here = self;
+    self.deb("In EXPR, at {$here.pos}") if $DEBUG::EXPR;
+
+    my $stop = 0;
+    $state = &termstate;
+    until $stop {
+        $here.deb("At {$here.pos}, {@opstack.dump}; {@termstack.dump}") if $DEBUG::EXPR;
+        $stop = $state();
+    }
+    $here.deb("Stop code $stop") if $DEBUG::EXPR;
+    return () if $stop == 2;
+    reduce() while +@opstack > 1;
+    $here.deb("After final reduction, ", @termstack.dump, @opstack.dump) if $DEBUG::EXPR;
+
+    if @termstack {
+        +@termstack == 1 or $here.panic("Internal operator parser error, termstack == " ~ (+@termstack));
+        return @( Match.synthetic(:to($here.pos), :from(self.pos),
+                :cursor(self), :method,
+                :captures( root => @termstack[0] )), );
+    }
+    return ();
+}
+
 ####################
 # Service Routines #
 ####################
diff --git a/v6/TODO b/v6/TODO
index f0115527..73d2e1b7 100644
--- a/v6/TODO
+++ b/v6/TODO
@@ -2,11 +2,9 @@ Cursor.cursor_all
 Cursor.cursor_fresh
 Cursor.mixin
 Cursor.suppose
-Match.synthetic
 Parcel.LISTSTORE
 Highwater stuff
-Audit termish, nulltermish, nibble
-Write tests for EXPR
+Audit nibble
 self in regexes
 token { $param-role-var }
 
@@ -51,6 +49,7 @@ invert(%hash)
 &item
 &join
 Match.iterator should return numbered captures
+Match.synthetic
 not($bool)
 ¬e
 pop(@array)
@@ -63,6 +62,7 @@ $
 temp $*FOO
 try
 token { :my $var = expr; $var }
+Write tests for EXPR
 
 AVERTED: