X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FClass%2FC3.pm;h=c86f76dcfb5d979fae3643ae8583ef4de345415b;hb=bad9dc5953e918bfb388c7a7f430493a3e44a8d0;hp=385fc1dce0c0afd9480415e825c9fdeacba8ee80;hpb=d0e2efe52be112c88ad2b401516ae5121b620f06;p=gitmo%2FClass-C3.git diff --git a/lib/Class/C3.pm b/lib/Class/C3.pm index 385fc1d..c86f76d 100644 --- a/lib/Class/C3.pm +++ b/lib/Class/C3.pm @@ -6,7 +6,7 @@ use warnings; use Scalar::Util 'blessed'; -our $VERSION = '0.02'; +our $VERSION = '0.09'; # this is our global stash of both # MRO's and method dispatch tables @@ -18,10 +18,11 @@ our $VERSION = '0.02'; # methods => { # orig => , # code => \& -# } +# }, +# has_overload_fallback => (1 | 0) # } # -my %MRO; +our %MRO; # use these for debugging ... sub _dump_MRO_table { %MRO } @@ -35,7 +36,7 @@ sub import { return if $TURN_OFF_C3; # make a note to calculate $class # during INIT phase - $MRO{$class} = undef; + $MRO{$class} = undef unless exists $MRO{$class}; } ## initializers @@ -55,12 +56,14 @@ sub initialize { return unless keys %MRO; _calculate_method_dispatch_tables(); _apply_method_dispatch_tables(); + %next::METHOD_CACHE = (); } sub uninitialize { # why bother if we don't have anything ... return unless keys %MRO; _remove_method_dispatch_tables(); + %next::METHOD_CACHE = (); } sub reinitialize { @@ -83,12 +86,18 @@ sub _calculate_method_dispatch_table { no strict 'refs'; my @MRO = calculateMRO($class); $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}; @@ -99,7 +108,8 @@ sub _calculate_method_dispatch_table { } } # now stash them in our %MRO table - $MRO{$class}->{methods} = \%methods; + $MRO{$class}->{methods} = \%methods; + $MRO{$class}->{has_overload_fallback} = $has_overload_fallback; } sub _apply_method_dispatch_tables { @@ -111,6 +121,8 @@ sub _apply_method_dispatch_tables { sub _apply_method_dispatch_table { 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}; } @@ -125,8 +137,11 @@ sub _remove_method_dispatch_tables { sub _remove_method_dispatch_table { my $class = shift; no strict 'refs'; + delete ${"${class}::"}{"()"} if $MRO{$class}->{has_overload_fallback}; foreach my $method (keys %{$MRO{$class}->{methods}}) { - delete ${"${class}::"}{$method}; + delete ${"${class}::"}{$method} + if defined *{"${class}::${method}"}{CODE} && + (*{"${class}::${method}"}{CODE} eq $MRO{$class}->{methods}->{$method}->{code}); } } @@ -137,12 +152,14 @@ sub _remove_method_dispatch_table { # http://www.python.org/2.3/mro.html sub _merge { my (@seqs) = @_; + my $class_being_merged = $seqs[0]->[0]; my @res; while (1) { # remove all empty seqences my @nonemptyseqs = (map { (@{$_} ? $_ : ()) } @seqs); # return the list if we have no more no-empty sequences return @res if not @nonemptyseqs; + my $reject; my $cand; # a canidate .. foreach my $seq (@nonemptyseqs) { $cand = $seq->[0]; # get the head of the list @@ -154,12 +171,15 @@ sub _merge { # jump out as soon as we find one matching # there is no reason not too. However, if # we find one, then just remove the '&& last' - $nothead++ && last if exists $in_tail{$cand}; + ++$nothead && last if exists $in_tail{$cand}; } last unless $nothead; # leave the loop with our canidate ... + $reject = $cand; $cand = undef; # otherwise, reject it ... } - die "Inconsistent hierarchy" if not $cand; + die "Inconsistent hierarchy found while merging '$class_being_merged':\n\t" . + "current merge results [\n\t\t" . (join ",\n\t\t" => @res) . "\n\t]\n\t" . + "mergeing failed on '$reject'\n" if not $cand; push @res => $cand; # now loop through our non-empties and pop # off the head if it matches our canidate @@ -179,6 +199,55 @@ sub calculateMRO { ); } +package # hide me from PAUSE + next; + +use strict; +use warnings; + +use Scalar::Util 'blessed'; + +our $VERSION = '0.05'; + +our %METHOD_CACHE; + +sub method { + my $level = 1; + my ($method_caller, $label, @label); + while ($method_caller = (caller($level++))[3]) { + @label = (split '::', $method_caller); + $label = pop @label; + last unless + $label eq '(eval)' || + $label eq '__ANON__'; + } + my $caller = join '::' => @label; + my $self = $_[0]; + my $class = blessed($self) || $self; + + goto &{ $METHOD_CACHE{"$class|$caller|$label"} ||= do { + + my @MRO = Class::C3::calculateMRO($class); + + my $current; + while ($current = shift @MRO) { + last if $caller eq $current; + } + + no strict 'refs'; + my $found; + foreach my $class (@MRO) { + next if (defined $Class::C3::MRO{$class} && + defined $Class::C3::MRO{$class}{methods}{$label}); + last if (defined ($found = *{$class . '::' . $label}{CODE})); + } + + die "No next::method '$label' found for $self" unless $found; + + $found; + } }; +} + 1; __END__ @@ -316,6 +385,47 @@ operation. =back +=head1 METHOD REDISPATCHING + +It is always useful to be able to re-dispatch your method call to the "next most applicable method". This +module provides a pseudo package along the lines of C or C which will re-dispatch the +method along the C3 linearization. This is best show with an examples. + + # a classic diamond MI pattern ... + + / \ + + \ / + + + package A; + use c3; + sub foo { 'A::foo' } + + package B; + use base 'A'; + use c3; + sub foo { 'B::foo => ' . (shift)->next::method() } + + package B; + use base 'A'; + use c3; + sub foo { 'C::foo => ' . (shift)->next::method() } + + package D; + use base ('B', 'C'); + use c3; + sub foo { 'D::foo => ' . (shift)->next::method() } + + print D->foo; # prints out "D::foo => B::foo => C::foo => A::foo" + +A few things to note. First, we do not require you to add on the method name to the C +call (this is unlike C and C which do require that). This helps to enforce the rule +that you cannot dispatch to a method of a different name (this is how C behaves as well). + +The next thing to keep in mind is that you will need to pass all arguments to C it can +not automatically use the current C<@_>. + =head1 CAVEATS Let me first say, this is an experimental module, and so it should not be used for anything other @@ -332,8 +442,8 @@ And now, onto the caveats. The idea of C under multiple inheritence is ambigious, and generally not recomended anyway. However, it's use in conjuntion with this module is very much not recommended, and in fact very -discouraged. In the future I plan to support a C style interface to be used to move to the -next most appropriate method in the MRO. +discouraged. The recommended approach is to instead use the supplied C feature, see +more details on it's usage above. =item Changing C<@ISA>. @@ -360,12 +470,20 @@ C for any changes you make to take effect. You can never have enough tests :) -=item call-next-method / NEXT:: / next METHOD +=back -I am contemplating some kind of psudeo-package which can dispatch to the next most relevant method in the -MRO. This should not be too hard to implement when the time comes. +=head1 CODE COVERAGE -=back +I use B to test the code coverage of my tests, below is the B report on this +module's test suite. + + ---------------------------- ------ ------ ------ ------ ------ ------ ------ + File stmt bran cond sub pod time total + ---------------------------- ------ ------ ------ ------ ------ ------ ------ + Class/C3.pm 98.6 90.9 73.3 96.0 100.0 96.8 95.3 + ---------------------------- ------ ------ ------ ------ ------ ------ ------ + Total 98.6 90.9 73.3 96.0 100.0 96.8 95.3 + ---------------------------- ------ ------ ------ ------ ------ ------ ------ =head1 SEE ALSO @@ -413,6 +531,18 @@ MRO. This should not be too hard to implement when the time comes. =back +=head1 ACKNOWLEGEMENTS + +=over 4 + +=item Thanks to Matt S. Trout for using this module in his module L +and finding many bugs and providing fixes. + +=item Thanks to Justin Guenther for making C more robust by handling +calls inside C and anon-subs. + +=back + =head1 AUTHOR Stevan Little, Estevan@iinteractive.comE