factor out Filter::Keyword::Parser master
Matt S Trout [Wed, 14 Dec 2011 11:59:12 +0000 (11:59 +0000)]
lib/Filter/Keyword.pm
lib/Filter/Keyword/Parser.pm [new file with mode: 0644]
t/simple.t

index 9d01adf..6b31e99 100644 (file)
@@ -1,96 +1,36 @@
 package Filter::Keyword;
 
-# we need the PP implementation's version of remove_symbol
-use Package::Stash::PP;
 use Filter::Util::Call;
-use B qw(svref_2object);
+use Filter::Keyword::Parser;
 use Moo;
 
-has target_package => (is => 'ro', required => 1);
+has parser => (is => 'ro', required => 1);
 
-has stash => (is => 'lazy');
+has parser_object => (is => 'lazy');
 
-sub _build_stash {
+sub _build_parser_object {
   my ($self) = @_;
-  Package::Stash::PP->new($self->target_package);
-}
-
-has keyword_name => (is => 'ro', required => 1);
-
-has globref => (is => 'lazy', clearer => 'clear_globref');
-
-sub _build_globref {
-  no strict 'refs'; no warnings 'once';
-  \*{join'::',$_[0]->target_package,$_[0]->keyword_name}
-}
-
-after clear_globref => sub {
-  my ($self) = @_;
-  $self->stash->remove_symbol('&'.$self->keyword_name);
-  $self->globref_refcount(undef);
-};
-
-has globref_refcount => (is => 'rw');
-
-sub save_refcount {
-  my ($self) = @_;
-  warn "Save: ".$self->globref_refcount(svref_2object($self->globref)->REFCNT);
-}
-
-sub refcount_changed {
-  my ($self) = @_;
-  return 0 unless defined($self->globref_refcount);
-  svref_2object($self->globref)->REFCNT > $self->globref_refcount;
-}
-
-has info => (is => 'rw', predicate => 'has_info', clearer => 'clear_info');
-
-sub setup {
-  my ($self) = @_;
-  $self->globref;
-  filter_add($self);
+  my %args = %{$self->parser};
+  $args{reader} = sub { my $r = filter_read; ($_, $r) };
+  $args{re_add} = sub {
+    my $parser = shift;
+    filter_add(sub {
+      my ($string, $code) = $parser->get_next;
+      $_ = $string;
+      return $code;
+    });
+  };
+  Filter::Keyword::Parser->new(\%args);
 }
 
-my $name_re = '[A-Za-z][A-Za-z_0-9]*';
-
-sub filter {
+sub install {
   my ($self) = @_;
-  if ($self->has_info) {
-    if (delete $self->info->{first}) {
-      warn "Attempting short circuit";
-      filter_add($self);
-      return 0;
-    }
-    my $info = $self->clear_info;
-    $_ = $info->{rest};
-    if ($self->refcount_changed) {
-      warn "Trapped: ".$info->{name};
-      my $name = $info->{name};
-      ${$info->{inner}} = sub { warn "Define ${name}" };
-      #$self->clear_globref;
-      s/{/; sub ${\$info->{name}} { my \$self = shift;/;
-    }
-warn "Line: $_";
-    return 1;
-  }
-  my $status = filter_read();
-warn "Line: $_";
-  my $kw = $self->keyword_name;
-  if (/(.*?$kw\s+(${name_re}))(.*)\Z/s) {
-    my ($start, $name, $rest) = ($1, $2, $3);
-    $self->clear_globref if $self->refcount_changed;
-    no warnings 'redefine';
-    my $inner = sub {};
-    *{$self->globref} = sub (*;@) { $inner->(@_) };
-    $self->save_refcount;
-    $_ = $start;
-    $self->info({
-      name => $name, rest => $rest, first => 1,
-      inner => \$inner
-    });
-    return 1;
-  }
-  return $status;
+  my $parser = $self->parser_object;
+  filter_add(sub {
+    my ($string, $code) = $parser->get_next;
+    $_ = $string;
+    return $code;
+  });
 }
 
 1;
diff --git a/lib/Filter/Keyword/Parser.pm b/lib/Filter/Keyword/Parser.pm
new file mode 100644 (file)
index 0000000..6f4c8fb
--- /dev/null
@@ -0,0 +1,116 @@
+package Filter::Keyword::Parser;
+
+use Package::Stash::PP;
+use B qw(svref_2object);
+use Scalar::Util qw(set_prototype);
+use Moo;
+
+has parser => (is => 'ro', required => 1);
+
+has reader => (is => 'ro', required => 1);
+
+has re_add => (is => 'ro', required => 1);
+
+has target_package => (is => 'ro', required => 1);
+
+has keyword_name => (is => 'ro', required => 1);
+
+has stash => (is => 'lazy');
+
+sub _build_stash {
+  my ($self) = @_;
+  Package::Stash::PP->new($self->target_package);
+}
+
+has globref => (is => 'lazy', clearer => 'clear_globref');
+
+sub _build_globref {
+  no strict 'refs'; no warnings 'once';
+  \*{join'::',$_[0]->target_package,$_[0]->keyword_name}
+}
+
+after clear_globref => sub {
+  my ($self) = @_;
+  $self->stash->remove_symbol('&'.$self->keyword_name);
+  $self->globref_refcount(undef);
+};
+
+has globref_refcount => (is => 'rw');
+
+sub save_refcount {
+  my ($self) = @_;
+  $self->globref_refcount(svref_2object($self->globref)->REFCNT);
+}
+
+sub have_match {
+  my ($self) = @_;
+  return 0 unless defined($self->globref_refcount);
+  svref_2object($self->globref)->REFCNT > $self->globref_refcount;
+}
+
+has current_match => (is => 'rw');
+
+has short_circuit => (is => 'rw');
+
+has code => (is => 'rw', default => sub { '' });
+
+sub get_next {
+  my ($self) = @_;
+  if ($self->short_circuit) {
+    $self->short_circuit(0);
+    $self->${\$self->re_add};
+    return ('', 0);
+  }
+  if ($self->have_match) {
+    $self->clear_globref;
+    return $self->${\$self->parser};
+  }
+  return $self->check_match;
+}
+
+sub fetch_more {
+  my ($self) = @_;
+  my $code = $self->code||'';
+  my ($extra_code, $not_eof) = $self->${\$self->reader};
+  $code .= $extra_code;
+  $self->code($code);
+  return $not_eof;
+}
+
+sub match_source {
+  my ($self, $first, $second) = @_;
+  $self->fetch_more while $self->code =~ /$first\s+\Z/;
+  if (my @match = ($self->code =~ /(.*?${first}\s+${second})(.*)\Z/)) {
+    $self->code(pop @match);
+    my $found = shift(@match);
+    return ($found, \@match);
+  }
+  return;
+}
+
+sub check_match {
+  my ($self) = @_;
+  unless ($self->code) {
+    $self->fetch_more
+      or return ('', 0);
+  }
+  if (
+    my ($stripped, $matches)
+      = $self->match_source(
+          $self->keyword_name, qr/(\(|[A-Za-z][A-Za-z_0-9]*|{)/
+        )
+  ) {
+    my $sub = sub {};
+    set_prototype(\&$sub, '*;@') unless $matches->[0] eq '(';
+    { no warnings 'redefine'; *{$self->globref} = $sub; }
+    $self->save_refcount;
+    $self->current_match($matches);
+    $self->short_circuit(1);
+    return ($stripped, 1);
+  }
+  my $code = $self->code;
+  $self->code('');
+  return ($code, 1);
+}
+
+1;
index edbef45..2c438d7 100644 (file)
@@ -4,9 +4,21 @@ use Filter::Keyword;
 
 BEGIN {
   (our $Kw = Filter::Keyword->new(
-    target_package => __PACKAGE__,
-    keyword_name => 'method'
-  ))->setup;
+    parser => {
+      target_package => __PACKAGE__,
+      keyword_name => 'method',
+      parser => sub {
+        my $obj = shift;
+        if (my ($stripped, $matches) = $obj->match_source('', '{')) {
+          my $name = $obj->current_match->[0];
+          $stripped =~ s/{/; sub ${name} { my \$self = shift;/;
+          return ($stripped, 1);
+        } else {
+          return ('', 1);
+        }
+      }
+    },
+  ))->install;
 }
 
 method main { 'YAY '.$self };