| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# You may distribute under the terms of either the GNU General Public License |
|
2
|
|
|
|
|
|
|
# or the Artistic License (the same terms as Perl itself) |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# (C) Paul Evans, 2012-2024 -- leonerd@leonerd.org.uk |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Struct::Dumb 0.16; |
|
7
|
|
|
|
|
|
|
|
|
8
|
17
|
|
|
17
|
|
2199671
|
use v5.14; |
|
|
17
|
|
|
|
|
57
|
|
|
9
|
17
|
|
|
17
|
|
642
|
use warnings; |
|
|
17
|
|
|
|
|
1349
|
|
|
|
17
|
|
|
|
|
602
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
17
|
|
|
11
|
|
1703
|
use Carp; |
|
|
11
|
|
|
|
|
30
|
|
|
|
11
|
|
|
|
|
808
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
11
|
|
|
11
|
|
69
|
use Scalar::Util qw( blessed refaddr ); |
|
|
11
|
|
|
|
|
379
|
|
|
|
11
|
|
|
|
|
780
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# 'overloading.pm' was only added in 5.10 |
|
16
|
|
|
|
|
|
|
# Before that we can't easily implement forbidding of @{} overload, so lets not |
|
17
|
11
|
|
|
11
|
|
289
|
use constant HAVE_OVERLOADING => eval { require overloading }; |
|
|
11
|
|
|
|
|
25
|
|
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
1436
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
11
|
|
|
11
|
|
60
|
use constant HAVE_FEATURE_CLASS => defined eval { require feature; $feature::feature{class} }; |
|
|
11
|
|
|
|
|
18
|
|
|
|
11
|
|
|
|
|
31
|
|
|
|
11
|
|
|
|
|
324
|
|
|
|
11
|
|
|
|
|
1733
|
|
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
BEGIN { |
|
22
|
11
|
|
|
11
|
|
194
|
if( HAVE_FEATURE_CLASS ) { |
|
23
|
|
|
|
|
|
|
# Before Perl 5.43.4, a class with no fields in it would cause thread |
|
24
|
|
|
|
|
|
|
# cloning to segfault. |
|
25
|
|
|
|
|
|
|
# https://github.com/Perl/perl5/issues/23771 |
|
26
|
11
|
|
|
|
|
31
|
my $dummy_field = ""; |
|
27
|
11
|
50
|
|
|
|
195
|
$dummy_field = 'field $dummy;' if $^V lt v5.43.4; |
|
28
|
|
|
|
|
|
|
|
|
29
|
11
|
|
|
11
|
|
1204
|
my $ok = eval <<"EOF"; |
|
|
10
|
|
|
|
|
5441
|
|
|
|
10
|
|
|
|
|
41347
|
|
|
|
10
|
|
|
|
|
69
|
|
|
30
|
|
|
|
|
|
|
{ use experimental 'class'; class Struct::Dumb::Struct {$dummy_field}; } |
|
31
|
|
|
|
|
|
|
1 |
|
32
|
|
|
|
|
|
|
EOF |
|
33
|
|
|
|
|
|
|
|
|
34
|
10
|
50
|
|
|
|
3338
|
die $@ unless $ok; |
|
35
|
|
|
|
|
|
|
} |
|
36
|
|
|
|
|
|
|
else { |
|
37
|
|
|
|
|
|
|
eval "{ package Struct::Dumb::Struct; }"; |
|
38
|
|
|
|
|
|
|
} |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 NAME |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
C - make simple lightweight record-like structures |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
use Struct::Dumb; |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
struct Point => [qw( x y )]; |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
my $point = Point(10, 20); |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
printf "Point is at (%d, %d)\n", $point->x, $point->y; |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
$point->y = 30; |
|
56
|
|
|
|
|
|
|
printf "Point is now at (%d, %d)\n", $point->x, $point->y; |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Z<> |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
struct Point3D => [qw( x y z )], named_constructor => 1; |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
my $point3d = Point3D( z => 12, x => 100, y => 50 ); |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
printf "Point3d's height is %d\n", $point3d->z; |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Z<> |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
struct Point3D => [qw( x y z )], predicate => "is_Point3D"; |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
my $point3d = Point3D( 1, 2, 3 ); |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
printf "This is a Point3D\n" if is_Point3D( $point3d ); |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Z<> |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
use Struct::Dumb qw( -named_constructors ) |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
struct Point3D => [qw( x y z )]; |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
my $point3d = Point3D( x => 100, z => 12, y => 50 ); |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
C creates record-like structure types, similar to the C |
|
85
|
|
|
|
|
|
|
keyword in C, C++ or C#, or C in Pascal. An invocation of this module |
|
86
|
|
|
|
|
|
|
will create a construction function which returns new object references with |
|
87
|
|
|
|
|
|
|
the given field values. These references all respond to lvalue methods that |
|
88
|
|
|
|
|
|
|
access or modify the values stored. |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
It's specifically and intentionally not meant to be an object class. You |
|
91
|
|
|
|
|
|
|
cannot subclass it. You cannot provide additional methods. You cannot apply |
|
92
|
|
|
|
|
|
|
roles or mixins or metaclasses or traits or antlers or whatever else is in |
|
93
|
|
|
|
|
|
|
fashion this week. |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
On the other hand, it is tiny, creates cheap lightweight array-backed |
|
96
|
|
|
|
|
|
|
structures, uses nothing outside of core. It's intended simply to be a |
|
97
|
|
|
|
|
|
|
slightly nicer way to store data structures, where otherwise you might be |
|
98
|
|
|
|
|
|
|
tempted to abuse a hash, complete with the risk of typoing key names. The |
|
99
|
|
|
|
|
|
|
constructor will C if passed the wrong number of arguments, as will |
|
100
|
|
|
|
|
|
|
attempts to refer to fields that don't exist. Accessor-mutators will C |
|
101
|
|
|
|
|
|
|
if invoked with arguments. (This helps detect likely bugs such as accidentally |
|
102
|
|
|
|
|
|
|
passing in the new value as an argument, or attempting to invoke a stored |
|
103
|
|
|
|
|
|
|
C reference by passing argument values directly to the accessor.) |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
$ perl -E 'use Struct::Dumb; struct Point => [qw( x y )]; Point(30)' |
|
106
|
|
|
|
|
|
|
usage: main::Point($x, $y) at -e line 1 |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
$ perl -E 'use Struct::Dumb; struct Point => [qw( x y )]; Point(10,20)->z' |
|
109
|
|
|
|
|
|
|
main::Point does not have a 'z' field at -e line 1 |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
$ perl -E 'use Struct::Dumb; struct Point => [qw( x y )]; Point(1,2)->x(3)' |
|
112
|
|
|
|
|
|
|
main::Point->x invoked with arguments at -e line 1. |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Objects in this class are (currently) backed by an ARRAY reference store, |
|
115
|
|
|
|
|
|
|
though this is an internal implementation detail and should not be relied on |
|
116
|
|
|
|
|
|
|
by using code. Attempting to dereference the object as an ARRAY will throw an |
|
117
|
|
|
|
|
|
|
exception. |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
I: That on development perls that support C |
|
120
|
|
|
|
|
|
|
is used instead of a blessed ARRAY reference. This implementation choice |
|
121
|
|
|
|
|
|
|
should be transparent to the end-user, as all the same features are supported. |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head2 CONSTRUCTOR FORMS |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
The C and C declarations create two different kinds |
|
126
|
|
|
|
|
|
|
of constructor function, depending on the setting of the C |
|
127
|
|
|
|
|
|
|
option. When false, the constructor takes positional values in the same order |
|
128
|
|
|
|
|
|
|
as the fields were declared. When true, the constructor takes a key/value pair |
|
129
|
|
|
|
|
|
|
list in no particular order, giving the value of each named field. |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
This option can be specified to the C and C |
|
132
|
|
|
|
|
|
|
functions. It defaults to false, but it can be set on a per-package basis to |
|
133
|
|
|
|
|
|
|
default true by supplying the C<-named_constructors> option on the C |
|
134
|
|
|
|
|
|
|
statement. |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
When using named constructors, individual fields may be declared as being |
|
137
|
|
|
|
|
|
|
optional. By preceeding the field name with a C> character, the constructor |
|
138
|
|
|
|
|
|
|
is instructed not to complain if a named parameter is not given for that |
|
139
|
|
|
|
|
|
|
field; instead it will be set to C. |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
struct Person => [qw( name age ?address )], |
|
142
|
|
|
|
|
|
|
named_constructor => 1; |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
my $bob = Person( name => "Bob", age => 20 ); |
|
145
|
|
|
|
|
|
|
# This is valid because 'address' is marked as optional |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=cut |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub import |
|
150
|
|
|
|
|
|
|
{ |
|
151
|
12
|
|
|
12
|
|
117
|
my $pkg = shift; |
|
152
|
12
|
|
|
|
|
32
|
my $caller = caller; |
|
153
|
|
|
|
|
|
|
|
|
154
|
12
|
|
|
|
|
26
|
my %default_opts; |
|
155
|
|
|
|
|
|
|
my %syms; |
|
156
|
|
|
|
|
|
|
|
|
157
|
12
|
|
|
|
|
36
|
foreach ( @_ ) { |
|
158
|
2
|
100
|
|
|
|
10
|
if( $_ eq "-named_constructors" ) { |
|
159
|
1
|
|
|
|
|
3
|
$default_opts{named_constructor} = 1; |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
else { |
|
162
|
1
|
|
|
|
|
4
|
$syms{$_}++; |
|
163
|
|
|
|
|
|
|
} |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
12
|
100
|
|
|
|
56
|
keys %syms or $syms{struct}++; |
|
167
|
|
|
|
|
|
|
|
|
168
|
12
|
|
|
|
|
20
|
my %export; |
|
169
|
|
|
|
|
|
|
|
|
170
|
12
|
100
|
|
|
|
37
|
if( delete $syms{struct} ) { |
|
171
|
|
|
|
|
|
|
$export{struct} = sub { |
|
172
|
14
|
|
|
14
|
|
1446917
|
my ( $name, $fields, @opts ) = @_; |
|
173
|
14
|
|
|
|
|
105
|
_struct( $name, $fields, scalar caller, lvalue => 1, %default_opts, @opts ); |
|
174
|
11
|
|
|
|
|
60
|
}; |
|
175
|
|
|
|
|
|
|
} |
|
176
|
12
|
100
|
|
|
|
36
|
if( delete $syms{readonly_struct} ) { |
|
177
|
|
|
|
|
|
|
$export{readonly_struct} = sub { |
|
178
|
1
|
|
|
1
|
|
189824
|
my ( $name, $fields, @opts ) = @_; |
|
179
|
1
|
|
|
|
|
7
|
_struct( $name, $fields, scalar caller, lvalue => 0, %default_opts, @opts ); |
|
180
|
1
|
|
|
|
|
5
|
}; |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
12
|
50
|
|
|
|
35
|
if( keys %syms ) { |
|
184
|
0
|
|
|
|
|
0
|
croak "Unrecognised export symbols " . join( ", ", keys %syms ); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
10
|
|
|
10
|
|
99
|
no strict 'refs'; |
|
|
10
|
|
|
|
|
22
|
|
|
|
10
|
|
|
|
|
5131
|
|
|
188
|
12
|
|
|
|
|
70
|
*{"${caller}::$_"} = $export{$_} for keys %export; |
|
|
12
|
|
|
|
|
18656
|
|
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head1 FUNCTIONS |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=cut |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
my %_STRUCT_PACKAGES; |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub _struct |
|
198
|
|
|
|
|
|
|
{ |
|
199
|
15
|
|
|
15
|
|
71
|
my ( $name, $_fields, $caller, %opts ) = @_; |
|
200
|
|
|
|
|
|
|
|
|
201
|
15
|
|
|
|
|
48
|
my $lvalue = !!$opts{lvalue}; |
|
202
|
15
|
|
|
|
|
38
|
my $named = !!$opts{named_constructor}; |
|
203
|
|
|
|
|
|
|
|
|
204
|
15
|
|
|
|
|
42
|
my $pkg = "${caller}::$name"; |
|
205
|
|
|
|
|
|
|
|
|
206
|
15
|
|
|
|
|
45
|
my @fields = @$_fields; |
|
207
|
|
|
|
|
|
|
|
|
208
|
15
|
|
|
|
|
27
|
my %optional; |
|
209
|
15
|
|
66
|
|
|
111
|
s/^\?// and $optional{$_}++ for @fields; |
|
210
|
|
|
|
|
|
|
|
|
211
|
15
|
|
|
|
|
32
|
my %subs; |
|
212
|
15
|
|
|
0
|
|
90
|
$subs{DESTROY} = sub {}; |
|
213
|
|
|
|
|
|
|
$subs{AUTOLOAD} = sub :lvalue { |
|
214
|
2
|
|
|
2
|
|
419
|
my ( $field ) = our $AUTOLOAD =~ m/::([^:]+)$/; |
|
215
|
2
|
|
|
|
|
289
|
croak "$pkg does not have a '$field' field"; |
|
216
|
0
|
|
|
|
|
0
|
my $dummy; ## croak can't be last because it isn't lvalue, so this line is required |
|
217
|
15
|
|
|
|
|
79
|
}; |
|
218
|
|
|
|
|
|
|
|
|
219
|
15
|
|
|
|
|
33
|
my $constructor; |
|
220
|
|
|
|
|
|
|
|
|
221
|
15
|
|
|
|
|
25
|
if( HAVE_FEATURE_CLASS ) { |
|
222
|
15
|
|
|
|
|
89
|
_build_class_for_feature_class( $pkg, \@fields, \%optional, $named, $lvalue, \$constructor ); |
|
223
|
|
|
|
|
|
|
} |
|
224
|
|
|
|
|
|
|
else { |
|
225
|
|
|
|
|
|
|
_build_class_for_classical( $pkg, \@fields, \%optional, $named, $lvalue, \$constructor ); |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
|
|
228
|
10
|
|
|
10
|
|
85
|
no strict 'refs'; |
|
|
10
|
|
|
|
|
28
|
|
|
|
10
|
|
|
|
|
8023
|
|
|
229
|
15
|
|
|
|
|
97
|
*{"${pkg}::$_"} = $subs{$_} for keys %subs; |
|
|
30
|
|
|
|
|
228
|
|
|
230
|
15
|
|
|
|
|
32
|
*{"${caller}::$name"} = $constructor; |
|
|
15
|
|
|
|
|
59
|
|
|
231
|
|
|
|
|
|
|
|
|
232
|
15
|
100
|
|
|
|
60
|
if( my $predicate = $opts{predicate} ) { |
|
233
|
1
|
|
50
|
2
|
|
6
|
*{"${caller}::$predicate"} = sub { ( ref($_[0]) || "" ) eq $pkg }; |
|
|
1
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
24
|
|
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
|
|
236
|
15
|
|
|
|
|
76
|
*{"${pkg}::_forbid_arrayification"} = sub { |
|
237
|
1
|
|
|
1
|
|
3
|
return if !HAVE_OVERLOADING and caller eq __PACKAGE__; |
|
238
|
1
|
|
|
|
|
123
|
croak "Cannot use $pkg as an ARRAY reference" |
|
239
|
15
|
|
|
|
|
78
|
}; |
|
240
|
|
|
|
|
|
|
|
|
241
|
15
|
|
|
|
|
97
|
require overload; |
|
242
|
|
|
|
|
|
|
$pkg->overload::OVERLOAD( |
|
243
|
1
|
|
|
1
|
|
7
|
'@{}' => sub { $_[0]->_forbid_arrayification; return $_[0] }, |
|
|
0
|
|
|
|
|
0
|
|
|
244
|
1
|
|
|
1
|
|
205
|
'0+' => sub { refaddr $_[0] }, |
|
245
|
1
|
|
|
1
|
|
12
|
'""' => sub { sprintf "%s=Struct::Dumb(%#x)", $pkg, refaddr $_[0] }, |
|
246
|
1
|
|
|
1
|
|
3
|
'bool' => sub { 1 }, |
|
247
|
15
|
|
|
|
|
285
|
fallback => 1, |
|
248
|
|
|
|
|
|
|
); |
|
249
|
|
|
|
|
|
|
|
|
250
|
15
|
|
|
|
|
1379
|
$_STRUCT_PACKAGES{$pkg} = { |
|
251
|
|
|
|
|
|
|
named => $named, |
|
252
|
|
|
|
|
|
|
fields => \@fields, |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub _build_class_for_classical |
|
257
|
|
|
|
|
|
|
{ |
|
258
|
0
|
|
|
0
|
|
0
|
my ( $pkg, $fields, $optional, $named, $lvalue, $constructorvar ) = @_; |
|
259
|
0
|
|
|
|
|
0
|
my @fields = @$fields; |
|
260
|
|
|
|
|
|
|
|
|
261
|
0
|
0
|
|
|
|
0
|
if( $named ) { |
|
262
|
|
|
|
|
|
|
$$constructorvar = sub { |
|
263
|
0
|
|
|
0
|
|
0
|
my %values = @_; |
|
264
|
0
|
|
|
|
|
0
|
my @values; |
|
265
|
0
|
|
|
|
|
0
|
foreach ( @fields ) { |
|
266
|
0
|
0
|
0
|
|
|
0
|
exists $values{$_} or $optional->{$_} or |
|
267
|
|
|
|
|
|
|
croak "usage: $pkg requires '$_'"; |
|
268
|
0
|
|
|
|
|
0
|
push @values, delete $values{$_}; |
|
269
|
|
|
|
|
|
|
} |
|
270
|
0
|
0
|
|
|
|
0
|
if( my ( $extrakey ) = keys %values ) { |
|
271
|
0
|
|
|
|
|
0
|
croak "usage: $pkg does not recognise '$extrakey'"; |
|
272
|
|
|
|
|
|
|
} |
|
273
|
0
|
|
|
|
|
0
|
bless \@values, $pkg; |
|
274
|
0
|
|
|
|
|
0
|
}; |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
else { |
|
277
|
0
|
|
|
|
|
0
|
my $fieldcount = @fields; |
|
278
|
0
|
|
|
|
|
0
|
my $argnames = join ", ", map "\$$_", @fields; |
|
279
|
|
|
|
|
|
|
$$constructorvar = sub { |
|
280
|
0
|
0
|
|
0
|
|
0
|
@_ == $fieldcount or croak "usage: $pkg($argnames)"; |
|
281
|
0
|
|
|
|
|
0
|
bless [ @_ ], $pkg; |
|
282
|
0
|
|
|
|
|
0
|
}; |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
|
|
285
|
0
|
|
|
|
|
0
|
my %subs; |
|
286
|
0
|
|
|
|
|
0
|
foreach ( 0 .. $#fields ) { |
|
287
|
0
|
|
|
|
|
0
|
my $idx = $_; |
|
288
|
0
|
|
|
|
|
0
|
my $field = $fields[$idx]; |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
BEGIN { |
|
291
|
10
|
|
|
10
|
|
2157
|
overloading->unimport if HAVE_OVERLOADING; |
|
292
|
|
|
|
|
|
|
} |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
$subs{$field} = $lvalue |
|
295
|
0
|
0
|
|
0
|
|
0
|
? sub :lvalue { @_ > 1 and croak "$pkg->$field invoked with arguments"; |
|
296
|
0
|
|
|
|
|
0
|
shift->[$idx] } |
|
297
|
0
|
0
|
|
0
|
|
0
|
: sub { @_ > 1 and croak "$pkg->$field invoked with arguments"; |
|
298
|
0
|
0
|
|
|
|
0
|
shift->[$idx] }; |
|
|
0
|
|
|
|
|
0
|
|
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
|
|
301
|
10
|
|
|
10
|
|
132
|
no strict 'refs'; |
|
|
10
|
|
|
|
|
35
|
|
|
|
10
|
|
|
|
|
18348
|
|
|
302
|
0
|
|
|
|
|
0
|
*{"${pkg}::ISA"} = [ 'Struct::Dumb::Struct' ]; |
|
|
0
|
|
|
|
|
0
|
|
|
303
|
0
|
|
|
|
|
0
|
*{"${pkg}::$_"} = $subs{$_} for keys %subs; |
|
|
0
|
|
|
|
|
0
|
|
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
sub _build_class_for_feature_class |
|
307
|
|
|
|
|
|
|
{ |
|
308
|
15
|
|
|
15
|
|
89
|
my ( $pkg, $fields, $optional, $named, $lvalue, $constructorvar ) = @_; |
|
309
|
15
|
|
|
|
|
43
|
my @fields = @$fields; |
|
310
|
15
|
|
|
|
|
39
|
my %optional = %$optional; |
|
311
|
|
|
|
|
|
|
|
|
312
|
15
|
100
|
|
|
|
52
|
if( $named ) { |
|
313
|
5
|
|
|
|
|
14
|
my %fieldnames = map { $_ => 1 } @fields; |
|
|
12
|
|
|
|
|
57
|
|
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
$$constructorvar = sub { |
|
316
|
8
|
|
|
8
|
|
1967
|
my %values = @_; |
|
317
|
8
|
|
|
|
|
22
|
foreach ( @fields ) { |
|
318
|
21
|
100
|
100
|
|
|
318
|
exists $values{$_} or $optional{$_} or |
|
319
|
|
|
|
|
|
|
croak "usage: $pkg requires '$_'"; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
7
|
|
66
|
|
|
215
|
$fieldnames{$_} or croak "usage: $pkg does not recognise '$_'" for keys %values; |
|
322
|
6
|
|
|
|
|
279
|
return $pkg->new( %values ); |
|
323
|
5
|
|
|
|
|
51
|
}; |
|
324
|
|
|
|
|
|
|
} |
|
325
|
|
|
|
|
|
|
else { |
|
326
|
10
|
|
|
|
|
21
|
my $fieldcount = @fields; |
|
327
|
10
|
|
|
|
|
76
|
my $argnames = join ", ", map "\$$_", @fields; |
|
328
|
|
|
|
|
|
|
$$constructorvar = sub { |
|
329
|
14
|
100
|
|
14
|
|
1469
|
@_ == $fieldcount or croak "usage: $pkg($argnames)"; |
|
330
|
13
|
|
|
|
|
20
|
my %values; @values{@fields} = @_; |
|
|
13
|
|
|
|
|
53
|
|
|
331
|
13
|
|
|
|
|
632
|
return $pkg->new( %values ); |
|
332
|
10
|
|
|
|
|
65
|
}; |
|
333
|
|
|
|
|
|
|
} |
|
334
|
|
|
|
|
|
|
|
|
335
|
15
|
100
|
|
|
|
99
|
$lvalue = $lvalue ? " :lvalue" : ""; |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
my @fieldcode = map { |
|
338
|
15
|
|
|
|
|
41
|
my $name = $_; |
|
|
33
|
|
|
|
|
66
|
|
|
339
|
33
|
50
|
|
|
|
159
|
die "Field names are not permitted to contain ( or ) chars" if $name =~ m/[()]/; |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# Mangle the field name until it has only valid identifiers |
|
342
|
|
|
|
|
|
|
# TODO: If someone tries to name a field foo_xDDbar then it might |
|
343
|
|
|
|
|
|
|
# conflict with a mangled one. |
|
344
|
33
|
|
|
|
|
126
|
my $var = '$' . $name =~ s/^(\P{ID_Start})/sprintf "_x%X", ord $1/er |
|
|
4
|
|
|
|
|
46
|
|
|
345
|
3
|
|
|
|
|
19
|
=~ s/(\P{ID_Continue})/sprintf "_x%X", ord $1/ger; |
|
346
|
|
|
|
|
|
|
|
|
347
|
33
|
100
|
|
|
|
147
|
if( $var eq '$'.$name ) { |
|
348
|
27
|
|
|
|
|
151
|
" field $var :param = undef;", |
|
349
|
|
|
|
|
|
|
" method $name$lvalue { \@_ and croak \"$pkg->$name invoked with arguments\"; $var }", |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
else { |
|
352
|
6
|
|
|
|
|
17
|
my $qname = quotemeta $name; |
|
353
|
6
|
|
|
|
|
16
|
my $qqname = $name =~ s/"/\\"/gr; |
|
354
|
6
|
|
|
|
|
63
|
" field $var :param($name) = undef;", |
|
355
|
|
|
|
|
|
|
" { no strict 'refs'; *{\"${pkg}::${qname}\"} = method$lvalue { \@_ and croak \"$pkg->$qqname invoked with argument\"; $var } }", |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
} @$fields; |
|
358
|
|
|
|
|
|
|
|
|
359
|
15
|
|
|
|
|
97
|
my $code = join( "\n", |
|
360
|
|
|
|
|
|
|
"use experimental 'class';", |
|
361
|
|
|
|
|
|
|
"class $pkg :isa(Struct::Dumb::Struct) {", |
|
362
|
|
|
|
|
|
|
" use Carp;", |
|
363
|
|
|
|
|
|
|
@fieldcode, |
|
364
|
|
|
|
|
|
|
"}", "" ); |
|
365
|
|
|
|
|
|
|
|
|
366
|
15
|
50
|
|
8
|
|
1719
|
unless( eval "$code; 1" ) { |
|
|
8
|
|
|
8
|
|
65
|
|
|
|
8
|
|
|
|
|
16
|
|
|
|
8
|
|
|
|
|
63
|
|
|
|
8
|
|
|
|
|
2582
|
|
|
|
8
|
|
|
|
|
15
|
|
|
|
8
|
|
|
|
|
2031
|
|
|
367
|
0
|
|
|
|
|
0
|
my $e = $@; |
|
368
|
0
|
|
|
|
|
0
|
$e =~ s/at \(eval \d+\) line \d+/"at eval() in ".__FILE__." line ".__LINE__/eg; |
|
|
0
|
|
|
|
|
0
|
|
|
369
|
0
|
|
|
|
|
0
|
die $e; |
|
370
|
|
|
|
|
|
|
} |
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=head2 struct |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
struct $name => [ @fieldnames ], |
|
376
|
|
|
|
|
|
|
named_constructor => (1|0), |
|
377
|
|
|
|
|
|
|
predicate => "is_$name"; |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
Creates a new structure type. This exports a new function of the type's name |
|
380
|
|
|
|
|
|
|
into the caller's namespace. Invoking this function returns a new instance of |
|
381
|
|
|
|
|
|
|
a type that implements those field names, as accessors and mutators for the |
|
382
|
|
|
|
|
|
|
fields. |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
Takes the following options: |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=over 4 |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=item named_constructor => BOOL |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
Determines whether the structure will take positional or named arguments. |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=item predicate => STR |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
If defined, gives the name of a second function to export to the caller's |
|
395
|
|
|
|
|
|
|
namespace. This function will be a type test predicate; that is, a function |
|
396
|
|
|
|
|
|
|
that takes a single argmuent, and returns true if-and-only-if that argument is |
|
397
|
|
|
|
|
|
|
an instance of this structure type. |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=back |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=cut |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=head2 readonly_struct |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
readonly_struct $name => [ @fieldnames ], |
|
406
|
|
|
|
|
|
|
... |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Similar to L, but instances of this type are immutable once |
|
409
|
|
|
|
|
|
|
constructed. The field accessor methods will not be marked with the |
|
410
|
|
|
|
|
|
|
C<:lvalue> attribute. |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
Takes the same options as L. |
|
413
|
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=cut |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=head2 dumper_info |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
my $info = Struct::Dumb::dumper_info( $struct ); |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
I |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
This function takes any instance of a Struct::Dumb-style struct and returns a |
|
423
|
|
|
|
|
|
|
hashref with the keys C, C, and C. "named" is a |
|
424
|
|
|
|
|
|
|
boolean, which is true if the struct uses a named constructor. "fields" is an |
|
425
|
|
|
|
|
|
|
arrayref giving the names of the struct's fields. "values" is an arrayref |
|
426
|
|
|
|
|
|
|
giving the values for those fields in corresponding order. |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
This is just meant for tools like pretty printers to be able to inspect |
|
429
|
|
|
|
|
|
|
structures. We can't stop you from using it for nefarious purposes, but please |
|
430
|
|
|
|
|
|
|
don't. |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
If the given C<$struct> is not an instance of a known class, an exception will |
|
433
|
|
|
|
|
|
|
be raised. |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
This function is not exported. |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=cut |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
sub dumper_info |
|
440
|
|
|
|
|
|
|
{ |
|
441
|
4
|
|
|
4
|
1
|
9
|
my ( $struct ) = @_; |
|
442
|
|
|
|
|
|
|
|
|
443
|
4
|
|
|
|
|
10
|
my $class = blessed $struct; |
|
444
|
|
|
|
|
|
|
|
|
445
|
4
|
50
|
33
|
|
|
44
|
unless( defined $class and $struct->isa( 'Struct::Dumb::Struct' ) ) { |
|
446
|
0
|
|
|
|
|
0
|
croak "Struct::Dumb::dumper_info invoked with non-struct argument: $struct"; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# Perl drops the leading `main::` in `main::StructName` so if there's no |
|
450
|
|
|
|
|
|
|
# `::` we'll have to put it back |
|
451
|
4
|
100
|
|
|
|
18
|
$class =~ m/::/ or $class = "main::$class"; |
|
452
|
4
|
|
|
|
|
10
|
my $meta = $_STRUCT_PACKAGES{$class}; |
|
453
|
|
|
|
|
|
|
|
|
454
|
4
|
50
|
|
|
|
24
|
unless( $meta ) { |
|
455
|
0
|
|
|
|
|
0
|
confess "Struct::Dumb::dumper_info encountered unknown struct class $class (this should not happen!)"; |
|
456
|
|
|
|
|
|
|
} |
|
457
|
|
|
|
|
|
|
|
|
458
|
4
|
|
|
|
|
9
|
my $fields = $meta->{fields}; |
|
459
|
|
|
|
|
|
|
return { |
|
460
|
|
|
|
|
|
|
named => !!$meta->{named}, |
|
461
|
|
|
|
|
|
|
fields => [ @$fields ], |
|
462
|
4
|
|
|
|
|
622
|
values => [ map {; scalar $struct->$_ } @$fields ], |
|
|
8
|
|
|
|
|
208
|
|
|
463
|
|
|
|
|
|
|
}; |
|
464
|
|
|
|
|
|
|
} |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=head1 DATA::DUMP FILTER |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
I |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
If L is loaded, an extra filter is applied so that struct |
|
471
|
|
|
|
|
|
|
instances are printed in a format matching that which would construct them. |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
struct Colour => [qw( red green blue )]; |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
use Data::Dump; |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
my %hash = ( col => Colour( 0.8, 0.5, 0.2 ) ); |
|
478
|
|
|
|
|
|
|
Data::Dump::dd \%hash; |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# prints {col => main::Colour(0.8, 0.5, 0.2)} |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=head1 NOTES |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=head2 Allowing ARRAY dereference |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
The way that forbidding access to instances as if they were ARRAY references |
|
487
|
|
|
|
|
|
|
is currently implemented uses an internal method on the generated structure |
|
488
|
|
|
|
|
|
|
class called C<_forbid_arrayification>. If special circumstances require that |
|
489
|
|
|
|
|
|
|
this exception mechanism be bypassed, the method can be overloaded with an |
|
490
|
|
|
|
|
|
|
empty C body, allowing the struct instances in that class to be |
|
491
|
|
|
|
|
|
|
accessed like normal ARRAY references. For good practice this should be |
|
492
|
|
|
|
|
|
|
limited by a C override. |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
For example, L needs to access the instances as plain ARRAY |
|
495
|
|
|
|
|
|
|
references so it can walk the data structure looking for reference cycles. |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
use Devel::Cycle; |
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
{ |
|
500
|
|
|
|
|
|
|
no warnings 'redefine'; |
|
501
|
|
|
|
|
|
|
local *Point::_forbid_arrayification = sub {}; |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
memory_cycle_ok( $point ); |
|
504
|
|
|
|
|
|
|
} |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head1 TODO |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=over 4 |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=item * |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
Consider adding an C option, giving name of another function to |
|
513
|
|
|
|
|
|
|
convert structs to key/value pairs, or a HASH ref. |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=back |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=head1 AUTHOR |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
Paul Evans |
|
520
|
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=cut |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
sub maybe_apply_datadump_filter |
|
524
|
|
|
|
|
|
|
{ |
|
525
|
2
|
50
|
|
2
|
0
|
15
|
return unless $INC{"Data/Dump.pm"}; |
|
526
|
|
|
|
|
|
|
|
|
527
|
2
|
|
|
|
|
1362
|
require Data::Dump::Filtered; |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
Data::Dump::Filtered::add_dump_filter( sub { |
|
530
|
9
|
|
|
9
|
|
3055
|
my ( $ctx, $obj ) = @_; |
|
531
|
9
|
100
|
|
|
|
20
|
return undef unless $_STRUCT_PACKAGES{ $ctx->class }; |
|
532
|
|
|
|
|
|
|
|
|
533
|
3
|
|
|
|
|
40
|
my $dump = dumper_info( $obj ); |
|
534
|
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
return { |
|
536
|
|
|
|
|
|
|
dump => sprintf "%s(%s)", $ctx->class, |
|
537
|
|
|
|
|
|
|
join ", ", map { |
|
538
|
|
|
|
|
|
|
( $dump->{named} ? "$dump->{fields}[$_] => " : "" ) . |
|
539
|
6
|
100
|
|
|
|
277
|
Data::Dump::dump($dump->{values}[$_]), |
|
540
|
3
|
|
|
|
|
14
|
} 0 .. $#{$dump->{fields}}, |
|
|
3
|
|
|
|
|
23
|
|
|
541
|
|
|
|
|
|
|
}; |
|
542
|
2
|
|
|
|
|
1076
|
}); |
|
543
|
|
|
|
|
|
|
} |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
if( defined &Data::Dump::dump ) { |
|
546
|
|
|
|
|
|
|
maybe_apply_datadump_filter; |
|
547
|
|
|
|
|
|
|
} |
|
548
|
|
|
|
|
|
|
else { |
|
549
|
|
|
|
|
|
|
# A package var we observe that Data/Dump.pm seems to set when loaded |
|
550
|
|
|
|
|
|
|
# We can't attach to VERSION because too many other things get upset by |
|
551
|
|
|
|
|
|
|
# that. |
|
552
|
|
|
|
|
|
|
$Data::Dump::DEBUG = bless \( my $x = \&maybe_apply_datadump_filter ), |
|
553
|
|
|
|
|
|
|
"Struct::Dumb::_DestroyWatch"; |
|
554
|
|
|
|
|
|
|
} |
|
555
|
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
{ |
|
557
|
|
|
|
|
|
|
package Struct::Dumb::_DestroyWatch; |
|
558
|
|
|
|
|
|
|
my $GD = 0; |
|
559
|
9
|
|
|
9
|
|
261728
|
END { $GD = 1 } |
|
560
|
1
|
50
|
|
1
|
|
8547
|
sub DESTROY { ${$_[0]}->() unless $GD; } |
|
|
1
|
|
|
|
|
13
|
|
|
561
|
|
|
|
|
|
|
} |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
0x55AA; |