line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Parse::Binary; |
2
|
|
|
|
|
|
|
$Parse::Binary::VERSION = '0.11'; |
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
24466
|
use 5.005; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
44
|
|
5
|
1
|
|
|
1
|
|
1779
|
use bytes; |
|
1
|
|
|
|
|
14
|
|
|
1
|
|
|
|
|
7
|
|
6
|
1
|
|
|
1
|
|
34
|
use strict; |
|
1
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
38
|
|
7
|
1
|
|
|
1
|
|
1474
|
use integer; |
|
1
|
|
|
|
|
10
|
|
|
1
|
|
|
|
|
5
|
|
8
|
1
|
|
|
1
|
|
910
|
use Parse::Binary::FixedFormat; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
55
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Parse::Binary - Unpack binary data structures into object hierarchies |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 VERSION |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
This document describes version 0.11 of Parse::Binary, released |
17
|
|
|
|
|
|
|
January 25, 2009. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 SYNOPSIS |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# This class represents a Win32 F<.ico> file: |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
package IconFile; |
24
|
|
|
|
|
|
|
use base 'Parse::Binary'; |
25
|
|
|
|
|
|
|
use constant FORMAT => ( |
26
|
|
|
|
|
|
|
Magic => 'a2', |
27
|
|
|
|
|
|
|
Type => 'v', |
28
|
|
|
|
|
|
|
Count => 'v', |
29
|
|
|
|
|
|
|
'Icon' => [ 'a16', '{$Count}', 1 ], |
30
|
|
|
|
|
|
|
Data => 'a*', |
31
|
|
|
|
|
|
|
); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# An individual icon resource: |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
package Icon; |
36
|
|
|
|
|
|
|
use base 'Parse::Binary'; |
37
|
|
|
|
|
|
|
use constant FORMAT => ( |
38
|
|
|
|
|
|
|
Width => 'C', |
39
|
|
|
|
|
|
|
Height => 'C', |
40
|
|
|
|
|
|
|
ColorCount => 'C', |
41
|
|
|
|
|
|
|
Reserved => 'C', |
42
|
|
|
|
|
|
|
Planes => 'v', |
43
|
|
|
|
|
|
|
BitCount => 'v', |
44
|
|
|
|
|
|
|
ImageSize => 'V', |
45
|
|
|
|
|
|
|
ImageOffset => 'v', |
46
|
|
|
|
|
|
|
); |
47
|
|
|
|
|
|
|
sub Data { |
48
|
|
|
|
|
|
|
my ($self) = @_; |
49
|
|
|
|
|
|
|
return $self->parent->substr($self->ImageOffset, $self->ImageSize); |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# Simple F<.ico> file dumper that uses them: |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
use IconFile; |
55
|
|
|
|
|
|
|
my $icon_file = IconFile->new('input.ico'); |
56
|
|
|
|
|
|
|
foreach my $icon ($icon_file->members) { |
57
|
|
|
|
|
|
|
print "Dimension: ", $icon->Width, "x", $icon->Height, $/; |
58
|
|
|
|
|
|
|
print "Colors: ", 2 ** $icon->BitCount, $/; |
59
|
|
|
|
|
|
|
print "Image Size: ", $icon->ImageSize, " bytes", $/; |
60
|
|
|
|
|
|
|
print "Actual Size: ", length($icon->Data), " bytes", $/, $/; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
$icon_file->write('output.ico'); # save as another .ico file |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 DESCRIPTION |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
This module makes parsing binary data structures much easier, by serving |
67
|
|
|
|
|
|
|
as a base class for classes that represents the binary data, which may |
68
|
|
|
|
|
|
|
contain objects of other classes to represent parts of itself. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Documentation is unfortunately a bit lacking at this moment. Please read |
71
|
|
|
|
|
|
|
the tests and source code of L and L for examples |
72
|
|
|
|
|
|
|
of using this module. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=cut |
75
|
|
|
|
|
|
|
|
76
|
1
|
|
|
|
|
112
|
use constant PROPERTIES => qw( |
77
|
|
|
|
|
|
|
%struct $filename $size $parent @siblings %children |
78
|
|
|
|
|
|
|
$output $lazy $iterator $iterated |
79
|
1
|
|
|
1
|
|
7
|
); |
|
1
|
|
|
|
|
2
|
|
80
|
1
|
|
|
1
|
|
7
|
use constant ENCODED_FIELDS => ( 'Data' ); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
128
|
|
81
|
1
|
|
|
1
|
|
6
|
use constant FORMAT => ( Data => 'a*' ); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
56
|
|
82
|
1
|
|
|
1
|
|
13
|
use constant SUBFORMAT => (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
44
|
|
83
|
1
|
|
|
1
|
|
5
|
use constant DEFAULT_ARGS => (); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
56
|
|
84
|
1
|
|
|
1
|
|
5
|
use constant DELEGATE_SUBS => (); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
49
|
|
85
|
1
|
|
|
1
|
|
5
|
use constant DISPATCH_TABLE => (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
57
|
|
86
|
|
|
|
|
|
|
|
87
|
1
|
|
|
1
|
|
6
|
use constant DISPATCH_FIELD => undef; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
48
|
|
88
|
1
|
|
|
1
|
|
13
|
use constant BASE_CLASS => undef; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
42
|
|
89
|
1
|
|
|
1
|
|
5
|
use constant ENCODING => undef; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
48
|
|
90
|
1
|
|
|
1
|
|
5
|
use constant PADDING => undef; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
221
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
unless (eval { require Scalar::Util; 1 }) { |
93
|
|
|
|
|
|
|
*Scalar::Util::weaken = sub { 1 }; |
94
|
|
|
|
|
|
|
*Scalar::Util::blessed = sub { UNIVERSAL::can($_[0], 'can') }; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
### Constructors ### |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub new { |
100
|
0
|
|
|
0
|
0
|
|
my ($self, $input, $attr) = @_; |
101
|
|
|
|
|
|
|
|
102
|
1
|
|
|
1
|
|
6
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
229
|
|
103
|
0
|
|
|
|
|
|
my $class = $self->class; |
104
|
0
|
0
|
|
|
|
|
$class->init unless ${"$class\::init_done"}; |
|
0
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
|
106
|
0
|
|
0
|
|
|
|
$attr ||= {}; |
107
|
0
|
0
|
0
|
|
|
|
$attr->{filename} ||= $input unless ref $input; |
108
|
|
|
|
|
|
|
|
109
|
0
|
|
|
|
|
|
my $obj = $class->spawn; |
110
|
0
|
|
|
|
|
|
%$obj = (%$obj, %$attr); |
111
|
|
|
|
|
|
|
|
112
|
0
|
|
|
|
|
|
my $data = $obj->read_data($input); |
113
|
0
|
|
|
|
|
|
$obj->load($data, $attr); |
114
|
|
|
|
|
|
|
|
115
|
0
|
0
|
|
|
|
|
if ($obj->{lazy}) { |
|
|
0
|
|
|
|
|
|
116
|
0
|
|
|
|
|
|
$obj->{lazy} = $obj; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
elsif (!$obj->{iterator}) { |
119
|
0
|
|
|
|
|
|
$obj->make_members; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
|
return $obj; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub dispatch_field { |
126
|
0
|
|
|
0
|
0
|
|
return undef; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
1
|
|
|
1
|
|
6
|
use vars qw(%HasMembers %DefaultArgs); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
82
|
|
130
|
1
|
|
|
1
|
|
6
|
use vars qw(%Fields %MemberFields %MemberClass %Packer %Parser %FieldPackFormat); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
160
|
|
131
|
1
|
|
|
1
|
|
8
|
use vars qw(%DispatchField %DispatchTable); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
136
|
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub init { |
134
|
1
|
|
|
1
|
|
7
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
152
|
|
135
|
0
|
0
|
|
0
|
0
|
|
return if ${"$_[0]\::init_done"}; |
|
0
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
|
137
|
0
|
|
|
|
|
|
my $class = shift; |
138
|
|
|
|
|
|
|
|
139
|
0
|
0
|
|
0
|
|
|
*{"$class\::class"} = sub { ref($_[0]) || $_[0] }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
140
|
0
|
|
|
|
|
|
*{"$class\::is_type"} = \&is_type; |
|
0
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
|
142
|
0
|
|
|
|
|
|
foreach my $item ($class->PROPERTIES) { |
143
|
1
|
|
|
1
|
|
6
|
no strict 'refs'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
6661
|
|
144
|
0
|
|
|
|
|
|
my ($sigil, $name) = split(//, $item, 2); |
145
|
0
|
|
|
|
|
|
*{"$class\::$name"} = |
146
|
0
|
|
|
0
|
|
|
($sigil eq '$') ? sub { $_[0]{$name} } : |
147
|
0
|
0
|
0
|
0
|
|
|
($sigil eq '@') ? sub { wantarray ? @{$_[0]{$name}||=[]} : ($_[0]{$name}||=[]) } : |
|
0
|
|
0
|
|
|
|
|
148
|
0
|
|
0
|
0
|
|
|
($sigil eq '%') ? sub { $_[0]{$name}||={} } : |
149
|
0
|
0
|
|
|
|
|
die "Unknown sigil: $sigil"; |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
150
|
0
|
|
|
|
|
|
*{"$class\::set_$name"} = |
151
|
0
|
|
|
0
|
|
|
($sigil eq '$') ? sub { $_[0]->{$name} = $_[1] } : |
152
|
0
|
0
|
0
|
0
|
|
|
($sigil eq '@') ? sub { @{$_[0]->{$name}||=$_[1]||[]} = @{$_[1]||[]} } : |
|
0
|
|
0
|
|
|
|
|
|
0
|
|
|
|
|
|
|
153
|
0
|
0
|
0
|
0
|
|
|
($sigil eq '%') ? sub { %{$_[0]->{$name}||=$_[1]||{}} = %{$_[1]||{}} } : |
|
0
|
|
0
|
|
|
|
|
|
0
|
|
|
|
|
|
|
154
|
0
|
0
|
|
|
|
|
die "Unknown sigil: $sigil"; |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
|
my @args = $class->default_args; |
158
|
0
|
|
|
|
|
|
*{"$class\::default_args"} = \@args; |
|
0
|
|
|
|
|
|
|
159
|
0
|
|
|
0
|
|
|
*{"$class\::default_args"} = sub { @args }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
|
my $delegate_subs = $class->delegate_subs; |
161
|
0
|
0
|
|
|
|
|
if (defined(&{"$class\::DELEGATE_SUBS"})) { |
|
0
|
|
|
|
|
|
|
162
|
0
|
|
|
|
|
|
$delegate_subs = { $class->DELEGATE_SUBS }; |
163
|
|
|
|
|
|
|
} |
164
|
0
|
|
|
0
|
|
|
*{"$class\::delegate_subs"} = sub { $delegate_subs }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
165
|
0
|
|
|
|
|
|
while (my ($subclass, $methods) = each %$delegate_subs) { |
166
|
0
|
0
|
|
|
|
|
$methods = [ $methods ] unless ref $methods; |
167
|
0
|
|
|
|
|
|
foreach my $method (grep length, @$methods) { |
168
|
0
|
|
|
|
|
|
*{"$class\::$method"} = sub { |
169
|
0
|
|
|
0
|
|
|
goto &{$_[0]->require_class($subclass)->can($method)}; |
|
0
|
|
|
|
|
|
|
170
|
0
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
0
|
|
|
|
|
|
my $dispatch_table = $class->dispatch_table; |
174
|
0
|
0
|
|
|
|
|
if (defined(&{"$class\::DISPATCH_TABLE"})) { |
|
0
|
|
|
|
|
|
|
175
|
0
|
|
|
|
|
|
$dispatch_table = { $class->DISPATCH_TABLE }; |
176
|
|
|
|
|
|
|
} |
177
|
0
|
|
|
|
|
|
$DispatchTable{$class} = $dispatch_table; |
178
|
0
|
|
|
0
|
|
|
*{"$class\::dispatch_table"} = sub { $dispatch_table }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
|
my $dispatch_field = undef; |
181
|
0
|
0
|
|
|
|
|
if (defined(&{"$class\::DISPATCH_FIELD"})) { |
|
0
|
|
|
|
|
|
|
182
|
0
|
|
|
|
|
|
$dispatch_field = $class->DISPATCH_FIELD; |
183
|
|
|
|
|
|
|
} |
184
|
0
|
|
|
|
|
|
$DispatchField{$class} = $dispatch_field; |
185
|
0
|
|
|
0
|
|
|
*{"$class\::dispatch_field"} = sub { $dispatch_field }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
|
my @format = $class->format_list; |
188
|
0
|
0
|
|
|
|
|
if (my @subformat = $class->subformat_list) { |
189
|
0
|
|
|
|
|
|
my @new_format; |
190
|
0
|
|
|
|
|
|
while (my ($field, $format) = splice(@format, 0, 2)) { |
191
|
0
|
0
|
|
|
|
|
if ($field eq 'Data') { |
192
|
0
|
|
|
|
|
|
push @new_format, @subformat; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
else { |
195
|
0
|
|
|
|
|
|
push @new_format, ($field => $format); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
} |
198
|
0
|
|
|
|
|
|
@format = @new_format; |
199
|
|
|
|
|
|
|
} |
200
|
0
|
|
|
|
|
|
my @format_list = @format; |
201
|
0
|
|
|
0
|
|
|
*{"$class\::format_list"} = sub { @format_list }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
|
203
|
0
|
|
|
|
|
|
my (@fields, @formats, @pack_formats, $underscore_count); |
204
|
0
|
|
|
|
|
|
my (%field_format, %field_pack_format); |
205
|
0
|
|
|
|
|
|
my (%field_parser, %field_packer, %field_length); |
206
|
0
|
|
|
|
|
|
my (@member_fields, %member_class); |
207
|
0
|
|
|
|
|
|
while (my ($field, $format) = splice(@format, 0, 2)) { |
208
|
0
|
0
|
|
|
|
|
if ($field eq '_') { |
209
|
|
|
|
|
|
|
# "we don't care" fields |
210
|
0
|
|
|
|
|
|
$underscore_count++; |
211
|
0
|
|
|
|
|
|
$field = "_${underscore_count}_$class"; |
212
|
0
|
|
|
|
|
|
$field =~ s/:/_/g; |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
0
|
0
|
|
|
|
|
if (ref $format) { |
216
|
0
|
|
|
|
|
|
$member_class{$field} = $class->classname($field); |
217
|
0
|
|
|
|
|
|
$field =~ s/:/_/g; |
218
|
0
|
|
|
|
|
|
$member_class{$field} = $class->classname($field); |
219
|
0
|
|
|
|
|
|
$class->require($member_class{$field}); |
220
|
0
|
|
|
|
|
|
push @member_fields, $field; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
else { |
223
|
0
|
|
|
|
|
|
$format = [ $format ]; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
0
|
|
|
|
|
|
push @fields, $field; |
227
|
|
|
|
|
|
|
|
228
|
0
|
|
|
|
|
|
my $string = join(':', $field, @$format); |
229
|
0
|
|
|
|
|
|
$field_format{$field} = [ @$format ]; |
230
|
0
|
0
|
|
|
|
|
if (!grep /\{/, @$format) { |
231
|
0
|
|
|
|
|
|
$field_length{$field} = length(pack($format->[0], 0)); |
232
|
0
|
|
|
|
|
|
$field_parser{$field} = Parse::Binary::FixedFormat->new( [ $string ] ); |
233
|
|
|
|
|
|
|
} |
234
|
0
|
|
|
|
|
|
push @formats, $string; |
235
|
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
s/\s*X\s*//g for @$format; |
237
|
0
|
|
|
|
|
|
my $pack_string = join(':', $field, @$format); |
238
|
0
|
|
|
|
|
|
$field_pack_format{$field} = [ @$format ]; |
239
|
0
|
|
|
|
|
|
$field_packer{$field} = Parse::Binary::FixedFormat->new( [ $pack_string ] ); |
240
|
0
|
|
|
|
|
|
push @pack_formats, $pack_string; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
0
|
|
|
|
|
|
my $parser = $class->make_formatter(@formats); |
244
|
0
|
|
|
|
|
|
my $packer = $class->make_formatter(@pack_formats); |
245
|
|
|
|
|
|
|
|
246
|
0
|
|
|
|
|
|
$Packer{$class} = $packer; |
247
|
0
|
|
|
|
|
|
$Parser{$class} = $parser; |
248
|
0
|
|
|
|
|
|
$Fields{$class} = \@fields; |
249
|
0
|
0
|
|
|
|
|
$HasMembers{$class} = @member_fields ? 1 : 0; |
250
|
0
|
|
|
|
|
|
$DefaultArgs{$class} = \@args; |
251
|
0
|
|
|
|
|
|
$MemberClass{$class} = \%member_class; |
252
|
0
|
|
|
|
|
|
$MemberFields{$class} = \@member_fields; |
253
|
0
|
0
|
|
|
|
|
$FieldPackFormat{$class} = { map { ref($_) ? $_->[0] : $_ } %field_pack_format }; |
|
0
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
|
255
|
0
|
|
|
|
|
|
*{"$class\::fields"} = \@fields; |
|
0
|
|
|
|
|
|
|
256
|
0
|
|
|
|
|
|
*{"$class\::member_fields"} = \@member_fields; |
|
0
|
|
|
|
|
|
|
257
|
0
|
0
|
|
0
|
|
|
*{"$class\::has_members"} = @member_fields ? sub { 1 } : sub { 0 }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
258
|
0
|
|
|
0
|
|
|
*{"$class\::fields"} = sub { @fields }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
259
|
0
|
|
|
0
|
|
|
*{"$class\::formats"} = sub { @formats }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
260
|
0
|
|
|
0
|
|
|
*{"$class\::member_fields"} = sub { @member_fields }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
261
|
0
|
|
|
0
|
|
|
*{"$class\::member_class"} = sub { $member_class{$_[1]} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
262
|
0
|
|
|
0
|
|
|
*{"$class\::pack_formats"} = sub { @pack_formats }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
263
|
0
|
|
|
0
|
|
|
*{"$class\::field_format"} = sub { $field_format{$_[1]}[0] }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
264
|
0
|
|
|
0
|
|
|
*{"$class\::field_pack_format"} = sub { $field_pack_format{$_[1]}[0] }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
265
|
0
|
|
|
0
|
|
|
*{"$class\::field_length"} = sub { $field_length{$_[1]} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
|
267
|
0
|
|
|
0
|
|
|
*{"$class\::parser"} = sub { $parser }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
268
|
0
|
|
|
0
|
|
|
*{"$class\::packer"} = sub { $packer }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
269
|
0
|
|
|
|
|
|
*{"$class\::field_parser"} = sub { |
270
|
0
|
|
|
0
|
|
|
my ($self, $field) = @_; |
271
|
0
|
0
|
|
|
|
|
$field_parser{$field} || do { |
272
|
0
|
|
|
|
|
|
Parse::Binary::FixedFormat->new( [ |
273
|
|
|
|
|
|
|
$self->eval_format( |
274
|
|
|
|
|
|
|
$self->{struct}, |
275
|
0
|
|
|
|
|
|
join(':', $field, @{$field_format{$field}}), |
276
|
|
|
|
|
|
|
), |
277
|
|
|
|
|
|
|
] ); |
278
|
|
|
|
|
|
|
}; |
279
|
0
|
|
|
|
|
|
}; |
280
|
|
|
|
|
|
|
|
281
|
0
|
|
|
0
|
|
|
*{"$class\::field_packer"} = sub { $field_packer{$_[1]} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
282
|
0
|
|
|
0
|
|
|
*{"$class\::has_field"} = sub { $field_packer{$_[1]} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
|
284
|
0
|
|
|
|
|
|
my %enc_fields = map { ($_ => 1) } $class->ENCODED_FIELDS; |
|
0
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
|
286
|
0
|
|
|
|
|
|
foreach my $field (@fields) { |
287
|
0
|
0
|
|
|
|
|
next if defined &{"$class\::$field"}; |
|
0
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
|
289
|
0
|
0
|
0
|
|
|
|
if ($enc_fields{$field} and my $encoding = $class->ENCODING) { |
290
|
0
|
|
|
|
|
|
require Encode; |
291
|
|
|
|
|
|
|
|
292
|
0
|
|
|
|
|
|
*{"$class\::$field"} = sub { |
293
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
294
|
0
|
|
|
|
|
|
return Encode::decode($encoding => $self->{struct}{$field}); |
295
|
0
|
|
|
|
|
|
}; |
296
|
|
|
|
|
|
|
|
297
|
0
|
|
|
|
|
|
*{"$class\::Set$field"} = sub { |
298
|
0
|
|
|
0
|
|
|
my ($self, $data) = @_; |
299
|
0
|
|
|
|
|
|
$self->{struct}{$field} = Encode::encode($encoding => $data); |
300
|
0
|
|
|
|
|
|
}; |
301
|
0
|
|
|
|
|
|
next; |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
0
|
|
|
0
|
|
|
*{"$class\::$field"} = sub { $_[0]->{struct}{$field} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
305
|
0
|
|
|
0
|
|
|
*{"$class\::Set$field"} = sub { $_[0]->{struct}{$field} = $_[1] }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
0
|
|
|
|
|
|
${"$class\::init_done"} = 1; |
|
0
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
sub initialize { |
312
|
0
|
|
|
0
|
0
|
|
return 1; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
### Miscellanous ### |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
sub field { |
318
|
0
|
|
|
0
|
0
|
|
my ($self, $field) = @_; |
319
|
0
|
|
|
|
|
|
return $self->{struct}{$field}; |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
sub set_field { |
323
|
0
|
|
|
0
|
0
|
|
my ($self, $field, $data) = @_; |
324
|
0
|
|
|
|
|
|
$self->{struct}{$field} = $data; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub classname { |
328
|
0
|
|
|
0
|
0
|
|
my ($self, $class) = @_; |
329
|
0
|
0
|
|
|
|
|
return undef unless $class; |
330
|
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
|
$class =~ s/__/::/g; |
332
|
|
|
|
|
|
|
|
333
|
0
|
0
|
|
|
|
|
my $base_class = $self->BASE_CLASS or return $class; |
334
|
0
|
0
|
|
|
|
|
return $base_class if $class eq '::BASE::'; |
335
|
|
|
|
|
|
|
|
336
|
0
|
|
|
|
|
|
return "$base_class\::$class"; |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub member_fields { |
340
|
0
|
|
|
0
|
0
|
|
return (); |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
sub dispatch_class { |
344
|
0
|
|
|
0
|
0
|
|
my ($self, $field) = @_; |
345
|
0
|
|
|
|
|
|
my $table = $DispatchTable{ref $self}; |
346
|
0
|
0
|
|
|
|
|
my $class = exists($table->{$field}) ? $table->{$field} : $table->{'*'}; |
347
|
|
|
|
|
|
|
|
348
|
0
|
0
|
|
|
|
|
$class = &$class($self, $field) if UNIVERSAL::isa($class, 'CODE'); |
349
|
0
|
0
|
|
|
|
|
defined $class or return; |
350
|
|
|
|
|
|
|
|
351
|
0
|
0
|
|
|
|
|
if (my $members = $self->{parent}{callback_members}) { |
352
|
0
|
0
|
|
|
|
|
return unless $members->{$class}; |
353
|
|
|
|
|
|
|
} |
354
|
0
|
0
|
|
|
|
|
my $subclass = $self->classname($class) or return; |
355
|
0
|
0
|
|
|
|
|
return if $subclass eq $class; |
356
|
0
|
|
|
|
|
|
return $subclass; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub require { |
360
|
0
|
|
|
0
|
0
|
|
my ($class, $module) = @_; |
361
|
0
|
0
|
|
|
|
|
return unless defined $module; |
362
|
|
|
|
|
|
|
|
363
|
0
|
|
|
|
|
|
my $file = "$module.pm"; |
364
|
0
|
|
|
|
|
|
$file =~ s{::}{/}g; |
365
|
|
|
|
|
|
|
|
366
|
0
|
0
|
|
|
|
|
return $module if (eval { require $file; 1 }); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
367
|
0
|
0
|
|
|
|
|
die $@ unless $@ =~ /^Can't locate /; |
368
|
0
|
|
|
|
|
|
return; |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sub require_class { |
372
|
0
|
|
|
0
|
0
|
|
my ($class, $subclass) = @_; |
373
|
0
|
|
|
|
|
|
return $class->require($class->classname($subclass)); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
sub format_list { |
377
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
378
|
0
|
|
|
|
|
|
return $self->FORMAT; |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub subformat_list { |
382
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
383
|
0
|
0
|
|
|
|
|
$self->SUBFORMAT ? $self->SUBFORMAT : (); |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub default_args { |
387
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
388
|
0
|
0
|
|
|
|
|
$self->DEFAULT_ARGS ? $self->DEFAULT_ARGS : (); |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
sub dispatch_table { |
392
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
393
|
0
|
0
|
|
|
|
|
$self->DISPATCH_TABLE ? { $self->DISPATCH_TABLE } : {}; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
sub delegate_subs { |
397
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
398
|
0
|
0
|
|
|
|
|
$self->DELEGATE_SUBS ? { $self->DELEGATE_SUBS } : {}; |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
sub class { |
402
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
403
|
0
|
|
0
|
|
|
|
return(ref($self) || $self); |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
sub make_formatter { |
407
|
0
|
|
|
0
|
0
|
|
my ($self, @formats) = @_; |
408
|
0
|
|
|
|
|
|
return Parse::Binary::FixedFormat->new( $self->make_format(@formats) ); |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub make_format { |
412
|
0
|
|
|
0
|
0
|
|
my ($self, @formats) = @_; |
413
|
0
|
0
|
|
|
|
|
return \@formats unless grep /\{/, @formats; |
414
|
|
|
|
|
|
|
|
415
|
0
|
|
|
|
|
|
my @prefix; |
416
|
0
|
|
|
|
|
|
foreach my $format (@formats) { |
417
|
0
|
0
|
|
|
|
|
last if $format =~ /\{/; |
418
|
0
|
|
|
|
|
|
push @prefix, $format; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
return { |
421
|
0
|
|
|
0
|
|
|
Chooser => sub { $self->chooser(@_) }, |
422
|
0
|
|
|
|
|
|
Formats => [ \@prefix, \@formats ], |
423
|
|
|
|
|
|
|
}; |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
sub chooser { |
427
|
0
|
|
|
0
|
0
|
|
my ($self, $rec, $obj, $mode) = @_; |
428
|
0
|
|
|
|
|
|
my $idx = @{$obj->{Layouts}}; |
|
0
|
|
|
|
|
|
|
429
|
0
|
|
|
|
|
|
my @format = $self->eval_format($rec, @{$obj->{Formats}[1]}); |
|
0
|
|
|
|
|
|
|
430
|
0
|
|
|
|
|
|
$obj->{Layouts}[$idx] = $self->make_formatter(@format); |
431
|
0
|
|
|
|
|
|
return $idx; |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub eval_format { |
435
|
0
|
|
|
0
|
0
|
|
my ($self, $rec, @format) = @_; |
436
|
0
|
|
|
|
|
|
foreach my $key (sort keys %$rec) { |
437
|
0
|
|
|
|
|
|
s/\$$key\b/$rec->{$key}/ for @format; |
438
|
|
|
|
|
|
|
} |
439
|
0
|
|
0
|
|
|
|
!/\$/ and s/\{(.*?)\}/$1/eeg for @format; |
|
0
|
|
|
|
|
|
|
440
|
0
|
0
|
|
|
|
|
die $@ if $@; |
441
|
0
|
|
|
|
|
|
return @format; |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
sub padding { |
445
|
0
|
|
|
0
|
0
|
|
return ''; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
sub load_struct { |
449
|
0
|
|
|
0
|
0
|
|
my ($self, $data) = @_; |
450
|
0
|
|
|
|
|
|
$self->{struct} = $Parser{ref $self}->unformat($$data . $self->padding, $self->{lazy}, $self); |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
sub load_size { |
454
|
0
|
|
|
0
|
0
|
|
my ($self, $data) = @_; |
455
|
0
|
|
|
|
|
|
$self->{size} = length($$data); |
456
|
0
|
|
|
|
|
|
return 1; |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
sub lazy_load { |
460
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
461
|
0
|
0
|
|
|
|
|
ref(my $sub = $self->{lazy}) or return; |
462
|
0
|
|
|
|
|
|
$self->{lazy} = 1; |
463
|
0
|
0
|
|
|
|
|
$self->make_members unless $self->{iterator}; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
my %DispatchClass; |
467
|
|
|
|
|
|
|
sub load { |
468
|
0
|
|
|
0
|
0
|
|
my ($self, $data, $attr) = @_; |
469
|
0
|
0
|
|
|
|
|
return $self unless defined $data; |
470
|
|
|
|
|
|
|
|
471
|
1
|
|
|
1
|
|
57
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
216
|
|
472
|
0
|
|
0
|
|
|
|
my $class = ref($self) || $self; |
473
|
0
|
0
|
|
|
|
|
$class->init unless ${"$class\::init_done"}; |
|
0
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
|
475
|
0
|
|
|
|
|
|
$self->load_struct($data); |
476
|
0
|
|
|
|
|
|
$self->load_size($data); |
477
|
|
|
|
|
|
|
|
478
|
0
|
0
|
|
|
|
|
if (my $field = $DispatchField{$class}) { |
479
|
0
|
0
|
0
|
|
|
|
if ( |
480
|
|
|
|
|
|
|
my $subclass = $DispatchClass{$class}{ $self->{struct}{$field} } |
481
|
|
|
|
|
|
|
||= $self->dispatch_class( $self->{struct}{$field}) |
482
|
|
|
|
|
|
|
) { |
483
|
0
|
|
|
|
|
|
$self->require($subclass); |
484
|
0
|
|
|
|
|
|
bless($self, $subclass); |
485
|
0
|
|
|
|
|
|
$self->load($data, $attr); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
0
|
|
|
|
|
|
return $self; |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
my (%classname, %fill_cache); |
493
|
|
|
|
|
|
|
sub spawn { |
494
|
0
|
|
|
0
|
0
|
|
my ($self, %args) = @_; |
495
|
0
|
|
0
|
|
|
|
my $class = ref($self) || $self; |
496
|
|
|
|
|
|
|
|
497
|
1
|
|
|
1
|
|
6
|
no strict 'refs'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
1813
|
|
498
|
|
|
|
|
|
|
|
499
|
0
|
0
|
|
|
|
|
if (my $subclass = delete($args{Class})) { |
500
|
0
|
|
0
|
|
|
|
$class = $classname{$subclass} ||= do { |
501
|
0
|
|
|
|
|
|
my $name = $self->classname($subclass); |
502
|
0
|
|
|
|
|
|
$self->require($name); |
503
|
0
|
|
|
|
|
|
$name->init; |
504
|
0
|
|
|
|
|
|
$name; |
505
|
|
|
|
|
|
|
}; |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
bless({ |
509
|
0
|
|
|
|
|
|
struct => { |
510
|
|
|
|
|
|
|
%args, |
511
|
0
|
|
0
|
|
|
|
@{ $DefaultArgs{$class} }, |
512
|
0
|
|
|
|
|
|
%{ $fill_cache{$class} ||= $class->fill_in }, |
513
|
|
|
|
|
|
|
}, |
514
|
|
|
|
|
|
|
}, $class); |
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
sub fill_in { |
518
|
0
|
|
|
0
|
0
|
|
my $class = shift; |
519
|
0
|
|
|
|
|
|
my $entries = {}; |
520
|
|
|
|
|
|
|
|
521
|
0
|
|
|
|
|
|
foreach my $super_class ($class->superclasses) { |
522
|
0
|
0
|
|
|
|
|
my $field = $DispatchField{$super_class} or next; |
523
|
0
|
0
|
|
|
|
|
my $table = $DispatchTable{$super_class} or next; |
524
|
0
|
|
|
|
|
|
foreach my $code (reverse sort keys %$table) { |
525
|
0
|
0
|
|
|
|
|
$class->is_type($table->{$code}) or next; |
526
|
0
|
|
|
|
|
|
$entries->{$field} = $code; |
527
|
0
|
|
|
|
|
|
last; |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
0
|
|
|
|
|
|
return $entries; |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
sub spawn_sibling { |
535
|
0
|
|
|
0
|
0
|
|
my ($self, %args) = @_; |
536
|
0
|
0
|
|
|
|
|
my $parent = $self->{parent} or die "$self has no parent"; |
537
|
|
|
|
|
|
|
|
538
|
0
|
|
|
|
|
|
my $obj = $self->spawn(%args); |
539
|
0
|
|
|
|
|
|
@{$obj}{qw( lazy parent output siblings )} = |
|
0
|
|
|
|
|
|
|
540
|
0
|
|
|
|
|
|
@{$self}{qw( lazy parent output siblings )}; |
541
|
0
|
|
|
|
|
|
$obj->{size} = length($obj->dump); |
542
|
0
|
|
|
|
|
|
$obj->refresh_parent; |
543
|
0
|
|
|
|
|
|
$obj->initialize; |
544
|
|
|
|
|
|
|
|
545
|
0
|
|
|
|
|
|
return $obj; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
sub sibling_index { |
549
|
0
|
|
|
0
|
0
|
|
my ($self, $obj) = @_; |
550
|
0
|
|
0
|
|
|
|
$obj ||= $self; |
551
|
|
|
|
|
|
|
|
552
|
0
|
|
|
|
|
|
my @siblings = @{$self->{siblings}}; |
|
0
|
|
|
|
|
|
|
553
|
0
|
|
0
|
|
|
|
foreach my $index (($obj->{index}||0) .. $#siblings) { |
554
|
0
|
0
|
|
|
|
|
return $index if $obj == $siblings[$index]; |
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
|
557
|
0
|
|
|
|
|
|
return undef; |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
sub gone { |
561
|
0
|
|
|
0
|
0
|
|
my ($self, $obj) = @_; |
562
|
0
|
|
0
|
|
|
|
$self->{parent}{struct}{Data} .= ($obj || $self)->dump; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub prepend_obj { |
566
|
0
|
|
|
0
|
0
|
|
my ($self, %args) = @_; |
567
|
0
|
0
|
|
|
|
|
if ($self->{lazy}) { |
568
|
0
|
|
|
|
|
|
my $obj = $self->spawn(%args); |
569
|
0
|
|
|
|
|
|
$self->gone($obj); |
570
|
0
|
|
|
|
|
|
return; |
571
|
|
|
|
|
|
|
} |
572
|
0
|
|
|
|
|
|
my $obj = $self->spawn_sibling(%args); |
573
|
0
|
|
|
|
|
|
my $siblings = $self->{siblings}; |
574
|
0
|
0
|
|
|
|
|
my $index = $self->{index} ? $self->{index}++ : $self->sibling_index; |
575
|
0
|
|
|
|
|
|
$obj->{index} = $index; |
576
|
|
|
|
|
|
|
|
577
|
0
|
|
|
|
|
|
splice(@$siblings, $index, 0, $obj); |
578
|
0
|
|
|
|
|
|
return $obj; |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
sub append_obj { |
582
|
0
|
|
|
0
|
0
|
|
my ($self, %args) = @_; |
583
|
0
|
|
|
|
|
|
my $obj = $self->spawn_sibling(%args); |
584
|
|
|
|
|
|
|
|
585
|
0
|
0
|
|
|
|
|
@{$self->{siblings}} = ( |
|
0
|
|
|
|
|
|
|
586
|
0
|
|
|
|
|
|
map { $_, (($_ == $self) ? $obj : ()) } @{$self->{siblings}} |
|
0
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
); |
588
|
0
|
|
|
|
|
|
return $obj; |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
sub remove { |
592
|
0
|
|
|
0
|
0
|
|
my ($self, %args) = @_; |
593
|
0
|
|
|
|
|
|
my $siblings = $self->{siblings}; |
594
|
0
|
|
|
|
|
|
splice(@$siblings, $self->sibling_index, 1, undef); |
595
|
|
|
|
|
|
|
|
596
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self->{parent}); |
597
|
0
|
|
|
|
|
|
Scalar::Util::weaken($self); |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
sub read_data { |
601
|
0
|
|
|
0
|
0
|
|
my ($self, $data) = @_; |
602
|
0
|
0
|
|
|
|
|
return undef unless defined $data; |
603
|
0
|
0
|
|
|
|
|
return \($data->dump) if UNIVERSAL::can($data, 'dump'); |
604
|
0
|
0
|
|
|
|
|
return $data if UNIVERSAL::isa($data, 'SCALAR'); |
605
|
0
|
|
|
|
|
|
return \($self->read_file($data)); |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
sub read_file { |
609
|
0
|
|
|
0
|
0
|
|
my ($self, $file) = @_; |
610
|
|
|
|
|
|
|
|
611
|
0
|
|
|
|
|
|
local *FH; local $/; |
|
0
|
|
|
|
|
|
|
612
|
0
|
0
|
|
|
|
|
open FH, "< $file" or die "Cannot open $file for reading: $!"; |
613
|
0
|
|
|
|
|
|
binmode(FH); |
614
|
|
|
|
|
|
|
|
615
|
0
|
|
|
|
|
|
return scalar ; |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
sub make_members { |
619
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
620
|
|
|
|
|
|
|
|
621
|
0
|
0
|
|
|
|
|
$HasMembers{ref $self} or return; |
622
|
0
|
|
|
|
|
|
%{$self->{children}} = (); |
|
0
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
|
624
|
0
|
|
|
|
|
|
foreach my $field (@{$MemberFields{ref $self}}) { |
|
0
|
|
|
|
|
|
|
625
|
0
|
|
|
|
|
|
my ($format) = $self->eval_format( |
626
|
|
|
|
|
|
|
$self->{struct}, |
627
|
|
|
|
|
|
|
$FieldPackFormat{ref $self}{$field}, |
628
|
|
|
|
|
|
|
); |
629
|
|
|
|
|
|
|
|
630
|
0
|
|
|
|
|
|
my $members = [ map { |
631
|
0
|
|
|
|
|
|
$self->new_member( $field, \pack($format, @$_) ) |
632
|
|
|
|
|
|
|
} $self->validate_memberdata($field) ]; |
633
|
0
|
|
|
|
|
|
$self->set_field_children( $field, $members ); |
634
|
|
|
|
|
|
|
} |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
sub set_members { |
638
|
0
|
|
|
0
|
0
|
|
my ($self, $field, $members) = @_; |
639
|
0
|
|
|
|
|
|
$field =~ s/:/_/g; |
640
|
0
|
|
|
|
|
|
$self->set_field_children( |
641
|
|
|
|
|
|
|
$field, |
642
|
0
|
|
|
|
|
|
[ map { $self->new_member( $field, $_ ) } @$members ], |
643
|
|
|
|
|
|
|
); |
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
sub set_field_children { |
647
|
0
|
|
|
0
|
0
|
|
my ($self, $field, $data) = @_; |
648
|
0
|
|
|
|
|
|
my $children = $self->field_children($field); |
649
|
0
|
|
|
|
|
|
@$children = @$data; |
650
|
0
|
|
|
|
|
|
return $children; |
651
|
|
|
|
|
|
|
} |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
sub field_children { |
654
|
0
|
|
|
0
|
0
|
|
my ($self, $field) = @_; |
655
|
0
|
|
0
|
|
|
|
my $children = ($self->{children}{$field} ||= []); |
656
|
|
|
|
|
|
|
# $_->lazy_load for @$children; |
657
|
0
|
0
|
|
|
|
|
return(wantarray ? @$children : $children); |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
sub validate_memberdata { |
661
|
0
|
|
|
0
|
0
|
|
my ($self, $field) = @_; |
662
|
0
|
0
|
|
|
|
|
return @{$self->{struct}{$field}||[]}; |
|
0
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
} |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
sub first_member { |
666
|
0
|
|
|
0
|
0
|
|
my ($self, $type) = @_; |
667
|
0
|
|
|
|
|
|
$self->lazy_load; |
668
|
|
|
|
|
|
|
|
669
|
0
|
0
|
|
|
|
|
return undef unless $HasMembers{ref $self}; |
670
|
|
|
|
|
|
|
|
671
|
1
|
|
|
1
|
|
9
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
540
|
|
672
|
0
|
|
|
|
|
|
foreach my $field (@{$MemberFields{ref $self}}) { |
|
0
|
|
|
|
|
|
|
673
|
0
|
|
|
|
|
|
foreach my $member ($self->field_children($field)) { |
674
|
0
|
0
|
|
|
|
|
return $member if $member->is_type($type); |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
} |
677
|
0
|
|
|
|
|
|
return undef; |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
sub next_member { |
681
|
0
|
|
|
0
|
0
|
|
my ($self, $type) = @_; |
682
|
0
|
0
|
|
|
|
|
return undef unless $HasMembers{ref $self}; |
683
|
|
|
|
|
|
|
|
684
|
0
|
0
|
0
|
|
|
|
if ($self->{lazy} and !$self->{iterated}) { |
685
|
0
|
0
|
|
|
|
|
if (ref($self->{lazy})) { |
686
|
0
|
|
|
|
|
|
%{$self->{children}} = (); |
|
0
|
|
|
|
|
|
|
687
|
0
|
|
|
|
|
|
$self->{iterator} = $self->make_next_member; |
688
|
0
|
|
|
|
|
|
$self->lazy_load; |
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
|
691
|
0
|
|
|
|
|
|
while (my $member = &{$self->{iterator}}) { |
|
0
|
|
|
|
|
|
|
692
|
0
|
0
|
|
|
|
|
return $member if $member->is_type($type); |
693
|
|
|
|
|
|
|
} |
694
|
0
|
|
|
|
|
|
$self->{iterated} = 1; |
695
|
0
|
|
|
|
|
|
return; |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
0
|
|
0
|
|
|
|
$self->{_next_member}{$type} ||= $self->members($type); |
699
|
|
|
|
|
|
|
|
700
|
0
|
0
|
|
|
|
|
shift(@{$self->{_next_member}{$type}}) |
|
0
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
|| undef($self->{_next_member}{$type}); |
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
sub make_next_member { |
705
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
706
|
0
|
|
|
|
|
|
my $class = ref($self); |
707
|
0
|
|
|
|
|
|
my ($field_idx, $item_idx, $format) = (0, 0, undef); |
708
|
0
|
|
|
|
|
|
my @fields = @{$MemberFields{$class}}; |
|
0
|
|
|
|
|
|
|
709
|
0
|
|
|
|
|
|
my $struct = $self->{struct}; |
710
|
0
|
|
|
|
|
|
my $formats = $FieldPackFormat{$class}; |
711
|
|
|
|
|
|
|
|
712
|
0
|
0
|
|
|
|
|
sub { LOOP: { |
713
|
0
|
|
|
0
|
|
|
my $field = $fields[$field_idx] or return; |
714
|
|
|
|
|
|
|
|
715
|
0
|
|
|
|
|
|
my $items = $struct->{$field}; |
716
|
0
|
0
|
|
|
|
|
if ($item_idx > $#$items) { |
717
|
0
|
|
|
|
|
|
$field_idx++; |
718
|
0
|
|
|
|
|
|
$item_idx = 0; |
719
|
0
|
|
|
|
|
|
undef $format; |
720
|
0
|
|
|
|
|
|
redo; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
0
|
|
0
|
|
|
|
$format ||= ($self->eval_format( $struct, $formats->{$field} ))[0]; |
724
|
|
|
|
|
|
|
|
725
|
0
|
|
|
|
|
|
my $item = $items->[$item_idx++]; |
726
|
0
|
0
|
|
|
|
|
$item = $item->($self, $items) if UNIVERSAL::isa($item, 'CODE'); |
727
|
0
|
0
|
|
|
|
|
$self->valid_memberdata($item) or redo; |
728
|
|
|
|
|
|
|
|
729
|
0
|
|
|
|
|
|
my $member = $self->new_member( $field, \pack($format, @$item) ); |
730
|
0
|
|
|
|
|
|
$member->{index} = (push @{$self->{children}{$field}}, $member) - 1; |
|
0
|
|
|
|
|
|
|
731
|
0
|
|
|
|
|
|
return $member; |
732
|
0
|
|
|
|
|
|
} }; |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
sub members { |
736
|
0
|
|
|
0
|
0
|
|
my ($self, $type) = @_; |
737
|
0
|
|
|
|
|
|
$self->lazy_load; |
738
|
|
|
|
|
|
|
|
739
|
1
|
|
|
1
|
|
6
|
no strict 'refs'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
732
|
|
740
|
0
|
0
|
|
|
|
|
my @members = map { |
741
|
0
|
|
|
|
|
|
grep { $type ? $_->is_type($type) : 1 } $self->field_children($_) |
|
0
|
|
|
|
|
|
|
742
|
0
|
|
|
|
|
|
} @{$MemberFields{ref $self}}; |
743
|
0
|
0
|
|
|
|
|
wantarray ? @members : \@members; |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
sub members_recursive { |
747
|
0
|
|
|
0
|
0
|
|
my ($self, $type) = @_; |
748
|
0
|
|
|
|
|
|
my @members = ( |
749
|
|
|
|
|
|
|
( $self->is_type($type) ? $self : () ), |
750
|
0
|
0
|
|
|
|
|
map { $_->members_recursive($type) } $self->members |
751
|
|
|
|
|
|
|
); |
752
|
0
|
0
|
|
|
|
|
wantarray ? @members : \@members; |
753
|
|
|
|
|
|
|
} |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
sub new_member { |
756
|
0
|
|
|
0
|
0
|
|
my ($self, $field, $data) = @_; |
757
|
0
|
|
|
|
|
|
my $obj = $MemberClass{ref $self}{$field}->new( |
758
|
|
|
|
|
|
|
$data, { lazy => $self->{lazy}, parent => $self } |
759
|
|
|
|
|
|
|
); |
760
|
|
|
|
|
|
|
|
761
|
0
|
|
|
|
|
|
$obj->{output} = $self->{output}; |
762
|
0
|
|
0
|
|
|
|
$obj->{siblings} = $self->{children}{$field}||=[]; |
763
|
0
|
|
|
|
|
|
$obj->initialize; |
764
|
|
|
|
|
|
|
|
765
|
0
|
|
|
|
|
|
return $obj; |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
sub valid_memberdata { |
769
|
0
|
|
|
0
|
0
|
|
length($_[-1][0]) |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
sub dump_members { |
773
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
774
|
0
|
|
|
|
|
|
return $Packer{ref $self}->format($self->{struct}); |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
sub dump { |
778
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
779
|
0
|
0
|
|
|
|
|
return $self->dump_members if $HasMembers{ref $self}; |
780
|
0
|
|
|
|
|
|
return $Packer{ref $self}->format($self->{struct}); |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
sub write { |
784
|
0
|
|
|
0
|
0
|
|
my ($self, $file) = @_; |
785
|
|
|
|
|
|
|
|
786
|
0
|
0
|
0
|
|
|
|
if (ref($file)) { |
|
|
0
|
|
|
|
|
|
787
|
0
|
|
|
|
|
|
$$file = $self->dump; |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
elsif (!defined($file) and my $fh = $self->{output}) { |
790
|
0
|
|
|
|
|
|
print $fh $self->dump; |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
else { |
793
|
0
|
0
|
|
|
|
|
$file = $self->{filename} unless defined $file; |
794
|
0
|
0
|
|
|
|
|
$self->write_file($file, $self->dump) if defined $file; |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
} |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
sub write_file { |
799
|
0
|
|
|
0
|
0
|
|
my ($self, $file, $data) = @_; |
800
|
0
|
|
|
|
|
|
local *FH; |
801
|
0
|
0
|
|
|
|
|
open FH, "> $file" or die "Cannot open $file for writing: $!"; |
802
|
0
|
|
|
|
|
|
binmode(FH); |
803
|
0
|
|
|
|
|
|
print FH $data; |
804
|
|
|
|
|
|
|
}; |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
sub superclasses { |
807
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
808
|
0
|
|
|
|
|
|
my $class = $self->class; |
809
|
|
|
|
|
|
|
|
810
|
1
|
|
|
1
|
|
8
|
no strict 'refs'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
371
|
|
811
|
0
|
|
|
|
|
|
return @{"$class\::ISA"}; |
|
0
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
} |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
my %type_cache; |
815
|
|
|
|
|
|
|
sub is_type { |
816
|
0
|
|
|
0
|
0
|
|
my ($self, $type) = @_; |
817
|
0
|
0
|
|
|
|
|
return 1 unless defined $type; |
818
|
|
|
|
|
|
|
|
819
|
0
|
|
0
|
|
|
|
my $class = ref($self) || $self; |
820
|
|
|
|
|
|
|
|
821
|
0
|
0
|
|
|
|
|
if (exists $type_cache{$class}{$type}) { |
822
|
0
|
|
|
|
|
|
return $type_cache{$class}{$type}; |
823
|
|
|
|
|
|
|
} |
824
|
|
|
|
|
|
|
|
825
|
0
|
|
|
|
|
|
$type_cache{$class}{$type} = 1; |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
|
828
|
0
|
|
|
|
|
|
$type =~ s/__/::/g; |
829
|
0
|
|
|
|
|
|
$type =~ s/[^\w:]//g; |
830
|
0
|
0
|
|
|
|
|
return 1 if ($class =~ /::$type$/); |
831
|
|
|
|
|
|
|
|
832
|
1
|
|
|
1
|
|
50
|
no strict 'refs'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
870
|
|
833
|
0
|
|
|
|
|
|
foreach my $super_class ($class->superclasses) { |
834
|
0
|
0
|
|
|
|
|
return 1 if $super_class->is_type($type); |
835
|
|
|
|
|
|
|
}; |
836
|
|
|
|
|
|
|
|
837
|
0
|
|
|
|
|
|
$type_cache{$class}{$type} = 0; |
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
sub refresh { |
841
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
842
|
|
|
|
|
|
|
|
843
|
0
|
|
|
|
|
|
foreach my $field (@{$MemberFields{ref $self}}) { |
|
0
|
|
|
|
|
|
|
844
|
0
|
|
|
|
|
|
my $parser = $self->field_parser($field); |
845
|
0
|
|
|
|
|
|
my $padding = $self->padding; |
846
|
|
|
|
|
|
|
|
847
|
0
|
|
|
0
|
|
|
local $SIG{__WARN__} = sub {}; |
|
0
|
|
|
|
|
|
|
848
|
0
|
|
|
|
|
|
@{$self->{struct}{$field}} = map { |
|
0
|
|
|
|
|
|
|
849
|
0
|
0
|
|
|
|
|
$parser->unformat( $_->dump . $padding, 0, $self)->{$field}[0] |
850
|
0
|
|
|
|
|
|
} grep defined, @{$self->{children}{$field}||[]}; |
851
|
|
|
|
|
|
|
|
852
|
0
|
|
|
|
|
|
$self->validate_memberdata; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
0
|
|
|
|
|
|
$self->refresh_parent; |
856
|
|
|
|
|
|
|
} |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
sub refresh_parent { |
859
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
860
|
0
|
0
|
|
|
|
|
my $parent = $self->{parent} or return; |
861
|
0
|
0
|
0
|
|
|
|
$parent->refresh unless !Scalar::Util::blessed($parent) or $parent->{lazy}; |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
sub first_parent { |
865
|
0
|
|
|
0
|
0
|
|
my ($self, $type) = @_; |
866
|
0
|
0
|
|
|
|
|
return $self if $self->is_type($type); |
867
|
0
|
0
|
|
|
|
|
my $parent = $self->{parent} or return; |
868
|
0
|
|
|
|
|
|
return $parent->first_parent($type); |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
sub substr { |
872
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
873
|
0
|
|
|
|
|
|
my $data = $self->Data; |
874
|
0
|
|
|
|
|
|
my $offset = shift(@_) - ($self->{size} - length($data)); |
875
|
0
|
0
|
|
|
|
|
my $length = @_ ? shift(@_) : (length($data) - $offset); |
876
|
0
|
|
|
|
|
|
my $replace = shift; |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
# XXX - Check for "substr outside string" |
879
|
0
|
0
|
|
|
|
|
return if $offset > length($data); |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
# Fetch a range |
882
|
0
|
0
|
|
|
|
|
return substr($data, $offset, $length) if !defined $replace; |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
# Substitute a range |
885
|
0
|
|
|
|
|
|
substr($data, $offset, $length, $replace); |
886
|
0
|
|
|
|
|
|
$self->{struct}{Data} = $data; |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
sub set_output_file { |
890
|
0
|
|
|
0
|
0
|
|
my ($self, $file) = @_; |
891
|
|
|
|
|
|
|
|
892
|
0
|
0
|
|
|
|
|
open my $fh, '>', $file or die $!; |
893
|
0
|
|
|
|
|
|
binmode($fh); |
894
|
0
|
|
|
|
|
|
$self->{output} = $fh; |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
my %callback_map; |
898
|
|
|
|
|
|
|
sub callback { |
899
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
900
|
0
|
|
0
|
|
|
|
my $pkg = shift || caller; |
901
|
0
|
0
|
|
|
|
|
my $types = shift or return; |
902
|
|
|
|
|
|
|
|
903
|
0
|
|
0
|
|
|
|
my $map = $callback_map{"@$types"} ||= $self->callback_map($pkg, $types); |
904
|
0
|
0
|
0
|
|
|
|
my $sub = $map->{ref $self} || $map->{'*'} or return; |
905
|
0
|
|
|
|
|
|
unshift @_, $self; |
906
|
0
|
|
|
|
|
|
goto &$sub; |
907
|
|
|
|
|
|
|
} |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
sub callback_map { |
910
|
0
|
|
|
0
|
0
|
|
my ($self, $pkg, $types) = @_; |
911
|
0
|
|
|
|
|
|
my %map; |
912
|
0
|
|
|
|
|
|
my $base = $self->BASE_CLASS; |
913
|
0
|
|
|
|
|
|
foreach my $type (map "$_", @$types) { |
914
|
1
|
|
|
1
|
|
9
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
326
|
|
915
|
0
|
|
|
|
|
|
my $method = $type; |
916
|
0
|
|
|
|
|
|
$method =~ s/::/_/g; |
917
|
0
|
|
|
|
|
|
$method =~ s/\*/__/g; |
918
|
|
|
|
|
|
|
|
919
|
0
|
0
|
|
|
|
|
defined &{"$pkg\::$method"} or next; |
|
0
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
|
921
|
0
|
0
|
|
|
|
|
$type = "$base\::$type" unless $type eq '*'; |
922
|
0
|
|
|
|
|
|
$map{$type} = \&{"$pkg\::$method"}; |
|
0
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
} |
924
|
0
|
|
|
|
|
|
return \%map; |
925
|
|
|
|
|
|
|
} |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
sub callback_members { |
928
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
929
|
0
|
|
|
|
|
|
$self->{callback_members} = { map { ($_ => 1) } @{$_[0]} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
|
931
|
0
|
|
|
|
|
|
while (my $member = $self->next_member) { |
932
|
0
|
|
|
|
|
|
$member->callback(scalar caller, @_); |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
} |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
sub done { |
937
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
938
|
0
|
0
|
|
|
|
|
return unless $self->{lazy}; |
939
|
0
|
|
|
|
|
|
$self->write; |
940
|
0
|
|
|
|
|
|
$self->remove; |
941
|
|
|
|
|
|
|
} |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
1; |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
__END__ |