File Coverage

lib/Algorithm/Diff.pm
Criterion Covered Total %
statement 307 345 88.9
branch 111 144 77.0
condition 74 95 77.8
subroutine 40 50 80.0
pod 9 9 100.0
total 541 643 84.1


line stmt bran cond sub pod time code
1             package Algorithm::Diff;
2             # Skip to first "=head" line for documentation.
3 2     2   3986 use strict;
  2         8  
  2         58  
4              
5 2     2   1058 use integer; # see below in _replaceNextLargerWith() for mod to make
  2         28  
  2         10  
6             # if you don't use this
7 2     2   86 use vars qw( $VERSION @EXPORT_OK );
  2         4  
  2         5055  
8             $VERSION = '1.201';
9              
10             require Exporter;
11             *import = \&Exporter::import;
12             @EXPORT_OK = qw(
13             prepare LCS LCSidx LCS_length
14             diff sdiff compact_diff
15             traverse_sequences traverse_balanced
16             );
17              
18             # McIlroy-Hunt diff algorithm
19             # Adapted from the Smalltalk code of Mario I. Wolczko,
20             # by Ned Konz, perl@bike-nomad.com
21             # Updates by Tye McQueen, http://perlmonks.org/?node=tye
22              
23             # Create a hash that maps each element of $aCollection to the set of
24             # positions it occupies in $aCollection, restricted to the elements
25             # within the range of indexes specified by $start and $end.
26             # The fourth parameter is a subroutine reference that will be called to
27             # generate a string to use as a key.
28             # Additional parameters, if any, will be passed to this subroutine.
29             #
30             # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
31              
32             sub _withPositionsOfInInterval
33             {
34 36     36   44 my $aCollection = shift; # array ref
35 36         47 my $start = shift;
36 36         40 my $end = shift;
37 36         37 my $keyGen = shift;
38 36         62 my %d;
39             my $index;
40 36         89 for ( $index = $start ; $index <= $end ; $index++ )
41             {
42 154         172 my $element = $aCollection->[$index];
43 154 50       188 my $key = $keyGen ? &$keyGen( $element, @_ ) : $element;
44 154 100       190 if ( exists( $d{$key} ) )
45             {
46 1         1 unshift ( @{ $d{$key} }, $index );
  1         4  
47             }
48             else
49             {
50 153         340 $d{$key} = [$index];
51             }
52             }
53 36 50       104 return wantarray ? %d : \%d;
54             }
55              
56             # Find the place at which aValue would normally be inserted into the
57             # array. If that place is already occupied by aValue, do nothing, and
58             # return undef. If the place does not exist (i.e., it is off the end of
59             # the array), add it to the end, otherwise replace the element at that
60             # point with aValue. It is assumed that the array's values are numeric.
61             # This is where the bulk (75%) of the time is spent in this module, so
62             # try to make it fast!
63              
64             sub _replaceNextLargerWith
65             {
66 55     55   75 my ( $array, $aValue, $high ) = @_;
67 55   66     154 $high ||= $#$array;
68              
69             # off the end?
70 55 50 66     158 if ( $high == -1 || $aValue > $array->[-1] )
71             {
72 55         84 push ( @$array, $aValue );
73 55         84 return $high + 1;
74             }
75              
76             # binary search for insertion point...
77 0         0 my $low = 0;
78 0         0 my $index;
79             my $found;
80 0         0 while ( $low <= $high )
81             {
82 0         0 $index = ( $high + $low ) / 2;
83              
84             # $index = int(( $high + $low ) / 2); # without 'use integer'
85 0         0 $found = $array->[$index];
86              
87 0 0       0 if ( $aValue == $found )
    0          
88             {
89 0         0 return undef;
90             }
91             elsif ( $aValue > $found )
92             {
93 0         0 $low = $index + 1;
94             }
95             else
96             {
97 0         0 $high = $index - 1;
98             }
99             }
100              
101             # now insertion point is in $low.
102 0         0 $array->[$low] = $aValue; # overwrite next larger
103 0         0 return $low;
104             }
105              
106             # This method computes the longest common subsequence in $a and $b.
107              
108             # Result is array or ref, whose contents is such that
109             # $a->[ $i ] == $b->[ $result[ $i ] ]
110             # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
111              
112             # An additional argument may be passed; this is a hash or key generating
113             # function that should return a string that uniquely identifies the given
114             # element. It should be the case that if the key is the same, the elements
115             # will compare the same. If this parameter is undef or missing, the key
116             # will be the element as a string.
117              
118             # By default, comparisons will use "eq" and elements will be turned into keys
119             # using the default stringizing operator '""'.
120              
121             # Additional parameters, if any, will be passed to the key generation
122             # routine.
123              
124             sub _longestCommonSubsequence
125             {
126 36     36   88 my $a = shift; # array ref or hash ref
127 36         39 my $b = shift; # array ref or hash ref
128 36         42 my $counting = shift; # scalar
129 36         39 my $keyGen = shift; # code ref
130 36         41 my $compare; # code ref
131              
132 36 50       82 if ( ref($a) eq 'HASH' )
133             { # prepared hash must be in $b
134 0         0 my $tmp = $b;
135 0         0 $b = $a;
136 0         0 $a = $tmp;
137             }
138              
139             # Check for bogus (non-ref) argument values
140 36 50 33     137 if ( !ref($a) || !ref($b) )
141             {
142 0         0 my @callerInfo = caller(1);
143 0         0 die 'error: must pass array or hash references to ' . $callerInfo[3];
144             }
145              
146             # set up code refs
147             # Note that these are optimized.
148 36 50       65 if ( $keyGen ) # optimize for strings
149             {
150             $compare = sub {
151 0     0   0 my $a = shift;
152 0         0 my $b = shift;
153 0         0 &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
154 0         0 };
155             }
156              
157 36         89 my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] );
158 36         71 my ( $prunedCount, $bMatches ) = ( 0, {} );
159              
160 36 50       74 if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us?
161             {
162 0         0 $bMatches = $b;
163             }
164             else
165             {
166 36         59 my ( $bStart, $bFinish ) = ( 0, $#$b );
167              
168             # First we prune off any common elements at the beginning
169 36 50 100     185 while ( $aStart <= $aFinish
      100        
170             and $bStart <= $bFinish
171             and ( $keyGen ? &$compare( $a->[$aStart], $b->[$bStart], @_ )
172             : ( $a->[$aStart] eq $b->[$bStart] ) ) )
173             {
174 13         28 $matchVector->[ $aStart++ ] = $bStart++;
175 13         47 $prunedCount++;
176             }
177              
178             # now the end
179 36 50 100     171 while ( $aStart <= $aFinish
      100        
180             and $bStart <= $bFinish
181             and ( $keyGen ? &$compare( $a->[$aFinish], $b->[$bFinish], @_ )
182             : ( $a->[$aFinish] eq $b->[$bFinish] ) ) )
183             {
184 16         27 $matchVector->[ $aFinish-- ] = $bFinish--;
185 16         50 $prunedCount++;
186             }
187              
188             # Now compute the equivalence classes of positions of elements
189             $bMatches =
190 36         71 _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
191             }
192 36         49 my $thresh = [];
193 36         66 my $links = [];
194              
195 36         42 my ( $i, $ai, $j, $k );
196 36         87 for ( $i = $aStart ; $i <= $aFinish ; $i++ )
197             {
198 129 50       194 $ai = $keyGen ? &$keyGen( $a->[$i], @_ ) : $a->[$i];
199 129 100       221 if ( exists( $bMatches->{$ai} ) )
200             {
201 55         58 $k = 0;
202 55         52 for $j ( @{ $bMatches->{$ai} } )
  55         84  
203             {
204              
205             # optimization: most of the time this will be true
206 55 50 33     114 if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
      33        
207             {
208 0         0 $thresh->[$k] = $j;
209             }
210             else
211             {
212 55         78 $k = _replaceNextLargerWith( $thresh, $j, $k );
213             }
214              
215             # oddly, it's faster to always test this (CPU cache?).
216 55 50       86 if ( defined($k) )
217             {
218 55 100       186 $links->[$k] =
219             [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
220             }
221             }
222             }
223             }
224              
225 36 100       75 if (@$thresh)
    50          
226             {
227 14 50       27 return $prunedCount + @$thresh if $counting;
228 14         39 for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
229             {
230 55         107 $matchVector->[ $link->[1] ] = $link->[2];
231             }
232             }
233             elsif ($counting)
234             {
235 0         0 return $prunedCount;
236             }
237              
238 36 100       133 return wantarray ? @$matchVector : $matchVector;
239             }
240              
241             sub traverse_sequences
242             {
243 3     3 1 538 my $a = shift; # array ref
244 3         5 my $b = shift; # array ref
245 3   50     7 my $callbacks = shift || {};
246 3         4 my $keyGen = shift;
247 3   50 0   6 my $matchCallback = $callbacks->{'MATCH'} || sub { };
248 3   50 0   8 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
249 3         5 my $finishedACallback = $callbacks->{'A_FINISHED'};
250 3   50 0   6 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
251 3         4 my $finishedBCallback = $callbacks->{'B_FINISHED'};
252 3         7 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
253              
254             # Process all the lines in @$matchVector
255 3         4 my $lastA = $#$a;
256 3         6 my $lastB = $#$b;
257 3         3 my $bi = 0;
258 3         4 my $ai;
259              
260 3         7 for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
261             {
262 24         84 my $bLine = $matchVector->[$ai];
263 24 100       29 if ( defined($bLine) ) # matched
264             {
265 18         36 &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
266 18         78 &$matchCallback( $ai, $bi++, @_ );
267             }
268             else
269             {
270 6         10 &$discardACallback( $ai, $bi, @_ );
271             }
272             }
273              
274             # The last entry (if any) processed was a match.
275             # $ai and $bi point just past the last matching lines in their sequences.
276              
277 3   100     23 while ( $ai <= $lastA or $bi <= $lastB )
278             {
279              
280             # last A?
281 9 100 66     37 if ( $ai == $lastA + 1 and $bi <= $lastB )
282             {
283 3 100       4 if ( defined($finishedACallback) )
284             {
285 1         4 &$finishedACallback( $lastA, @_ );
286 1         5 $finishedACallback = undef;
287             }
288             else
289             {
290 2         8 &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
291             }
292             }
293              
294             # last B?
295 9 50 66     26 if ( $bi == $lastB + 1 and $ai <= $lastA )
296             {
297 0 0       0 if ( defined($finishedBCallback) )
298             {
299 0         0 &$finishedBCallback( $lastB, @_ );
300 0         0 $finishedBCallback = undef;
301             }
302             else
303             {
304 0         0 &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
305             }
306             }
307              
308 9 100       41 &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
309 9 100       32 &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
310             }
311              
312 3         11 return 1;
313             }
314              
315             sub traverse_balanced
316             {
317 20     20 1 945 my $a = shift; # array ref
318 20         23 my $b = shift; # array ref
319 20   50     42 my $callbacks = shift || {};
320 20         21 my $keyGen = shift;
321 20   50 0   47 my $matchCallback = $callbacks->{'MATCH'} || sub { };
322 20   50 0   32 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
323 20   50 0   59 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
324 20         26 my $changeCallback = $callbacks->{'CHANGE'};
325 20         40 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
326              
327             # Process all the lines in match vector
328 20         31 my $lastA = $#$a;
329 20         22 my $lastB = $#$b;
330 20         21 my $bi = 0;
331 20         22 my $ai = 0;
332 20         20 my $ma = -1;
333 20         21 my $mb;
334              
335 20         19 while (1)
336             {
337              
338             # Find next match indices $ma and $mb
339 52   100     86 do {
340 71         177 $ma++;
341             } while(
342             $ma <= $#$matchVector
343             && !defined $matchVector->[$ma]
344             );
345              
346 52 100       88 last if $ma > $#$matchVector; # end of matchVector?
347 32         36 $mb = $matchVector->[$ma];
348              
349             # Proceed with discard a/b or change events until
350             # next match
351 32   100     72 while ( $ai < $ma || $bi < $mb )
352             {
353              
354 38 100 100     127 if ( $ai < $ma && $bi < $mb )
    100          
355             {
356              
357             # Change
358 10 100       16 if ( defined $changeCallback )
359             {
360 9         19 &$changeCallback( $ai++, $bi++, @_ );
361             }
362             else
363             {
364 1         4 &$discardACallback( $ai++, $bi, @_ );
365 1         6 &$discardBCallback( $ai, $bi++, @_ );
366             }
367             }
368             elsif ( $ai < $ma )
369             {
370 9         15 &$discardACallback( $ai++, $bi, @_ );
371             }
372             else
373             {
374              
375             # $bi < $mb
376 19         27 &$discardBCallback( $ai, $bi++, @_ );
377             }
378             }
379              
380             # Match
381 32         93 &$matchCallback( $ai++, $bi++, @_ );
382             }
383              
384 20   100     54 while ( $ai <= $lastA || $bi <= $lastB )
385             {
386 22 100 100     72 if ( $ai <= $lastA && $bi <= $lastB )
    100          
387             {
388              
389             # Change
390 10 50       14 if ( defined $changeCallback )
391             {
392 10         17 &$changeCallback( $ai++, $bi++, @_ );
393             }
394             else
395             {
396 0         0 &$discardACallback( $ai++, $bi, @_ );
397 0         0 &$discardBCallback( $ai, $bi++, @_ );
398             }
399             }
400             elsif ( $ai <= $lastA )
401             {
402 7         14 &$discardACallback( $ai++, $bi, @_ );
403             }
404             else
405             {
406              
407             # $bi <= $lastB
408 5         9 &$discardBCallback( $ai, $bi++, @_ );
409             }
410             }
411              
412 20         50 return 1;
413             }
414              
415             sub prepare
416             {
417 0     0 1 0 my $a = shift; # array ref
418 0         0 my $keyGen = shift; # code ref
419              
420             # set up code ref
421 0 0   0   0 $keyGen = sub { $_[0] } unless defined($keyGen);
  0         0  
422              
423 0         0 return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ );
424             }
425              
426             sub LCS
427             {
428 1     1 1 323 my $a = shift; # array ref
429 1         2 my $b = shift; # array ref or hash ref
430 1         3 my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ );
431 1         2 my @retval;
432             my $i;
433 1         4 for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
434             {
435 8 100       17 if ( defined( $matchVector->[$i] ) )
436             {
437 6         10 push ( @retval, $a->[$i] );
438             }
439             }
440 1 50       6 return wantarray ? @retval : \@retval;
441             }
442              
443             sub LCS_length
444             {
445 0     0 1 0 my $a = shift; # array ref
446 0         0 my $b = shift; # array ref or hash ref
447 0         0 return _longestCommonSubsequence( $a, $b, 1, @_ );
448             }
449              
450             sub LCSidx
451             {
452 11     11 1 19 my $a= shift @_;
453 11         18 my $b= shift @_;
454 11         30 my $match= _longestCommonSubsequence( $a, $b, 0, @_ );
455 11         52 my @am= grep defined $match->[$_], 0..$#$match;
456 11         18 my @bm= @{$match}[@am];
  11         26  
457 11         31 return \@am, \@bm;
458             }
459              
460             sub compact_diff
461             {
462 11     11 1 20 my $a= shift @_;
463 11         17 my $b= shift @_;
464 11         32 my( $am, $bm )= LCSidx( $a, $b, @_ );
465 11         13 my @cdiff;
466 11         18 my( $ai, $bi )= ( 0, 0 );
467 11         19 push @cdiff, $ai, $bi;
468 11         13 while( 1 ) {
469 22   100     93 while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) {
      100        
470 22         25 shift @$am;
471 22         21 shift @$bm;
472 22         65 ++$ai, ++$bi;
473             }
474 22         38 push @cdiff, $ai, $bi;
475 22 100       43 last if ! @$am;
476 11         13 $ai = $am->[0];
477 11         12 $bi = $bm->[0];
478 11         35 push @cdiff, $ai, $bi;
479             }
480 11 100 100     47 push @cdiff, 0+@$a, 0+@$b
481             if $ai < @$a || $bi < @$b;
482 11 50       30 return wantarray ? @cdiff : \@cdiff;
483             }
484              
485             sub diff
486             {
487 1     1 1 90 my $a = shift; # array ref
488 1         1 my $b = shift; # array ref
489 1         3 my $retval = [];
490 1         1 my $hunk = [];
491             my $discard = sub {
492 4     4   11 push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ];
493 1         7 };
494             my $add = sub {
495 6     6   18 push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ];
496 1         4 };
497             my $match = sub {
498 7 100   7   13 push @$retval, $hunk
499             if 0 < @$hunk;
500 7         13 $hunk = []
501 1         3 };
502 1         5 traverse_sequences( $a, $b,
503             { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
504 1         3 &$match();
505 1 50       8 return wantarray ? @$retval : $retval;
506             }
507              
508             sub sdiff
509             {
510 12     12 1 3130 my $a = shift; # array ref
511 12         13 my $b = shift; # array ref
512 12         17 my $retval = [];
513 12     13   46 my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
  13         40  
514 12     24   30 my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
  24         79  
515             my $change = sub {
516 8     8   29 push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
517 12         27 };
518             my $match = sub {
519 23     23   53 push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
520 12         25 };
521 12         55 traverse_balanced(
522             $a,
523             $b,
524             {
525             MATCH => $match,
526             DISCARD_A => $discard,
527             DISCARD_B => $add,
528             CHANGE => $change,
529             },
530             @_
531             );
532 12 50       105 return wantarray ? @$retval : $retval;
533             }
534              
535             ########################################
536             my $Root= __PACKAGE__;
537             package Algorithm::Diff::_impl;
538 2     2   18 use strict;
  2         3  
  2         3024  
539              
540             sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices
541             # 1 # $me->[1]: Ref to first sequence
542             # 2 # $me->[2]: Ref to second sequence
543             sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos
544             sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
545             sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
546             sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected
547             sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position
548             sub _Min() { -2 } # Added to _Off to get min instead of max+1
549              
550             sub Die
551             {
552 230     230   1051 require Carp;
553 230         25088 Carp::confess( @_ );
554             }
555              
556             sub _ChkPos
557             {
558 1052     1052   1150 my( $me )= @_;
559 1052 100       1939 return if $me->[_Pos];
560 110         532 my $meth= ( caller(1) )[3];
561 110         300 Die( "Called $meth on 'reset' object" );
562             }
563              
564             sub _ChkSeq
565             {
566 650     650   785 my( $me, $seq )= @_;
567 650 100 100     1790 return $seq + $me->[_Off]
568             if 1 == $seq || 2 == $seq;
569 110         551 my $meth= ( caller(1) )[3];
570 110         322 Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
571             }
572              
573             sub getObjPkg
574             {
575 13     13   25 my( $us )= @_;
576 13 50       31 return ref $us if ref $us;
577 13         42 return $us . "::_obj";
578             }
579              
580             sub new
581             {
582 11     11   1905 my( $us, $seq1, $seq2, $opts ) = @_;
583 11         13 my @args;
584 11         40 for( $opts->{keyGen} ) {
585 11 50       29 push @args, $_ if $_;
586             }
587 11         24 for( $opts->{keyGenArgs} ) {
588 11 50       28 push @args, @$_ if $_;
589             }
590 11         61 my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
591 11         18 my $same= 1;
592 11 100 66     38 if( 0 == $cdif->[2] && 0 == $cdif->[3] ) {
593 8         10 $same= 0;
594 8         17 splice @$cdif, 0, 2;
595             }
596 11         25 my @obj= ( $cdif, $seq1, $seq2 );
597 11         21 $obj[_End] = (1+@$cdif)/2;
598 11         25 $obj[_Same] = $same;
599 11         18 $obj[_Base] = 0;
600 11         27 my $me = bless \@obj, $us->getObjPkg();
601 11         28 $me->Reset( 0 );
602 11         34 return $me;
603             }
604              
605             sub Reset
606             {
607 330     330   2002 my( $me, $pos )= @_;
608 330   100     573 $pos= int( $pos || 0 );
609 330 100       496 $pos += $me->[_End]
610             if $pos < 0;
611 330 100 100     907 $pos= 0
612             if $pos < 0 || $me->[_End] <= $pos;
613 330   100     599 $me->[_Pos]= $pos || !1;
614 330         408 $me->[_Off]= 2*$pos - 1;
615 330         443 return $me;
616             }
617              
618             sub Base
619             {
620 162     162   293 my( $me, $base )= @_;
621 162         199 my $oldBase= $me->[_Base];
622 162 100       261 $me->[_Base]= 0+$base if defined $base;
623 162         386 return $oldBase;
624             }
625              
626             sub Copy
627             {
628 54     54   105 my( $me, $pos, $base )= @_;
629 54         121 my @obj= @$me;
630 54         117 my $you= bless \@obj, ref($me);
631 54 100       105 $you->Reset( $pos ) if defined $pos;
632 54         94 $you->Base( $base );
633 54         126 return $you;
634             }
635              
636             sub Next {
637 347     347   4835 my( $me, $steps )= @_;
638 347 100       572 $steps= 1 if ! defined $steps;
639 347 100       492 if( $steps ) {
640 242         312 my $pos= $me->[_Pos];
641 242         282 my $new= $pos + $steps;
642 242 100 100     562 $new= 0 if $pos && $new < 0;
643 242         373 $me->Reset( $new )
644             }
645 347         765 return $me->[_Pos];
646             }
647              
648             sub Prev {
649 109     109   4463 my( $me, $steps )= @_;
650 109 100       196 $steps= 1 if ! defined $steps;
651 109         192 my $pos= $me->Next(-$steps);
652 109 100       198 $pos -= $me->[_End] if $pos;
653 109         284 return $pos;
654             }
655              
656             sub Diff {
657 81     81   107 my( $me )= @_;
658 81         137 $me->_ChkPos();
659 70 100       170 return 0 if $me->[_Same] == ( 1 & $me->[_Pos] );
660 50         56 my $ret= 0;
661 50         51 my $off= $me->[_Off];
662 50         76 for my $seq ( 1, 2 ) {
663 100 100       220 $ret |= $seq
664             if $me->[_Idx][ $off + $seq + _Min ]
665             < $me->[_Idx][ $off + $seq ];
666             }
667 50         105 return $ret;
668             }
669              
670             sub Min {
671 91     91   5648 my( $me, $seq, $base )= @_;
672 91         172 $me->_ChkPos();
673 80         115 my $off= $me->_ChkSeq($seq);
674 50 100       96 $base= $me->[_Base] if !defined $base;
675 50         128 return $base + $me->[_Idx][ $off + _Min ];
676             }
677              
678             sub Max {
679 91     91   5536 my( $me, $seq, $base )= @_;
680 91         170 $me->_ChkPos();
681 80         116 my $off= $me->_ChkSeq($seq);
682 50 100       94 $base= $me->[_Base] if !defined $base;
683 50         132 return $base + $me->[_Idx][ $off ] -1;
684             }
685              
686             sub Range {
687 295     295   6046 my( $me, $seq, $base )= @_;
688 295         500 $me->_ChkPos();
689 284         404 my $off = $me->_ChkSeq($seq);
690 254 100       401 if( !wantarray ) {
691 104         308 return $me->[_Idx][ $off ]
692             - $me->[_Idx][ $off + _Min ];
693             }
694 150 100       237 $base= $me->[_Base] if !defined $base;
695 150         658 return ( $base + $me->[_Idx][ $off + _Min ] )
696             .. ( $base + $me->[_Idx][ $off ] - 1 );
697             }
698              
699             sub Items {
700 217     217   4752 my( $me, $seq )= @_;
701 217         344 $me->_ChkPos();
702 206         273 my $off = $me->_ChkSeq($seq);
703 186 100       320 if( !wantarray ) {
704 48         125 return $me->[_Idx][ $off ]
705             - $me->[_Idx][ $off + _Min ];
706             }
707             return
708 138         230 @{$me->[$seq]}[
  138         570  
709             $me->[_Idx][ $off + _Min ]
710             .. ( $me->[_Idx][ $off ] - 1 )
711             ];
712             }
713              
714             sub Same {
715 156     156   3875 my( $me )= @_;
716 156         286 $me->_ChkPos();
717 112 50       286 return wantarray ? () : 0
    100          
718             if $me->[_Same] != ( 1 & $me->[_Pos] );
719 62         92 return $me->Items(1);
720             }
721              
722             my %getName;
723             BEGIN {
724 2     2   675 %getName= (
725             same => \&Same,
726             diff => \&Diff,
727             base => \&Base,
728             min => \&Min,
729             max => \&Max,
730             range=> \&Range,
731             items=> \&Items, # same thing
732             );
733             }
734              
735             sub Get
736             {
737 121     121   6410 my $me= shift @_;
738 121         213 $me->_ChkPos();
739 110         120 my @value;
740 110         168 for my $arg ( @_ ) {
741 130         284 for my $word ( split ' ', $arg ) {
742 160         163 my $meth;
743 160 50 33     1199 if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/
744             || not $meth= $getName{ lc $2 }
745             ) {
746 0         0 Die( $Root, ", Get: Invalid request ($word)" );
747             }
748 160         382 my( $base, $name, $seq )= ( $1, $2, $3 );
749 160 100       329 push @value, scalar(
750             4 == length($name)
751             ? $meth->( $me )
752             : $meth->( $me, $seq, $base )
753             );
754             }
755             }
756 80 100       159 if( wantarray ) {
    100          
757 30         173 return @value;
758             } elsif( 1 == @value ) {
759 40         138 return $value[0];
760             }
761 10         38 Die( 0+@value, " values requested from ",
762             $Root, "'s Get in scalar context" );
763             }
764              
765              
766             my $Obj= getObjPkg($Root);
767 2     2   16 no strict 'refs';
  2         3  
  2         272  
768              
769             for my $meth ( qw( new getObjPkg ) ) {
770             *{$Root."::".$meth} = \&{$meth};
771             *{$Obj ."::".$meth} = \&{$meth};
772             }
773             for my $meth ( qw(
774             Next Prev Reset Copy Base Diff
775             Same Items Range Min Max Get
776             _ChkPos _ChkSeq
777             ) ) {
778             *{$Obj."::".$meth} = \&{$meth};
779             }
780              
781             1;
782             __END__