2727a043e271b8030d71feb5aaee2e8436982906
[p5sagit/p5-mst-13.2.git] / lib / fields.pm
1 package fields;
2
3 =head1 NAME
4
5 fields - compile-time class fields
6
7 =head1 SYNOPSIS
8
9     {
10         package Foo;
11         use fields qw(foo bar _private);
12     }
13     ...
14     my Foo $var = new Foo;
15     $var->{foo} = 42;
16
17     # This will generate a compile-time error.
18     $var->{zap} = 42;
19
20     {
21         package Bar;
22         use base 'Foo';
23         use fields 'bar';             # hides Foo->{bar}
24         use fields qw(baz _private);  # not shared with Foo
25     }
26
27 =head1 DESCRIPTION
28
29 The C<fields> pragma enables compile-time verified class fields.  It
30 does so by updating the %FIELDS hash in the calling package.
31
32 If a typed lexical variable holding a reference is used to access a
33 hash element and the %FIELDS hash of the given type exists, then the
34 operation is turned into an array access at compile time.  The %FIELDS
35 hash maps from hash element names to the array indices.  If the hash
36 element is not present in the %FIELDS hash, then a compile-time error
37 is signaled.
38
39 Since the %FIELDS hash is used at compile-time, it must be set up at
40 compile-time too.  This is made easier with the help of the 'fields'
41 and the 'base' pragma modules.  The 'base' pragma will copy fields
42 from base classes and the 'fields' pragma adds new fields.  Field
43 names that start with an underscore character are made private to a
44 class and are not visible to subclasses.  Inherited fields can be
45 overridden but will generate a warning if used together with the C<-w>
46 switch.
47
48 The effect of all this is that you can have objects with named fields
49 which are as compact and as fast arrays to access.  This only works
50 as long as the objects are accessed through properly typed variables.
51 For untyped access to work you have to make sure that a reference to
52 the proper %FIELDS hash is assigned to the 0'th element of the array
53 object (so that the objects can be treated like an pseudo-hash).  A
54 constructor like this does the job:
55
56   sub new
57   {
58       my $class = shift;
59       no strict 'refs';
60       my $self = bless [\%{"$class\::FIELDS"}], $class;
61       $self;
62   }
63
64
65 =head1 SEE ALSO
66
67 L<base>,
68 L<perlref/Pseudo-hashes: Using an array as a hash>
69
70 =cut
71
72 use strict;
73 no strict 'refs';
74 use vars qw(%attr $VERSION);
75
76 $VERSION = "1.01";
77
78 # some constants
79 sub _PUBLIC    () { 1 }
80 sub _PRIVATE   () { 2 }
81
82 # The %attr hash holds the attributes of the currently assigned fields
83 # per class.  The hash is indexed by class names and the hash value is
84 # an array reference.  The first element in the array is the lowest field
85 # number not belonging to a base class.  The remaining elements' indices
86 # are the field numbers.  The values are integer bit masks, or undef
87 # in the case of base class private fields (which occupy a slot but are
88 # otherwise irrelevant to the class).
89
90 sub import {
91     my $class = shift;
92     return unless @_;
93     my $package = caller(0);
94     my $fields = \%{"$package\::FIELDS"};
95     my $fattr = ($attr{$package} ||= [1]);
96     my $next = @$fattr;
97
98     if ($next > $fattr->[0]
99         and ($fields->{$_[0]} || 0) >= $fattr->[0])
100     {
101         # There are already fields not belonging to base classes.
102         # Looks like a possible module reload...
103         $next = $fattr->[0];
104     }
105     foreach my $f (@_) {
106         my $fno = $fields->{$f};
107
108         # Allow the module to be reloaded so long as field positions
109         # have not changed.
110         if ($fno and $fno != $next) {
111             require Carp;
112             if ($fno < $fattr->[0]) {
113                 Carp::carp("Hides field '$f' in base class") if $^W;
114             } else {
115                 Carp::croak("Field name '$f' already in use");
116             }
117         }
118         $fields->{$f} = $next;
119         $fattr->[$next] = ($f =~ /^_/) ? _PRIVATE : _PUBLIC;
120         $next += 1;
121     }
122     if (@$fattr > $next) {
123         # Well, we gave them the benefit of the doubt by guessing the
124         # module was reloaded, but they appear to be declaring fields
125         # in more than one place.  We can't be sure (without some extra
126         # bookkeeping) that the rest of the fields will be declared or
127         # have the same positions, so punt.
128         require Carp;
129         Carp::croak ("Reloaded module must declare all fields at once");
130     }
131 }
132
133 sub inherit  # called by base.pm when $base_fields is nonempty
134 {
135     my($derived, $base) = @_;
136     my $base_attr = $attr{$base};
137     my $derived_attr = $attr{$derived} ||= [];
138     my $base_fields    = \%{"$base\::FIELDS"};
139     my $derived_fields = \%{"$derived\::FIELDS"};
140
141     $derived_attr->[0] = $base_attr ? scalar(@$base_attr) : 1;
142     while (my($k,$v) = each %$base_fields) {
143         my($fno);
144         if ($fno = $derived_fields->{$k} and $fno != $v) {
145             require Carp;
146             Carp::croak ("Inherited %FIELDS can't override existing %FIELDS");
147         }
148         if ($base_attr->[$v] & _PRIVATE) {
149             $derived_attr->[$v] = undef;
150         } else {
151             $derived_attr->[$v] = $base_attr->[$v];
152             $derived_fields->{$k} = $v;
153         }
154      }
155 }
156
157 sub _dump  # sometimes useful for debugging
158 {
159    for my $pkg (sort keys %attr) {
160       print "\n$pkg";
161       if (@{"$pkg\::ISA"}) {
162          print " (", join(", ", @{"$pkg\::ISA"}), ")";
163       }
164       print "\n";
165       my $fields = \%{"$pkg\::FIELDS"};
166       for my $f (sort {$fields->{$a} <=> $fields->{$b}} keys %$fields) {
167          my $no = $fields->{$f};
168          print "   $no: $f";
169          my $fattr = $attr{$pkg}[$no];
170          if (defined $fattr) {
171             my @a;
172             push(@a, "public")    if $fattr & _PUBLIC;
173             push(@a, "private")   if $fattr & _PRIVATE;
174             push(@a, "inherited") if $no < $attr{$pkg}[0];
175             print "\t(", join(", ", @a), ")";
176          }
177          print "\n";
178       }
179    }
180 }
181
182 1;