factor out Filter::Keyword::Parser
[p5sagit/Filter-Keyword.git] / lib / Filter / Keyword / Parser.pm
CommitLineData
c46d1069 1package Filter::Keyword::Parser;
2
3use Package::Stash::PP;
4use B qw(svref_2object);
5use Scalar::Util qw(set_prototype);
6use Moo;
7
8has parser => (is => 'ro', required => 1);
9
10has reader => (is => 'ro', required => 1);
11
12has re_add => (is => 'ro', required => 1);
13
14has target_package => (is => 'ro', required => 1);
15
16has keyword_name => (is => 'ro', required => 1);
17
18has stash => (is => 'lazy');
19
20sub _build_stash {
21 my ($self) = @_;
22 Package::Stash::PP->new($self->target_package);
23}
24
25has globref => (is => 'lazy', clearer => 'clear_globref');
26
27sub _build_globref {
28 no strict 'refs'; no warnings 'once';
29 \*{join'::',$_[0]->target_package,$_[0]->keyword_name}
30}
31
32after clear_globref => sub {
33 my ($self) = @_;
34 $self->stash->remove_symbol('&'.$self->keyword_name);
35 $self->globref_refcount(undef);
36};
37
38has globref_refcount => (is => 'rw');
39
40sub save_refcount {
41 my ($self) = @_;
42 $self->globref_refcount(svref_2object($self->globref)->REFCNT);
43}
44
45sub have_match {
46 my ($self) = @_;
47 return 0 unless defined($self->globref_refcount);
48 svref_2object($self->globref)->REFCNT > $self->globref_refcount;
49}
50
51has current_match => (is => 'rw');
52
53has short_circuit => (is => 'rw');
54
55has code => (is => 'rw', default => sub { '' });
56
57sub get_next {
58 my ($self) = @_;
59 if ($self->short_circuit) {
60 $self->short_circuit(0);
61 $self->${\$self->re_add};
62 return ('', 0);
63 }
64 if ($self->have_match) {
65 $self->clear_globref;
66 return $self->${\$self->parser};
67 }
68 return $self->check_match;
69}
70
71sub fetch_more {
72 my ($self) = @_;
73 my $code = $self->code||'';
74 my ($extra_code, $not_eof) = $self->${\$self->reader};
75 $code .= $extra_code;
76 $self->code($code);
77 return $not_eof;
78}
79
80sub match_source {
81 my ($self, $first, $second) = @_;
82 $self->fetch_more while $self->code =~ /$first\s+\Z/;
83 if (my @match = ($self->code =~ /(.*?${first}\s+${second})(.*)\Z/)) {
84 $self->code(pop @match);
85 my $found = shift(@match);
86 return ($found, \@match);
87 }
88 return;
89}
90
91sub check_match {
92 my ($self) = @_;
93 unless ($self->code) {
94 $self->fetch_more
95 or return ('', 0);
96 }
97 if (
98 my ($stripped, $matches)
99 = $self->match_source(
100 $self->keyword_name, qr/(\(|[A-Za-z][A-Za-z_0-9]*|{)/
101 )
102 ) {
103 my $sub = sub {};
104 set_prototype(\&$sub, '*;@') unless $matches->[0] eq '(';
105 { no warnings 'redefine'; *{$self->globref} = $sub; }
106 $self->save_refcount;
107 $self->current_match($matches);
108 $self->short_circuit(1);
109 return ($stripped, 1);
110 }
111 my $code = $self->code;
112 $self->code('');
113 return ($code, 1);
114}
115
1161;