Fix calling of shadowed subs
[p5sagit/Filter-Keyword.git] / lib / Filter / Keyword.pm
CommitLineData
3b08744b 1package Filter::Keyword;
3b08744b 2use Moo;
3
c15f7959 4use Filter::Keyword::Filter;
fd60cdd6 5use Filter::Util::Call;
c15f7959 6use Scalar::Util qw(weaken);
7use Package::Stash::PP;
8use B qw(svref_2object);
6c0ec68b 9use B::Hooks::EndOfScope;
68363889 10use Scalar::Util qw(set_prototype);
3b08744b 11
c15f7959 12sub _compiling_file () {
13 my $depth = 0;
14 while (my @caller = caller(++$depth)) {
15 if ($caller[3] =~ /::BEGIN$/) {
16 # older perls report the BEGIN in the wrong file
17 return $depth > 1 ? (caller($depth-1))[1] : $caller[1];
18 #return $caller[1];
19 }
20 }
21 die;
22}
3b08744b 23
c15f7959 24my %filters;
25sub install {
3b08744b 26 my ($self) = @_;
c15f7959 27 $self->shadow_sub;
6c0ec68b 28
29 my $file = _compiling_file;
c15f7959 30 my $filter = $filters{$file} ||= Filter::Keyword::Filter->new;
31 $filter->install;
6c0ec68b 32
c15f7959 33 my $parser = $filter->parser;
34 $parser->add_keyword($self);
35 $self->keyword_parser($parser);
6c0ec68b 36
37 on_scope_end {
38 $self->remove;
39 };
3b08744b 40}
41
b40e1ccd 42has _shadowed_sub => (is => 'rw', clearer => 1);
17022343 43
c15f7959 44sub shadow_sub {
45 my $self = shift;
46 my $stash = $self->stash;
47 if (my $shadowed = $stash->get_symbol('&'.$self->keyword_name)) {
17022343 48 $self->_shadowed_sub($shadowed);
c15f7959 49 $stash->remove_symbol('&'.$self->keyword_name);
c15f7959 50 }
51}
52
53sub remove {
54 my ($self) = @_;
55 $self->keyword_parser->remove_keyword($self);
56 $self->clear_keyword_parser;
57 $self->clear_globref;
17022343 58 my $stash = $self->stash;
59 if (my $shadowed = $self->_shadowed_sub) {
60 $self->_clear_shadowed_sub;
17022343 61 $stash->add_symbol('&'.$self->keyword_name, $shadowed);
62 }
c15f7959 63}
64
809ae673 65has keyword_parser => (
66 is => 'rw',
67 weak_ref => 1,
68 clearer => 1,
69 handles => [
70 'match_source',
71 'current_match',
72 ],
73);
c15f7959 74
75has target_package => (is => 'ro', required => 1);
76has keyword_name => (is => 'ro', required => 1);
77has parser => (is => 'ro', required => 1);
78
b40e1ccd 79sub parse {
80 my $self = shift;
81 $self->${\$self->parser}(@_);
82}
83
c15f7959 84has stash => (is => 'lazy');
85
86sub _build_stash {
87 my ($self) = @_;
88 Package::Stash::PP->new($self->target_package);
89}
90
91has globref => (is => 'lazy', clearer => 'clear_globref');
92
93sub _build_globref {
94 no strict 'refs'; no warnings 'once';
95 \*{join'::',$_[0]->target_package,$_[0]->keyword_name}
96}
97
98after clear_globref => sub {
99 my ($self) = @_;
100 $self->stash->remove_symbol('&'.$self->keyword_name);
ba6b83aa 101 $self->globref_refcount(undef);
102 $self->restore_shadow;
103};
104
105sub restore_shadow {
106 my ($self) = @_;
b40e1ccd 107 if (my $shadowed = $self->_shadowed_sub) {
108 { no warnings 'redefine', 'prototype'; *{$self->globref} = $shadowed; }
109 }
ba6b83aa 110}
c15f7959 111
112has globref_refcount => (is => 'rw');
113
114sub save_refcount {
115 my ($self) = @_;
116 $self->globref_refcount(svref_2object($self->globref)->REFCNT);
117}
118
68363889 119sub install_matcher {
120 my ($self, $post) = @_;
121 my $stash = $self->stash;
122 my $sub = sub {};
123 set_prototype(\&$sub, '*;@') unless $post eq '(';
124 { no warnings 'redefine', 'prototype'; *{$self->globref} = $sub; }
125 $self->save_refcount;
126}
127
c15f7959 128sub have_match {
129 my ($self) = @_;
130 return 0 unless defined($self->globref_refcount);
131 svref_2object($self->globref)->REFCNT > $self->globref_refcount;
132}
133
bee972ee 134sub inject_after_scope {
135 my $inject = shift;
136 on_scope_end {
137 filter_add(sub {
138 my($status) ;
139 $status = filter_read();
140 if ($status >= 0) {
141 $_ = $inject . $_;
142 }
143 filter_del();
144 $status ;
145 });
146 };
3b08744b 147}
148
1491;