Rashed Abdel-Tawab | 4db47f4 | 2019-09-06 10:38:22 -0700 | [diff] [blame] | 1 | use 5.008; |
| 2 | package fields; |
| 3 | |
| 4 | require 5.005; |
| 5 | use strict; |
| 6 | no strict 'refs'; |
| 7 | unless( eval q{require warnings::register; warnings::register->import; 1} ) { |
| 8 | *warnings::warnif = sub { |
| 9 | require Carp; |
| 10 | Carp::carp(@_); |
| 11 | } |
| 12 | } |
| 13 | use vars qw(%attr $VERSION); |
| 14 | |
| 15 | $VERSION = '2.23'; |
| 16 | $VERSION =~ tr/_//d; |
| 17 | |
| 18 | # constant.pm is slow |
| 19 | sub PUBLIC () { 2**0 } |
| 20 | sub PRIVATE () { 2**1 } |
| 21 | sub INHERITED () { 2**2 } |
| 22 | sub PROTECTED () { 2**3 } |
| 23 | |
| 24 | # The %attr hash holds the attributes of the currently assigned fields |
| 25 | # per class. The hash is indexed by class names and the hash value is |
| 26 | # an array reference. The first element in the array is the lowest field |
| 27 | # number not belonging to a base class. The remaining elements' indices |
| 28 | # are the field numbers. The values are integer bit masks, or undef |
| 29 | # in the case of base class private fields (which occupy a slot but are |
| 30 | # otherwise irrelevant to the class). |
| 31 | |
| 32 | sub import { |
| 33 | my $class = shift; |
| 34 | return unless @_; |
| 35 | my $package = caller(0); |
| 36 | # avoid possible typo warnings |
| 37 | %{"$package\::FIELDS"} = () unless %{"$package\::FIELDS"}; |
| 38 | my $fields = \%{"$package\::FIELDS"}; |
| 39 | my $fattr = ($attr{$package} ||= [1]); |
| 40 | my $next = @$fattr; |
| 41 | |
| 42 | # Quiet pseudo-hash deprecation warning for uses of fields::new. |
| 43 | bless \%{"$package\::FIELDS"}, 'pseudohash'; |
| 44 | |
| 45 | if ($next > $fattr->[0] |
| 46 | and ($fields->{$_[0]} || 0) >= $fattr->[0]) |
| 47 | { |
| 48 | # There are already fields not belonging to base classes. |
| 49 | # Looks like a possible module reload... |
| 50 | $next = $fattr->[0]; |
| 51 | } |
| 52 | foreach my $f (@_) { |
| 53 | my $fno = $fields->{$f}; |
| 54 | |
| 55 | # Allow the module to be reloaded so long as field positions |
| 56 | # have not changed. |
| 57 | if ($fno and $fno != $next) { |
| 58 | require Carp; |
| 59 | if ($fno < $fattr->[0]) { |
| 60 | if ($] < 5.006001) { |
| 61 | warn("Hides field '$f' in base class") if $^W; |
| 62 | } else { |
| 63 | warnings::warnif("Hides field '$f' in base class") ; |
| 64 | } |
| 65 | } else { |
| 66 | Carp::croak("Field name '$f' already in use"); |
| 67 | } |
| 68 | } |
| 69 | $fields->{$f} = $next; |
| 70 | $fattr->[$next] = ($f =~ /^_/) ? PRIVATE : PUBLIC; |
| 71 | $next += 1; |
| 72 | } |
| 73 | if (@$fattr > $next) { |
| 74 | # Well, we gave them the benefit of the doubt by guessing the |
| 75 | # module was reloaded, but they appear to be declaring fields |
| 76 | # in more than one place. We can't be sure (without some extra |
| 77 | # bookkeeping) that the rest of the fields will be declared or |
| 78 | # have the same positions, so punt. |
| 79 | require Carp; |
| 80 | Carp::croak ("Reloaded module must declare all fields at once"); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | sub inherit { |
| 85 | require base; |
| 86 | goto &base::inherit_fields; |
| 87 | } |
| 88 | |
| 89 | sub _dump # sometimes useful for debugging |
| 90 | { |
| 91 | for my $pkg (sort keys %attr) { |
| 92 | print "\n$pkg"; |
| 93 | if (@{"$pkg\::ISA"}) { |
| 94 | print " (", join(", ", @{"$pkg\::ISA"}), ")"; |
| 95 | } |
| 96 | print "\n"; |
| 97 | my $fields = \%{"$pkg\::FIELDS"}; |
| 98 | for my $f (sort {$fields->{$a} <=> $fields->{$b}} keys %$fields) { |
| 99 | my $no = $fields->{$f}; |
| 100 | print " $no: $f"; |
| 101 | my $fattr = $attr{$pkg}[$no]; |
| 102 | if (defined $fattr) { |
| 103 | my @a; |
| 104 | push(@a, "public") if $fattr & PUBLIC; |
| 105 | push(@a, "private") if $fattr & PRIVATE; |
| 106 | push(@a, "inherited") if $fattr & INHERITED; |
| 107 | print "\t(", join(", ", @a), ")"; |
| 108 | } |
| 109 | print "\n"; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | if ($] < 5.009) { |
| 115 | *new = sub { |
| 116 | my $class = shift; |
| 117 | $class = ref $class if ref $class; |
| 118 | return bless [\%{$class . "::FIELDS"}], $class; |
| 119 | } |
| 120 | } else { |
| 121 | *new = sub { |
| 122 | my $class = shift; |
| 123 | $class = ref $class if ref $class; |
| 124 | require Hash::Util; |
| 125 | my $self = bless {}, $class; |
| 126 | |
| 127 | # The lock_keys() prototype won't work since we require Hash::Util :( |
| 128 | &Hash::Util::lock_keys(\%$self, _accessible_keys($class)); |
| 129 | return $self; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | sub _accessible_keys { |
| 134 | my ($class) = @_; |
| 135 | return ( |
| 136 | keys %{$class.'::FIELDS'}, |
| 137 | map(_accessible_keys($_), @{$class.'::ISA'}), |
| 138 | ); |
| 139 | } |
| 140 | |
| 141 | sub phash { |
| 142 | die "Pseudo-hashes have been removed from Perl" if $] >= 5.009; |
| 143 | my $h; |
| 144 | my $v; |
| 145 | if (@_) { |
| 146 | if (ref $_[0] eq 'ARRAY') { |
| 147 | my $a = shift; |
| 148 | @$h{@$a} = 1 .. @$a; |
| 149 | if (@_) { |
| 150 | $v = shift; |
| 151 | unless (! @_ and ref $v eq 'ARRAY') { |
| 152 | require Carp; |
| 153 | Carp::croak ("Expected at most two array refs\n"); |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | else { |
| 158 | if (@_ % 2) { |
| 159 | require Carp; |
| 160 | Carp::croak ("Odd number of elements initializing pseudo-hash\n"); |
| 161 | } |
| 162 | my $i = 0; |
| 163 | @$h{grep ++$i % 2, @_} = 1 .. @_ / 2; |
| 164 | $i = 0; |
| 165 | $v = [grep $i++ % 2, @_]; |
| 166 | } |
| 167 | } |
| 168 | else { |
| 169 | $h = {}; |
| 170 | $v = []; |
| 171 | } |
| 172 | [ $h, @$v ]; |
| 173 | |
| 174 | } |
| 175 | |
| 176 | 1; |
| 177 | |
| 178 | __END__ |
| 179 | |