adding the some preliminary junk
[gitmo/Class-C3-XS.git] / lib / Class / C3 / XS.pm
CommitLineData
8995e827 1
2package Class::C3::XS;
3
4our $VERSION = '0.15';
5
6=pod
7
8=head1 NAME
9
10Class::C3::XS - The XS implementation of Class::C3
11
12=head1 DESCRIPTION
13
14This is the XS implementation of L<Class::C3>. The main L<Class::C3> package will
15first attempt to load L<Class::C3::XS>, and then failing that, will fall back to
16L<Class::C3::PurePerl>. Do not use this package directly, use L<Class::C3> instead.
17
18=head1 AUTHOR
19
20Stevan Little, E<lt>stevan@iinteractive.comE<gt>
21
22Brandon L. Black, E<lt>blblack@gmail.comE<gt>
23
24=head1 COPYRIGHT AND LICENSE
25
26Copyright 2005, 2006 by Infinity Interactive, Inc.
27
28L<http://www.iinteractive.com>
29
30This library is free software; you can redistribute it and/or modify
31it under the same terms as Perl itself.
32
33=cut
34
35package # hide me from PAUSE
36 Class::C3;
37
38use strict;
39use warnings;
40
41use Scalar::Util 'blessed';
42use Algorithm::C3;
43
44# this is our global stash of both
45# MRO's and method dispatch tables
46# the structure basically looks like
47# this:
48#
49# $MRO{$class} = {
50# MRO => [ <class precendence list> ],
51# methods => {
52# orig => <original location of method>,
53# code => \&<ref to original method>
54# },
55# has_overload_fallback => (1 | 0)
56# }
57#
58our %MRO;
59
60# use these for debugging ...
61sub _dump_MRO_table { %MRO }
62our $TURN_OFF_C3 = 0;
63
64# state tracking for initialize()/uninitialize()
65our $_initialized = 0;
66
67sub import {
68 my $class = caller();
69 # skip if the caller is main::
70 # since that is clearly not relevant
71 return if $class eq 'main';
72 return if $TURN_OFF_C3;
73 # make a note to calculate $class
74 # during INIT phase
75 $MRO{$class} = undef unless exists $MRO{$class};
76}
77
78## initializers
79
80sub initialize {
81 # why bother if we don't have anything ...
82 return unless keys %MRO;
83 if($_initialized) {
84 uninitialize();
85 $MRO{$_} = undef foreach keys %MRO;
86 }
87 _calculate_method_dispatch_tables();
88 _apply_method_dispatch_tables();
89 %next::METHOD_CACHE = ();
90 $_initialized = 1;
91}
92
93sub uninitialize {
94 # why bother if we don't have anything ...
95 return unless keys %MRO;
96 _remove_method_dispatch_tables();
97 %next::METHOD_CACHE = ();
98 $_initialized = 0;
99}
100
101sub reinitialize { goto &initialize }
102
103## functions for applying C3 to classes
104
105sub _calculate_method_dispatch_tables {
106 my %merge_cache;
107 foreach my $class (keys %MRO) {
108 _calculate_method_dispatch_table($class, \%merge_cache);
109 }
110}
111
112sub _calculate_method_dispatch_table {
113 my ($class, $merge_cache) = @_;
114 no strict 'refs';
115 my @MRO = calculateMRO($class, $merge_cache);
116 $MRO{$class} = { MRO => \@MRO };
117 my $has_overload_fallback = 0;
118 my %methods;
119 # NOTE:
120 # we do @MRO[1 .. $#MRO] here because it
121 # makes no sense to interogate the class
122 # which you are calculating for.
123 foreach my $local (@MRO[1 .. $#MRO]) {
124 # if overload has tagged this module to
125 # have use "fallback", then we want to
126 # grab that value
127 $has_overload_fallback = ${"${local}::()"}
128 if defined ${"${local}::()"};
129 foreach my $method (grep { defined &{"${local}::$_"} } keys %{"${local}::"}) {
130 # skip if already overriden in local class
131 next unless !defined *{"${class}::$method"}{CODE};
132 $methods{$method} = {
133 orig => "${local}::$method",
134 code => \&{"${local}::$method"}
135 } unless exists $methods{$method};
136 }
137 }
138 # now stash them in our %MRO table
139 $MRO{$class}->{methods} = \%methods;
140 $MRO{$class}->{has_overload_fallback} = $has_overload_fallback;
141}
142
143sub _apply_method_dispatch_tables {
144 foreach my $class (keys %MRO) {
145 _apply_method_dispatch_table($class);
146 }
147}
148
149sub _apply_method_dispatch_table {
150 my $class = shift;
151 no strict 'refs';
152 ${"${class}::()"} = $MRO{$class}->{has_overload_fallback}
153 if $MRO{$class}->{has_overload_fallback};
154 foreach my $method (keys %{$MRO{$class}->{methods}}) {
155 *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code};
156 }
157}
158
159sub _remove_method_dispatch_tables {
160 foreach my $class (keys %MRO) {
161 _remove_method_dispatch_table($class);
162 }
163}
164
165sub _remove_method_dispatch_table {
166 my $class = shift;
167 no strict 'refs';
168 delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback};
169 foreach my $method (keys %{$MRO{$class}->{methods}}) {
170 delete ${"${class}::"}{$method}
171 if defined *{"${class}::${method}"}{CODE} &&
172 (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code});
173 }
174}
175
176## functions for calculating C3 MRO
177
178sub calculateMRO {
179 my ($class, $merge_cache) = @_;
180 return Algorithm::C3::merge($class, sub {
181 no strict 'refs';
182 @{$_[0] . '::ISA'};
183 }, $merge_cache);
184}
185
186package # hide me from PAUSE
187 next;
188
189use strict;
190use warnings;
191
192our $VERSION = 0.15;
193
194use Scalar::Util 'blessed';
195
196our %METHOD_CACHE;
197
198sub method {
199 my $indirect = caller() =~ /^(?:next|maybe::next)$/;
200 my $level = $indirect ? 2 : 1;
201
202 my ($method_caller, $label, @label);
203 while ($method_caller = (caller($level++))[3]) {
204 @label = (split '::', $method_caller);
205 $label = pop @label;
206 last unless
207 $label eq '(eval)' ||
208 $label eq '__ANON__';
209 }
210 my $caller = join '::' => @label;
211 my $self = $_[0];
212 my $class = blessed($self) || $self;
213
214 my $method = $METHOD_CACHE{"$class|$caller|$label"} ||= do {
215
216 my @MRO = Class::C3::calculateMRO($class);
217
218 my $current;
219 while ($current = shift @MRO) {
220 last if $caller eq $current;
221 }
222
223 no strict 'refs';
224 my $found;
225 foreach my $class (@MRO) {
226 next if (defined $Class::C3::MRO{$class} &&
227 defined $Class::C3::MRO{$class}{methods}{$label});
228 last if (defined ($found = *{$class . '::' . $label}{CODE}));
229 }
230
231 $found;
232 };
233
234 return $method if $indirect;
235
236 die "No next::method '$label' found for $self" if !$method;
237
238 goto &{$method};
239}
240
241sub can { method($_[0]) }
242
243package # hide me from PAUSE
244 maybe::next;
245
246use strict;
247use warnings;
248
249our $VERSION = 0.15;
250
251sub method { (next::method($_[0]) || return)->(@_) }
252
2531;