X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FClass%2FC3.pm;h=11753558e383da3a35a6396c69493617832eadd6;hb=8d45f443f7bad158e1a37618b9370c554439e557;hp=5c624222f79012eba9bf1a2ce7c83397db6efeaa;hpb=ecb0388de108f3f6fe103a3ca4f6e28e26892a70;p=gitmo%2FClass-C3.git diff --git a/lib/Class/C3.pm b/lib/Class/C3.pm index 5c62422..1175355 100644 --- a/lib/Class/C3.pm +++ b/lib/Class/C3.pm @@ -4,16 +4,186 @@ package Class::C3; use strict; use warnings; -our $VERSION = '0.15'; +our $VERSION = '0.15_01'; + +our $C3_IN_CORE; BEGIN { - eval { require Class::C3::XS }; + eval "require mro"; # XXX in the future, this should be a version check if($@) { - eval { require Class::C3::PurePerl }; + die $@ if $@ !~ /locate/; + eval "require Class::C3::XS"; if($@) { - die 'Could not load Class::C3::XS or Class::C3::PurePerl!'; + die $@ if $@ !~ /locate/; + eval "require Algorithm::C3; require Class::C3::next"; + die $@ if $@; } } + else { + $C3_IN_CORE = 1; + } +} + +# this is our global stash of both +# MRO's and method dispatch tables +# the structure basically looks like +# this: +# +# $MRO{$class} = { +# MRO => [ ], +# methods => { +# orig => , +# code => \& +# }, +# has_overload_fallback => (1 | 0) +# } +# +our %MRO; + +# use these for debugging ... +sub _dump_MRO_table { %MRO } +our $TURN_OFF_C3 = 0; + +# state tracking for initialize()/uninitialize() +our $_initialized = 0; + +sub import { + my $class = caller(); + # skip if the caller is main:: + # since that is clearly not relevant + return if $class eq 'main'; + + return if $TURN_OFF_C3; + mro::set_mro($class, 'c3') if $C3_IN_CORE; + + # make a note to calculate $class + # during INIT phase + $MRO{$class} = undef unless exists $MRO{$class}; +} + +## initializers + +sub initialize { + %next::METHOD_CACHE = (); + # why bother if we don't have anything ... + return unless keys %MRO; + if($C3_IN_CORE) { + mro::set_mro($_, 'c3') for keys %MRO; + } + else { + if($_initialized) { + uninitialize(); + $MRO{$_} = undef foreach keys %MRO; + } + _calculate_method_dispatch_tables(); + _apply_method_dispatch_tables(); + $_initialized = 1; + } +} + +sub uninitialize { + # why bother if we don't have anything ... + %next::METHOD_CACHE = (); + return unless keys %MRO; + if($C3_IN_CORE) { + mro::set_mro($_, 'dfs') for keys %MRO; + } + else { + _remove_method_dispatch_tables(); + $_initialized = 0; + } +} + +sub reinitialize { goto &initialize } + +## functions for applying C3 to classes + +sub _calculate_method_dispatch_tables { + return if $C3_IN_CORE; + my %merge_cache; + foreach my $class (keys %MRO) { + _calculate_method_dispatch_table($class, \%merge_cache); + } +} + +sub _calculate_method_dispatch_table { + return if $C3_IN_CORE; + my ($class, $merge_cache) = @_; + no strict 'refs'; + my @MRO = calculateMRO($class, $merge_cache); + $MRO{$class} = { MRO => \@MRO }; + my $has_overload_fallback = 0; + my %methods; + # NOTE: + # we do @MRO[1 .. $#MRO] here because it + # makes no sense to interogate the class + # which you are calculating for. + foreach my $local (@MRO[1 .. $#MRO]) { + # if overload has tagged this module to + # have use "fallback", then we want to + # grab that value + $has_overload_fallback = ${"${local}::()"} + if defined ${"${local}::()"}; + foreach my $method (grep { defined &{"${local}::$_"} } keys %{"${local}::"}) { + # skip if already overriden in local class + next unless !defined *{"${class}::$method"}{CODE}; + $methods{$method} = { + orig => "${local}::$method", + code => \&{"${local}::$method"} + } unless exists $methods{$method}; + } + } + # now stash them in our %MRO table + $MRO{$class}->{methods} = \%methods; + $MRO{$class}->{has_overload_fallback} = $has_overload_fallback; +} + +sub _apply_method_dispatch_tables { + return if $C3_IN_CORE; + foreach my $class (keys %MRO) { + _apply_method_dispatch_table($class); + } +} + +sub _apply_method_dispatch_table { + return if $C3_IN_CORE; + my $class = shift; + no strict 'refs'; + ${"${class}::()"} = $MRO{$class}->{has_overload_fallback} + if $MRO{$class}->{has_overload_fallback}; + foreach my $method (keys %{$MRO{$class}->{methods}}) { + *{"${class}::$method"} = $MRO{$class}->{methods}->{$method}->{code}; + } +} + +sub _remove_method_dispatch_tables { + return if $C3_IN_CORE; + foreach my $class (keys %MRO) { + _remove_method_dispatch_table($class); + } +} + +sub _remove_method_dispatch_table { + return if $C3_IN_CORE; + my $class = shift; + no strict 'refs'; + delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback}; + foreach my $method (keys %{$MRO{$class}->{methods}}) { + delete ${"${class}::"}{$method} + if defined *{"${class}::${method}"}{CODE} && + (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code}); + } +} + +sub calculateMRO { + my ($class, $merge_cache) = @_; + + return @{mro::get_linear_isa($class)} if $C3_IN_CORE; + + return Algorithm::C3::merge($class, sub { + no strict 'refs'; + @{$_[0] . '::ISA'}; + }, $merge_cache); } 1;