NEXT/next::method thing needed updated for removal of the c3 assumption in next:...
[gitmo/Class-C3.git] / lib / Class / C3 / PurePerl.pm
CommitLineData
ecb0388d 1
2package Class::C3::PurePerl;
3
4our $VERSION = '0.15';
5
6=pod
7
8=head1 NAME
9
10Class::C3::PurePerl - The default pure-Perl implementation of Class::C3
11
12=head1 DESCRIPTION
13
14This is the plain pure-Perl implementation of Class::C3. The main Class::C3 package will
15first attempt to load L<Class::C3::XS>, and then failing that, will fall back to this. Do
16not 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';
7f657ca3 42
43our $VERSION = '0.15';
44our $C3_IN_CORE;
45
46BEGIN {
8fca9ed2 47 eval "require mro"; # XXX in the future, this should be a version check
7f657ca3 48 if($@) {
49 eval "require Algorithm::C3";
8fca9ed2 50 die "No core C3 support and could not load 'Algorithm::C3'!" if $@;
51 eval "require Class::C3::PurePerl::next";
52 die "No core C3 support and could not load 'Class::C3::PurePerl::next'!" if $@;
7f657ca3 53 }
54 else {
55 $C3_IN_CORE = 1;
56 }
57}
ecb0388d 58
59# this is our global stash of both
60# MRO's and method dispatch tables
61# the structure basically looks like
62# this:
63#
64# $MRO{$class} = {
65# MRO => [ <class precendence list> ],
66# methods => {
67# orig => <original location of method>,
68# code => \&<ref to original method>
69# },
70# has_overload_fallback => (1 | 0)
71# }
72#
73our %MRO;
74
75# use these for debugging ...
76sub _dump_MRO_table { %MRO }
77our $TURN_OFF_C3 = 0;
78
79# state tracking for initialize()/uninitialize()
80our $_initialized = 0;
81
82sub import {
83 my $class = caller();
84 # skip if the caller is main::
85 # since that is clearly not relevant
86 return if $class eq 'main';
7f657ca3 87
ecb0388d 88 return if $TURN_OFF_C3;
8fca9ed2 89 mro::set_mro($class, 'c3') if $C3_IN_CORE;
7f657ca3 90
ecb0388d 91 # make a note to calculate $class
92 # during INIT phase
93 $MRO{$class} = undef unless exists $MRO{$class};
94}
95
96## initializers
97
98sub initialize {
7f657ca3 99 %next::METHOD_CACHE = ();
ecb0388d 100 # why bother if we don't have anything ...
101 return unless keys %MRO;
7f657ca3 102 if($C3_IN_CORE) {
8fca9ed2 103 mro::set_mro($_, 'c3') for keys %MRO;
7f657ca3 104 }
105 else {
106 if($_initialized) {
107 uninitialize();
108 $MRO{$_} = undef foreach keys %MRO;
109 }
110 _calculate_method_dispatch_tables();
111 _apply_method_dispatch_tables();
112 $_initialized = 1;
ecb0388d 113 }
ecb0388d 114}
115
116sub uninitialize {
117 # why bother if we don't have anything ...
ecb0388d 118 %next::METHOD_CACHE = ();
7f657ca3 119 return unless keys %MRO;
120 if($C3_IN_CORE) {
8fca9ed2 121 mro::set_mro($_, 'dfs') for keys %MRO;
7f657ca3 122 }
123 else {
124 _remove_method_dispatch_tables();
125 $_initialized = 0;
126 }
ecb0388d 127}
128
129sub reinitialize { goto &initialize }
130
131## functions for applying C3 to classes
132
133sub _calculate_method_dispatch_tables {
7f657ca3 134 return if $C3_IN_CORE;
ecb0388d 135 my %merge_cache;
136 foreach my $class (keys %MRO) {
137 _calculate_method_dispatch_table($class, \%merge_cache);
138 }
139}
140
141sub _calculate_method_dispatch_table {
7f657ca3 142 return if $C3_IN_CORE;
ecb0388d 143 my ($class, $merge_cache) = @_;
144 no strict 'refs';
145 my @MRO = calculateMRO($class, $merge_cache);
146 $MRO{$class} = { MRO => \@MRO };
147 my $has_overload_fallback = 0;
148 my %methods;
149 # NOTE:
150 # we do @MRO[1 .. $#MRO] here because it
151 # makes no sense to interogate the class
152 # which you are calculating for.
153 foreach my $local (@MRO[1 .. $#MRO]) {
154 # if overload has tagged this module to
155 # have use "fallback", then we want to
156 # grab that value
157 $has_overload_fallback = ${"${local}::()"}
158 if defined ${"${local}::()"};
159 foreach my $method (grep { defined &{"${local}::$_"} } keys %{"${local}::"}) {
160 # skip if already overriden in local class
161 next unless !defined *{"${class}::$method"}{CODE};
162 $methods{$method} = {
163 orig => "${local}::$method",
164 code => \&{"${local}::$method"}
165 } unless exists $methods{$method};
166 }
167 }
168 # now stash them in our %MRO table
169 $MRO{$class}->{methods} = \%methods;
170 $MRO{$class}->{has_overload_fallback} = $has_overload_fallback;
171}
172
173sub _apply_method_dispatch_tables {
7f657ca3 174 return if $C3_IN_CORE;
ecb0388d 175 foreach my $class (keys %MRO) {
176 _apply_method_dispatch_table($class);
177 }
178}
179
180sub _apply_method_dispatch_table {
7f657ca3 181 return if $C3_IN_CORE;
ecb0388d 182 my $class = shift;
183 no strict 'refs';
184 ${"${class}::()"} = $MRO{$class}->{has_overload_fallback}
185 if $MRO{$class}->{has_overload_fallback};
186 foreach my $method (keys %{$MRO{$class}->{methods}}) {
187 *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code};
188 }
189}
190
191sub _remove_method_dispatch_tables {
7f657ca3 192 return if $C3_IN_CORE;
ecb0388d 193 foreach my $class (keys %MRO) {
194 _remove_method_dispatch_table($class);
195 }
196}
197
198sub _remove_method_dispatch_table {
7f657ca3 199 return if $C3_IN_CORE;
ecb0388d 200 my $class = shift;
201 no strict 'refs';
202 delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback};
203 foreach my $method (keys %{$MRO{$class}->{methods}}) {
204 delete ${"${class}::"}{$method}
205 if defined *{"${class}::${method}"}{CODE} &&
206 (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code});
207 }
208}
209
210## functions for calculating C3 MRO
211
212sub calculateMRO {
213 my ($class, $merge_cache) = @_;
7f657ca3 214
8fca9ed2 215 return @{mro::get_linear_isa($class)} if $C3_IN_CORE;
7f657ca3 216
ecb0388d 217 return Algorithm::C3::merge($class, sub {
218 no strict 'refs';
219 @{$_[0] . '::ISA'};
220 }, $merge_cache);
221}
222
ecb0388d 2231;